cartethyia 2.7.5 → 2.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +4 -1
- package/README.md +1 -1
- package/dist/dashboard/assets/BannedPage-B1N603cM.js +1 -0
- package/dist/dashboard/assets/BannedPage-B1N603cM.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js +3 -0
- package/dist/dashboard/assets/CliToolDetailPage-9q4_4NoK.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js +1 -0
- package/dist/dashboard/assets/CliToolsPage-DwshGtK-.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-D762MIkp.js +1 -0
- package/dist/dashboard/assets/CombosPage-D762MIkp.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-D5rol2j2.js → ConsoleLogPage-KRYjHZsX.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-KRYjHZsX.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js +1 -0
- package/dist/dashboard/assets/CustomizationPage-DXsxk5uM.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-BEjAjD4F.js.gz +0 -0
- package/dist/dashboard/assets/{InjectSystemPromptPanel-B5nZvmf0.js → InjectSystemPromptPanel-d5v3K_Yc.js} +245 -2
- package/dist/dashboard/assets/InjectSystemPromptPanel-d5v3K_Yc.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js +1 -0
- package/dist/dashboard/assets/LoginPage-Ccv4tjNS.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-DnQPSIA8.js → ModelPicker-C6WlpU5E.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-C6WlpU5E.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js +1 -0
- package/dist/dashboard/assets/OverviewPage-DN9fYNWA.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js +5 -0
- package/dist/dashboard/assets/ProviderDetailPage-BY0ieUgz.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-jjqXJDA3.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-DNsbfea2.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-5SnUSy3j.js → ProxyPage-DxjvUzSD.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-DxjvUzSD.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-OaHScjpX.js +1 -0
- package/dist/dashboard/assets/QuotaPage-OaHScjpX.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-BJtRZ4qD.js → SettingsPage-BW4AKj2H.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-BW4AKj2H.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-44BZrxFC.js +1 -0
- package/dist/dashboard/assets/SetupPage-44BZrxFC.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-BfAb5-o_.js → StudioPage-DRPavCdU.js} +4 -4
- package/dist/dashboard/assets/StudioPage-DRPavCdU.js.gz +0 -0
- package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js +1 -0
- package/dist/dashboard/assets/ToolIcon-CfeLA-Z_.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-BNzeZ2Vk.js → UsagePage-B7M7LvMM.js} +13 -13
- package/dist/dashboard/assets/UsagePage-B7M7LvMM.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js +1 -0
- package/dist/dashboard/assets/api-keys-KvX83mRe.js.gz +0 -0
- package/dist/dashboard/assets/app-DRnQLhsc.js +9 -0
- package/dist/dashboard/assets/app-DRnQLhsc.js.gz +0 -0
- package/dist/dashboard/assets/app-mweRcsKp.css +2 -0
- package/dist/dashboard/assets/app-mweRcsKp.css.gz +0 -0
- package/dist/dashboard/assets/{assert-C0K-c4eo.js → assert-CidQVoNo.js} +1 -1
- package/dist/dashboard/assets/assert-CidQVoNo.js.gz +0 -0
- package/dist/dashboard/assets/{badge-DfbhQU4k.js → badge-BaX2GTrn.js} +1 -1
- package/dist/dashboard/assets/badge-BaX2GTrn.js.gz +0 -0
- package/dist/dashboard/assets/{banner-BllT4LcF.js → banner-DFH4Bkte.js} +1 -1
- package/dist/dashboard/assets/banner-DFH4Bkte.js.gz +0 -0
- package/dist/dashboard/assets/{card-CtkX66Wd.js → card-CxDw0lK6.js} +1 -1
- package/dist/dashboard/assets/card-CxDw0lK6.js.gz +0 -0
- package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js +1 -0
- package/dist/dashboard/assets/circle-question-mark-Cz4un8sH.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-CQXVMn4P.js → cli-tools-Bwv5-zGZ.js} +1 -1
- package/dist/dashboard/assets/cli-tools-Bwv5-zGZ.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-BQqBQd2n.js → clipboard-button-DfS1ntiq.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DfS1ntiq.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BK-NRr5u.js +1 -0
- package/dist/dashboard/assets/dialog-BK-NRr5u.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BrTEZ8zu.js → drawer-QqPul6sC.js} +1 -1
- package/dist/dashboard/assets/drawer-QqPul6sC.js.gz +0 -0
- package/dist/dashboard/assets/entry-BMlYJVFj.js +1 -0
- package/dist/dashboard/assets/entry-BMlYJVFj.js.gz +0 -0
- package/dist/dashboard/assets/entry-D6WvojeD.js +4 -0
- package/dist/dashboard/assets/entry-D6WvojeD.js.gz +0 -0
- package/dist/dashboard/assets/entry-Dmx7gBiN.js +1 -0
- package/dist/dashboard/assets/entry-Dmx7gBiN.js.gz +0 -0
- package/dist/dashboard/assets/{formatters-BAACA727.js → formatters-u7JPB5BX.js} +1 -1
- package/dist/dashboard/assets/formatters-u7JPB5BX.js.gz +0 -0
- package/dist/dashboard/assets/house-h9AZlJuT.js +1 -0
- package/dist/dashboard/assets/house-h9AZlJuT.js.gz +0 -0
- package/dist/dashboard/assets/{index-FQlj0HUT.js → index-Ko4dLRnV.js} +2 -2
- package/dist/dashboard/assets/index-Ko4dLRnV.js.gz +0 -0
- package/dist/dashboard/assets/{inline-C4gU5iOL.js → inline-CHJQwxLa.js} +1 -1
- package/dist/dashboard/assets/inline-CHJQwxLa.js.gz +0 -0
- package/dist/dashboard/assets/{input-BHHX_Tmm.js → input-BlmlizVV.js} +1 -1
- package/dist/dashboard/assets/input-BlmlizVV.js.gz +0 -0
- package/dist/dashboard/assets/{layout-CajrYUnD.js → layout-Bz93I-yE.js} +1 -1
- package/dist/dashboard/assets/layout-Bz93I-yE.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-Bx2I7XUe.js → list-row-Ml16KCfu.js} +1 -1
- package/dist/dashboard/assets/list-row-Ml16KCfu.js.gz +0 -0
- package/dist/dashboard/assets/{live-DNNqRbj2.js → live-7_QpnYGu.js} +1 -1
- package/dist/dashboard/assets/live-7_QpnYGu.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-Rr0TIvcj.js → mutation-GU7XdEjz.js} +1 -1
- package/dist/dashboard/assets/mutation-GU7XdEjz.js.gz +0 -0
- package/dist/dashboard/assets/{network-C5UBACrJ.js → network-CuMDaTzT.js} +1 -1
- package/dist/dashboard/assets/network-CuMDaTzT.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CoqpaDMx.js +2 -0
- package/dist/dashboard/assets/numbers-CoqpaDMx.js.gz +0 -0
- package/dist/dashboard/assets/{pagination-Xsi9Qx04.js → pagination-B5aOoweZ.js} +1 -1
- package/dist/dashboard/assets/pagination-B5aOoweZ.js.gz +0 -0
- package/dist/dashboard/assets/providers/zai.webp +0 -0
- package/dist/dashboard/assets/providers/zcode.webp +0 -0
- package/dist/dashboard/assets/{query-keys-BloKtaXJ.js → query-keys-YSoQOUh6.js} +1 -1
- package/dist/dashboard/assets/{query-keys-BloKtaXJ.js.gz → query-keys-YSoQOUh6.js.gz} +0 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js +1 -0
- package/dist/dashboard/assets/quota-DJ1QlAOq.js.gz +0 -0
- package/dist/dashboard/assets/{routing-in0f_IfK.js → routing-fnCsxgNO.js} +1 -1
- package/dist/dashboard/assets/routing-fnCsxgNO.js.gz +0 -0
- package/dist/dashboard/assets/{select-BLULNqTp.js → select-BHqpd-bg.js} +2 -2
- package/dist/dashboard/assets/select-BHqpd-bg.js.gz +0 -0
- package/dist/dashboard/assets/{settings-CcGczj6I.js → settings-BxslsErn.js} +1 -1
- package/dist/dashboard/assets/settings-BxslsErn.js.gz +0 -0
- package/dist/dashboard/assets/{stack-D2lIR0tg.js → stack-j7cXjgYX.js} +1 -1
- package/dist/dashboard/assets/stack-j7cXjgYX.js.gz +0 -0
- package/dist/dashboard/assets/{state-DTARjfiW.js → state-DHmEnLa2.js} +1 -1
- package/dist/dashboard/assets/state-DHmEnLa2.js.gz +0 -0
- package/dist/dashboard/assets/{switch-c0oPSnqW.js → switch-DmZcyTIC.js} +1 -1
- package/dist/dashboard/assets/switch-DmZcyTIC.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-m8WGNPrz.js → useMutation-CbCP4w1o.js} +1 -1
- package/dist/dashboard/assets/useMutation-CbCP4w1o.js.gz +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +57 -57
- package/migrations/full-postgres/0042_add_telemetry_degenerate_stop.sql +14 -0
- package/migrations/lite-sqlite3/0042_add_telemetry_degenerate_stop.sql +12 -0
- package/package.json +1 -1
- package/src/boot/dependencies.ts +10 -2
- package/src/config/providers.ts +13 -7
- package/src/config/spec.ts +18 -11
- package/src/config/usage.ts +27 -0
- package/src/console/api-keys/routes.ts +19 -23
- package/src/console/cli-tools/contracts.ts +39 -295
- package/src/console/cli-tools/fs-ops.ts +29 -0
- package/src/console/cli-tools/injectors/claude.ts +3 -3
- package/src/console/cli-tools/injectors/codex.ts +6 -6
- package/src/console/cli-tools/injectors/driver.ts +8 -105
- package/src/console/cli-tools/injectors/kilo.ts +146 -21
- package/src/console/cli-tools/injectors/opencode.ts +28 -5
- package/src/console/cli-tools/routes.ts +6 -0
- package/src/console/cli-tools/service.ts +7 -6
- package/src/console/observability/contracts.ts +6 -0
- package/src/console/observability/store.ts +3 -0
- package/src/console/share/share-stats.ts +7 -9
- package/src/observability/event-log.ts +7 -0
- package/src/observability/event-query.ts +2 -0
- package/src/observability/telemetry-buffer.ts +7 -0
- package/src/persistence/schema.ts +8 -0
- package/src/persistence/stores/api-key-store.ts +19 -1
- package/src/protocol/dsml.ts +5 -76
- package/src/providers/adapters/compatible-adapter.ts +29 -1
- package/src/providers/default-registry.ts +12 -2
- package/src/providers/integrations/antigravity/antigravity-protocol.ts +88 -14
- package/src/providers/integrations/antigravity/antigravity-quota.ts +18 -6
- package/src/providers/integrations/antigravity/antigravity.ts +20 -11
- package/src/providers/integrations/cursor/cursor.ts +2 -3
- package/src/providers/integrations/github/github-discovery.ts +15 -2
- package/src/providers/integrations/github/github-oauth.ts +178 -9
- package/src/providers/integrations/github/github.ts +95 -14
- package/src/providers/integrations/kiro/kiro.ts +1 -2
- package/src/providers/integrations/qoder/qoder-catalog.ts +9 -3
- package/src/providers/integrations/qoder/qoder.ts +147 -88
- package/src/providers/integrations/trae/trae.ts +2 -2
- package/src/providers/operations/account-error-classification.ts +67 -17
- package/src/providers/operations/byok-wire-profile.ts +19 -15
- package/src/providers/provider-usage.ts +15 -49
- package/src/security/admission/service.ts +11 -4
- package/src/security/api-key-auth.ts +5 -3
- package/src/transport/dispatch/attempt-finalize.ts +69 -21
- package/src/transport/dispatch/attempt-loop.ts +22 -0
- package/src/transport/failure-policy.ts +15 -1
- package/src/transport/guards/error-lifecycle.ts +4 -0
- package/src/transport/request/state.ts +6 -0
- package/src/transport/routing/router.ts +7 -0
- package/src/transport/surface/reasoning-summary.ts +9 -14
- package/src/transport/vision-handoff.ts +83 -28
- package/dist/dashboard/assets/BannedPage-Cj15GsMp.js +0 -1
- package/dist/dashboard/assets/BannedPage-Cj15GsMp.js.gz +0 -0
- package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js +0 -1
- package/dist/dashboard/assets/ClaimGiftsButton-D83pBJ6J.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-DZKmVuJN.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js +0 -1
- package/dist/dashboard/assets/CliToolsPage-BIccZmeL.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DEEKkYD-.js +0 -1
- package/dist/dashboard/assets/CombosPage-DEEKkYD-.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-D5rol2j2.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js +0 -1
- package/dist/dashboard/assets/CustomizationPage-BKj_r0rp.js.gz +0 -0
- package/dist/dashboard/assets/InjectSystemPromptPanel-B5nZvmf0.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DkXb6dx-.js +0 -1
- package/dist/dashboard/assets/LoginPage-DkXb6dx-.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-DnQPSIA8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js +0 -1
- package/dist/dashboard/assets/OverviewPage-Ci2wQKUz.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js +0 -5
- package/dist/dashboard/assets/ProviderDetailPage-odlgYrOg.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js +0 -1
- package/dist/dashboard/assets/ProviderIcon-EfeloBn4.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-CqFBQEB8.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-5SnUSy3j.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js +0 -1
- package/dist/dashboard/assets/QuotaPage-CmnMDzhP.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-BJtRZ4qD.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-WTa0sktC.js +0 -1
- package/dist/dashboard/assets/SetupPage-WTa0sktC.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-BfAb5-o_.js.gz +0 -0
- package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js +0 -1
- package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BNzeZ2Vk.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-l29JROPv.js +0 -1
- package/dist/dashboard/assets/api-keys-l29JROPv.js.gz +0 -0
- package/dist/dashboard/assets/app--PJWq8_C.css +0 -2
- package/dist/dashboard/assets/app--PJWq8_C.css.gz +0 -0
- package/dist/dashboard/assets/app-Bgd3zANQ.js +0 -9
- package/dist/dashboard/assets/app-Bgd3zANQ.js.gz +0 -0
- package/dist/dashboard/assets/assert-C0K-c4eo.js.gz +0 -0
- package/dist/dashboard/assets/badge-DfbhQU4k.js.gz +0 -0
- package/dist/dashboard/assets/banner-BllT4LcF.js.gz +0 -0
- package/dist/dashboard/assets/card-CtkX66Wd.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-CQXVMn4P.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-BQqBQd2n.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BEK7KP0H.js +0 -1
- package/dist/dashboard/assets/dialog-BEK7KP0H.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BrTEZ8zu.js.gz +0 -0
- package/dist/dashboard/assets/entry-CHWxbS56.js +0 -1
- package/dist/dashboard/assets/entry-CHWxbS56.js.gz +0 -0
- package/dist/dashboard/assets/entry-EBHJ3DrE.js +0 -1
- package/dist/dashboard/assets/entry-EBHJ3DrE.js.gz +0 -0
- package/dist/dashboard/assets/entry-LCS85hiq.js +0 -4
- package/dist/dashboard/assets/entry-LCS85hiq.js.gz +0 -0
- package/dist/dashboard/assets/formatters-BAACA727.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-YSCE4UC2.js +0 -1
- package/dist/dashboard/assets/github-badge-YSCE4UC2.js.gz +0 -0
- package/dist/dashboard/assets/index-FQlj0HUT.js.gz +0 -0
- package/dist/dashboard/assets/inline-C4gU5iOL.js.gz +0 -0
- package/dist/dashboard/assets/input-BHHX_Tmm.js.gz +0 -0
- package/dist/dashboard/assets/layout-CajrYUnD.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Bx2I7XUe.js.gz +0 -0
- package/dist/dashboard/assets/live-DNNqRbj2.js.gz +0 -0
- package/dist/dashboard/assets/mutation-Rr0TIvcj.js.gz +0 -0
- package/dist/dashboard/assets/network-C5UBACrJ.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CUWAZK4J.js +0 -2
- package/dist/dashboard/assets/numbers-CUWAZK4J.js.gz +0 -0
- package/dist/dashboard/assets/pagination-Xsi9Qx04.js.gz +0 -0
- package/dist/dashboard/assets/providers/zai-coding-plan.svg +0 -6
- package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
- package/dist/dashboard/assets/quota-DvQbD_sr.js +0 -1
- package/dist/dashboard/assets/quota-DvQbD_sr.js.gz +0 -0
- package/dist/dashboard/assets/routing-in0f_IfK.js.gz +0 -0
- package/dist/dashboard/assets/select-BLULNqTp.js.gz +0 -0
- package/dist/dashboard/assets/settings-CcGczj6I.js.gz +0 -0
- package/dist/dashboard/assets/stack-D2lIR0tg.js.gz +0 -0
- package/dist/dashboard/assets/state-DTARjfiW.js.gz +0 -0
- package/dist/dashboard/assets/switch-c0oPSnqW.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-m8WGNPrz.js.gz +0 -0
- package/src/console/cli-tools/injectors/cline.ts +0 -110
- package/src/console/cli-tools/injectors/copilot.ts +0 -108
- package/src/console/cli-tools/injectors/cowork.ts +0 -64
- package/src/console/cli-tools/injectors/deepseek-tui.ts +0 -85
- package/src/console/cli-tools/injectors/droid.ts +0 -96
- package/src/console/cli-tools/injectors/grok-build.ts +0 -70
- package/src/console/cli-tools/injectors/hermes.ts +0 -101
- package/src/console/cli-tools/injectors/jcode.ts +0 -115
- package/src/console/cli-tools/injectors/openclaw.ts +0 -161
|
@@ -6,25 +6,15 @@ import type {
|
|
|
6
6
|
ApplyResult,
|
|
7
7
|
DownloadResult,
|
|
8
8
|
InjectorSpec,
|
|
9
|
-
ToolDef,
|
|
10
9
|
ToolId,
|
|
11
10
|
ToolInjector,
|
|
12
11
|
ToolStatus,
|
|
13
12
|
} from "../contracts";
|
|
14
|
-
import { TOOL_IDS
|
|
15
|
-
import { checkBinaryInstalled, ensureDir,
|
|
13
|
+
import { TOOL_IDS } from "../contracts";
|
|
14
|
+
import { checkBinaryInstalled, ensureDir, fileExists, keyPrefix } from "../fs-ops";
|
|
16
15
|
import { claudeSpec } from './claude';
|
|
17
|
-
import { clineSpec } from './cline';
|
|
18
16
|
import { codexSpec } from './codex';
|
|
19
|
-
import { copilotSpec } from './copilot';
|
|
20
|
-
import { coworkSpec } from './cowork';
|
|
21
|
-
import { deepseekTuiSpec } from './deepseek-tui';
|
|
22
|
-
import { droidSpec } from './droid';
|
|
23
|
-
import { grokBuildSpec } from './grok-build';
|
|
24
|
-
import { hermesSpec } from './hermes';
|
|
25
|
-
import { jcodeSpec } from './jcode';
|
|
26
17
|
import { kiloSpec } from './kilo';
|
|
27
|
-
import { openclawSpec } from './openclaw';
|
|
28
18
|
import { opencodeSpec } from './opencode';
|
|
29
19
|
|
|
30
20
|
// ── injectors/driver.ts ──
|
|
@@ -92,11 +82,12 @@ export function createFileInjector(spec: InjectorSpec): ToolInjector {
|
|
|
92
82
|
const path = spec.resolvePath();
|
|
93
83
|
const dir = spec.resolveDir ? spec.resolveDir() : dirname(path);
|
|
94
84
|
await ensureDir(dir);
|
|
95
|
-
await spec.apply(input, path);
|
|
85
|
+
const warnings = await spec.apply(input, path);
|
|
96
86
|
return {
|
|
97
87
|
success: true,
|
|
98
88
|
settingsPath: path,
|
|
99
89
|
message: spec.messages?.applied ?? `${spec.displayName ?? spec.toolId} settings applied`,
|
|
90
|
+
...(Array.isArray(warnings) && warnings.length > 0 ? { warnings } : {}),
|
|
100
91
|
};
|
|
101
92
|
},
|
|
102
93
|
|
|
@@ -131,114 +122,26 @@ export function createFileInjector(spec: InjectorSpec): ToolInjector {
|
|
|
131
122
|
};
|
|
132
123
|
}
|
|
133
124
|
|
|
134
|
-
/** Create a guide injector for a specific tool definition (any `configType: "guide"` entry). */
|
|
135
|
-
function guideInjectorFor(def: ToolDef): ToolInjector {
|
|
136
|
-
return {
|
|
137
|
-
toolId: def.id,
|
|
138
|
-
|
|
139
|
-
async getStatus(): Promise<ToolStatus> {
|
|
140
|
-
// Guide tools are always "not installed" from an injection standpoint —
|
|
141
|
-
// we can't detect the binary reliably, and config is manual.
|
|
142
|
-
return {
|
|
143
|
-
toolId: def.id,
|
|
144
|
-
installed: false,
|
|
145
|
-
configured: false,
|
|
146
|
-
settingsPath: null,
|
|
147
|
-
currentEndpoint: null,
|
|
148
|
-
currentApiKeyPrefix: null,
|
|
149
|
-
currentModels: null,
|
|
150
|
-
message: "Guide-only tool — use Download Config to get setup instructions",
|
|
151
|
-
};
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
async apply(): Promise<ApplyResult> {
|
|
155
|
-
return { success: false, message: `${def.name} is a guide-only tool — use Download Config` };
|
|
156
|
-
},
|
|
157
|
-
|
|
158
|
-
async reset(): Promise<ApplyResult> {
|
|
159
|
-
return { success: true, message: `${def.name} has no injected config to reset` };
|
|
160
|
-
},
|
|
161
|
-
|
|
162
|
-
async download(input: ApplyInput): Promise<DownloadResult> {
|
|
163
|
-
const baseUrl = ensureV1Suffix(input.endpoint);
|
|
164
|
-
const model = input.activeModel ?? input.modelIds[0] ?? "";
|
|
165
|
-
const apiKey = input.apiKey;
|
|
166
|
-
const codeBlock = def.codeBlock;
|
|
167
|
-
|
|
168
|
-
if (codeBlock) {
|
|
169
|
-
const content = codeBlock.code
|
|
170
|
-
.replace(/\{\{baseUrl\}\}/g, baseUrl)
|
|
171
|
-
.replace(/\{\{apiKey\}\}/g, apiKey)
|
|
172
|
-
.replace(/\{\{model\}\}/g, model);
|
|
173
|
-
const ext =
|
|
174
|
-
codeBlock.language === "json" ? "json" : codeBlock.language === "bash" ? "sh" : "txt";
|
|
175
|
-
return {
|
|
176
|
-
content,
|
|
177
|
-
filename: `${def.id}-config.${ext}`,
|
|
178
|
-
mimeType: codeBlock.language === "json" ? "application/json" : "text/plain",
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// No code block — generate a simple guide summary.
|
|
183
|
-
const steps = def.guideSteps ?? [];
|
|
184
|
-
const lines = [`# ${def.name} — Setup Guide`, ""];
|
|
185
|
-
for (const step of steps) {
|
|
186
|
-
lines.push(`## Step ${step.step}: ${step.title}`);
|
|
187
|
-
if (step.desc) lines.push(step.desc);
|
|
188
|
-
if (step.value) {
|
|
189
|
-
lines.push("```");
|
|
190
|
-
lines.push(
|
|
191
|
-
step.value
|
|
192
|
-
.replace(/\{\{baseUrl\}\}/g, baseUrl)
|
|
193
|
-
.replace(/\{\{apiKey\}\}/g, apiKey)
|
|
194
|
-
.replace(/\{\{model\}\}/g, model),
|
|
195
|
-
);
|
|
196
|
-
lines.push("```");
|
|
197
|
-
}
|
|
198
|
-
lines.push("");
|
|
199
|
-
}
|
|
200
|
-
lines.push(`Endpoint: ${baseUrl}`);
|
|
201
|
-
lines.push(`API Key: ${apiKey}`);
|
|
202
|
-
lines.push(`Model: ${model}`);
|
|
203
|
-
return { content: lines.join("\n"), filename: `${def.id}-guide.txt`, mimeType: "text/plain" };
|
|
204
|
-
},
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
125
|
// ── injectors/index.ts ──
|
|
209
126
|
/**
|
|
210
127
|
* Single dispatch point for CliToolService: given a toolId, look up the
|
|
211
128
|
* injector and call getStatus/apply/reset/download.
|
|
212
129
|
*
|
|
213
130
|
* File-based injectors are driven by a generic driver (createFileInjector)
|
|
214
|
-
* configured via declarative specs.
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
* built exhaustively from that registry so a non-guide tool without an
|
|
218
|
-
* injector fails loudly at import, never silently at lookup time.
|
|
131
|
+
* configured via declarative specs. The map is built exhaustively from that
|
|
132
|
+
* registry so a tool without an injector fails loudly at import, never
|
|
133
|
+
* silently at lookup time.
|
|
219
134
|
*/
|
|
220
135
|
const FILE_INJECTORS: Partial<Record<ToolId, ToolInjector>> = {
|
|
221
136
|
claude: createFileInjector(claudeSpec),
|
|
222
137
|
codex: createFileInjector(codexSpec),
|
|
223
|
-
cline: createFileInjector(clineSpec),
|
|
224
138
|
opencode: createFileInjector(opencodeSpec),
|
|
225
|
-
droid: createFileInjector(droidSpec),
|
|
226
|
-
hermes: createFileInjector(hermesSpec),
|
|
227
|
-
"grok-build": createFileInjector(grokBuildSpec),
|
|
228
|
-
copilot: createFileInjector(copilotSpec),
|
|
229
|
-
"deepseek-tui": createFileInjector(deepseekTuiSpec),
|
|
230
|
-
jcode: createFileInjector(jcodeSpec),
|
|
231
139
|
kilo: createFileInjector(kiloSpec),
|
|
232
|
-
openclaw: createFileInjector(openclawSpec),
|
|
233
|
-
cowork: createFileInjector(coworkSpec),
|
|
234
140
|
};
|
|
235
141
|
|
|
236
142
|
export const INJECTORS: Record<ToolId, ToolInjector> = Object.fromEntries(
|
|
237
143
|
TOOL_IDS.map((id) => {
|
|
238
|
-
const injector =
|
|
239
|
-
TOOL_REGISTRY[id].configType === "guide"
|
|
240
|
-
? guideInjectorFor(TOOL_REGISTRY[id])
|
|
241
|
-
: FILE_INJECTORS[id];
|
|
144
|
+
const injector = FILE_INJECTORS[id];
|
|
242
145
|
if (injector === undefined) {
|
|
243
146
|
throw new Error(`No injector registered for tool: ${id}`);
|
|
244
147
|
}
|
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
import { platform } from "node:os";
|
|
2
2
|
import type {} from "../contracts";
|
|
3
|
-
import {
|
|
4
|
-
import { homeDir, isLocalEndpoint, join, readJsonFile,
|
|
3
|
+
import { jsonDownload } from "../contracts";
|
|
4
|
+
import { homeDir, isLocalEndpoint, join, readJsonFile, writeJsonFileWithBackup } from "../fs-ops";
|
|
5
5
|
import type { InjectorSpec } from "../contracts";
|
|
6
6
|
|
|
7
7
|
const IS_WIN = platform() === "win32";
|
|
8
8
|
const IS_MAC = platform() === "darwin";
|
|
9
9
|
|
|
10
10
|
// Kilo Code injector spec.
|
|
11
|
+
//
|
|
12
|
+
// Kilo Code is a fork of OpenCode, but it now reads its own config file —
|
|
13
|
+
// `~/.config/kilo/kilo.jsonc` (Windows: `%USERPROFILE%\.config\kilo\kilo.jsonc`)
|
|
14
|
+
// — with the same provider shape as OpenCode: `provider.<id>.npm/options/models`
|
|
15
|
+
// plus a top-level `model`. Unlike OpenCode, Kilo runs on the AI SDK, so the
|
|
16
|
+
// `npm` value must be an `@ai-sdk/*` package (default `@ai-sdk/openai-compatible`).
|
|
17
|
+
//
|
|
18
|
+
// The older VS Code extension also keeps a credential store at
|
|
19
|
+
// `~/.local/share/kilo/auth.json`; we still write the endpoint there for
|
|
20
|
+
// backward compatibility, but the canonical config is now `kilo.jsonc`.
|
|
21
|
+
|
|
22
|
+
const KILO_PROVIDER_ID = "cartethyia";
|
|
23
|
+
|
|
24
|
+
function resolveKiloConfigDirectory(): string {
|
|
25
|
+
if (IS_WIN) return join(process.env.USERPROFILE ?? homeDir(), ".config", "kilo");
|
|
26
|
+
if (IS_MAC) return join(homeDir(), ".config", "kilo");
|
|
27
|
+
return join(homeDir(), ".config", "kilo");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function kiloConfigPath(): string {
|
|
31
|
+
return join(resolveKiloConfigDirectory(), "kilo.jsonc");
|
|
32
|
+
}
|
|
33
|
+
|
|
11
34
|
function resolveKiloDataDirectory(): string {
|
|
12
35
|
if (IS_WIN) return join(process.env.LOCALAPPDATA ?? homeDir(), "kilo");
|
|
13
36
|
if (IS_MAC) return join(homeDir(), "Library", "Application Support", "kilo");
|
|
@@ -21,15 +44,52 @@ function kiloVscodeSettingsPath(): string {
|
|
|
21
44
|
return join(homeDir(), ".config", "Code", "User", "settings.json");
|
|
22
45
|
}
|
|
23
46
|
|
|
47
|
+
function kiloModelDef(model: string): unknown {
|
|
48
|
+
return {
|
|
49
|
+
name: model,
|
|
50
|
+
limit: { context: 128000, output: 64000 },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildKiloModels(models: readonly string[]): Record<string, unknown> {
|
|
55
|
+
const map: Record<string, unknown> = {};
|
|
56
|
+
for (const m of models) {
|
|
57
|
+
if (!m) continue;
|
|
58
|
+
map[m] = kiloModelDef(m);
|
|
59
|
+
}
|
|
60
|
+
return map;
|
|
61
|
+
}
|
|
62
|
+
|
|
24
63
|
export const kiloSpec: InjectorSpec = {
|
|
25
64
|
toolId: "kilo",
|
|
26
65
|
displayName: "Kilo Code",
|
|
27
66
|
binary: "kilo",
|
|
28
|
-
resolveDir:
|
|
29
|
-
resolvePath:
|
|
67
|
+
resolveDir: resolveKiloConfigDirectory,
|
|
68
|
+
resolvePath: kiloConfigPath,
|
|
30
69
|
|
|
31
70
|
async readStatus(path) {
|
|
32
|
-
const
|
|
71
|
+
const config = (await readJsonFile(path)) as {
|
|
72
|
+
provider?: Record<string, { npm?: string; options?: { baseURL?: string; apiKey?: string }; models?: Record<string, unknown> }>;
|
|
73
|
+
model?: string;
|
|
74
|
+
} | null;
|
|
75
|
+
const provider = config?.provider?.[KILO_PROVIDER_ID];
|
|
76
|
+
const endpoint = provider?.options?.baseURL ?? null;
|
|
77
|
+
const models = provider?.models ? Object.keys(provider.models) : null;
|
|
78
|
+
const active =
|
|
79
|
+
typeof config?.model === "string" && config.model.startsWith(`${KILO_PROVIDER_ID}/`)
|
|
80
|
+
? config.model.split("/").slice(1).join("/")
|
|
81
|
+
: null;
|
|
82
|
+
if (endpoint) {
|
|
83
|
+
return {
|
|
84
|
+
configured: isLocalEndpoint(endpoint),
|
|
85
|
+
currentEndpoint: endpoint,
|
|
86
|
+
rawApiKey: provider?.options?.apiKey ?? null,
|
|
87
|
+
currentModels: active && models ? [active, ...models.filter((m) => m !== active)] : models,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Fallback to the legacy auth.json store (older VS Code extension).
|
|
92
|
+
const auth = (await readJsonFile(join(resolveKiloDataDirectory(), "auth.json"))) as Record<
|
|
33
93
|
string,
|
|
34
94
|
{ baseUrl?: string; baseURL?: string; apiKey?: string; model?: string }
|
|
35
95
|
> | null;
|
|
@@ -47,22 +107,77 @@ export const kiloSpec: InjectorSpec = {
|
|
|
47
107
|
},
|
|
48
108
|
|
|
49
109
|
async apply(input, path) {
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
110
|
+
// Canonical config: ~/.config/kilo/kilo.jsonc
|
|
111
|
+
const existing = (await readJsonFile(path)) as Record<string, unknown> | null;
|
|
112
|
+
const config: Record<string, unknown> = (existing ?? {}) as Record<string, unknown>;
|
|
113
|
+
if (!config.provider || typeof config.provider !== "object") config.provider = {};
|
|
114
|
+
const providers = config.provider as Record<string, unknown>;
|
|
115
|
+
const prior = (providers[KILO_PROVIDER_ID] as Record<string, unknown> | undefined) ?? {};
|
|
116
|
+
const priorModels = (prior.models as Record<string, unknown> | undefined) ?? {};
|
|
117
|
+
const otherProviders = Object.keys(providers).filter((id) => id !== KILO_PROVIDER_ID);
|
|
118
|
+
providers[KILO_PROVIDER_ID] = {
|
|
119
|
+
npm: input.sdk ?? (prior as { npm?: string }).npm ?? "@ai-sdk/openai-compatible",
|
|
120
|
+
options: {
|
|
121
|
+
...((prior.options as Record<string, unknown> | undefined) ?? {}),
|
|
122
|
+
baseURL: input.endpoint,
|
|
123
|
+
apiKey: input.apiKey,
|
|
124
|
+
},
|
|
125
|
+
models: { ...priorModels, ...buildKiloModels(input.modelIds) },
|
|
126
|
+
};
|
|
127
|
+
const activeModel = input.activeModel ?? input.modelIds[0] ?? "";
|
|
128
|
+
config.model = activeModel === "" ? "" : `${KILO_PROVIDER_ID}/${activeModel}`;
|
|
129
|
+
|
|
130
|
+
// YOLO mode: auto-approve shell/read/write.
|
|
131
|
+
if (input.bypassPermissions === true) {
|
|
132
|
+
config.permissions = [
|
|
133
|
+
{ action: "shell", resource: "*", effect: "allow" },
|
|
134
|
+
{ action: "external_directory", resource: "*", effect: "allow" },
|
|
135
|
+
{ action: "question", resource: "*", effect: "deny" },
|
|
136
|
+
];
|
|
137
|
+
} else if (input.bypassPermissions === false) {
|
|
138
|
+
delete config.permissions;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
await writeJsonFileWithBackup(path, config);
|
|
142
|
+
|
|
143
|
+
// Legacy credential store for the older VS Code extension.
|
|
144
|
+
const authPath = join(resolveKiloDataDirectory(), "auth.json");
|
|
145
|
+
const auth = ((await readJsonFile(authPath)) as Record<string, unknown> | null) ?? {};
|
|
146
|
+
auth["openai-compatible"] = { baseUrl: input.endpoint, apiKey: input.apiKey, model: activeModel };
|
|
147
|
+
await writeJsonFileWithBackup(authPath, auth);
|
|
54
148
|
|
|
55
149
|
const settings =
|
|
56
150
|
((await readJsonFile(kiloVscodeSettingsPath())) as Record<string, unknown> | null) ?? {};
|
|
57
151
|
settings["kilo.code.authProviderOverride"] = "openai-compatible";
|
|
58
|
-
await
|
|
152
|
+
await writeJsonFileWithBackup(kiloVscodeSettingsPath(), settings);
|
|
153
|
+
|
|
154
|
+
return otherProviders.length > 0
|
|
155
|
+
? [
|
|
156
|
+
`Config already has other provider(s): ${otherProviders.join(", ")}. They were kept as-is — only ${KILO_PROVIDER_ID} was written/updated.`,
|
|
157
|
+
]
|
|
158
|
+
: undefined;
|
|
59
159
|
},
|
|
60
160
|
|
|
61
161
|
async reset(path) {
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
162
|
+
const config = (await readJsonFile(path)) as Record<string, unknown> | null;
|
|
163
|
+
if (config && config.provider && typeof config.provider === "object") {
|
|
164
|
+
const providers = config.provider as Record<string, unknown>;
|
|
165
|
+
delete providers[KILO_PROVIDER_ID];
|
|
166
|
+
if (Object.keys(providers).length === 0) delete config.provider;
|
|
167
|
+
}
|
|
168
|
+
if (typeof config?.model === "string" && config.model.startsWith(`${KILO_PROVIDER_ID}/`)) {
|
|
169
|
+
delete config.model;
|
|
170
|
+
}
|
|
171
|
+
if (config) await writeJsonFileWithBackup(path, config);
|
|
172
|
+
|
|
173
|
+
const auth = (await readJsonFile(join(resolveKiloDataDirectory(), "auth.json"))) as Record<
|
|
174
|
+
string,
|
|
175
|
+
unknown
|
|
176
|
+
> | null;
|
|
177
|
+
if (auth) {
|
|
178
|
+
delete auth["openai-compatible"];
|
|
179
|
+
await writeJsonFileWithBackup(join(resolveKiloDataDirectory(), "auth.json"), auth);
|
|
180
|
+
}
|
|
66
181
|
|
|
67
182
|
const settings = (await readJsonFile(kiloVscodeSettingsPath())) as Record<
|
|
68
183
|
string,
|
|
@@ -70,21 +185,31 @@ export const kiloSpec: InjectorSpec = {
|
|
|
70
185
|
> | null;
|
|
71
186
|
if (settings && "kilo.code.authProviderOverride" in settings) {
|
|
72
187
|
delete settings["kilo.code.authProviderOverride"];
|
|
73
|
-
await
|
|
188
|
+
await writeJsonFileWithBackup(kiloVscodeSettingsPath(), settings);
|
|
74
189
|
}
|
|
75
190
|
},
|
|
76
191
|
|
|
77
192
|
download(input) {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
193
|
+
const activeModel = input.activeModel ?? input.modelIds[0] ?? "";
|
|
194
|
+
const config: Record<string, unknown> = {
|
|
195
|
+
provider: {
|
|
196
|
+
[KILO_PROVIDER_ID]: {
|
|
197
|
+
npm: input.sdk ?? "@ai-sdk/openai-compatible",
|
|
198
|
+
options: {
|
|
199
|
+
baseURL: input.endpoint,
|
|
200
|
+
apiKey: input.apiKey,
|
|
201
|
+
},
|
|
202
|
+
models: buildKiloModels(input.modelIds),
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
model: activeModel === "" ? "" : `${KILO_PROVIDER_ID}/${activeModel}`,
|
|
206
|
+
};
|
|
207
|
+
return jsonDownload(config, { filename: "kilo.jsonc" });
|
|
82
208
|
},
|
|
83
209
|
|
|
84
210
|
messages: {
|
|
85
211
|
applied: "Kilo Code settings applied",
|
|
86
212
|
reset: "Cartethyia settings removed from Kilo Code",
|
|
87
|
-
resetMissing: "No
|
|
213
|
+
resetMissing: "No config file to reset",
|
|
88
214
|
},
|
|
89
215
|
};
|
|
90
|
-
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {} from "../contracts";
|
|
2
|
-
import { ensureV1Suffix, homeDir, join, readJsonFile,
|
|
2
|
+
import { ensureV1Suffix, homeDir, join, readJsonFile, writeJsonFileWithBackup } from "../fs-ops";
|
|
3
3
|
import type { InjectorSpec } from "../contracts";
|
|
4
|
+
import { DEFAULT_SDK } from "../contracts";
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
// OpenCode injector spec.
|
|
@@ -71,8 +72,9 @@ export const opencodeSpec: InjectorSpec = {
|
|
|
71
72
|
|
|
72
73
|
if (!config.provider) config.provider = {};
|
|
73
74
|
const prior = config.provider.cartethyia;
|
|
75
|
+
const otherProviders = Object.keys(config.provider).filter((id) => id !== "cartethyia");
|
|
74
76
|
config.provider.cartethyia = {
|
|
75
|
-
npm:
|
|
77
|
+
npm: input.sdk ?? prior?.npm ?? DEFAULT_SDK,
|
|
76
78
|
options: {
|
|
77
79
|
...(prior?.options ?? {}),
|
|
78
80
|
baseURL: ensureV1Suffix(input.endpoint),
|
|
@@ -84,6 +86,22 @@ export const opencodeSpec: InjectorSpec = {
|
|
|
84
86
|
const activeModel = input.activeModel ?? input.modelIds[0] ?? "";
|
|
85
87
|
config.model = activeModel === "" ? "" : `cartethyia/${activeModel}`;
|
|
86
88
|
|
|
89
|
+
if (input.websearch !== undefined) {
|
|
90
|
+
config.websearch = { provider: input.websearch };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// YOLO mode: auto-approve shell/read/write. When on, write OpenCode's
|
|
94
|
+
// permission rules; when off, leave whatever the user had (or nothing).
|
|
95
|
+
if (input.bypassPermissions === true) {
|
|
96
|
+
config.permissions = [
|
|
97
|
+
{ action: "shell", resource: "*", effect: "allow" },
|
|
98
|
+
{ action: "external_directory", resource: "*", effect: "allow" },
|
|
99
|
+
{ action: "question", resource: "*", effect: "deny" },
|
|
100
|
+
];
|
|
101
|
+
} else if (input.bypassPermissions === false) {
|
|
102
|
+
delete config.permissions;
|
|
103
|
+
}
|
|
104
|
+
|
|
87
105
|
if (!config.agent) config.agent = {};
|
|
88
106
|
const subagentModel = input.subagentModel ?? input.modelIds[0] ?? activeModel;
|
|
89
107
|
config.agent.explorer = {
|
|
@@ -92,7 +110,12 @@ export const opencodeSpec: InjectorSpec = {
|
|
|
92
110
|
model: `cartethyia/${subagentModel}`,
|
|
93
111
|
};
|
|
94
112
|
|
|
95
|
-
await
|
|
113
|
+
await writeJsonFileWithBackup(path, config);
|
|
114
|
+
return otherProviders.length > 0
|
|
115
|
+
? [
|
|
116
|
+
`Config already has other provider(s): ${otherProviders.join(", ")}. They were kept as-is — only cartethyia was written/updated.`,
|
|
117
|
+
]
|
|
118
|
+
: undefined;
|
|
96
119
|
},
|
|
97
120
|
|
|
98
121
|
async reset(path) {
|
|
@@ -113,7 +136,7 @@ export const opencodeSpec: InjectorSpec = {
|
|
|
113
136
|
if (Object.keys(config.agent).length === 0) delete config.agent;
|
|
114
137
|
}
|
|
115
138
|
|
|
116
|
-
await
|
|
139
|
+
await writeJsonFileWithBackup(path, config);
|
|
117
140
|
},
|
|
118
141
|
|
|
119
142
|
download(input) {
|
|
@@ -122,7 +145,7 @@ export const opencodeSpec: InjectorSpec = {
|
|
|
122
145
|
const config: OpencodeConfig = {
|
|
123
146
|
provider: {
|
|
124
147
|
cartethyia: {
|
|
125
|
-
npm:
|
|
148
|
+
npm: input.sdk ?? DEFAULT_SDK,
|
|
126
149
|
options: {
|
|
127
150
|
baseURL: ensureV1Suffix(input.endpoint),
|
|
128
151
|
apiKey: input.apiKey,
|
|
@@ -102,6 +102,10 @@ function parseApplyInput(
|
|
|
102
102
|
...(value.mode === "file" || value.mode === "remote" || value.mode === "both"
|
|
103
103
|
? { mode: value.mode }
|
|
104
104
|
: {}),
|
|
105
|
+
...(typeof value.sdk === "string" && value.sdk.length > 0 ? { sdk: value.sdk } : {}),
|
|
106
|
+
...(typeof value.websearch === "string" && value.websearch.length > 0
|
|
107
|
+
? { websearch: value.websearch }
|
|
108
|
+
: {}),
|
|
105
109
|
};
|
|
106
110
|
}
|
|
107
111
|
|
|
@@ -128,6 +132,8 @@ const applyBody = t.Object({
|
|
|
128
132
|
}),
|
|
129
133
|
),
|
|
130
134
|
bypassPermissions: t.Optional(t.Boolean()),
|
|
135
|
+
sdk: t.Optional(t.String()),
|
|
136
|
+
websearch: t.Optional(t.String()),
|
|
131
137
|
});
|
|
132
138
|
|
|
133
139
|
const mappingBody = t.Object({
|
|
@@ -44,8 +44,6 @@ function toToolRegistryEntry(def: ToolDef): ToolRegistryEntry {
|
|
|
44
44
|
...(def.settingsFile === undefined ? {} : { settingsFile: def.settingsFile }),
|
|
45
45
|
...(def.docsUrl === undefined ? {} : { docsUrl: def.docsUrl }),
|
|
46
46
|
...(def.notes === undefined ? {} : { notes: def.notes }),
|
|
47
|
-
...(def.guideSteps === undefined ? {} : { guideSteps: def.guideSteps }),
|
|
48
|
-
...(def.codeBlock === undefined ? {} : { codeBlock: def.codeBlock }),
|
|
49
47
|
};
|
|
50
48
|
}
|
|
51
49
|
|
|
@@ -64,8 +62,8 @@ function applyMessage(outcome: ApplyOutcome, toolId: string, fileMessage: string
|
|
|
64
62
|
case "remote":
|
|
65
63
|
return `Saved the remote route for ${toolId}. No config file was written.`;
|
|
66
64
|
case "none":
|
|
67
|
-
//
|
|
68
|
-
//
|
|
65
|
+
// Nothing was written and no remote route was saved; surface the
|
|
66
|
+
// injector's own message verbatim.
|
|
69
67
|
return fileMessage.length > 0 ? fileMessage : `Nothing to apply for ${toolId}.`;
|
|
70
68
|
}
|
|
71
69
|
}
|
|
@@ -274,8 +272,8 @@ export class CliToolService {
|
|
|
274
272
|
*
|
|
275
273
|
* `mode` defaults to `both` so the common single-machine case does the
|
|
276
274
|
* obviously-right thing without the operator choosing. A tool that has no
|
|
277
|
-
* injectable config
|
|
278
|
-
*
|
|
275
|
+
* injectable config reports `remote` honestly instead of claiming a file
|
|
276
|
+
* write it never performed.
|
|
279
277
|
*/
|
|
280
278
|
async applyConfig(
|
|
281
279
|
tenantId: string,
|
|
@@ -292,12 +290,14 @@ export class CliToolService {
|
|
|
292
290
|
let wroteFile = false;
|
|
293
291
|
let settingsPath: string | undefined;
|
|
294
292
|
let fileMessage = "";
|
|
293
|
+
let fileWarnings: readonly string[] | undefined;
|
|
295
294
|
|
|
296
295
|
if (wantsFile) {
|
|
297
296
|
const result: ApplyResult = await injector.apply(input);
|
|
298
297
|
wroteFile = result.success;
|
|
299
298
|
if (result.settingsPath !== undefined) settingsPath = result.settingsPath;
|
|
300
299
|
fileMessage = result.message;
|
|
300
|
+
fileWarnings = result.warnings;
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
let savedRemoteRoute = false;
|
|
@@ -321,6 +321,7 @@ export class CliToolService {
|
|
|
321
321
|
savedRemoteRoute,
|
|
322
322
|
...(settingsPath === undefined ? {} : { settingsPath }),
|
|
323
323
|
message: applyMessage(outcome, toolId, fileMessage),
|
|
324
|
+
...(fileWarnings && fileWarnings.length > 0 ? { warnings: fileWarnings } : {}),
|
|
324
325
|
};
|
|
325
326
|
}
|
|
326
327
|
|
|
@@ -232,6 +232,12 @@ export interface UsageRequestItem {
|
|
|
232
232
|
* written before the column existed.
|
|
233
233
|
*/
|
|
234
234
|
payloadCaptureState?: string;
|
|
235
|
+
/**
|
|
236
|
+
* True for a completed request whose output was far smaller than the model's
|
|
237
|
+
* ceiling and which ended with an ordinary `stop` (a "degenerate stop").
|
|
238
|
+
* Absent for everything else — including rows written before the column.
|
|
239
|
+
*/
|
|
240
|
+
degenerateStop?: boolean;
|
|
235
241
|
/**
|
|
236
242
|
* Bitmask of the payload stages captured for this request: 1 request,
|
|
237
243
|
* 2 provider request, 4 provider response, 8 client response. Read from the
|
|
@@ -165,6 +165,9 @@ function mapUsageRequestItem(
|
|
|
165
165
|
// Why the drawer is empty for this request — see the contract field. This is
|
|
166
166
|
// what makes the blank panels diagnosable without server logs.
|
|
167
167
|
...(event.payloadCaptureState ? { payloadCaptureState: event.payloadCaptureState } : {}),
|
|
168
|
+
// Degenerate stop (short output ended by an ordinary `stop`) — carried so
|
|
169
|
+
// the request detail can flag the pattern.
|
|
170
|
+
...(event.degenerateStop ? { degenerateStop: true } : {}),
|
|
168
171
|
};
|
|
169
172
|
}
|
|
170
173
|
async function resolveProxyLabel(
|
|
@@ -371,11 +371,9 @@ async function computeFamilyStatsBody(
|
|
|
371
371
|
.where(eq(apiKeys.id, templateKeyId))
|
|
372
372
|
.limit(1),
|
|
373
373
|
]);
|
|
374
|
-
// Cumulative family spend: the template's own counter
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
// likewise stay counted, so the running total is monotonic and consistent
|
|
378
|
-
// across the overview, the share dialog, and this page.
|
|
374
|
+
// Cumulative family spend: the template's own counter is the live
|
|
375
|
+
// accumulator (updated by every recipient commit), so it IS the total.
|
|
376
|
+
// Telemetry still drives the bounded daily/monthly windows below.
|
|
379
377
|
const templateLifetime = Number(template[0]?.lifetime ?? 0);
|
|
380
378
|
|
|
381
379
|
// The ranked tables need real events, bounded to the 24-hour window. The
|
|
@@ -398,10 +396,10 @@ async function computeFamilyStatsBody(
|
|
|
398
396
|
});
|
|
399
397
|
rollup.totals = {
|
|
400
398
|
...rollup.totals,
|
|
401
|
-
// The template counter
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
totalTokens: templateLifetime
|
|
399
|
+
// The template counter IS the running family total (live accumulator).
|
|
400
|
+
// Daily/monthly windows stay telemetry-only (the counter has no time
|
|
401
|
+
// axis), so those keep their own sums.
|
|
402
|
+
totalTokens: templateLifetime,
|
|
405
403
|
todayTokens,
|
|
406
404
|
monthTokens,
|
|
407
405
|
// Lifetime counts come from the durable rollup, not the page: a family
|
|
@@ -57,6 +57,12 @@ export interface TelemetryEventRecord {
|
|
|
57
57
|
readonly lastEventAtMs: number | null;
|
|
58
58
|
/** Why the drawer payloads are empty, or `captured`; absent when unrecorded. */
|
|
59
59
|
readonly payloadCaptureState?: string | null;
|
|
60
|
+
/**
|
|
61
|
+
* True for a completed request whose output was far smaller than the model's
|
|
62
|
+
* ceiling and which ended with an ordinary `stop` (a "degenerate stop").
|
|
63
|
+
* NULL/unset for everything else.
|
|
64
|
+
*/
|
|
65
|
+
readonly degenerateStop?: boolean;
|
|
60
66
|
}
|
|
61
67
|
|
|
62
68
|
function asString(value: unknown): string | null {
|
|
@@ -109,6 +115,7 @@ function normalize(raw: Record<string, unknown>): TelemetryEventRecord | undefin
|
|
|
109
115
|
tokensPerSec: asNumber(raw["tokensPerSec"]),
|
|
110
116
|
firstContentDeltaAtMs: asNumber(raw["firstContentDeltaAtMs"]),
|
|
111
117
|
lastEventAtMs: asNumber(raw["lastEventAtMs"]),
|
|
118
|
+
...(raw["degenerateStop"] === true ? { degenerateStop: true } : {}),
|
|
112
119
|
};
|
|
113
120
|
}
|
|
114
121
|
|
|
@@ -184,6 +184,7 @@ function eventRecordFromRow(row: {
|
|
|
184
184
|
readonly firstContentDeltaAtMs: number | null;
|
|
185
185
|
readonly lastEventAtMs: number | null;
|
|
186
186
|
readonly payloadCaptureState: string | null;
|
|
187
|
+
readonly degenerateStop: boolean | null;
|
|
187
188
|
}): TelemetryEventRecord {
|
|
188
189
|
return {
|
|
189
190
|
id: row.id ?? row.requestId,
|
|
@@ -219,6 +220,7 @@ function eventRecordFromRow(row: {
|
|
|
219
220
|
firstContentDeltaAtMs: row.firstContentDeltaAtMs,
|
|
220
221
|
lastEventAtMs: row.lastEventAtMs,
|
|
221
222
|
payloadCaptureState: row.payloadCaptureState,
|
|
223
|
+
...(row.degenerateStop === true ? { degenerateStop: true } : {}),
|
|
222
224
|
};
|
|
223
225
|
}
|
|
224
226
|
|
|
@@ -65,6 +65,12 @@ export interface TelemetryEventInput {
|
|
|
65
65
|
* list instead of needing the server's logs.
|
|
66
66
|
*/
|
|
67
67
|
readonly payloadCaptureState?: string;
|
|
68
|
+
/**
|
|
69
|
+
* True for a completed request whose output was far smaller than the model's
|
|
70
|
+
* ceiling and which ended with an ordinary `stop` (a "degenerate stop").
|
|
71
|
+
* Carried onto the telemetry row so this pattern can be measured.
|
|
72
|
+
*/
|
|
73
|
+
readonly degenerateStop?: boolean;
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
function tokenCountToInt(value: number | "unavailable" | undefined): number | undefined {
|
|
@@ -155,6 +161,7 @@ function telemetryEventRow(event: TelemetryEventInput) {
|
|
|
155
161
|
firstContentDeltaAtMs: event.firstContentDeltaAtMs ?? null,
|
|
156
162
|
lastEventAtMs: event.lastEventAtMs ?? null,
|
|
157
163
|
payloadCaptureState: event.payloadCaptureState ?? null,
|
|
164
|
+
degenerateStop: event.degenerateStop ?? null,
|
|
158
165
|
};
|
|
159
166
|
}
|
|
160
167
|
|
|
@@ -1483,6 +1483,14 @@ export const telemetryEvents = pgTable(
|
|
|
1483
1483
|
* tell "not configured" from "broken".
|
|
1484
1484
|
*/
|
|
1485
1485
|
payloadCaptureState: text("payload_capture_state"),
|
|
1486
|
+
/**
|
|
1487
|
+
* Set for a completed request whose output is far smaller than what the
|
|
1488
|
+
* model can produce AND which the model ended with an ordinary `stop`
|
|
1489
|
+
* (a "degenerate stop"). Introduced to measure how often a model
|
|
1490
|
+
* truncates itself on a cached prefix — the pattern that surfaced as
|
|
1491
|
+
* short, cut-off chat replies. NULL/unset for everything else.
|
|
1492
|
+
*/
|
|
1493
|
+
degenerateStop: boolean("degenerate_stop"),
|
|
1486
1494
|
},
|
|
1487
1495
|
(table) => [
|
|
1488
1496
|
index("telemetry_events_created_at_idx").on(table.createdAt),
|