mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
Merge branch 'main' into local-review-base-picker
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Improve the JetBrains model picker with sections, favorites, and smart filtering.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Use the fallback logo in old Windows terminal emulators while keeping the Unicode logo available over SSH.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Add a JetBrains tool window action for starting new sessions and let recent sessions reopen in the side panel without duplicate views.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Scale JetBrains chat transcript fonts from the editor font settings.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Keep the JetBrains model picker at the same row position when models are favorited or unfavorited.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Show JetBrains session connection status inline above the prompt, cap expanded connection details, and keep empty sessions in the shared chat scroll area.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Show detailed JetBrains session connection and configuration issues in the connection panel, and make retry recover app-side problems more reliably.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Match VS Code-style reasoning and tool run rendering in JetBrains chat.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Center description text and remove the fixed-width cap from the empty session panel so recent sessions can stretch naturally.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Open existing JetBrains sessions scrolled to the latest message after history loads.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Remember the JetBrains session header metrics expansion preference and show file names in read/write timeline tooltips.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Improve JetBrains history navigation with Back support and a Show History action in recent sessions.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Improve the JetBrains history panel with native tabs, independent local and cloud search, and date-grouped cloud sessions.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Improve the JetBrains mode picker with descriptions, deprecated badges, and native IntelliJ selection behavior.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Show JetBrains question prompts one question at a time with aligned native option rows.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Keep the JetBrains chat transcript pinned to bottom reliably while responses stream.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Show a scroll-to-bottom button when JetBrains chat sessions are scrolled away from the latest message.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Render active question and permission prompts inside the scrollable JetBrains chat transcript.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Support rebinding the JetBrains prompt Send action in the IntelliJ keymap.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Show collapsible session metadata, context usage, token usage, and compaction controls in the JetBrains session header.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Add a native session history panel to the JetBrains plugin.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Control JetBrains chat transcript spacing from the session layout.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Show loading immediately when opening JetBrains sessions without showing duplicate progress for new sessions.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Reduce redundant JetBrains chat scroll layout work while keeping bottom-follow behavior.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Show completed assistant steps in the JetBrains session timeline.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Support stopping active JetBrains sessions from the prompt action button.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Avoid leaving mouse and advanced keyboard modes enabled after exiting the TUI in mintty and MINGW terminals.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Size the JetBrains model picker from its rendered model rows instead of fixed popup dimensions.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Support reasoning effort selection and resetting JetBrains chat model overrides to the default model.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Align checked and unchecked reasoning picker rows in the JetBrains plugin.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Keep the JetBrains session connection status above the prompt without shifting the chat layout.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Refine JetBrains session loading UI and settings actions.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Preserve JetBrains chat scroll position when reviewing earlier session output.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Avoid flickering transient loading and connection error states in JetBrains sessions.
|
||||
@@ -175,6 +175,7 @@ jobs:
|
||||
cli_version: ${{ needs.version.outputs.version }}
|
||||
secrets: inherit
|
||||
# kilocode_change end
|
||||
|
||||
publish:
|
||||
needs:
|
||||
- version
|
||||
@@ -208,6 +209,7 @@ jobs:
|
||||
with:
|
||||
node-version: "24"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
package-manager-cache: false # kilocode_change
|
||||
|
||||
- name: Install @vscode/vsce
|
||||
run: bun install -g @vscode/vsce
|
||||
@@ -265,7 +267,6 @@ jobs:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
AUR_KEY: ${{ secrets.AUR_KEY }}
|
||||
GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NPM_CONFIG_PROVENANCE: true
|
||||
VSCE_PAT: ${{ secrets.VSCE_TOKEN }}
|
||||
OPENVSX_TOKEN: ${{ secrets.OVSX_TOKEN }}
|
||||
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
turbo-${{ runner.os }}-
|
||||
|
||||
- name: Run unit tests
|
||||
run: bun turbo test:ci --filter=!@kilocode/kilo-jetbrains # kilocode_change
|
||||
run: bun turbo test:ci
|
||||
env:
|
||||
KILO_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }}
|
||||
|
||||
|
||||
@@ -17,5 +17,13 @@ jobs:
|
||||
- name: Setup Bun
|
||||
uses: ./.github/actions/setup-bun
|
||||
|
||||
# kilocode_change start
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "21"
|
||||
# kilocode_change end
|
||||
|
||||
- name: Run typecheck
|
||||
run: bun typecheck
|
||||
|
||||
@@ -34,6 +34,7 @@ a.out
|
||||
target
|
||||
.scripts
|
||||
.direnv/
|
||||
packages/kilo-jetbrains/build-tasks/.gradle/
|
||||
|
||||
# Local dev files
|
||||
opencode-dev
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
v1.14.34
|
||||
v1.14.41
|
||||
|
||||
@@ -12,7 +12,7 @@ Kilo CLI is an open source AI coding agent that generates code from natural lang
|
||||
- **Dev**: `bun run dev` (runs from root) or `bun run --cwd packages/opencode --conditions=browser src/index.ts`
|
||||
- **Dev with params**: `bun dev -- help`
|
||||
- **Extension**: `bun run extension` (build + launch VS Code with the extension in dev mode). Pass `--no-build` to skip the build.
|
||||
- **Typecheck**: `bun turbo typecheck` (uses `tsgo`, not `tsc`)
|
||||
- **Typecheck**: `bun turbo typecheck` (uses `tsgo`, not `tsc`). Includes the JetBrains plugin — requires Java 21. Check with `java -version` before running. If missing, install via SDKMAN: `sdk install java 21-tem && sdk use java 21-tem`. If SDKMAN is not installed, see https://sdkman.io/install.
|
||||
- **Test**: `bun test` from `packages/opencode/` (NOT from root -- root blocks tests)
|
||||
- **Single test**: `bun test ./test/tool/tool-define.test.ts` from `packages/opencode/`
|
||||
- **CLI build artifact size check**: after `bun run script/build.ts --single --skip-install` in `packages/opencode/`, use `du -h dist/*/*/bin/kilo` (scoped package output lives under `dist/@kilocode/`)
|
||||
@@ -34,6 +34,7 @@ Before saying an implementation is ready, run the smallest relevant checks that
|
||||
| CLI | From `packages/opencode/`: `bun run typecheck`, `bun test` or targeted `bun test ./path/to/file.test.ts` |
|
||||
| VS Code extension | From `packages/kilo-vscode/`: `bun run typecheck`, `bun run lint`, `bun run test:unit` or `bun run test` |
|
||||
| Extension build/package | From `packages/kilo-vscode/`: `bun run compile` or `bun run package` when touching build, packaging, SDK, or webview integration paths |
|
||||
| JetBrains plugin | From `packages/kilo-jetbrains/`: `./gradlew typecheck`, `./gradlew test`. Requires Java 21 — check first with `java -version`. Install via SDKMAN if missing: `sdk install java 21-tem && sdk use java 21-tem`. |
|
||||
| CI-only guards | Run affected guards documented above, such as `bun run knip`, `bun run check-kilocode-change`, `bun run script/check-opencode-annotations.ts`, or source link extraction |
|
||||
|
||||
Never run root `bun test`; the root script prints `do not run tests from root` and exits with code 1. Use package-level tests instead.
|
||||
|
||||
+43
-1
@@ -14,9 +14,34 @@ There are lots of ways to contribute to the project:
|
||||
|
||||
The Kilo Community is [on Discord](https://kilo.ai/discord).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Bun 1.3.13+** — required for all packages.
|
||||
- **Java 21** — required by the JetBrains plugin. The root `bun turbo typecheck` and `bun turbo test:ci` commands include `@kilocode/kilo-jetbrains` and will fail without Java 21.
|
||||
|
||||
The preferred way to install Java is via [SDKMAN](https://sdkman.io/install):
|
||||
|
||||
```bash
|
||||
# Install SDKMAN (if not already installed)
|
||||
curl -s "https://get.sdkman.io" | bash
|
||||
|
||||
# Install and activate Java 21 (Eclipse Temurin)
|
||||
sdk install java 21-tem
|
||||
sdk use java 21-tem
|
||||
|
||||
# Verify
|
||||
java -version
|
||||
```
|
||||
|
||||
If you don't plan to work on the JetBrains plugin, you can still run non-JetBrains checks directly:
|
||||
|
||||
```bash
|
||||
bun turbo typecheck --filter=!@kilocode/kilo-jetbrains
|
||||
```
|
||||
|
||||
## Developing Kilo CLI
|
||||
|
||||
- **Requirements:** Bun 1.3.13+
|
||||
- **Requirements:** Bun 1.3.13+, Java 21 (see [Prerequisites](#prerequisites) above)
|
||||
- Install dependencies and start the dev server from the repo root:
|
||||
|
||||
```bash
|
||||
@@ -34,6 +59,23 @@ bun run extension # Build + launch in dev mode
|
||||
|
||||
This auto-detects VS Code on macOS, Linux, and Windows. Override with `--app-path PATH` or `VSCODE_EXEC_PATH`. Use `--insiders` to prefer Insiders, `--workspace PATH` to open a specific folder, or `--clean` to reset cached state.
|
||||
|
||||
### Developing the JetBrains Plugin
|
||||
|
||||
Requires Java 21 (see [Prerequisites](#prerequisites)). From `packages/kilo-jetbrains/`:
|
||||
|
||||
```bash
|
||||
./gradlew typecheck # Compile-check all Kotlin sources
|
||||
./gradlew test # Run all tests (backend + frontend)
|
||||
./gradlew runIde # Launch sandboxed IntelliJ with the plugin
|
||||
```
|
||||
|
||||
Or via the root turbo filter to run only JetBrains checks from the repo root:
|
||||
|
||||
```bash
|
||||
bun turbo typecheck --filter=@kilocode/kilo-jetbrains
|
||||
bun turbo test:ci --filter=@kilocode/kilo-jetbrains
|
||||
```
|
||||
|
||||
### Running against a different directory
|
||||
|
||||
By default, `bun dev` runs Kilo CLI in the `packages/opencode` directory. To run it against a different directory or repository:
|
||||
|
||||
@@ -28,12 +28,12 @@
|
||||
"prettier": "3.6.2",
|
||||
"semver": "^7.6.0",
|
||||
"sst": "3.18.10",
|
||||
"turbo": "2.8.13",
|
||||
"turbo": "2.9.14",
|
||||
},
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -68,7 +68,7 @@
|
||||
},
|
||||
"packages/kilo-docs": {
|
||||
"name": "@kilocode/kilo-docs",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"dependencies": {
|
||||
"@docsearch/css": "^4",
|
||||
"@docsearch/js": "^4",
|
||||
@@ -98,7 +98,7 @@
|
||||
},
|
||||
"packages/kilo-gateway": {
|
||||
"name": "@kilocode/kilo-gateway",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"dependencies": {
|
||||
"@ai-sdk/alibaba": "1.0.17",
|
||||
"@ai-sdk/anthropic": "3.0.71",
|
||||
@@ -134,7 +134,7 @@
|
||||
},
|
||||
"packages/kilo-i18n": {
|
||||
"name": "@kilocode/kilo-i18n",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
@@ -144,7 +144,7 @@
|
||||
},
|
||||
"packages/kilo-indexing": {
|
||||
"name": "@kilocode/kilo-indexing",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-bedrock-runtime": "3.1005.0",
|
||||
"@aws-sdk/credential-provider-ini": "3.972.31",
|
||||
@@ -176,10 +176,11 @@
|
||||
},
|
||||
"packages/kilo-jetbrains": {
|
||||
"name": "@kilocode/kilo-jetbrains",
|
||||
"version": "7.3.8",
|
||||
},
|
||||
"packages/kilo-telemetry": {
|
||||
"name": "@kilocode/kilo-telemetry",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"dependencies": {
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
"posthog-node": "4.4.0",
|
||||
@@ -193,7 +194,7 @@
|
||||
},
|
||||
"packages/kilo-ui": {
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@kobalte/core": "0.13.11",
|
||||
@@ -230,7 +231,7 @@
|
||||
},
|
||||
"packages/kilo-vscode": {
|
||||
"name": "kilo-code",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
@@ -293,7 +294,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "@kilocode/cli",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"bin": {
|
||||
"kilo": "./bin/kilo",
|
||||
"kilocode": "./bin/kilo",
|
||||
@@ -301,7 +302,7 @@
|
||||
"dependencies": {
|
||||
"@actions/core": "1.11.1",
|
||||
"@actions/github": "6.0.1",
|
||||
"@agentclientprotocol/sdk": "0.16.1",
|
||||
"@agentclientprotocol/sdk": "0.21.0",
|
||||
"@ai-sdk/alibaba": "1.0.17",
|
||||
"@ai-sdk/amazon-bedrock": "4.0.96",
|
||||
"@ai-sdk/anthropic": "3.0.71",
|
||||
@@ -451,7 +452,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@kilocode/plugin",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -476,7 +477,7 @@
|
||||
},
|
||||
"packages/script": {
|
||||
"name": "@opencode-ai/script",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"dependencies": {
|
||||
"semver": "^7.6.3",
|
||||
},
|
||||
@@ -487,7 +488,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@kilocode/sdk",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -502,7 +503,7 @@
|
||||
},
|
||||
"packages/storybook": {
|
||||
"name": "@opencode-ai/storybook",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"devDependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@solidjs/meta": "catalog:",
|
||||
@@ -525,7 +526,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -614,7 +615,7 @@
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@opentui/core": "0.2.2",
|
||||
"@opentui/solid": "0.2.2",
|
||||
"@pierre/diffs": "1.1.0-beta.18",
|
||||
"@pierre/diffs": "1.1.22",
|
||||
"@playwright/test": "1.59.1",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
@@ -626,7 +627,7 @@
|
||||
"@types/bun": "1.3.12",
|
||||
"@types/cross-spawn": "6.0.6",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "22.13.9",
|
||||
"@types/node": "24.12.2",
|
||||
"@types/semver": "7.7.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260316.1",
|
||||
"ai": "6.0.168",
|
||||
@@ -672,7 +673,7 @@
|
||||
|
||||
"@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
|
||||
|
||||
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.16.1", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-1ad+Sc/0sCtZGHthxxvgEUo5Wsbw16I+aF+YwdiLnPwkZG8KAGUEAPK6LM6Pf69lCyJPt1Aomk1d+8oE3C4ZEw=="],
|
||||
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.21.0", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-ONj+Q8qOdNQp5XbH5jnMwzT9IKZJsSN0p0lkceS4GtUtNOPVLpNzSS8gqQdGMKfBvA0ESbkL8BTaSN1Rc9miEw=="],
|
||||
|
||||
"@ai-sdk/alibaba": ["@ai-sdk/alibaba@1.0.17", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZbE+U5bWz2JBc5DERLowx5+TKbjGBE93LqKZAWvuEn7HOSQMraxFMZuc0ST335QZJAyfBOzh7m1mPQ+y7EaaoA=="],
|
||||
|
||||
@@ -1644,9 +1645,9 @@
|
||||
|
||||
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
|
||||
|
||||
"@pierre/diffs": ["@pierre/diffs@1.1.0-beta.18", "", { "dependencies": { "@pierre/theme": "0.0.22", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-7ZF3YD9fxdbYsPnltz5cUqHacN7ztp8RX/fJLxwv8wIEORpP4+7dHz1h/qx3o4EW2xUrIhmbM8ImywLasB787Q=="],
|
||||
"@pierre/diffs": ["@pierre/diffs@1.1.22", "", { "dependencies": { "@pierre/theme": "0.0.28", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-1Iv7kdl6OABFCd1n2HQbGUiRHouXPaHoIjcb7Lwg8zeJKY5ph+cESFcGEyIiwW0NCbKGtYS2bTnXmI+Eze5dwg=="],
|
||||
|
||||
"@pierre/theme": ["@pierre/theme@0.0.22", "", {}, "sha512-ePUIdQRNGjrveELTU7fY89Xa7YGHHEy5Po5jQy/18lm32eRn96+tnYJEtFooGdffrx55KBUtOXfvVy/7LDFFhA=="],
|
||||
"@pierre/theme": ["@pierre/theme@0.0.28", "", {}, "sha512-1j/H/fECBuc9dEvntdWI+l435HZapw+RCJTlqCA6BboQ5TjlnE005j/ROWutXIs8aq5OAc82JI2Kwk4A1WWBgw=="],
|
||||
|
||||
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
|
||||
|
||||
@@ -2028,6 +2029,18 @@
|
||||
|
||||
"@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="],
|
||||
|
||||
"@turbo/darwin-64": ["@turbo/darwin-64@2.9.14", "", { "os": "darwin", "cpu": "x64" }, "sha512-t7QiPflaEyBE4oayeZtSmu4mEfjgIrcNlNNl1z1dmIVPqEdtA7+CfTf8d7KXsOGPh6aNgWjKxyvQg9uGfDQF+A=="],
|
||||
|
||||
"@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.9.14", "", { "os": "darwin", "cpu": "arm64" }, "sha512-d23147mC9BsCPA9mJ0h/ubcpbRgcJBXbcG3+Vq7YLhjz3IXuvQsJ1UXH8f4MD76ZjJ4m/E4aRdJV+MW88CDfbw=="],
|
||||
|
||||
"@turbo/linux-64": ["@turbo/linux-64@2.9.14", "", { "os": "linux", "cpu": "x64" }, "sha512-P3ZKB5tuUDdDQWuAsACGUR1qv9W7BNWxdxqVJ0kZNuNNPRaVYTPPikLcp79+GiEcW3npsR+KyP38lnQiBc5aSA=="],
|
||||
|
||||
"@turbo/linux-arm64": ["@turbo/linux-arm64@2.9.14", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZRTlzcUMrrPv9ZuDzRF9n60Ym13bKeG9jDB8WjxyLhWNzV+AJQN+zdpIk3NJYf2zQsGUm1mNar2P0elRzLw25g=="],
|
||||
|
||||
"@turbo/windows-64": ["@turbo/windows-64@2.9.14", "", { "os": "win32", "cpu": "x64" }, "sha512-exanwN6sIduZwykYeiTQj8kCmOhazP5WOz3bvXMcYtjhL6Z3iRWLewKrXCBq0bqwSP3iBMb/AerRCnHI4lx46A=="],
|
||||
|
||||
"@turbo/windows-arm64": ["@turbo/windows-arm64@2.9.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-fVdCsnmYoKICsycbWuuGp6Jvi51/3G/UluFWuAUCvR8PIW5IJkAk5BM9UF8PSm0Q2IphWHFZjYEgjHsh3B9y/g=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
|
||||
@@ -2152,7 +2165,7 @@
|
||||
|
||||
"@types/mssql": ["@types/mssql@9.1.11", "", { "dependencies": { "@types/node": "*", "tarn": "^3.0.1", "tedious": "*" } }, "sha512-vcujgrDbDezCxNDO4KY6gjwduLYOKfrexpRUwhoysRvcXZ3+IgZ/PMYFDgh8c3cQIxZ6skAwYo+H6ibMrBWPjQ=="],
|
||||
|
||||
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
"@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="],
|
||||
|
||||
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
|
||||
|
||||
@@ -4296,19 +4309,7 @@
|
||||
|
||||
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
|
||||
|
||||
"turbo": ["turbo@2.8.13", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.13", "turbo-darwin-arm64": "2.8.13", "turbo-linux-64": "2.8.13", "turbo-linux-arm64": "2.8.13", "turbo-windows-64": "2.8.13", "turbo-windows-arm64": "2.8.13" }, "bin": { "turbo": "bin/turbo" } }, "sha512-nyM99hwFB9/DHaFyKEqatdayGjsMNYsQ/XBNO6MITc7roncZetKb97MpHxWf3uiU+LB9c9HUlU3Jp2Ixei2k1A=="],
|
||||
|
||||
"turbo-darwin-64": ["turbo-darwin-64@2.8.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-PmOvodQNiOj77+Zwoqku70vwVjKzL34RTNxxoARjp5RU5FOj/CGiC6vcDQhNtFPUOWSAaogHF5qIka9TBhX4XA=="],
|
||||
|
||||
"turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kI+anKcLIM4L8h+NsM7mtAUpElkCOxv5LgiQVQR8BASyDFfc8Efj5kCk3cqxuxOvIqx0sLfCX7atrHQ2kwuNJQ=="],
|
||||
|
||||
"turbo-linux-64": ["turbo-linux-64@2.8.13", "", { "os": "linux", "cpu": "x64" }, "sha512-j29KnQhHyzdzgCykBFeBqUPS4Wj7lWMnZ8CHqytlYDap4Jy70l4RNG46pOL9+lGu6DepK2s1rE86zQfo0IOdPw=="],
|
||||
|
||||
"turbo-linux-arm64": ["turbo-linux-arm64@2.8.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-OEl1YocXGZDRDh28doOUn49QwNe82kXljO1HXApjU0LapkDiGpfl3jkAlPKxEkGDSYWc8MH5Ll8S16Rf5tEBYg=="],
|
||||
|
||||
"turbo-windows-64": ["turbo-windows-64@2.8.13", "", { "os": "win32", "cpu": "x64" }, "sha512-717bVk1+Pn2Jody7OmWludhEirEe0okoj1NpRbSm5kVZz/yNN/jfjbxWC6ilimXMz7xoMT3IDfQFJsFR3PMANA=="],
|
||||
|
||||
"turbo-windows-arm64": ["turbo-windows-arm64@2.8.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-R819HShLIT0Wj6zWVnIsYvSNtRNj1q9VIyaUz0P24SMcLCbQZIm1sV09F4SDbg+KCCumqD2lcaR2UViQ8SnUJA=="],
|
||||
"turbo": ["turbo@2.9.14", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.14", "@turbo/darwin-arm64": "2.9.14", "@turbo/linux-64": "2.9.14", "@turbo/linux-arm64": "2.9.14", "@turbo/windows-64": "2.9.14", "@turbo/windows-arm64": "2.9.14" }, "bin": { "turbo": "bin/turbo" } }, "sha512-BQqXRr4UoWI3UPFrtznCLykYHxwxWh53iCB57x092jPMjIlW1wnm3N895g5irpiXmnxUhREBB0n6+y8BHhs4nw=="],
|
||||
|
||||
"turndown": ["turndown@7.2.0", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="],
|
||||
|
||||
@@ -4338,7 +4339,7 @@
|
||||
|
||||
"undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
|
||||
|
||||
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
"unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
|
||||
|
||||
@@ -4552,6 +4553,8 @@
|
||||
|
||||
"@antfu/install-pkg/tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="],
|
||||
|
||||
"@anthropic-ai/sdk/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
@@ -4656,6 +4659,8 @@
|
||||
|
||||
"@kilocode/kilo-indexing/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
||||
|
||||
"@manypkg/find-root/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@manypkg/find-root/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
|
||||
"@manypkg/find-root/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
|
||||
@@ -4804,6 +4809,30 @@
|
||||
|
||||
"@textlint/linter-formatter/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@types/cacache/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/cross-spawn/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/mssql/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/node-fetch/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/npm-registry-fetch/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/npmcli__arborist/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/npmlog/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/pacote/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/qrcode/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/readable-stream/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/ssri/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@types/ws/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||
|
||||
"@vscode/ripgrep/yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
|
||||
@@ -4840,6 +4869,8 @@
|
||||
|
||||
"ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.5.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-r1fJL1Cb3gQDa2MpWH/sfx1BsEW0uzlRriJM6eihaKqbtKDmZoBisF32VcVaQYassighX7NGCkF68EsrZA43uQ=="],
|
||||
|
||||
"apache-arrow/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
@@ -4874,6 +4905,8 @@
|
||||
|
||||
"buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"bun-types/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||
|
||||
"c12/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
|
||||
@@ -4966,6 +4999,8 @@
|
||||
|
||||
"gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
|
||||
|
||||
"image-q/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"isomorphic-git/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
@@ -5088,6 +5123,8 @@
|
||||
|
||||
"pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||
|
||||
"protobufjs/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"proxy-addr/ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
|
||||
|
||||
"qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
|
||||
@@ -5154,6 +5191,8 @@
|
||||
|
||||
"tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
|
||||
|
||||
"tedious/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"test-exclude/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
@@ -5212,6 +5251,8 @@
|
||||
|
||||
"@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||
|
||||
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||
@@ -5250,10 +5291,14 @@
|
||||
|
||||
"@kilocode/kilo-gateway/@opentui/solid/babel-preset-solid": ["babel-preset-solid@1.9.9", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.1" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.8" }, "optionalPeers": ["solid-js"] }, "sha512-pCnxWrciluXCeli/dj5PIEHgbNzim3evtTn12snjqqg8QZWJNMjH1AWIp4iG/tbVjqQ72aBEymMSagvmgxubXw=="],
|
||||
|
||||
"@manypkg/find-root/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@manypkg/find-root/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"@morphllm/morphsdk/ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.95", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ=="],
|
||||
|
||||
"@morphllm/morphsdk/openai/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"@morphllm/morphsdk/openai/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
||||
@@ -5360,6 +5405,30 @@
|
||||
|
||||
"@textlint/linter-formatter/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@types/cacache/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@types/cross-spawn/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@types/mssql/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@types/node-fetch/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@types/npm-registry-fetch/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@types/npmcli__arborist/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@types/npmlog/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@types/pacote/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@types/qrcode/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@types/readable-stream/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@types/ssri/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@types/ws/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@vscode/ripgrep/yauzl/buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
|
||||
|
||||
"@vscode/test-cli/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
@@ -5402,6 +5471,8 @@
|
||||
|
||||
"ai-gateway-provider/@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||
|
||||
"apache-arrow/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"archiver-utils/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"archiver-utils/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
|
||||
@@ -5432,6 +5503,8 @@
|
||||
|
||||
"bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"bun-types/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||
|
||||
"c8/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
@@ -5536,10 +5609,14 @@
|
||||
|
||||
"gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||
|
||||
"image-q/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"jszip/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
||||
|
||||
"jszip/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
|
||||
"kilo-code/openai/@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||
|
||||
"lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
||||
|
||||
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
@@ -5602,6 +5679,8 @@
|
||||
|
||||
"posthog-js/@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.7.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-DT12SXVwV2eoJrGf4nnsvZojxxeQo+LlNAsoYGRRObPWTeN6APiqZ2+nqDCQDvQX40eLi1AePONS0onoASp3yQ=="],
|
||||
|
||||
"protobufjs/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"qrcode/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
|
||||
|
||||
"qrcode/yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
@@ -5632,6 +5711,8 @@
|
||||
|
||||
"tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"tedious/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"test-exclude/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"test-exclude/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
|
||||
@@ -5664,6 +5745,8 @@
|
||||
|
||||
"@morphllm/morphsdk/ai/@ai-sdk/gateway/@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="],
|
||||
|
||||
"@morphllm/morphsdk/openai/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"@octokit/graphql/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="],
|
||||
|
||||
"@octokit/rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="],
|
||||
@@ -5776,6 +5859,8 @@
|
||||
|
||||
"gray-matter/js-yaml/argparse/sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
|
||||
|
||||
"kilo-code/openai/@types/node/undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
"mocha/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"mocha/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-lENbjYvGZewVWQcr7zXJn+v1hrvipHEnjE1yS+kEwLs=",
|
||||
"aarch64-linux": "sha256-yqTdPGtoLEOaFc7PhBFRiS/6gI3xUgHG/cQcULKytyM=",
|
||||
"aarch64-darwin": "sha256-lyn6yjD8qv2KwIDwlA8LXBPkcHTCZCtXIsktK5acoXM=",
|
||||
"x86_64-darwin": "sha256-diaLa3lmWzLcm4TeAwH1hFIqQYQQUcG4YD306vQWCqA="
|
||||
"x86_64-linux": "sha256-EbXkvlbexL1Gyy4vvGG5j+JWjv7SsVk6T5YnDfo3AQY=",
|
||||
"aarch64-linux": "sha256-+MuJE4XGr0dArD/HuozaoK9Oymfgvx1CisE/Sm4ZstY=",
|
||||
"aarch64-darwin": "sha256-Qdk+tZLydGld471ApL1cxfd85QQNFelTfqq7uAznfK4=",
|
||||
"x86_64-darwin": "sha256-Fo6W65MfAcXaulBGCaE7+GfHtq3VVJyVDJpIENvR7lQ="
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -9,7 +9,7 @@
|
||||
"dev": "bun run --cwd packages/opencode --conditions=browser src/index.ts",
|
||||
"dev:storybook": "bun --cwd packages/storybook storybook",
|
||||
"lint": "oxlint",
|
||||
"typecheck": "bun turbo typecheck --filter=!@kilocode/kilo-jetbrains",
|
||||
"typecheck": "bun turbo typecheck",
|
||||
"postinstall": "bun run --cwd packages/opencode fix-node-pty && bun run script/setup-git.ts",
|
||||
"prepare": "husky",
|
||||
"random": "echo 'Random script'",
|
||||
@@ -36,13 +36,13 @@
|
||||
"ulid": "3.0.1",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "22.13.9",
|
||||
"@types/node": "24.12.2",
|
||||
"@types/semver": "7.7.1",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/diffs": "1.1.0-beta.18",
|
||||
"@pierre/diffs": "1.1.22",
|
||||
"opentui-spinner": "0.0.6",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
@@ -91,7 +91,7 @@
|
||||
"prettier": "3.6.2",
|
||||
"semver": "^7.6.0",
|
||||
"sst": "3.18.10",
|
||||
"turbo": "2.8.13",
|
||||
"turbo": "2.9.14",
|
||||
"@types/bun": "catalog:",
|
||||
"@changesets/changelog-github": "^0.5.1",
|
||||
"@changesets/cli": "^2.27.10"
|
||||
@@ -145,6 +145,6 @@
|
||||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch"
|
||||
},
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -87,6 +87,8 @@ export const layer = Layer.effect(
|
||||
Effect.sync(() => Service.of(make())),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer
|
||||
|
||||
export const layerWith = (input: Partial<Interface>) =>
|
||||
Layer.effect(
|
||||
Service,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "kilo"
|
||||
name = "Kilo"
|
||||
description = "The open source coding agent."
|
||||
version = "7.3.4"
|
||||
version = "7.3.8"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/Kilo-Org/kilocode"
|
||||
@@ -11,26 +11,26 @@ name = "Kilo"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.4/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.8/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.4/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.8/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.4/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.8/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.4/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.8/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.4/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.3.8/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-docs",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --webpack --port 3002",
|
||||
|
||||
@@ -68,7 +68,7 @@ To fill empty seats or remove members ahead of seat deletion, see the [team mana
|
||||
|
||||
## Automatic Top-Up
|
||||
|
||||
Ensure your team has uninterrupted access to Kilo Code by enabling Automatic Top-Up. This feature keeps your organization's balance funded so you never have to worry about manual recharges.
|
||||
Ensure your team has uninterrupted access to Kilo Code by enabling [Automatic Top-Up](https://kilo.ai/features/auto-top-ups). This feature keeps your organization's balance funded so you never have to worry about manual recharges. Auto top-up is also available for individual accounts — see [Adding Credits](/docs/getting-started/adding-credits) for details.
|
||||
|
||||
### How It Works
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ Once you've used any initial free Kilo Credits, you can easily add more:
|
||||
|
||||
- Subscribe to the [Kilo Pass](https://kilo.ai/features/kilo-pass), the most cost effective way to add credits.
|
||||
- Purchase additional credits as a one-time transaction.
|
||||
- Enable automatic top-up, which purchases additional credits when your balance is below $5.
|
||||
- Enable [automatic top-up](https://kilo.ai/features/auto-top-ups), which purchases additional credits when your balance runs low. Auto top-up is available for both individual and organization accounts.
|
||||
|
||||
These options are available to purchase from your [personal profile page](https://app.kilo.ai/profile).
|
||||
These options are available to purchase from your [personal profile page](https://app.kilo.ai/profile). Organization owners can manage credits and auto top-up from the [Organization dashboard](https://app.kilo.ai).
|
||||
|
||||
You can also use subscriptions or credits you may have purchased directly with an AI provider by adding your keys on the [Bring your own Key (BYOK)](https://app.kilo.ai/byok) settings screen. For setup details and supported providers, see [AI Providers documentation](/docs/ai-providers). If your provider is not yet supported, you can also [directly connect your provider](/docs/getting-started/setup-authentication) in the extension and CLI.
|
||||
|
||||
|
||||
@@ -871,4 +871,10 @@ module.exports = [
|
||||
basePath: false,
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: "/auto-top-ups",
|
||||
destination: "https://kilo.ai/features/auto-top-ups",
|
||||
basePath: false,
|
||||
permanent: true,
|
||||
},
|
||||
]
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7aa0924c6679cbff6e816dd86371a2d483afb7f0840824503ef4ba325c25d6c5
|
||||
size 26282
|
||||
oid sha256:97728b91ed1c92556abfde08824b91536e87cc7afa4d121bb5b1b488946c1d05
|
||||
size 26009
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f28a551185ec9d7607d02295803b155e0f7bac378fcefdf0996c6ca51c0afad6
|
||||
size 21892
|
||||
oid sha256:7b8e9f6e0b9a9a62efc9b1e807936049c7627674ba241239dfd777698f951acc
|
||||
size 21820
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-gateway",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-i18n",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Kilo-specific i18n translations and overrides",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-indexing",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Standalone indexing engine and host helpers for Kilo Code",
|
||||
|
||||
@@ -302,10 +302,9 @@ export class CodeIndexConfigManager {
|
||||
}
|
||||
|
||||
public get currentModelDimension(): number | undefined {
|
||||
if (this.modelDimension && this.modelDimension > 0) return this.modelDimension
|
||||
const id = this.modelId ?? getDefaultModelId(this.embedderProvider)
|
||||
const dim = getModelDimension(this.embedderProvider, id)
|
||||
if (!dim && this.modelDimension && this.modelDimension > 0) return this.modelDimension
|
||||
return dim
|
||||
return getModelDimension(this.embedderProvider, id)
|
||||
}
|
||||
|
||||
public get currentSearchMinScore(): number {
|
||||
|
||||
@@ -25,7 +25,8 @@ interface EmbeddingItem {
|
||||
}
|
||||
|
||||
interface OpenRouterEmbeddingResponse {
|
||||
data: EmbeddingItem[]
|
||||
data?: EmbeddingItem[]
|
||||
error?: string | { code?: string | number; message?: string }
|
||||
usage?: {
|
||||
prompt_tokens?: number
|
||||
total_tokens?: number
|
||||
@@ -193,10 +194,7 @@ export class OpenRouterEmbedder implements IEmbedder {
|
||||
const requestParams: any = {
|
||||
input: batchTexts,
|
||||
model: model,
|
||||
// OpenAI package (as of v4.78.1) has a parsing issue that truncates embedding dimensions to 256
|
||||
// when processing numeric arrays, which breaks compatibility with models using larger dimensions.
|
||||
// By requesting base64 encoding, we bypass the package's parser and handle decoding ourselves.
|
||||
encoding_format: "base64",
|
||||
encoding_format: "float",
|
||||
}
|
||||
|
||||
if (this.dimensions !== undefined) {
|
||||
@@ -213,8 +211,24 @@ export class OpenRouterEmbedder implements IEmbedder {
|
||||
}
|
||||
|
||||
const response = (await this.embeddingsClient.embeddings.create(requestParams)) as OpenRouterEmbeddingResponse
|
||||
const err = response.error
|
||||
const msg = typeof err === "string" ? err : err?.message
|
||||
const code = typeof err === "object" && err ? err.code : undefined
|
||||
if (!response.data || response.data.length === 0) {
|
||||
log.warn("OpenRouter embedder batch returned invalid response", {
|
||||
location: "OpenRouterEmbedder:_embedBatchWithRetries",
|
||||
model,
|
||||
dimensions: this.dimensions,
|
||||
provider: this.specificProvider,
|
||||
code,
|
||||
err: msg,
|
||||
})
|
||||
const invalid = new Error(msg ?? "Invalid response from OpenRouter embedding endpoint") as HttpError
|
||||
invalid.status = typeof code === "number" ? code : 422
|
||||
throw invalid
|
||||
}
|
||||
|
||||
// Convert base64 embeddings to float32 arrays
|
||||
// Normalize base64 embeddings if OpenRouter returns them despite the float request.
|
||||
const processedEmbeddings = response.data.map((item: EmbeddingItem) => {
|
||||
if (typeof item.embedding === "string") {
|
||||
const buffer = Buffer.from(item.embedding, "base64")
|
||||
@@ -292,7 +306,7 @@ export class OpenRouterEmbedder implements IEmbedder {
|
||||
const requestParams: any = {
|
||||
input: testTexts,
|
||||
model: modelToUse,
|
||||
encoding_format: "base64",
|
||||
encoding_format: "float",
|
||||
}
|
||||
|
||||
if (this.dimensions !== undefined) {
|
||||
@@ -315,6 +329,18 @@ export class OpenRouterEmbedder implements IEmbedder {
|
||||
|
||||
// Check if we got a valid response
|
||||
if (!response?.data || response.data.length === 0) {
|
||||
const err = response?.error
|
||||
const msg = typeof err === "string" ? err : err?.message
|
||||
const code = typeof err === "object" && err ? err.code : undefined
|
||||
log.warn("OpenRouter embedder validation returned invalid response", {
|
||||
location: "OpenRouterEmbedder:validateConfiguration",
|
||||
model: modelToUse,
|
||||
dimensions: this.dimensions,
|
||||
provider: this.specificProvider,
|
||||
dataCount: response?.data?.length ?? 0,
|
||||
code,
|
||||
err: msg,
|
||||
})
|
||||
return {
|
||||
valid: false,
|
||||
error: "Invalid response from OpenRouter embedding endpoint",
|
||||
|
||||
@@ -20,7 +20,7 @@ export function resolveEmbeddingProfile(
|
||||
modelDimension?: number,
|
||||
): EmbeddingProfile | undefined {
|
||||
const id = modelId ?? getDefaultModelId(provider)
|
||||
const dim = getModelDimension(provider, id) ?? parseDimension(modelDimension)
|
||||
const dim = parseDimension(modelDimension) ?? getModelDimension(provider, id)
|
||||
if (!dim) return undefined
|
||||
return {
|
||||
provider,
|
||||
|
||||
@@ -48,6 +48,7 @@ const profiles: Record<string, Record<string, ModelProfile>> = {
|
||||
openrouter: {
|
||||
"openai/text-embedding-3-small": { dimension: 1536, scoreThreshold: 0.4 },
|
||||
"openai/text-embedding-3-large": { dimension: 3072, scoreThreshold: 0.4 },
|
||||
"google/gemini-embedding-2-preview": { dimension: 3072, scoreThreshold: 0.35 },
|
||||
},
|
||||
"openai-compatible": {},
|
||||
"vercel-ai-gateway": {
|
||||
|
||||
@@ -83,6 +83,20 @@ describe("CodeIndexConfigManager", () => {
|
||||
expect(cfg.currentModelDimension).toBe(2048)
|
||||
})
|
||||
|
||||
test("uses configured dimension before static model metadata", () => {
|
||||
const cfg = new CodeIndexConfigManager(
|
||||
createInput({
|
||||
embedderProvider: "openrouter",
|
||||
openAiKey: undefined,
|
||||
openRouterApiKey: "or-test",
|
||||
modelId: "google/gemini-embedding-2-preview",
|
||||
modelDimension: 1536,
|
||||
}),
|
||||
)
|
||||
|
||||
expect(cfg.currentModelDimension).toBe(1536)
|
||||
})
|
||||
|
||||
describe("loadConfiguration restart checks", () => {
|
||||
test("requires restart when model changes with same dimension", () => {
|
||||
const cfg = new CodeIndexConfigManager(createInput({ modelId: "text-embedding-3-small" }))
|
||||
|
||||
@@ -68,14 +68,10 @@ describe("OpenRouterEmbedder", () => {
|
||||
})
|
||||
|
||||
test("should create embeddings successfully", async () => {
|
||||
// Create base64 encoded embedding with values that can be exactly represented in Float32
|
||||
const testEmbedding = new Float32Array([0.25, 0.5, 0.75])
|
||||
const base64String = Buffer.from(testEmbedding.buffer).toString("base64")
|
||||
|
||||
const mockResponse = {
|
||||
data: [
|
||||
{
|
||||
embedding: base64String,
|
||||
embedding: [0.25, 0.5, 0.75],
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
@@ -91,7 +87,7 @@ describe("OpenRouterEmbedder", () => {
|
||||
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
|
||||
input: ["test text"],
|
||||
model: defaultModel,
|
||||
encoding_format: "base64",
|
||||
encoding_format: "float",
|
||||
})
|
||||
expect(result.embeddings).toHaveLength(1)
|
||||
expect(result.embeddings[0]).toEqual([0.25, 0.5, 0.75])
|
||||
@@ -99,6 +95,20 @@ describe("OpenRouterEmbedder", () => {
|
||||
expect(result.usage?.totalTokens).toBe(5)
|
||||
})
|
||||
|
||||
test("should not retry invalid responses without embedding data", async () => {
|
||||
mockEmbeddingsCreate.mockResolvedValue({
|
||||
error: {
|
||||
code: 404,
|
||||
message: "No successful provider responses.",
|
||||
},
|
||||
})
|
||||
|
||||
await expect(embedder.createEmbeddings(["test"])).rejects.toThrow(
|
||||
"Embedding request failed after 3 attempts with status 404: No successful provider responses.",
|
||||
)
|
||||
expect(mockEmbeddingsCreate).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test("should handle multiple texts", async () => {
|
||||
const embedding1 = new Float32Array([0.25, 0.5])
|
||||
const embedding2 = new Float32Array([0.75, 1.0])
|
||||
@@ -156,7 +166,7 @@ describe("OpenRouterEmbedder", () => {
|
||||
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
|
||||
input: ["test"],
|
||||
model: customModel,
|
||||
encoding_format: "base64",
|
||||
encoding_format: "float",
|
||||
})
|
||||
})
|
||||
|
||||
@@ -187,7 +197,7 @@ describe("OpenRouterEmbedder", () => {
|
||||
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
|
||||
input: ["test"],
|
||||
model: defaultModel,
|
||||
encoding_format: "base64",
|
||||
encoding_format: "float",
|
||||
provider: {
|
||||
order: [specificProvider],
|
||||
only: [specificProvider],
|
||||
@@ -221,7 +231,7 @@ describe("OpenRouterEmbedder", () => {
|
||||
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
|
||||
input: ["test"],
|
||||
model: defaultModel,
|
||||
encoding_format: "base64",
|
||||
encoding_format: "float",
|
||||
dimensions: 1024,
|
||||
})
|
||||
})
|
||||
@@ -257,7 +267,7 @@ describe("OpenRouterEmbedder", () => {
|
||||
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
|
||||
input: ["test"],
|
||||
model: defaultModel,
|
||||
encoding_format: "base64",
|
||||
encoding_format: "float",
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -271,13 +281,10 @@ describe("OpenRouterEmbedder", () => {
|
||||
})
|
||||
|
||||
test("should validate configuration successfully", async () => {
|
||||
const testEmbedding = new Float32Array([0.25, 0.5])
|
||||
const base64String = Buffer.from(testEmbedding.buffer).toString("base64")
|
||||
|
||||
const mockResponse = {
|
||||
data: [
|
||||
{
|
||||
embedding: base64String,
|
||||
embedding: [0.25, 0.5],
|
||||
},
|
||||
],
|
||||
usage: {
|
||||
@@ -296,7 +303,7 @@ describe("OpenRouterEmbedder", () => {
|
||||
{
|
||||
input: ["test"],
|
||||
model: defaultModel,
|
||||
encoding_format: "base64",
|
||||
encoding_format: "float",
|
||||
},
|
||||
{
|
||||
timeout: REMOTE_EMBEDDER_VALIDATION_TIMEOUT_MS,
|
||||
@@ -305,6 +312,20 @@ describe("OpenRouterEmbedder", () => {
|
||||
)
|
||||
})
|
||||
|
||||
test("should reject responses without embedding data", async () => {
|
||||
mockEmbeddingsCreate.mockResolvedValue({
|
||||
error: {
|
||||
code: 404,
|
||||
message: "No successful provider responses.",
|
||||
},
|
||||
})
|
||||
|
||||
const result = await embedder.validateConfiguration()
|
||||
|
||||
expect(result.valid).toBe(false)
|
||||
expect(result.error).toBe("Invalid response from OpenRouter embedding endpoint")
|
||||
})
|
||||
|
||||
test("should handle validation failure", async () => {
|
||||
const authError = new Error("Invalid API key")
|
||||
;(authError as any).status = 401
|
||||
@@ -346,7 +367,7 @@ describe("OpenRouterEmbedder", () => {
|
||||
{
|
||||
input: ["test"],
|
||||
model: defaultModel,
|
||||
encoding_format: "base64",
|
||||
encoding_format: "float",
|
||||
provider: {
|
||||
order: [specificProvider],
|
||||
only: [specificProvider],
|
||||
@@ -388,7 +409,7 @@ describe("OpenRouterEmbedder", () => {
|
||||
{
|
||||
input: ["test"],
|
||||
model: defaultModel,
|
||||
encoding_format: "base64",
|
||||
encoding_format: "float",
|
||||
dimensions: 1024,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -149,11 +149,42 @@ describe("CodeIndexServiceFactory", () => {
|
||||
expect(mockEmbeddingsCreate).toHaveBeenCalledWith({
|
||||
input: ["hello"],
|
||||
model: "openai/text-embedding-3-small",
|
||||
encoding_format: "base64",
|
||||
encoding_format: "float",
|
||||
dimensions: 1024,
|
||||
})
|
||||
})
|
||||
|
||||
test("creates vector store for OpenRouter Gemini embedding preview", () => {
|
||||
const factory = createFactory({
|
||||
embedderProvider: "openrouter",
|
||||
openAiKey: undefined,
|
||||
openRouterApiKey: "or-test",
|
||||
modelId: "google/gemini-embedding-2-preview",
|
||||
vectorStoreProvider: "lancedb",
|
||||
})
|
||||
|
||||
const store = factory.createVectorStore() as unknown as { vectorSize: number }
|
||||
|
||||
expect(store).toBeDefined()
|
||||
expect(store.vectorSize).toBe(3072)
|
||||
})
|
||||
|
||||
test("uses configured dimension before static model metadata for vector stores", () => {
|
||||
const factory = createFactory({
|
||||
embedderProvider: "openrouter",
|
||||
openAiKey: undefined,
|
||||
openRouterApiKey: "or-test",
|
||||
modelId: "openai/text-embedding-3-small",
|
||||
modelDimension: 1024,
|
||||
vectorStoreProvider: "lancedb",
|
||||
})
|
||||
|
||||
const store = factory.createVectorStore() as unknown as { vectorSize: number }
|
||||
|
||||
expect(store).toBeDefined()
|
||||
expect(store.vectorSize).toBe(1024)
|
||||
})
|
||||
|
||||
test("creates Kilo embedder with Cloud-provided model", async () => {
|
||||
const factory = createFactory({
|
||||
embedderProvider: "kilo",
|
||||
@@ -177,6 +208,7 @@ describe("CodeIndexServiceFactory", () => {
|
||||
input: ["hello"],
|
||||
model: "mistralai/mistral-embed-2312",
|
||||
encoding_format: "base64",
|
||||
dimensions: 1024,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,7 +9,17 @@ AI coding agent plugin for JetBrains IDEs.
|
||||
### Prerequisites
|
||||
|
||||
- **Bun** -- used to build CLI binaries and run build scripts
|
||||
- **JDK 21+** -- required by Gradle and the IntelliJ Platform SDK
|
||||
- **JDK 21+** -- required by Gradle and the IntelliJ Platform SDK. Check with `java -version`. The preferred way to install is via [SDKMAN](https://sdkman.io/install):
|
||||
|
||||
```bash
|
||||
# Install SDKMAN (if not already installed)
|
||||
curl -s "https://get.sdkman.io" | bash
|
||||
|
||||
# Install and activate Java 21 (Eclipse Temurin)
|
||||
sdk install java 21-tem
|
||||
sdk use java 21-tem
|
||||
```
|
||||
|
||||
- **IntelliJ IDEA** -- to run the plugin in a sandboxed IDE
|
||||
|
||||
---
|
||||
|
||||
@@ -86,6 +86,10 @@ tasks.named("compileKotlin") {
|
||||
dependsOn(fixGeneratedApi)
|
||||
}
|
||||
|
||||
tasks.named("compileTestKotlin") {
|
||||
dependsOn(fixGeneratedApi)
|
||||
}
|
||||
|
||||
val cliDir = layout.buildDirectory.dir("generated/cli/cli")
|
||||
val production = providers.gradleProperty("production").map { it.toBoolean() }.orElse(false)
|
||||
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package normalization
|
||||
|
||||
internal data class DuplicateTagRule(
|
||||
val original: String,
|
||||
val dedups: List<TagDedup>,
|
||||
)
|
||||
|
||||
internal data class TagDedup(
|
||||
val name: String,
|
||||
val ops: List<String> = emptyList(),
|
||||
)
|
||||
|
||||
internal val duplicateTagRules = listOf(
|
||||
DuplicateTagRule(
|
||||
original = "pty",
|
||||
dedups = listOf(
|
||||
TagDedup(name = "pty"),
|
||||
TagDedup(
|
||||
name = "pty-connect",
|
||||
ops = listOf("pty.connect"),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
+137
-83
@@ -11,105 +11,159 @@ internal object OpenApiSpecNormalizer {
|
||||
fun normalize(raw: String): String {
|
||||
val root = Json.parseToJsonElement(raw) as? JsonObject
|
||||
?: throw GradleException("OpenAPI spec root must be a JSON object.")
|
||||
val tags = root["tags"] as? JsonArray ?: return raw
|
||||
val (fixedTags, renames) = fixTags(tags)
|
||||
if (renames.isEmpty()) return raw
|
||||
val paths = root["paths"] as? JsonObject
|
||||
?: throw GradleException("OpenAPI spec paths must be a JSON object.")
|
||||
val fixed = JsonObject(
|
||||
root + mapOf(
|
||||
"tags" to fixedTags,
|
||||
"paths" to fixPaths(paths, renames),
|
||||
)
|
||||
)
|
||||
// Step 1: Remove duplicate dot-notation schemas and remap their $refs to
|
||||
// camelCase equivalents so the spec remains self-consistent.
|
||||
// Step 2: Strip operation-level tags so all routes land in DefaultApi.
|
||||
// Step 3: Deduplicate the root-level tags array.
|
||||
// Step 4: Fix nullable fields in the /kilo/profile response that Effect's
|
||||
// OpenAPI generator incorrectly emits as non-nullable.
|
||||
val (noDotsRoot, _) = remapDotSchemas(root)
|
||||
val stripped = stripTags(noDotsRoot)
|
||||
val deduped = dedupRootTags(stripped)
|
||||
val fixed = fixProfileNullable(deduped)
|
||||
return encode(fixed)
|
||||
}
|
||||
|
||||
private fun encode(obj: JsonObject): String {
|
||||
val json = Json { prettyPrint = true }
|
||||
.encodeToString(JsonElement.serializer(), fixed)
|
||||
.encodeToString(JsonElement.serializer(), obj)
|
||||
return "$json\n"
|
||||
}
|
||||
|
||||
private fun fixTags(tags: JsonArray): Pair<JsonArray, Map<String, Rename>> {
|
||||
val rules = rules()
|
||||
val counts = mutableMapOf<String, Int>()
|
||||
val renames = mutableMapOf<String, Rename>()
|
||||
val fixed = tags.map { tag ->
|
||||
val item = tag as? JsonObject ?: return@map tag
|
||||
val name = text(item["name"]) ?: return@map tag
|
||||
val rule = rules[name] ?: return@map tag
|
||||
val index = counts.getOrDefault(name, 0)
|
||||
counts[name] = index + 1
|
||||
if (index >= rule.dedups.size) {
|
||||
throw GradleException("Missing final OpenAPI tag name for duplicate '$name' at index $index.")
|
||||
/**
|
||||
* Find schemas whose names contain dots (e.g. "Event.tui.command.execute").
|
||||
* If a camelCase equivalent (e.g. "EventTuiCommandExecute") exists in the
|
||||
* same component map, remove the dot schema and rewrite every `$ref` that
|
||||
* points to it to use the camelCase name instead.
|
||||
*/
|
||||
private fun remapDotSchemas(root: JsonObject): Pair<JsonObject, Map<String, String>> {
|
||||
val components = root["components"] as? JsonObject ?: return root to emptyMap()
|
||||
val schemas = components["schemas"] as? JsonObject ?: return root to emptyMap()
|
||||
|
||||
// Build a map of dot-name → camelCase-name for schemas that have a
|
||||
// camelCase duplicate in the same spec.
|
||||
val dotMap = schemas.keys
|
||||
.filter { "." in it }
|
||||
.mapNotNull { dot ->
|
||||
val camel = dot.split(".").joinToString("") { w -> w.replaceFirstChar { c -> c.uppercase() } }
|
||||
if (camel in schemas) dot to camel else null
|
||||
}
|
||||
val dedup = rule.dedups[index]
|
||||
if (index == 0) return@map tag
|
||||
renames[dedup.name] = Rename(rule.original, dedup)
|
||||
JsonObject(item + ("name" to JsonPrimitive(dedup.name)))
|
||||
}
|
||||
rules.forEach { (name, rule) ->
|
||||
val count = counts[name] ?: 0
|
||||
if (count > 1 && count != rule.dedups.size) {
|
||||
throw GradleException("Duplicate OpenAPI tag '$name' has $count entries but ${rule.dedups.size} final names.")
|
||||
}
|
||||
}
|
||||
return JsonArray(fixed) to renames
|
||||
.toMap()
|
||||
|
||||
if (dotMap.isEmpty()) return root to emptyMap()
|
||||
|
||||
// Remove dot schemas.
|
||||
val cleaned = JsonObject(schemas.filterKeys { it !in dotMap })
|
||||
val noDotsComponents = JsonObject(components + mapOf("schemas" to cleaned))
|
||||
val noDotsRoot = JsonObject(root + mapOf("components" to noDotsComponents))
|
||||
|
||||
// Rewrite $ref strings throughout the whole spec.
|
||||
val rewritten = rewriteRefs(noDotsRoot, dotMap)
|
||||
return rewritten to dotMap
|
||||
}
|
||||
|
||||
private fun fixPaths(paths: JsonObject, renames: Map<String, Rename>): JsonObject {
|
||||
val ops = renames.values.flatMap { rename ->
|
||||
rename.dedup.ops.map { id -> id to rename }
|
||||
}.toMap()
|
||||
val hits = mutableMapOf<String, Int>()
|
||||
val fixed = JsonObject(paths.mapValues { (_, item) ->
|
||||
/**
|
||||
* Recursively rewrite every JsonPrimitive `$ref` value that matches a
|
||||
* dot-notation schema name, replacing it with the camelCase equivalent.
|
||||
*/
|
||||
private fun rewriteRefs(element: JsonElement, map: Map<String, String>): JsonObject {
|
||||
return rewriteElement(element, map) as JsonObject
|
||||
}
|
||||
|
||||
private fun rewriteElement(element: JsonElement, map: Map<String, String>): JsonElement =
|
||||
when (element) {
|
||||
is JsonObject -> JsonObject(element.mapValues { (key, value) ->
|
||||
if (key == "\$ref" && value is JsonPrimitive) {
|
||||
val ref = value.content
|
||||
val prefix = "#/components/schemas/"
|
||||
if (ref.startsWith(prefix)) {
|
||||
val name = ref.removePrefix(prefix)
|
||||
val replaced = map[name]
|
||||
if (replaced != null) JsonPrimitive("$prefix$replaced") else value
|
||||
} else value
|
||||
} else rewriteElement(value, map)
|
||||
})
|
||||
is JsonArray -> JsonArray(element.map { rewriteElement(it, map) })
|
||||
else -> element
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the "tags" field from every operation so that openapi-generator
|
||||
* collects all operations into a single DefaultApi class.
|
||||
*/
|
||||
private fun stripTags(root: JsonObject): JsonObject {
|
||||
val paths = root["paths"] as? JsonObject ?: return root
|
||||
val stripped = JsonObject(paths.mapValues { (_, item) ->
|
||||
val path = item as? JsonObject ?: return@mapValues item
|
||||
JsonObject(path.mapValues { (_, op) ->
|
||||
val obj = op as? JsonObject ?: return@mapValues op
|
||||
val id = text(obj["operationId"]) ?: return@mapValues op
|
||||
val rename = ops[id] ?: return@mapValues op
|
||||
hits[id] = hits.getOrDefault(id, 0) + 1
|
||||
fixOp(obj, rename)
|
||||
if ("tags" !in obj) return@mapValues op
|
||||
JsonObject(obj.filterKeys { it != "tags" })
|
||||
})
|
||||
})
|
||||
ops.keys.forEach { id ->
|
||||
val count = hits[id] ?: 0
|
||||
if (count != 1) {
|
||||
throw GradleException("Expected one OpenAPI operation '$id' for tag normalization, found $count.")
|
||||
}
|
||||
}
|
||||
return fixed
|
||||
return JsonObject(root + mapOf("paths" to stripped))
|
||||
}
|
||||
|
||||
private fun fixOp(op: JsonObject, rename: Rename): JsonObject {
|
||||
val tags = op["tags"] as? JsonArray
|
||||
?: throw GradleException("OpenAPI operation must declare tags before tag normalization.")
|
||||
val count = tags.count { tag -> text(tag) == rename.from }
|
||||
if (count != 1) {
|
||||
throw GradleException("Expected one '${rename.from}' operation tag, found $count.")
|
||||
}
|
||||
return JsonObject(op + ("tags" to JsonArray(tags.map { tag ->
|
||||
if (text(tag) != rename.from) return@map tag
|
||||
JsonPrimitive(rename.dedup.name)
|
||||
})))
|
||||
/**
|
||||
* Fix the `/kilo/profile` GET 200 response schema: Effect's OpenAPI generator
|
||||
* emits `balance` and `currentOrgId` as non-nullable required fields even
|
||||
* though the server schema is `Schema.NullOr(...)`. Wrap each non-nullable
|
||||
* property in `anyOf: [<original-schema>, {"type": "null"}]` so the generated
|
||||
* Kotlin model uses a nullable type. Already-nullable properties (those that
|
||||
* already have `anyOf` containing `{"type":"null"}`) are left untouched.
|
||||
*/
|
||||
private fun fixProfileNullable(root: JsonObject): JsonObject {
|
||||
val paths = root["paths"] as? JsonObject ?: return root
|
||||
val profileItem = paths["/kilo/profile"] as? JsonObject ?: return root
|
||||
val getOp = profileItem["get"] as? JsonObject ?: return root
|
||||
val schema = getOp["responses"]
|
||||
?.let { it as? JsonObject }?.get("200")
|
||||
?.let { it as? JsonObject }?.get("content")
|
||||
?.let { it as? JsonObject }?.get("application/json")
|
||||
?.let { it as? JsonObject }?.get("schema")
|
||||
as? JsonObject ?: return root
|
||||
val props = schema["properties"] as? JsonObject ?: return root
|
||||
|
||||
val nullable = setOf("balance", "currentOrgId")
|
||||
val fixed = JsonObject(props.mapValues { (key, value) ->
|
||||
if (key !in nullable) return@mapValues value
|
||||
val obj = value as? JsonObject ?: return@mapValues value
|
||||
// Skip if already wrapped (has anyOf containing {type:null}).
|
||||
val existing = obj["anyOf"] as? JsonArray
|
||||
if (existing != null && existing.any {
|
||||
(it as? JsonObject)?.get("type")?.let { t -> (t as? JsonPrimitive)?.content } == "null"
|
||||
}) return@mapValues value
|
||||
JsonObject(mapOf("anyOf" to JsonArray(listOf(obj, JsonObject(mapOf("type" to JsonPrimitive("null")))))))
|
||||
})
|
||||
|
||||
// Rebuild nested objects up to root.
|
||||
val responses = getOp["responses"]!! as JsonObject
|
||||
val resp200 = responses["200"]!! as JsonObject
|
||||
val content = resp200["content"]!! as JsonObject
|
||||
val appJson = content["application/json"]!! as JsonObject
|
||||
val newSchema = JsonObject(schema + mapOf("properties" to fixed))
|
||||
val newApp = JsonObject(appJson + mapOf("schema" to newSchema))
|
||||
val newContent = JsonObject(content + mapOf("application/json" to newApp))
|
||||
val new200 = JsonObject(resp200 + mapOf("content" to newContent))
|
||||
val newResponses = JsonObject(responses + mapOf("200" to new200))
|
||||
val newGet = JsonObject(getOp + mapOf("responses" to newResponses))
|
||||
val newProfile = JsonObject(profileItem + mapOf("get" to newGet))
|
||||
val newPaths = JsonObject(paths + mapOf("/kilo/profile" to newProfile))
|
||||
return JsonObject(root + mapOf("paths" to newPaths))
|
||||
}
|
||||
|
||||
private fun rules(): Map<String, DuplicateTagRule> {
|
||||
if (duplicateTagRules.map { it.original }.toSet().size != duplicateTagRules.size) {
|
||||
throw GradleException("OpenAPI duplicate tag config must not repeat original tag names.")
|
||||
}
|
||||
return duplicateTagRules.associateBy { rule ->
|
||||
if (rule.dedups.size < 2 || rule.dedups.first().name != rule.original) {
|
||||
throw GradleException("OpenAPI tag rule '${rule.original}' must keep the original tag first.")
|
||||
}
|
||||
if (rule.dedups.map { it.name }.toSet().size != rule.dedups.size) {
|
||||
throw GradleException("OpenAPI tag rule '${rule.original}' must use unique dedup names.")
|
||||
}
|
||||
rule.original
|
||||
/**
|
||||
* Deduplicate the root-level "tags" array by name — the spec validator
|
||||
* rejects repeated tag names even when they describe different things.
|
||||
*/
|
||||
private fun dedupRootTags(root: JsonObject): JsonObject {
|
||||
val tags = root["tags"] as? JsonArray ?: return root
|
||||
val seen = mutableSetOf<String>()
|
||||
val deduped = tags.filter { tag ->
|
||||
val name = (tag as? JsonObject)?.let { (it["name"] as? JsonPrimitive)?.content }
|
||||
?: return@filter true
|
||||
seen.add(name)
|
||||
}
|
||||
return JsonObject(root + mapOf("tags" to JsonArray(deduped)))
|
||||
}
|
||||
|
||||
private fun text(value: JsonElement?) = (value as? JsonPrimitive)?.content
|
||||
|
||||
private data class Rename(
|
||||
val from: String,
|
||||
val dedup: TagDedup,
|
||||
)
|
||||
}
|
||||
|
||||
+190
-46
@@ -5,15 +5,13 @@ import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import org.gradle.api.GradleException
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class OpenApiSpecNormalizerTest {
|
||||
@Test
|
||||
fun `renames configured duplicate tags and linked operations`() {
|
||||
fun `strips tags from all operations`() {
|
||||
val raw = """
|
||||
{
|
||||
"paths": {
|
||||
@@ -23,61 +21,207 @@ class OpenApiSpecNormalizerTest {
|
||||
"operationId": "pty.list"
|
||||
}
|
||||
},
|
||||
"/pty/{ptyID}/connect": {
|
||||
"/session": {
|
||||
"post": {
|
||||
"tags": ["session"],
|
||||
"operationId": "session.create"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val root = obj(OpenApiSpecNormalizer.normalize(raw))
|
||||
val paths = obj(root["paths"])
|
||||
val pty = obj(obj(paths["/pty"])["get"])
|
||||
val session = obj(obj(paths["/session"])["post"])
|
||||
|
||||
assertNull(pty["tags"], "tags should be stripped from pty operation")
|
||||
assertNull(session["tags"], "tags should be stripped from session operation")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `leaves operations without tags unchanged`() {
|
||||
val raw = """
|
||||
{
|
||||
"paths": {
|
||||
"/health": {
|
||||
"get": {
|
||||
"tags": ["pty"],
|
||||
"operationId": "pty.connect"
|
||||
"operationId": "health.get"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val root = obj(OpenApiSpecNormalizer.normalize(raw))
|
||||
val paths = obj(root["paths"])
|
||||
val health = obj(obj(paths["/health"])["get"])
|
||||
|
||||
assertNull(health["tags"])
|
||||
assertEquals("health.get", text(health["operationId"]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `removes dot schemas and rewrites refs to camelCase equivalents`() {
|
||||
val raw = """
|
||||
{
|
||||
"paths": {
|
||||
"/tui/publish": {
|
||||
"post": {
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"${'$'}ref": "#/components/schemas/Event.tui.command.execute"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"EventTuiCommandExecute": { "type": "object" },
|
||||
"Event.tui.command.execute": { "type": "object" },
|
||||
"Session": { "type": "object" }
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val root = obj(OpenApiSpecNormalizer.normalize(raw))
|
||||
val schemas = obj(obj(root["components"])["schemas"])
|
||||
|
||||
assertNull(schemas["Event.tui.command.execute"], "dot schema should be removed")
|
||||
assert("EventTuiCommandExecute" in schemas) { "camelCase schema should be kept" }
|
||||
assert("Session" in schemas) { "non-dot schema should be kept" }
|
||||
|
||||
// Check that the $ref was rewritten
|
||||
val post = obj(obj(obj(obj(root["paths"])["/tui/publish"])["post"])["requestBody"])
|
||||
val schema = obj(obj(obj(post["content"])["application/json"])["schema"])
|
||||
assertEquals("#/components/schemas/EventTuiCommandExecute", text(schema["\$ref"]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `makes balance and currentOrgId nullable in kilo profile response`() {
|
||||
val raw = """
|
||||
{
|
||||
"paths": {
|
||||
"/kilo/profile": {
|
||||
"get": {
|
||||
"operationId": "kilo.profile",
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"profile": { "type": "object", "properties": { "email": { "type": "string" } }, "required": ["email"], "additionalProperties": false },
|
||||
"balance": { "type": "object", "properties": { "balance": { "type": "number" } }, "required": ["balance"], "additionalProperties": false },
|
||||
"currentOrgId": { "type": "string" }
|
||||
},
|
||||
"required": ["profile", "balance", "currentOrgId"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val root = obj(OpenApiSpecNormalizer.normalize(raw))
|
||||
val schema = obj(obj(obj(obj(obj(obj(root["paths"])["/kilo/profile"])["get"])["responses"])["200"])["content"])
|
||||
val props = obj(obj(obj(schema["application/json"])["schema"])["properties"])
|
||||
|
||||
// balance must be anyOf [object, null]
|
||||
val balance = obj(props["balance"])
|
||||
val balanceAnyOf = arr(balance["anyOf"])
|
||||
assertEquals(2, balanceAnyOf.size, "balance should have anyOf with 2 entries")
|
||||
val balanceTypes = balanceAnyOf.map { (it as? JsonObject)?.get("type").let { t -> (t as? JsonPrimitive)?.content } }
|
||||
assert("null" in balanceTypes) { "balance anyOf should include null but got $balanceTypes" }
|
||||
assert(balanceAnyOf.any { it is JsonObject && "properties" in it }) { "balance anyOf should include the object schema" }
|
||||
|
||||
// currentOrgId must be anyOf [string, null]
|
||||
val orgId = obj(props["currentOrgId"])
|
||||
val orgIdAnyOf = arr(orgId["anyOf"])
|
||||
assertEquals(2, orgIdAnyOf.size, "currentOrgId should have anyOf with 2 entries")
|
||||
val orgIdTypes = orgIdAnyOf.map { (it as? JsonObject)?.get("type").let { t -> (t as? JsonPrimitive)?.content } }
|
||||
assert("null" in orgIdTypes) { "currentOrgId anyOf should include null but got $orgIdTypes" }
|
||||
assert("string" in orgIdTypes) { "currentOrgId anyOf should include string but got $orgIdTypes" }
|
||||
|
||||
// profile must remain unchanged (not wrapped in anyOf)
|
||||
val profile = obj(props["profile"])
|
||||
assertNull(profile["anyOf"], "profile should not be wrapped in anyOf")
|
||||
assertEquals("object", text(profile["type"]))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `leaves already-nullable fields unchanged in kilo profile response`() {
|
||||
// If balance already has anyOf (i.e. the spec was generated correctly), normalizer must not double-wrap it.
|
||||
val raw = """
|
||||
{
|
||||
"paths": {
|
||||
"/kilo/profile": {
|
||||
"get": {
|
||||
"operationId": "kilo.profile",
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"profile": { "type": "object", "properties": { "email": { "type": "string" } }, "required": ["email"], "additionalProperties": false },
|
||||
"balance": { "anyOf": [{ "type": "object", "properties": { "balance": { "type": "number" } }, "required": ["balance"], "additionalProperties": false }, { "type": "null" }] },
|
||||
"currentOrgId": { "anyOf": [{ "type": "string" }, { "type": "null" }] }
|
||||
},
|
||||
"required": ["profile", "balance", "currentOrgId"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val root = obj(OpenApiSpecNormalizer.normalize(raw))
|
||||
val schema = obj(obj(obj(obj(obj(obj(root["paths"])["/kilo/profile"])["get"])["responses"])["200"])["content"])
|
||||
val props = obj(obj(obj(schema["application/json"])["schema"])["properties"])
|
||||
|
||||
// balance must still have exactly 2 anyOf entries (not wrapped again)
|
||||
val balance = obj(props["balance"])
|
||||
val balanceAnyOf = arr(balance["anyOf"])
|
||||
assertEquals(2, balanceAnyOf.size, "balance should still have exactly 2 anyOf entries, not be double-wrapped")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `deduplicates root-level tags array`() {
|
||||
val raw = """
|
||||
{
|
||||
"paths": {},
|
||||
"tags": [
|
||||
{ "name": "pty", "description": "PTY routes." },
|
||||
{ "name": "pty", "description": "PTY WebSocket route." }
|
||||
{ "name": "pty", "description": "PTY WebSocket route." },
|
||||
{ "name": "session", "description": "Session routes." }
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val root = obj(OpenApiSpecNormalizer.normalize(raw))
|
||||
val tags = arr(root["tags"]).map { text(obj(it)["name"]) }
|
||||
val paths = obj(root["paths"])
|
||||
val pty = obj(obj(paths["/pty"])["get"])
|
||||
val link = obj(obj(paths["/pty/{ptyID}/connect"])["get"])
|
||||
|
||||
assertEquals(listOf("pty", "pty-connect"), tags)
|
||||
assertEquals(listOf("pty"), arr(pty["tags"]).map(::text))
|
||||
assertEquals(listOf("pty-connect"), arr(link["tags"]).map(::text))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `keeps specs without duplicate configured tags unchanged`() {
|
||||
val raw = """
|
||||
{
|
||||
"tags": [
|
||||
{ "name": "pty", "description": "PTY routes." }
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
assertEquals(raw, OpenApiSpecNormalizer.normalize(raw))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fails when configured operation for renamed tag is absent`() {
|
||||
val raw = """
|
||||
{
|
||||
"paths": {},
|
||||
"tags": [
|
||||
{ "name": "pty", "description": "PTY routes." },
|
||||
{ "name": "pty", "description": "PTY WebSocket route." }
|
||||
]
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val err = assertFailsWith<GradleException> {
|
||||
OpenApiSpecNormalizer.normalize(raw)
|
||||
}
|
||||
|
||||
assertTrue(err.message?.contains("Expected one OpenAPI operation 'pty.connect'") == true)
|
||||
assertEquals(listOf("pty", "session"), tags, "duplicate pty tag should be removed")
|
||||
}
|
||||
|
||||
private fun obj(raw: String) = Json.parseToJsonElement(raw) as JsonObject
|
||||
|
||||
@@ -7,5 +7,9 @@
|
||||
"typecheck": "./gradlew typecheck",
|
||||
"test": "./gradlew test",
|
||||
"test:ci": "bun script/test-ci.ts"
|
||||
}
|
||||
},
|
||||
"version": "7.3.8",
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-telemetry",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Telemetry for Kilo CLI - PostHog analytics integration",
|
||||
|
||||
@@ -50,6 +50,8 @@ describe("TelemetryEvent", () => {
|
||||
expect(TelemetryEvent.COMMAND_USED).toBeDefined()
|
||||
expect(TelemetryEvent.TOOL_USED).toBeDefined()
|
||||
expect(TelemetryEvent.AGENT_USED).toBeDefined()
|
||||
expect(TelemetryEvent.SUGGESTION_SHOWN).toBeDefined()
|
||||
expect(TelemetryEvent.SUGGESTION_ACCEPTED).toBeDefined()
|
||||
})
|
||||
|
||||
test("indexing events are defined", () => {
|
||||
@@ -88,4 +90,9 @@ describe("Telemetry", () => {
|
||||
expect(typeof Telemetry.trackIndexingBatchRetry).toBe("function")
|
||||
expect(typeof Telemetry.trackIndexingError).toBe("function")
|
||||
})
|
||||
|
||||
test("suggestion helper is exposed", () => {
|
||||
expect(typeof Telemetry.trackSuggestionShown).toBe("function")
|
||||
expect(typeof Telemetry.trackSuggestionAccepted).toBe("function")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,6 +16,8 @@ export enum TelemetryEvent {
|
||||
TOOL_USED = "Tool Used",
|
||||
AGENT_USED = "Agent Used",
|
||||
PLAN_FOLLOWUP = "Plan Followup",
|
||||
SUGGESTION_SHOWN = "Suggestion Shown",
|
||||
SUGGESTION_ACCEPTED = "Suggestion Accepted",
|
||||
|
||||
// Code Indexing
|
||||
INDEXING_STARTED = "Indexing Started",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { Telemetry } from "./telemetry.js"
|
||||
export { TelemetryEvent } from "./events.js"
|
||||
export { Identity } from "./identity.js"
|
||||
export type { TelemetryProperties } from "./telemetry.js"
|
||||
export type { ReviewCommand, TelemetryProperties } from "./telemetry.js"
|
||||
|
||||
@@ -10,6 +10,8 @@ export interface TelemetryProperties {
|
||||
vscodeVersion?: string
|
||||
}
|
||||
|
||||
export type ReviewCommand = "review" | "local-review" | "local-review-uncommitted"
|
||||
|
||||
export interface IndexingTelemetryProperties extends Record<string, unknown> {
|
||||
source: "scan" | "watcher"
|
||||
provider: string
|
||||
@@ -156,7 +158,8 @@ export namespace Telemetry {
|
||||
taskId?: string
|
||||
mode?: "review"
|
||||
feature?: "code_reviews"
|
||||
command?: "review" | "local-review" | "local-review-uncommitted"
|
||||
command?: ReviewCommand
|
||||
tool?: "suggest"
|
||||
apiProvider: string
|
||||
modelId: string
|
||||
inputTokens?: number
|
||||
@@ -187,6 +190,28 @@ export namespace Telemetry {
|
||||
track(TelemetryEvent.PLAN_FOLLOWUP, { sessionId, choice })
|
||||
}
|
||||
|
||||
export function trackSuggestionAccepted(properties: {
|
||||
sessionId: string
|
||||
requestId: string
|
||||
index: number
|
||||
tool: "suggest"
|
||||
command: ReviewCommand
|
||||
actionCount?: number
|
||||
}) {
|
||||
track(TelemetryEvent.SUGGESTION_ACCEPTED, properties)
|
||||
}
|
||||
|
||||
export function trackSuggestionShown(properties: {
|
||||
sessionId: string
|
||||
requestId: string
|
||||
index: number
|
||||
tool: "suggest"
|
||||
command: ReviewCommand
|
||||
actionCount?: number
|
||||
}) {
|
||||
track(TelemetryEvent.SUGGESTION_SHOWN, properties)
|
||||
}
|
||||
|
||||
export function trackIndexingStarted(properties: IndexingTelemetryProperties) {
|
||||
track(TelemetryEvent.INDEXING_STARTED, properties)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -197,7 +197,9 @@
|
||||
|
||||
[data-slot="user-message-text"] [data-highlight="file"][data-clickable] {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s, box-shadow 0.15s;
|
||||
transition:
|
||||
background-color 0.15s,
|
||||
box-shadow 0.15s;
|
||||
}
|
||||
|
||||
[data-slot="user-message-text"] [data-highlight="file"][data-clickable]:hover {
|
||||
|
||||
@@ -29,7 +29,9 @@ describe("reasoning heading", () => {
|
||||
})
|
||||
|
||||
test("promotes HTML headings while flattening inline tags", () => {
|
||||
expect(reasoningHeading('<h3 class="step">Check <em>provider</em> status</h3>\n\nContinue with the next item.')).toEqual({
|
||||
expect(
|
||||
reasoningHeading('<h3 class="step">Check <em>provider</em> status</h3>\n\nContinue with the next item.'),
|
||||
).toEqual({
|
||||
title: "Check provider status",
|
||||
body: "Continue with the next item.",
|
||||
})
|
||||
|
||||
@@ -280,8 +280,8 @@ html[data-theme="kilo-vscode"] {
|
||||
--syntax-diff-unknown: var(--vscode-charts-red);
|
||||
|
||||
/* ===== Pierre diff engine (theme its CSS vars to VS Code editor colors) ===== */
|
||||
--diffs-light-bg: var(--vscode-editor-background, #1e1e1e);
|
||||
--diffs-dark-bg: var(--vscode-editor-background, #1e1e1e);
|
||||
--diffs-light-bg: var(--background-stronger);
|
||||
--diffs-dark-bg: var(--background-stronger);
|
||||
|
||||
--diffs-bg-addition-override: color-mix(
|
||||
in lab,
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
# kilo-code
|
||||
|
||||
## 7.3.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#10117](https://github.com/Kilo-Org/kilocode/pull/10117) [`8502012`](https://github.com/Kilo-Org/kilocode/commit/85020124b6a56acd093e0b1abf709b420ca863e1) Thanks [@shssoichiro](https://github.com/shssoichiro)! - Fix token usage display to show usage for entire session
|
||||
|
||||
## 7.3.7
|
||||
|
||||
## 7.3.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#10481](https://github.com/Kilo-Org/kilocode/pull/10481) [`15bdd0f`](https://github.com/Kilo-Org/kilocode/commit/15bdd0f96f3f6713b552436806f6e218528443ab) - Prevent VS Code local CLI reconnect flapping while the event stream is unavailable.
|
||||
|
||||
- [#10479](https://github.com/Kilo-Org/kilocode/pull/10479) [`048338a`](https://github.com/Kilo-Org/kilocode/commit/048338a31422fd057bcaefc8db6c397cf33adff1) - Clear Agent Manager file diff previews stuck on `Loading diff...` when the active diff source stops or changes.
|
||||
|
||||
- [#10480](https://github.com/Kilo-Org/kilocode/pull/10480) [`9067de0`](https://github.com/Kilo-Org/kilocode/commit/9067de039ed820eab757d1e798abe264315c6f02) - Improve VS Code diff rendering stability across review and chat surfaces.
|
||||
|
||||
- Updated dependencies [[`205e22e`](https://github.com/Kilo-Org/kilocode/commit/205e22ee4672305d3cb2e0c34b607a4950f8f4e8), [`9067de0`](https://github.com/Kilo-Org/kilocode/commit/9067de039ed820eab757d1e798abe264315c6f02)]:
|
||||
- @kilocode/kilo-indexing@7.3.5
|
||||
- @kilocode/kilo-ui@7.3.5
|
||||
- @opencode-ai/ui@7.3.5
|
||||
|
||||
## 7.3.3
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "kilo-code",
|
||||
"displayName": "Kilo Code: AI Coding Agent, Copilot, and Autocomplete",
|
||||
"description": "Open Source AI coding agent that generates code from natural language, automates tasks, and runs terminal commands. Features inline autocomplete, browser automation, automated refactoring, and custom modes for planning, coding, and debugging. Supports 500+ AI models including Claude (Anthropic), Gemini, Grok, GPT, Codex and GLM.",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"icon": "assets/icons/logo-outline-black.png",
|
||||
"galleryBanner": {
|
||||
"color": "#FFFFFF",
|
||||
|
||||
@@ -23,6 +23,7 @@ const kiloVscodeDir = join(import.meta.dir, "..")
|
||||
const packagesDir = join(kiloVscodeDir, "..")
|
||||
const opencodeDir = join(packagesDir, "opencode")
|
||||
const coreDir = join(packagesDir, "core")
|
||||
const indexingDir = join(packagesDir, "kilo-indexing")
|
||||
|
||||
const targetBinDir = join(kiloVscodeDir, "bin")
|
||||
const binName = process.platform === "win32" ? "kilo.exe" : "kilo"
|
||||
@@ -37,7 +38,8 @@ async function cliSourceHash(): Promise<string | null> {
|
||||
try {
|
||||
const opencodeResult = await $`git log -1 --format=%H -- .`.cwd(opencodeDir).quiet()
|
||||
const coreResult = await $`git log -1 --format=%H -- .`.cwd(coreDir).quiet()
|
||||
return `${opencodeResult.text().trim()}-${coreResult.text().trim()}` || null
|
||||
const indexingResult = await $`git log -1 --format=%H -- .`.cwd(indexingDir).quiet()
|
||||
return `${opencodeResult.text().trim()}-${coreResult.text().trim()}-${indexingResult.text().trim()}` || null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
@@ -47,7 +49,12 @@ async function isDirty(): Promise<boolean> {
|
||||
try {
|
||||
const opencodeResult = await $`git status --porcelain -- .`.cwd(opencodeDir).quiet()
|
||||
const coreResult = await $`git status --porcelain -- .`.cwd(coreDir).quiet()
|
||||
return opencodeResult.text().trim().length > 0 || coreResult.text().trim().length > 0
|
||||
const indexingResult = await $`git status --porcelain -- .`.cwd(indexingDir).quiet()
|
||||
return (
|
||||
opencodeResult.text().trim().length > 0 ||
|
||||
coreResult.text().trim().length > 0 ||
|
||||
indexingResult.text().trim().length > 0
|
||||
)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -181,8 +181,12 @@ export class SourceController {
|
||||
return
|
||||
}
|
||||
const diff = await source.fetchFile(file).catch(() => null)
|
||||
// Drop the response if the source has been disposed/swapped while we waited.
|
||||
if (this.epoch !== epoch) return
|
||||
// Discard stale content after disposal/swap, but still complete the request
|
||||
// so consumers can clear per-file loading state.
|
||||
if (this.epoch !== epoch) {
|
||||
this.send(this.messages.diffFile(source, file, null))
|
||||
return
|
||||
}
|
||||
this.send(this.messages.diffFile(source, file, diff))
|
||||
}
|
||||
|
||||
|
||||
@@ -584,7 +584,8 @@ export class KiloConnectionService {
|
||||
|
||||
this.sseClient = new SdkSSEAdapter(this.client)
|
||||
|
||||
// Wait until SSE actually reaches a terminal state before resolving connect().
|
||||
// Wait until SSE yields its first server event before resolving connect().
|
||||
// Initial stream failures are handled by the adapter reconnect loop.
|
||||
let resolveConnected: (() => void) | null = null
|
||||
let rejectConnected: ((error: Error) => void) | null = null
|
||||
const connectedPromise = new Promise<void>((resolve, reject) => {
|
||||
@@ -601,11 +602,8 @@ export class KiloConnectionService {
|
||||
}
|
||||
})
|
||||
|
||||
this.sseClient.onError((error) => {
|
||||
this.sseClient.onError(() => {
|
||||
this.setState("error")
|
||||
rejectConnected?.(error)
|
||||
resolveConnected = null
|
||||
rejectConnected = null
|
||||
})
|
||||
|
||||
// Wire SSE state → broadcast to all registered state listeners
|
||||
|
||||
@@ -15,9 +15,8 @@ export type SSEStateHandler = (state: "connecting" | "connected" | "disconnected
|
||||
*
|
||||
* In this VS Code extension context the connection is localhost (extension ↔
|
||||
* child-process server), so zombie-connection scenarios are less likely than in
|
||||
* a browser client (which goes through proxies/CDNs). We keep the heartbeat for
|
||||
* consistency with the original strategy but use a generous 90 s timeout to
|
||||
* avoid false-positive reconnections during idle periods.
|
||||
* a browser client (which goes through proxies/CDNs). We still keep a heartbeat
|
||||
* grace window so dead local streams recover without waiting indefinitely.
|
||||
*
|
||||
* NOTE on event coalescing:
|
||||
* The app batches rapid events into 16 ms windows before flushing to the UI.
|
||||
@@ -37,6 +36,7 @@ export class SdkSSEAdapter {
|
||||
// Reduced from 90s: with 90s a dead connection could linger for ~1.5 minutes.
|
||||
private static readonly HEARTBEAT_TIMEOUT_MS = 15_000
|
||||
private static readonly RECONNECT_DELAY_MS = 250
|
||||
private static readonly MAX_RECONNECT_DELAY_MS = 5_000
|
||||
|
||||
constructor(private readonly client: KiloClient) {}
|
||||
|
||||
@@ -126,8 +126,11 @@ export class SdkSSEAdapter {
|
||||
* Main reconnection loop — mirrors the pattern in `global-sdk.tsx`.
|
||||
*/
|
||||
private async consumeLoop(signal: AbortSignal): Promise<void> {
|
||||
let delay = SdkSSEAdapter.RECONNECT_DELAY_MS
|
||||
|
||||
while (!signal.aborted) {
|
||||
const attempt = new AbortController()
|
||||
let ready = false
|
||||
|
||||
// Forward the outer abort to the per-attempt controller so
|
||||
// `disconnect()` cancels the current fetch immediately.
|
||||
@@ -159,8 +162,7 @@ export class SdkSSEAdapter {
|
||||
},
|
||||
})
|
||||
|
||||
console.log("[Kilo New] SSE: ✅ Stream opened successfully")
|
||||
this.notifyState("connected")
|
||||
console.log("[Kilo New] SSE: ⏳ Waiting for first stream event")
|
||||
this.resetHeartbeat(attempt)
|
||||
|
||||
for await (const event of events.stream) {
|
||||
@@ -170,6 +172,13 @@ export class SdkSSEAdapter {
|
||||
|
||||
this.resetHeartbeat(attempt)
|
||||
|
||||
if (!ready) {
|
||||
ready = true
|
||||
delay = SdkSSEAdapter.RECONNECT_DELAY_MS
|
||||
console.log("[Kilo New] SSE: ✅ Stream opened successfully")
|
||||
this.notifyState("connected")
|
||||
}
|
||||
|
||||
// The SDK yields GlobalEvent = { directory, payload: Event }.
|
||||
const globalEvent = event as GlobalEvent
|
||||
const type = (globalEvent.payload as { type: string }).type
|
||||
@@ -179,7 +188,9 @@ export class SdkSSEAdapter {
|
||||
this.notifyEvent(globalEvent.payload as Event, globalEvent.directory)
|
||||
}
|
||||
|
||||
console.log("[Kilo New] SSE: 📭 Stream ended normally")
|
||||
console.log(
|
||||
ready ? "[Kilo New] SSE: 📭 Stream ended normally" : "[Kilo New] SSE: 📭 Stream ended before first event",
|
||||
)
|
||||
} catch (error) {
|
||||
// Suppress AbortErrors — they are expected when the heartbeat timer
|
||||
// or reconnect() aborts the per-attempt controller.
|
||||
@@ -198,9 +209,11 @@ export class SdkSSEAdapter {
|
||||
break
|
||||
}
|
||||
|
||||
console.log(`[Kilo New] SSE: 🔄 Reconnecting in ${SdkSSEAdapter.RECONNECT_DELAY_MS}ms...`)
|
||||
const wait = delay
|
||||
delay = ready ? SdkSSEAdapter.RECONNECT_DELAY_MS : Math.min(delay * 2, SdkSSEAdapter.MAX_RECONNECT_DELAY_MS)
|
||||
console.log(`[Kilo New] SSE: 🔄 Reconnecting in ${wait}ms...`)
|
||||
this.notifyState("connecting")
|
||||
await new Promise((resolve) => setTimeout(resolve, SdkSSEAdapter.RECONNECT_DELAY_MS))
|
||||
await new Promise((resolve) => setTimeout(resolve, wait))
|
||||
}
|
||||
|
||||
this.notifyState("disconnected")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"peerDependencies": {}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import type { KiloClient } from "@kilocode/sdk/v2/client"
|
||||
import { KiloConnectionService } from "../../src/services/cli-backend/connection-service"
|
||||
import { SdkSSEAdapter } from "../../src/services/cli-backend/sdk-sse-adapter"
|
||||
|
||||
type Opts = {
|
||||
onSseError?: (error: unknown) => void
|
||||
signal?: AbortSignal
|
||||
}
|
||||
|
||||
type Stream = AsyncGenerator<unknown, void, unknown>
|
||||
|
||||
function client(open: (opts: Opts) => Stream): KiloClient {
|
||||
return {
|
||||
global: {
|
||||
event: async (opts: Opts) => ({ stream: open(opts) }),
|
||||
},
|
||||
} as unknown as KiloClient
|
||||
}
|
||||
|
||||
function event() {
|
||||
return {
|
||||
directory: "/repo",
|
||||
payload: {
|
||||
id: "evt_connected",
|
||||
type: "server.connected",
|
||||
properties: {},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function wait(ms: number) {
|
||||
return new Promise<void>((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
function aborted(signal?: AbortSignal) {
|
||||
if (!signal || signal.aborted) return Promise.resolve()
|
||||
return new Promise<void>((resolve) => signal.addEventListener("abort", () => resolve(), { once: true }))
|
||||
}
|
||||
|
||||
describe("SdkSSEAdapter", () => {
|
||||
it("reports connected only after the first SSE event arrives", async () => {
|
||||
let release = () => {}
|
||||
const gate = new Promise<void>((resolve) => {
|
||||
release = resolve
|
||||
})
|
||||
const adapter = new SdkSSEAdapter(
|
||||
client(async function* (opts) {
|
||||
await gate
|
||||
yield event()
|
||||
await aborted(opts.signal)
|
||||
}),
|
||||
)
|
||||
const states: string[] = []
|
||||
const connected = new Promise<void>((resolve) => {
|
||||
adapter.onStateChange((state) => {
|
||||
states.push(state)
|
||||
if (state === "connected") resolve()
|
||||
})
|
||||
})
|
||||
|
||||
adapter.connect()
|
||||
await wait(10)
|
||||
|
||||
expect(states).toEqual(["connecting"])
|
||||
|
||||
release()
|
||||
await connected
|
||||
|
||||
expect(states).toEqual(["connecting", "connected"])
|
||||
adapter.disconnect()
|
||||
})
|
||||
|
||||
it("backs off reconnects when an SSE fetch fails before opening", async () => {
|
||||
const timer = globalThis.setTimeout
|
||||
const delays: number[] = []
|
||||
let count = 0
|
||||
let finish = () => {}
|
||||
const reached = new Promise<void>((resolve) => {
|
||||
finish = resolve
|
||||
})
|
||||
globalThis.setTimeout = ((handler: TimerHandler, timeout?: number, ...args: unknown[]) => {
|
||||
if (typeof timeout === "number" && timeout <= 5_000) {
|
||||
delays.push(timeout)
|
||||
return timer(handler, 0, ...args)
|
||||
}
|
||||
return timer(handler, timeout, ...args)
|
||||
}) as typeof setTimeout
|
||||
|
||||
const failing = new SdkSSEAdapter(
|
||||
client((opts) => {
|
||||
count += 1
|
||||
if (count === 3) finish()
|
||||
return (async function* () {
|
||||
opts.onSseError?.(new TypeError("fetch failed"))
|
||||
})()
|
||||
}),
|
||||
)
|
||||
|
||||
try {
|
||||
failing.connect()
|
||||
await reached
|
||||
failing.disconnect()
|
||||
|
||||
expect(delays.slice(0, 2)).toEqual([250, 500])
|
||||
} finally {
|
||||
failing.disconnect()
|
||||
globalThis.setTimeout = timer
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("KiloConnectionService SSE startup", () => {
|
||||
it("waits through an initial SSE fetch failure until the stream opens", async () => {
|
||||
const original = globalThis.fetch
|
||||
const chunk = new TextEncoder().encode(
|
||||
'data: {"payload":{"id":"evt_connected","type":"server.connected","properties":{}}}\n\n',
|
||||
)
|
||||
let calls = 0
|
||||
globalThis.fetch = (async () => {
|
||||
calls += 1
|
||||
if (calls === 1) throw new TypeError("fetch failed")
|
||||
return new Response(
|
||||
new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
controller.enqueue(chunk)
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { "content-type": "text/event-stream" },
|
||||
},
|
||||
)
|
||||
}) as typeof fetch
|
||||
|
||||
const service = new KiloConnectionService({} as any)
|
||||
;(service as any).serverManager.getServer = async () => ({ port: 52512, password: "secret", process: {} })
|
||||
|
||||
try {
|
||||
await expect(service.connect("/tmp/workspace")).resolves.toBeUndefined()
|
||||
expect(calls).toBe(2)
|
||||
expect(service.getConnectionState()).toBe("connected")
|
||||
} finally {
|
||||
service.dispose()
|
||||
globalThis.fetch = original
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
computeStatus,
|
||||
calcTotalCost,
|
||||
calcContextUsage,
|
||||
calcTokenUsage,
|
||||
buildFamilyCosts,
|
||||
buildFamilyParents,
|
||||
buildFamilyLabels,
|
||||
@@ -141,6 +142,35 @@ describe("calcContextUsage", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("calcTokenUsage", () => {
|
||||
it("sums assistant message input, output, and cache read tokens", () => {
|
||||
const result = calcTokenUsage([
|
||||
{ role: "assistant", tokens: { input: 100, output: 40, reasoning: 8, cache: { read: 10, write: 5 } } },
|
||||
{ role: "assistant", tokens: { input: 25, output: 15, cache: { read: 7, write: 3 } } },
|
||||
])
|
||||
|
||||
expect(result).toEqual({ input: 125, output: 55, cached: 17 })
|
||||
})
|
||||
|
||||
it("ignores user messages, missing tokens, reasoning tokens, and cache writes", () => {
|
||||
const result = calcTokenUsage([
|
||||
{ role: "user", tokens: { input: 999, output: 999, cache: { read: 999, write: 999 } } },
|
||||
{ role: "assistant" },
|
||||
{ role: "assistant", tokens: { input: 10, output: 4, reasoning: 30, cache: { read: 2, write: 20 } } },
|
||||
])
|
||||
|
||||
expect(result).toEqual({ input: 10, output: 4, cached: 2 })
|
||||
})
|
||||
|
||||
it("returns undefined when there are no displayed token counts", () => {
|
||||
const result = calcTokenUsage([
|
||||
{ role: "assistant", tokens: { input: 0, output: 0, reasoning: 12, cache: { read: 0, write: 6 } } },
|
||||
])
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
// ── Cost breakdown helpers ──────────────────────────────────────────────
|
||||
|
||||
function msg(id: string, role: string, cost?: number) {
|
||||
|
||||
@@ -345,6 +345,40 @@ describe("SourceController.requestFile", () => {
|
||||
|
||||
controller.stop()
|
||||
})
|
||||
|
||||
it("posts null when a pending fetchFile result is invalidated by stop", async () => {
|
||||
let release: () => void = () => {}
|
||||
const workspace: DiffSource = {
|
||||
descriptor: WORKSPACE_DESC,
|
||||
async fetch() {
|
||||
return { diffs: [] }
|
||||
},
|
||||
async fetchFile() {
|
||||
await new Promise<void>((resolve) => (release = resolve))
|
||||
return {
|
||||
file: "foo.ts",
|
||||
before: "a",
|
||||
after: "b",
|
||||
additions: 1,
|
||||
deletions: 1,
|
||||
}
|
||||
},
|
||||
}
|
||||
const { controller, posted } = make({ workspace })
|
||||
|
||||
controller.setContext({ workspaceRoot: "/repo" })
|
||||
await controller.activate("workspace")
|
||||
posted.length = 0
|
||||
const request = controller.requestFile("foo.ts")
|
||||
controller.stop()
|
||||
release()
|
||||
await request
|
||||
|
||||
const files = byType(posted, "diffViewer.diffFile")
|
||||
expect(files).toHaveLength(1)
|
||||
expect(files[0]!.file).toBe("foo.ts")
|
||||
expect(files[0]!.diff).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe("SourceController.reactivate", () => {
|
||||
|
||||
@@ -14,7 +14,7 @@ import { Tooltip } from "@kilocode/kilo-ui/tooltip"
|
||||
import { Icon } from "@kilocode/kilo-ui/icon"
|
||||
import { Checkbox } from "@kilocode/kilo-ui/checkbox"
|
||||
import { useSession } from "../../context/session"
|
||||
import { collapseCostBreakdown } from "../../context/session-utils"
|
||||
import { calcTokenUsage, collapseCostBreakdown } from "../../context/session-utils"
|
||||
import { useLanguage } from "../../context/language"
|
||||
import { useVSCode } from "../../context/vscode"
|
||||
import { TaskTimeline } from "./TaskTimeline"
|
||||
@@ -66,18 +66,7 @@ export const TaskHeader: Component<TaskHeaderProps> = (props) => {
|
||||
return { tokens, pct }
|
||||
})
|
||||
|
||||
// Token breakdown from the last assistant message — only return if at least one value is > 0
|
||||
const tokens = createMemo(() => {
|
||||
const msgs = session.visibleMessages()
|
||||
for (let i = msgs.length - 1; i >= 0; i--) {
|
||||
const m = msgs[i]
|
||||
if (m.role !== "assistant" || !m.tokens) continue
|
||||
const tk = m.tokens
|
||||
const has = tk.input > 0 || tk.output > 0 || (tk.cache?.write ?? 0) > 0 || (tk.cache?.read ?? 0) > 0
|
||||
if (has) return tk
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
const tokens = createMemo(() => calcTokenUsage(session.visibleMessages()))
|
||||
|
||||
const hasTimeline = createMemo(() => {
|
||||
for (const m of session.visibleMessages()) {
|
||||
@@ -207,16 +196,10 @@ export const TaskHeader: Component<TaskHeaderProps> = (props) => {
|
||||
{fmtNum(tk().output)}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={tk().cache?.write && tk().cache!.write > 0}>
|
||||
<span class="task-header-tokens-value">
|
||||
<Icon name="arrow-up" size="small" />
|
||||
cache {fmtNum(tk().cache!.write)}
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={tk().cache?.read && tk().cache!.read > 0}>
|
||||
<Show when={tk().cached > 0}>
|
||||
<span class="task-header-tokens-value">
|
||||
<Icon name="arrow-down-to-line" size="small" />
|
||||
cache {fmtNum(tk().cache!.read)}
|
||||
cache {fmtNum(tk().cached)}
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
@@ -98,6 +98,35 @@ export function calcContextUsage(
|
||||
return { tokens: total, percentage }
|
||||
}
|
||||
|
||||
export type TokenUsageMessage = {
|
||||
role: string
|
||||
tokens?: {
|
||||
input: number
|
||||
output: number
|
||||
reasoning?: number
|
||||
cache?: { read: number; write: number }
|
||||
}
|
||||
}
|
||||
|
||||
export function calcTokenUsage(
|
||||
messages: TokenUsageMessage[],
|
||||
): { input: number; output: number; cached: number } | undefined {
|
||||
const total = messages.reduce(
|
||||
(sum, m) => {
|
||||
if (m.role !== "assistant" || !m.tokens) return sum
|
||||
return {
|
||||
input: sum.input + m.tokens.input,
|
||||
output: sum.output + m.tokens.output,
|
||||
cached: sum.cached + (m.tokens.cache?.read ?? 0),
|
||||
}
|
||||
},
|
||||
{ input: 0, output: 0, cached: 0 },
|
||||
)
|
||||
|
||||
if (total.input > 0 || total.output > 0 || total.cached > 0) return total
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a map of session ID → **own cost** for each session in the family
|
||||
* that has non-zero own cost.
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# @kilocode/cli
|
||||
|
||||
## 7.3.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#8403](https://github.com/Kilo-Org/kilocode/pull/8403) [`42844e5`](https://github.com/Kilo-Org/kilocode/commit/42844e505475650c16f92251421ad792c6429184) Thanks [@saschabuehrle](https://github.com/saschabuehrle)! - Accept `env` as an alias for `environment` in local MCP server configuration. Configurations using the more common `env` key (matching Docker, npm, and VS Code conventions) are now normalised on load instead of failing strict validation.
|
||||
|
||||
- [#10495](https://github.com/Kilo-Org/kilocode/pull/10495) [`ae0fbe8`](https://github.com/Kilo-Org/kilocode/commit/ae0fbe89dc5859fcea3c5d1e459a77eb459a8f71) - Show recent and favorited models in provider-specific model lists.
|
||||
|
||||
## 7.3.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#10297](https://github.com/Kilo-Org/kilocode/pull/10297) [`74e8604`](https://github.com/Kilo-Org/kilocode/commit/74e860431f3f9fcbfcea764711b8c1487d9a8f8d) Thanks [@IamCoder18](https://github.com/IamCoder18)! - Vertically center TUI dialogs on screen
|
||||
|
||||
## 7.3.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`205e22e`](https://github.com/Kilo-Org/kilocode/commit/205e22ee4672305d3cb2e0c34b607a4950f8f4e8)]:
|
||||
- @kilocode/kilo-indexing@7.3.5
|
||||
|
||||
## 7.3.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE `event_sequence` ADD `owner_id` text;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.3.4",
|
||||
"version": "7.3.8",
|
||||
"name": "@kilocode/cli",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -83,7 +83,7 @@
|
||||
"dependencies": {
|
||||
"@actions/core": "1.11.1",
|
||||
"@actions/github": "6.0.1",
|
||||
"@agentclientprotocol/sdk": "0.16.1",
|
||||
"@agentclientprotocol/sdk": "0.21.0",
|
||||
"@ai-sdk/alibaba": "1.0.17",
|
||||
"@ai-sdk/amazon-bedrock": "4.0.96",
|
||||
"@ai-sdk/anthropic": "3.0.71",
|
||||
|
||||
@@ -776,9 +776,9 @@ const scenarios: Scenario[] = [
|
||||
}))
|
||||
.status(200),
|
||||
http
|
||||
.post("/experimental/workspace/{id}/session-restore", "experimental.workspace.sessionRestore")
|
||||
.post("/experimental/workspace/warp", "experimental.workspace.warp")
|
||||
.at((ctx) => ({
|
||||
path: route("/experimental/workspace/{id}/session-restore", { id: "wrk_httpapi_missing" }),
|
||||
path: "/experimental/workspace/warp",
|
||||
headers: ctx.headers(),
|
||||
body: {},
|
||||
}))
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
# Typed error migration
|
||||
|
||||
Plan for moving `packages/opencode` from temporary defect/`NamedError`
|
||||
compatibility toward typed Effect service errors and explicit HTTP error
|
||||
contracts.
|
||||
|
||||
## Goal
|
||||
|
||||
- Expected service failures live on the Effect error channel.
|
||||
- Service interfaces expose those failures in their return types.
|
||||
- Domain errors are authored with Effect Schema so they are reusable by services,
|
||||
tests, HTTP routes, tools, and OpenAPI generation.
|
||||
- HTTP status codes and wire compatibility are handled at the HTTP boundary, not
|
||||
inside service modules.
|
||||
- `Effect.die`, `throw`, `catchDefect`, and global cause inspection are reserved
|
||||
for defects, compatibility bridges, or final fallback behavior.
|
||||
|
||||
## Current State
|
||||
|
||||
- Many migrated services use Effect internally, but expected failures are still a
|
||||
mix of `NamedError.create(...)`, `namedSchemaError(...)`, `class extends Error`,
|
||||
`throw`, and `Effect.die(...)`.
|
||||
- Some services already use `Schema.TaggedErrorClass`, for example `Account`,
|
||||
`Auth`, `Permission`, `Question`, `Installation`, and parts of
|
||||
`Workspace`.
|
||||
- Legacy Hono error handling recognizes `NamedError`, `Session.BusyError`, and a
|
||||
few name-based cases, then emits the legacy `{ name, data }` JSON body.
|
||||
- Effect `HttpApi` only knows how to encode errors that are declared on the
|
||||
endpoint, group, or middleware. Undeclared expected errors become defects and
|
||||
eventually fall through to generic HTTP handling.
|
||||
- The temporary HttpApi error middleware catches defect-wrapped legacy errors to
|
||||
preserve runtime behavior, but it is intentionally a bridge rather than the
|
||||
final model.
|
||||
|
||||
## End State
|
||||
|
||||
Service modules own domain failures.
|
||||
|
||||
```ts
|
||||
export class SessionBusyError extends Schema.TaggedErrorClass<SessionBusyError>()("SessionBusyError", {
|
||||
sessionID: SessionID,
|
||||
message: Schema.String,
|
||||
}) {}
|
||||
|
||||
export type Error = Storage.Error | SessionBusyError
|
||||
|
||||
export interface Interface {
|
||||
readonly get: (id: SessionID) => Effect.Effect<Info, Error>
|
||||
}
|
||||
```
|
||||
|
||||
HTTP modules own transport mapping.
|
||||
|
||||
```ts
|
||||
const get = Effect.fn("SessionHttpApi.get")(function* (ctx: { params: { sessionID: SessionID } }) {
|
||||
return yield* session
|
||||
.get(ctx.params.sessionID)
|
||||
.pipe(
|
||||
Effect.catchTag("StorageNotFoundError", () => new SessionNotFoundHttpError({ sessionID: ctx.params.sessionID })),
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
HTTP-visible error schemas carry their own response status through Effect
|
||||
HttpApi's `httpApiStatus` annotation. Prefer `HttpApiSchema.status(...)`, or the
|
||||
equivalent declaration annotation, instead of maintaining a parallel status map.
|
||||
|
||||
```ts
|
||||
export class SessionNotFoundHttpError extends Schema.TaggedErrorClass<SessionNotFoundHttpError>()(
|
||||
"SessionNotFoundHttpError",
|
||||
{
|
||||
sessionID: SessionID,
|
||||
message: Schema.String,
|
||||
},
|
||||
{ httpApiStatus: 404 },
|
||||
) {}
|
||||
```
|
||||
|
||||
Endpoint definitions still declare which HTTP-visible error schemas can be
|
||||
emitted. The status annotation is only used if the error is part of the endpoint,
|
||||
group, or middleware error schema and the handler fails with that error on the
|
||||
typed error channel.
|
||||
|
||||
```ts
|
||||
HttpApiEndpoint.get("get", SessionPaths.get, {
|
||||
success: Session.Info,
|
||||
error: [SessionNotFoundHttpError, SessionBusyHttpError],
|
||||
})
|
||||
```
|
||||
|
||||
The service error and HTTP error may be the same class when the wire shape is a
|
||||
deliberate public contract. They should be different classes when the service
|
||||
error contains internals, low-level causes, retry hints, or anything that should
|
||||
not be exposed to API clients.
|
||||
|
||||
## Rules
|
||||
|
||||
- Use `Schema.TaggedErrorClass` for new expected domain errors.
|
||||
- Include `cause: Schema.optional(Schema.Defect)` only when preserving an
|
||||
underlying unknown failure is useful for logs or callers.
|
||||
- Export a domain-level error union from each service module, for example
|
||||
`export type Error = NotFoundError | BusyError | Storage.Error`.
|
||||
- Put expected errors in service method signatures, for example
|
||||
`Effect.Effect<Result, Service.Error, R>`.
|
||||
- Use `yield* new DomainError(...)` for direct early failures inside
|
||||
`Effect.gen` / `Effect.fn`.
|
||||
- Use `Effect.try({ try, catch })`, `Effect.mapError`, or `Effect.catchTag` to
|
||||
convert external exceptions into domain errors.
|
||||
- Use `HttpApiSchema.status(...)` or `{ httpApiStatus: code }` on HTTP-visible
|
||||
error schemas so Effect `HttpApiBuilder` and OpenAPI generation get the status
|
||||
from the schema itself.
|
||||
- Do not use `Effect.die(...)` for user, IO, validation, missing-resource, auth,
|
||||
provider, worktree, or busy-state failures.
|
||||
- Do not use `catchDefect` to recover expected domain errors. If recovery is
|
||||
needed, the upstream effect should fail with a typed error instead.
|
||||
- Do not make service modules import `HttpApiError`, `HttpServerResponse`, HTTP
|
||||
status codes, or route-specific error schemas.
|
||||
- Keep raw `HttpRouter` routes free to use `HttpServerRespondable` when that is
|
||||
the right transport abstraction, but prefer declared `HttpApi` errors for
|
||||
normal JSON API endpoints.
|
||||
|
||||
## HTTP Boundary Shape
|
||||
|
||||
Create an HttpApi-local error module, likely
|
||||
`src/server/routes/instance/httpapi/errors.ts`.
|
||||
|
||||
That module should provide:
|
||||
|
||||
- Legacy-compatible public schemas for `{ name, data }` error bodies that must
|
||||
remain SDK-compatible during the Hono migration.
|
||||
- Small constructors or mapping helpers for common API errors such as not found,
|
||||
bad request, conflict, and unknown internal errors.
|
||||
- Route-group-specific adapters only when they encode domain-specific public
|
||||
data.
|
||||
- A single place to document which public error shape is legacy-compatible and
|
||||
which shape is new Effect-native API surface.
|
||||
|
||||
Avoid one giant `unknown -> status` mapper. Prefer small, explicit mappers close
|
||||
to the handler or route group.
|
||||
|
||||
```ts
|
||||
const mapSessionError = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
||||
effect.pipe(
|
||||
Effect.catchTag("StorageNotFoundError", (error) => new SessionNotFoundHttpError({ message: error.message })),
|
||||
Effect.catchTag("SessionBusyError", (error) => new SessionBusyHttpError({ message: error.message })),
|
||||
)
|
||||
```
|
||||
|
||||
Use built-in `HttpApiError.BadRequest`, `HttpApiError.NotFound`, and related
|
||||
types only when their generated response body and SDK surface are intentionally
|
||||
acceptable. Use a custom schema-backed error when clients need the legacy
|
||||
`{ name, data }` body or a domain-specific error payload.
|
||||
|
||||
## Migration Phases
|
||||
|
||||
### 1. Stabilize The Bridge
|
||||
|
||||
Keep the temporary HttpApi error middleware only as a compatibility bridge while
|
||||
typed errors are introduced.
|
||||
|
||||
- Add tests that prove the bridge catches legacy `NamedError` defects.
|
||||
- Add tests that prove declared HttpApi errors still use the declared endpoint
|
||||
contract.
|
||||
- Stop returning stack traces in unknown HTTP `500` responses; log the full
|
||||
`Cause.pretty(cause)` server-side instead.
|
||||
- Add a comment or TODO that names this plan and states the bridge must shrink
|
||||
as route groups migrate.
|
||||
|
||||
### 2. Define The Shared HTTP Error Helpers
|
||||
|
||||
Add the `httpapi/errors.ts` module before converting route groups.
|
||||
|
||||
- Define a legacy `{ name, data }` body helper for SDK-compatible errors.
|
||||
- Define `UnknownError` for generic internal failures with a safe public message.
|
||||
- Define `BadRequestError` and `NotFoundError` equivalents only if the actual
|
||||
wire body must match the legacy Hono SDK surface.
|
||||
- Put the HTTP status on the public schema with `HttpApiSchema.status(...)` or
|
||||
`{ httpApiStatus: code }`; do not keep a separate name-to-status table.
|
||||
- Keep conversion helpers pure and small. They should not inspect `Cause` or
|
||||
accept `unknown` unless they are final fallback helpers.
|
||||
|
||||
### 3. Convert One Vertical Slice
|
||||
|
||||
Start with session read routes because they already have local `mapNotFound`
|
||||
logic and are heavily covered by existing HttpApi tests.
|
||||
|
||||
- Convert `Session.BusyError` from a plain `Error` to a typed service error, or
|
||||
add a typed wrapper while preserving the old constructor until callers are
|
||||
migrated.
|
||||
- Replace `catchDefect` in `httpapi/handlers/session.ts` with typed error
|
||||
mapping.
|
||||
- Add endpoint error schemas for the affected session endpoints.
|
||||
- Prove behavior with focused tests in `test/server/httpapi-session.test.ts`.
|
||||
- Remove the migrated cases from the global compatibility middleware.
|
||||
|
||||
### 4. Convert Legacy NamedError Domains
|
||||
|
||||
Move legacy `NamedError.create(...)` services to Effect Schema-backed errors in
|
||||
small domain PRs.
|
||||
|
||||
Priority order:
|
||||
|
||||
1. `storage/storage.ts` and `storage/db.ts` not-found errors.
|
||||
2. `worktree/index.ts` `Worktree*` errors.
|
||||
3. `provider/auth.ts` validation failures and `provider/provider.ts` model-not-found errors.
|
||||
4. `mcp/index.ts`, `skill/index.ts`, `lsp/client.ts`, and `ide/index.ts` service errors.
|
||||
5. Config and CLI-only errors after HTTP-facing domains are stable.
|
||||
|
||||
For each domain:
|
||||
|
||||
- Replace `NamedError.create(...)` with `Schema.TaggedErrorClass` when the error
|
||||
is primarily a service error.
|
||||
- Keep or add a separate HTTP error schema when the legacy `{ name, data }` wire
|
||||
shape must remain stable.
|
||||
- Update service interface return types to include the new error union.
|
||||
- Replace `throw new X(...)` inside `Effect.fn` with `yield* new X(...)`.
|
||||
- Replace async exceptions with `Effect.try({ catch })` or explicit `mapError`.
|
||||
- Add service-level tests that assert the error tag and data, not just the HTTP
|
||||
status.
|
||||
|
||||
### 5. Declare HttpApi Errors Group By Group
|
||||
|
||||
For each HttpApi group:
|
||||
|
||||
- Inventory every service call and the typed errors it can return.
|
||||
- Add only the public error schemas that endpoint can actually emit.
|
||||
- Map service errors to HTTP errors in the handler file.
|
||||
- Keep built-in `HttpApiError` only for generic request/validation failures where
|
||||
the generated contract is accepted.
|
||||
- Update `httpapi/public.ts` compatibility transforms only when the generated
|
||||
spec cannot represent the desired source shape directly.
|
||||
- Regenerate the SDK after OpenAPI-visible changes and verify the diff is
|
||||
intentional.
|
||||
|
||||
Suggested route order:
|
||||
|
||||
1. `session` not-found and busy-state reads.
|
||||
2. `experimental` worktree mutations.
|
||||
3. `provider` auth and model selection errors.
|
||||
4. `mcp` OAuth and connection errors.
|
||||
5. Remaining route groups as Hono deletion work progresses.
|
||||
|
||||
### 6. Remove Defect Recovery
|
||||
|
||||
After enough route groups declare their expected errors:
|
||||
|
||||
- Delete `catchDefect` recovery for domain errors.
|
||||
- Delete name-prefix checks such as `error.name.startsWith("Worktree")` from
|
||||
HTTP middleware.
|
||||
- Delete `NamedError` branches from the Effect HttpApi compatibility middleware
|
||||
once no Effect route depends on them.
|
||||
- Leave one final unknown-defect fallback that logs server-side and returns a
|
||||
safe generic `500` body.
|
||||
|
||||
## Inventory Checklist
|
||||
|
||||
Use this checklist when touching a service or route group.
|
||||
|
||||
- [ ] Does the service interface expose every expected failure in the Effect
|
||||
error type?
|
||||
- [ ] Are user-caused, provider-caused, IO, auth, missing-resource, and busy-state
|
||||
failures modeled as typed errors instead of defects?
|
||||
- [ ] Does the service avoid importing HTTP status, `HttpApiError`, or response
|
||||
classes?
|
||||
- [ ] Does the handler map each service error into a declared endpoint error?
|
||||
- [ ] Does the endpoint `error` field include every public error the handler can
|
||||
emit?
|
||||
- [ ] Does OpenAPI/SDK output either stay byte-identical or have an explicitly
|
||||
reviewed diff?
|
||||
- [ ] Do tests cover both service-level error typing and HTTP-level status/body?
|
||||
- [ ] Did the PR remove any now-unneeded case from the temporary compatibility
|
||||
middleware?
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
For service conversions:
|
||||
|
||||
- Test the service method directly with `testEffect(...)`.
|
||||
- Assert on `_tag` or class identity and the structured fields.
|
||||
- Avoid testing by string-matching `Cause.pretty(...)`.
|
||||
|
||||
For HttpApi conversions:
|
||||
|
||||
- Add or update the focused `test/server/httpapi-*.test.ts` file.
|
||||
- Assert status code, content type, and exact JSON body for declared public
|
||||
errors.
|
||||
- Add a regression test that the temporary middleware is no longer needed for the
|
||||
migrated route.
|
||||
- Keep bridge/parity tests aligned with legacy Hono behavior until Hono is
|
||||
deleted or the SDK contract intentionally changes.
|
||||
|
||||
## Verification Commands
|
||||
|
||||
Run from `packages/opencode` unless noted otherwise.
|
||||
|
||||
```bash
|
||||
bun run prettier --write <changed files>
|
||||
bunx oxlint <changed files>
|
||||
bun typecheck
|
||||
bun run test -- test/server/httpapi-session.test.ts
|
||||
```
|
||||
|
||||
Run SDK generation from the repo root when schemas or OpenAPI-visible errors
|
||||
change.
|
||||
|
||||
```bash
|
||||
./packages/sdk/js/script/build.ts
|
||||
```
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should legacy V1 routes keep `{ name, data }` forever while V2 routes expose a
|
||||
more Effect-native tagged error body?
|
||||
- Should storage not-found remain generic, or should callers map it to
|
||||
domain-specific not-found errors before crossing service boundaries?
|
||||
- Should `namedSchemaError(...)` stay as a long-term public-wire helper, or only
|
||||
as a migration bridge for old `NamedError` contracts?
|
||||
- Which SDK version boundary lets us stop remapping built-in Effect HttpApi error
|
||||
schemas in `httpapi/public.ts`?
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- New service code no longer uses `die` for expected failures.
|
||||
- A route reviewer can read an endpoint definition and see every public error it
|
||||
can return.
|
||||
- The temporary HttpApi error middleware shrinks over time instead of gaining new
|
||||
name-based cases.
|
||||
- Service tests prove domain error types without going through HTTP.
|
||||
- HTTP tests prove status/body contracts without relying on defect recovery.
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
type AuthenticateRequest,
|
||||
type AuthMethod,
|
||||
type CancelNotification,
|
||||
type CloseSessionRequest,
|
||||
type CloseSessionResponse,
|
||||
type ForkSessionRequest,
|
||||
type ForkSessionResponse,
|
||||
type InitializeRequest,
|
||||
@@ -51,9 +53,9 @@ import { LoadAPIKeyError } from "ai"
|
||||
import type { AssistantMessage, Event, KiloClient, SessionMessageResponse, ToolPart } from "@kilocode/sdk/v2"
|
||||
import { applyPatch } from "diff"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { ShellID } from "@/tool/shell/id"
|
||||
|
||||
import { fetchDefaultModel } from "@kilocode/kilo-gateway" // kilocode_change
|
||||
import { ShellID } from "@/tool/shell/id"
|
||||
|
||||
type ModeOption = { id: string; name: string; description?: string }
|
||||
type ModelOption = { modelId: string; name: string }
|
||||
@@ -569,6 +571,7 @@ export class Agent implements ACPAgent {
|
||||
image: true,
|
||||
},
|
||||
sessionCapabilities: {
|
||||
close: {},
|
||||
fork: {},
|
||||
list: {},
|
||||
resume: {},
|
||||
@@ -631,6 +634,9 @@ export class Agent implements ACPAgent {
|
||||
// Store ACP session state
|
||||
await this.sessionManager.load(sessionId, params.cwd, params.mcpServers, model)
|
||||
|
||||
const messages = await this.loadSessionMessages(directory, sessionId)
|
||||
this.restoreSessionStateFromMessages(sessionId, messages)
|
||||
|
||||
log.info("load_session", { sessionId, mcpServers: params.mcpServers.length })
|
||||
|
||||
const result = await this.loadSessionMode({
|
||||
@@ -639,39 +645,6 @@ export class Agent implements ACPAgent {
|
||||
sessionId,
|
||||
})
|
||||
|
||||
// Replay session history
|
||||
const messages = await this.sdk.session
|
||||
.messages(
|
||||
{
|
||||
sessionID: sessionId,
|
||||
directory,
|
||||
},
|
||||
{ throwOnError: true },
|
||||
)
|
||||
.then((x) => x.data)
|
||||
.catch((err) => {
|
||||
log.error("unexpected error when fetching message", { error: err })
|
||||
return undefined
|
||||
})
|
||||
|
||||
const lastUser = messages?.findLast((m) => m.info.role === "user")?.info
|
||||
if (lastUser?.role === "user") {
|
||||
result.models.currentModelId = `${lastUser.model.providerID}/${lastUser.model.modelID}`
|
||||
this.sessionManager.setModel(sessionId, {
|
||||
providerID: ProviderID.make(lastUser.model.providerID),
|
||||
modelID: ModelID.make(lastUser.model.modelID),
|
||||
})
|
||||
if (result.modes?.availableModes.some((m) => m.id === lastUser.agent)) {
|
||||
result.modes.currentModeId = lastUser.agent
|
||||
this.sessionManager.setMode(sessionId, lastUser.agent)
|
||||
}
|
||||
result.configOptions = buildConfigOptions({
|
||||
currentModelId: result.models.currentModelId,
|
||||
availableModels: result.models.availableModels,
|
||||
modes: result.modes,
|
||||
})
|
||||
}
|
||||
|
||||
for (const msg of messages ?? []) {
|
||||
log.debug("replay message", msg)
|
||||
await this.processMessage(msg)
|
||||
@@ -760,6 +733,9 @@ export class Agent implements ACPAgent {
|
||||
const sessionId = forked.id
|
||||
await this.sessionManager.load(sessionId, directory, mcpServers, model)
|
||||
|
||||
const messages = await this.loadSessionMessages(directory, sessionId)
|
||||
this.restoreSessionStateFromMessages(sessionId, messages)
|
||||
|
||||
log.info("fork_session", { sessionId, mcpServers: mcpServers.length })
|
||||
|
||||
const mode = await this.loadSessionMode({
|
||||
@@ -768,20 +744,6 @@ export class Agent implements ACPAgent {
|
||||
sessionId,
|
||||
})
|
||||
|
||||
const messages = await this.sdk.session
|
||||
.messages(
|
||||
{
|
||||
sessionID: sessionId,
|
||||
directory,
|
||||
},
|
||||
{ throwOnError: true },
|
||||
)
|
||||
.then((x) => x.data)
|
||||
.catch((err) => {
|
||||
log.error("unexpected error when fetching message", { error: err })
|
||||
return undefined
|
||||
})
|
||||
|
||||
for (const msg of messages ?? []) {
|
||||
log.debug("replay message", msg)
|
||||
await this.processMessage(msg)
|
||||
@@ -801,7 +763,7 @@ export class Agent implements ACPAgent {
|
||||
}
|
||||
}
|
||||
|
||||
async unstable_resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse> {
|
||||
async resumeSession(params: ResumeSessionRequest): Promise<ResumeSessionResponse> {
|
||||
const directory = params.cwd
|
||||
const sessionId = params.sessionId
|
||||
const mcpServers = params.mcpServers ?? []
|
||||
@@ -810,6 +772,9 @@ export class Agent implements ACPAgent {
|
||||
const model = await defaultModel(this.config, directory)
|
||||
await this.sessionManager.load(sessionId, directory, mcpServers, model)
|
||||
|
||||
const messages = await this.loadSessionMessages(directory, sessionId, 20)
|
||||
this.restoreSessionStateFromMessages(sessionId, messages)
|
||||
|
||||
log.info("resume_session", { sessionId, mcpServers: mcpServers.length })
|
||||
|
||||
const result = await this.loadSessionMode({
|
||||
@@ -832,6 +797,27 @@ export class Agent implements ACPAgent {
|
||||
}
|
||||
}
|
||||
|
||||
async closeSession(params: CloseSessionRequest): Promise<CloseSessionResponse> {
|
||||
const session = this.sessionManager.remove(params.sessionId)
|
||||
if (!session) return {}
|
||||
|
||||
await this.sdk.session
|
||||
.abort(
|
||||
{
|
||||
sessionID: params.sessionId,
|
||||
directory: session.cwd,
|
||||
},
|
||||
{ throwOnError: true },
|
||||
)
|
||||
.catch((error) => {
|
||||
log.error("failed to abort session while closing ACP session", { error, sessionID: params.sessionId })
|
||||
})
|
||||
|
||||
this.permissionQueues.delete(params.sessionId)
|
||||
log.info("close_session", { sessionId: params.sessionId })
|
||||
return {}
|
||||
}
|
||||
|
||||
private async processMessage(message: SessionMessageResponse) {
|
||||
log.debug("process message", message)
|
||||
if (message.info.role !== "assistant" && message.info.role !== "user") return
|
||||
@@ -1163,23 +1149,26 @@ export class Agent implements ACPAgent {
|
||||
sessionId: string,
|
||||
): Promise<{ availableModes: ModeOption[]; currentModeId?: string }> {
|
||||
const availableModes = await this.loadAvailableModes(directory)
|
||||
const currentModeId =
|
||||
this.sessionManager.get(sessionId).modeId ||
|
||||
(await (async () => {
|
||||
if (!availableModes.length) return undefined
|
||||
const defaultAgentName = await AppRuntime.runPromise(AgentModule.Service.use((svc) => svc.defaultAgent()))
|
||||
const resolvedModeId = availableModes.find((mode) => mode.name === defaultAgentName)?.id ?? availableModes[0].id
|
||||
this.sessionManager.setMode(sessionId, resolvedModeId)
|
||||
return resolvedModeId
|
||||
})())
|
||||
const storedModeId = this.sessionManager.get(sessionId).modeId
|
||||
if (storedModeId && availableModes.some((mode) => mode.id === storedModeId)) {
|
||||
return { availableModes, currentModeId: storedModeId }
|
||||
}
|
||||
|
||||
const currentModeId = await (async () => {
|
||||
if (!availableModes.length) return undefined
|
||||
const defaultAgentName = await AppRuntime.runPromise(AgentModule.Service.use((svc) => svc.defaultAgent()))
|
||||
const resolvedModeId = availableModes.find((mode) => mode.name === defaultAgentName)?.id ?? availableModes[0].id
|
||||
this.sessionManager.setMode(sessionId, resolvedModeId)
|
||||
return resolvedModeId
|
||||
})()
|
||||
|
||||
return { availableModes, currentModeId }
|
||||
}
|
||||
|
||||
private async loadSessionMode(params: LoadSessionRequest) {
|
||||
const directory = params.cwd
|
||||
const model = await defaultModel(this.config, directory)
|
||||
const sessionId = params.sessionId
|
||||
const model = this.sessionManager.get(sessionId).model ?? (await defaultModel(this.config, directory))
|
||||
|
||||
const providers = await this.sdk.config.providers({ directory }).then((x) => x.data!.providers)
|
||||
const entries = sortProvidersByName(providers)
|
||||
@@ -1188,7 +1177,7 @@ export class Agent implements ACPAgent {
|
||||
if (currentVariant && !availableVariants.includes(currentVariant)) {
|
||||
this.sessionManager.setVariant(sessionId, undefined)
|
||||
}
|
||||
const availableModels = buildAvailableModels(entries, { includeVariants: true })
|
||||
const availableModels = buildAvailableModels(entries)
|
||||
const modeState = await this.resolveModeState(directory, sessionId)
|
||||
const currentModeId = modeState.currentModeId
|
||||
const modes = currentModeId
|
||||
@@ -1271,13 +1260,15 @@ export class Agent implements ACPAgent {
|
||||
return {
|
||||
sessionId,
|
||||
models: {
|
||||
currentModelId: formatModelIdWithVariant(model, currentVariant, availableVariants, true),
|
||||
currentModelId: formatModelIdWithVariant(model, currentVariant, availableVariants, false),
|
||||
availableModels,
|
||||
},
|
||||
modes,
|
||||
configOptions: buildConfigOptions({
|
||||
currentModelId: formatModelIdWithVariant(model, currentVariant, availableVariants, true),
|
||||
currentModelId: formatModelIdWithVariant(model, currentVariant, availableVariants, false),
|
||||
availableModels,
|
||||
currentVariant,
|
||||
availableVariants,
|
||||
modes,
|
||||
}),
|
||||
_meta: buildVariantMeta({
|
||||
@@ -1300,6 +1291,24 @@ export class Agent implements ACPAgent {
|
||||
|
||||
const entries = sortProvidersByName(providers)
|
||||
const availableVariants = modelVariantsFromProviders(entries, selection.model)
|
||||
const modeState = await this.resolveModeState(session.cwd, session.id)
|
||||
const modes = modeState.currentModeId
|
||||
? { availableModes: modeState.availableModes, currentModeId: modeState.currentModeId }
|
||||
: undefined
|
||||
|
||||
await this.connection.sessionUpdate({
|
||||
sessionId: session.id,
|
||||
update: {
|
||||
sessionUpdate: "config_option_update",
|
||||
configOptions: buildConfigOptions({
|
||||
currentModelId: formatModelIdWithVariant(selection.model, selection.variant, availableVariants, false),
|
||||
availableModels: buildAvailableModels(entries),
|
||||
currentVariant: selection.variant,
|
||||
availableVariants,
|
||||
modes,
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
_meta: buildVariantMeta({
|
||||
@@ -1331,6 +1340,14 @@ export class Agent implements ACPAgent {
|
||||
const selection = parseModelSelection(params.value, providers)
|
||||
this.sessionManager.setModel(session.id, selection.model)
|
||||
this.sessionManager.setVariant(session.id, selection.variant)
|
||||
} else if (params.configId === "effort") {
|
||||
if (typeof params.value !== "string") throw RequestError.invalidParams("effort value must be a string")
|
||||
const current = session.model ?? (await defaultModel(this.config, session.cwd))
|
||||
const availableVariants = modelVariantsFromProviders(entries, current)
|
||||
if (!availableVariants.includes(params.value)) {
|
||||
throw RequestError.invalidParams(JSON.stringify({ error: `Effort not found: ${params.value}` }))
|
||||
}
|
||||
this.sessionManager.setVariant(session.id, params.value)
|
||||
} else if (params.configId === "mode") {
|
||||
if (typeof params.value !== "string") throw RequestError.invalidParams("mode value must be a string")
|
||||
const availableModes = await this.loadAvailableModes(session.cwd)
|
||||
@@ -1345,15 +1362,21 @@ export class Agent implements ACPAgent {
|
||||
const updatedSession = this.sessionManager.get(session.id)
|
||||
const model = updatedSession.model ?? (await defaultModel(this.config, session.cwd))
|
||||
const availableVariants = modelVariantsFromProviders(entries, model)
|
||||
const currentModelId = formatModelIdWithVariant(model, updatedSession.variant, availableVariants, true)
|
||||
const availableModels = buildAvailableModels(entries, { includeVariants: true })
|
||||
const currentModelId = formatModelIdWithVariant(model, updatedSession.variant, availableVariants, false)
|
||||
const availableModels = buildAvailableModels(entries)
|
||||
const modeState = await this.resolveModeState(session.cwd, session.id)
|
||||
const modes = modeState.currentModeId
|
||||
? { availableModes: modeState.availableModes, currentModeId: modeState.currentModeId }
|
||||
: undefined
|
||||
|
||||
return {
|
||||
configOptions: buildConfigOptions({ currentModelId, availableModels, modes }),
|
||||
configOptions: buildConfigOptions({
|
||||
currentModelId,
|
||||
availableModels,
|
||||
currentVariant: updatedSession.variant,
|
||||
availableVariants,
|
||||
modes,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1550,6 +1573,37 @@ export class Agent implements ACPAgent {
|
||||
{ throwOnError: true },
|
||||
)
|
||||
}
|
||||
|
||||
private async loadSessionMessages(directory: string, sessionId: string, limit?: number) {
|
||||
return this.sdk.session
|
||||
.messages(
|
||||
{
|
||||
sessionID: sessionId,
|
||||
directory,
|
||||
limit,
|
||||
},
|
||||
{ throwOnError: true },
|
||||
)
|
||||
.then((x) => x.data)
|
||||
.catch((error) => {
|
||||
log.error("unexpected error when fetching message", { error })
|
||||
return undefined
|
||||
})
|
||||
}
|
||||
|
||||
private restoreSessionStateFromMessages(sessionId: string, messages: SessionMessageResponse[] | undefined) {
|
||||
const lastUser = messages?.findLast((message) => message.info.role === "user")?.info
|
||||
if (lastUser?.role !== "user") return
|
||||
|
||||
this.sessionManager.setModel(sessionId, {
|
||||
providerID: ProviderID.make(lastUser.model.providerID),
|
||||
modelID: ModelID.make(lastUser.model.modelID),
|
||||
})
|
||||
this.sessionManager.setVariant(sessionId, lastUser.model.variant)
|
||||
if (lastUser.agent) {
|
||||
this.sessionManager.setMode(sessionId, lastUser.agent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toToolKind(toolName: string): ToolKind {
|
||||
@@ -1634,6 +1688,9 @@ async function defaultModel(config: ACPConfig, cwd?: string): Promise<{ provider
|
||||
if (specified && !providers.length) return specified
|
||||
|
||||
// kilocode_change start
|
||||
const lastUsed = await lastUsedModel(sdk, directory, providers)
|
||||
if (lastUsed) return lastUsed
|
||||
|
||||
const kiloProvider = providers.find((p) => p.id === "kilo")
|
||||
if (kiloProvider) {
|
||||
const [best] = Provider.sort(Object.values(kiloProvider.models))
|
||||
@@ -1657,7 +1714,7 @@ async function defaultModel(config: ACPConfig, cwd?: string): Promise<{ provider
|
||||
|
||||
if (specified) return specified
|
||||
|
||||
// kilocode_change start
|
||||
// kilocode_change start - prefer kilo free-tier default before erroring.
|
||||
// Only fall back to the Kilo provider if it was present in the available
|
||||
// providers list. When teams configure enabled_providers to use only their
|
||||
// own models, this prevents silently routing requests to an external API.
|
||||
@@ -1667,8 +1724,39 @@ async function defaultModel(config: ACPConfig, cwd?: string): Promise<{ provider
|
||||
const freeModel = await fetchDefaultModel()
|
||||
return { providerID: ProviderID.kilo, modelID: ModelID.make(freeModel) }
|
||||
}
|
||||
throw new Error("no model available: no providers are configured and no default model is set")
|
||||
// kilocode_change end
|
||||
throw new Error("No models available")
|
||||
}
|
||||
|
||||
async function lastUsedModel(
|
||||
sdk: KiloClient,
|
||||
directory: string,
|
||||
providers: Array<{ id: string; models: Record<string, unknown> }>,
|
||||
): Promise<{ providerID: ProviderID; modelID: ModelID } | undefined> {
|
||||
const session = await sdk.session
|
||||
.list({ directory, roots: true, limit: 1 }, { throwOnError: true })
|
||||
.then((x) => x.data?.[0])
|
||||
.catch((error) => {
|
||||
log.error("failed to list sessions for default model", { error })
|
||||
return undefined
|
||||
})
|
||||
if (!session) return
|
||||
|
||||
const lastUser = await sdk.session
|
||||
.messages({ sessionID: session.id, directory, limit: 20 }, { throwOnError: true })
|
||||
.then((x) => x.data?.findLast((message) => message.info.role === "user")?.info)
|
||||
.catch((error) => {
|
||||
log.error("failed to load session messages for default model", { error, sessionID: session.id })
|
||||
return undefined
|
||||
})
|
||||
if (lastUser?.role !== "user") return
|
||||
|
||||
const provider = providers.find((entry) => entry.id === lastUser.model.providerID)
|
||||
if (!provider?.models[lastUser.model.modelID]) return
|
||||
return {
|
||||
providerID: ProviderID.make(lastUser.model.providerID),
|
||||
modelID: ModelID.make(lastUser.model.modelID),
|
||||
}
|
||||
}
|
||||
|
||||
function parseUri(
|
||||
@@ -1771,8 +1859,14 @@ function formatModelIdWithVariant(
|
||||
includeVariant: boolean,
|
||||
) {
|
||||
const base = `${model.providerID}/${model.modelID}`
|
||||
if (!includeVariant || !variant || !availableVariants.includes(variant)) return base
|
||||
return `${base}/${variant}`
|
||||
if (!includeVariant || availableVariants.length === 0) return base
|
||||
const selectedVariant =
|
||||
variant && availableVariants.includes(variant)
|
||||
? variant
|
||||
: availableVariants.includes(DEFAULT_VARIANT_VALUE)
|
||||
? DEFAULT_VARIANT_VALUE
|
||||
: availableVariants[0]
|
||||
return `${base}/${selectedVariant}`
|
||||
}
|
||||
|
||||
function buildVariantMeta(input: {
|
||||
@@ -1824,6 +1918,8 @@ function parseModelSelection(
|
||||
function buildConfigOptions(input: {
|
||||
currentModelId: string
|
||||
availableModels: ModelOption[]
|
||||
currentVariant?: string
|
||||
availableVariants?: string[]
|
||||
modes?: { availableModes: ModeOption[]; currentModeId: string } | undefined
|
||||
}): SessionConfigOption[] {
|
||||
const options: SessionConfigOption[] = [
|
||||
@@ -1836,6 +1932,22 @@ function buildConfigOptions(input: {
|
||||
options: input.availableModels.map((m) => ({ value: m.modelId, name: m.name })),
|
||||
},
|
||||
]
|
||||
if (input.availableVariants?.length) {
|
||||
options.push({
|
||||
id: "effort",
|
||||
name: "Effort",
|
||||
description: "Available effort levels for this model",
|
||||
category: "thought_level",
|
||||
type: "select",
|
||||
currentValue:
|
||||
input.currentVariant && input.availableVariants.includes(input.currentVariant)
|
||||
? input.currentVariant
|
||||
: input.availableVariants.includes(DEFAULT_VARIANT_VALUE)
|
||||
? DEFAULT_VARIANT_VALUE
|
||||
: input.availableVariants[0],
|
||||
options: input.availableVariants.map((variant) => ({ value: variant, name: formatVariantName(variant) })),
|
||||
})
|
||||
}
|
||||
if (input.modes) {
|
||||
options.push({
|
||||
id: "mode",
|
||||
@@ -1853,4 +1965,11 @@ function buildConfigOptions(input: {
|
||||
return options
|
||||
}
|
||||
|
||||
function formatVariantName(variant: string) {
|
||||
return variant
|
||||
.split(/[_-]/)
|
||||
.map((part) => (part ? part.charAt(0).toUpperCase() + part.slice(1) : part))
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
export * as ACP from "./agent"
|
||||
|
||||
@@ -113,4 +113,10 @@ export class ACPSessionManager {
|
||||
this.sessions.set(sessionId, session)
|
||||
return session
|
||||
}
|
||||
|
||||
remove(sessionId: string): ACPSessionState | undefined {
|
||||
const session = this.sessions.get(sessionId)
|
||||
this.sessions.delete(sessionId)
|
||||
return session
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export const AcpCommand = effectCmd({
|
||||
},
|
||||
handler: Effect.fn("Cli.acp")(function* (args) {
|
||||
process.env.KILO_CLIENT = "acp"
|
||||
const opts = yield* Effect.promise(() => resolveNetworkOptions(args))
|
||||
const opts = yield* resolveNetworkOptions(args)
|
||||
const server = yield* Effect.promise(() => Server.listen(opts))
|
||||
|
||||
const sdk = createKiloClient({
|
||||
|
||||
@@ -84,7 +84,7 @@ const AgentCreateCommand = effectCmd({
|
||||
// Determine scope/path
|
||||
let targetPath: string
|
||||
if (cliPath) {
|
||||
targetPath = path.join(cliPath, "agent")
|
||||
targetPath = path.join(cliPath, "agents")
|
||||
} else {
|
||||
let scope: "global" | "project" = "global"
|
||||
if (project.vcs === "git") {
|
||||
@@ -108,7 +108,7 @@ const AgentCreateCommand = effectCmd({
|
||||
}
|
||||
targetPath = path.join(
|
||||
scope === "global" ? Global.Path.config : path.join(ctx.worktree, ".kilo"), // kilocode_change
|
||||
"agent",
|
||||
"agents",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user