From 605bb69005d2af13c4ad8feee0d5d260173ad7b4 Mon Sep 17 00:00:00 2001 From: Z Dev Bot Date: Sun, 9 Aug 2026 07:50:28 +0000 Subject: [PATCH 01/71] feat(agent-core-v2): add visual model assignment for image inspection Mirror the experimental `[secondary_model]` slot as a new `[visual_model]` configuration section so users can pin a vision-capable model for image / screenshot / video inspection tasks even when their main coding model is text-only. - `[visual_model]` config section + `KIMI_VISUAL_MODEL` / `KIMI_VISUAL_EFFORT` env overrides, registered alongside the other kosong config sections (parallel to `SECONDARY_MODEL_SECTION`). - `visualModelOverlay` synthesizes the derived `__visual__` registry entry when the recipe carries patch fields (mirror of `secondaryModelOverlay`); the derived entry lives only in memory and is stripped from `config.toml` writes. - `visual-model` experimental flag (`KIMI_CODE_EXPERIMENTAL_VISUAL_MODEL`) gates the resolver; off by default. - `resolveVisualModel` / `resolveVisualBinding` / `buildVisualModelDescriptions` / `visualDisplayModel` / `stripVisualModelParameter` / `wrapVisualModelError` mirror the subagent resolver family. - `AgentMediaToolsRegistrar` now consults `resolveVisualModel`: when the caller's model is text-only but a vision-capable visual model is configured, the media tools register against the visual model's capabilities and requester so `ReadMediaFile` stays available. When unset, behavior is unchanged. - Regenerates `docs/config-manifest.toml` (now lists `visualModel` and the `visualModelOverlay`). - Bilingual VitePress docs (`docs/{en,zh}/configuration/config-files.md`) describe the new section, env vars, experiment flag, and the unchanged-when-unset contract. - Vitest coverage: `visualModelOverlay.test.ts` (apply / strip / no collision with `__secondary__`) and `session/visual/configSection.test.ts` (resolution + unset-fallback + display model + schema strip + error wrapping). The existing `read-media.test.ts` registrar construction site is updated for the two new constructor deps. Closes MoonshotAI/kimi-code#2750 --- .changeset/visual-model-assignment.md | 7 + docs/en/configuration/config-files.md | 28 +++ docs/zh/configuration/config-files.md | 28 +++ .../agent-core-v2/docs/config-manifest.toml | 27 ++- .../src/agent/media/mediaToolsRegistrar.ts | 62 ++++- .../src/app/kosongConfig/configSection.ts | 22 ++ .../app/kosongConfig/visualModelOverlay.ts | 105 ++++++++ packages/agent-core-v2/src/index.ts | 37 ++- .../src/session/visual/configSection.ts | 225 ++++++++++++++++++ .../agent-core-v2/src/session/visual/flag.ts | 34 +++ .../test/agent/media/tools/read-media.test.ts | 4 + .../kosongConfig/visualModelOverlay.test.ts | 129 ++++++++++ .../test/session/visual/configSection.test.ts | 203 ++++++++++++++++ 13 files changed, 900 insertions(+), 11 deletions(-) create mode 100644 .changeset/visual-model-assignment.md create mode 100644 packages/agent-core-v2/src/app/kosongConfig/visualModelOverlay.ts create mode 100644 packages/agent-core-v2/src/session/visual/configSection.ts create mode 100644 packages/agent-core-v2/src/session/visual/flag.ts create mode 100644 packages/agent-core-v2/test/app/kosongConfig/visualModelOverlay.test.ts create mode 100644 packages/agent-core-v2/test/session/visual/configSection.test.ts diff --git a/.changeset/visual-model-assignment.md b/.changeset/visual-model-assignment.md new file mode 100644 index 00000000000..cfc01264701 --- /dev/null +++ b/.changeset/visual-model-assignment.md @@ -0,0 +1,7 @@ +--- +'@moonshot-ai/kimi-code': minor +--- + +Add a dedicated `[visual_model]` configuration section that lets users pin a vision-capable model for image / screenshot / video inspection tasks, mirroring the existing experimental `[secondary_model]` slot. + +When the `visual-model` experiment is enabled (`KIMI_CODE_EXPERIMENTAL_VISUAL_MODEL=1`) and `[visual_model]` is configured, the `ReadMediaFile` tool registers against the visual model's capabilities and requester when the caller's main model is text-only — so the LLM keeps access to image inspection instead of silently losing the tool. When unset, behavior is unchanged. Adds parallel `resolveVisualModel` / `resolveVisualBinding` / `buildVisualModelDescriptions` resolvers and a `visual-model` experiment flag, all gated by the experiment and covered by vitest tests. diff --git a/docs/en/configuration/config-files.md b/docs/en/configuration/config-files.md index 27f5f03f366..607d8bb8706 100644 --- a/docs/en/configuration/config-files.md +++ b/docs/en/configuration/config-files.md @@ -221,6 +221,34 @@ max_output_size = 8192 When the experiment is enabled, the configuration is validated as the session starts: an unresolvable `model`, or a `default_effort` not listed by the (patched) model, produces a startup warning (also returned by the session-warnings API). The check is advisory — a broken secondary model still fails at spawn time, with the same source hint attached to the spawn error. +## `visual_model` + +The visual model is a companion model configuration for **vision-only work** — typically a vision-capable model you pin so image / screenshot / video inspection tasks can run even when your main coding model is text-only. Its consumer today is `ReadMediaFile`: when set, the media tool registers against the visual model's capabilities and requester when the caller's model cannot consume image or video input, so the LLM keeps the `ReadMediaFile` tool available for visual inspection instead of silently losing it. When unset, behavior is unchanged (the tool registers only when the caller's model is vision-capable, exactly as before). + +This is a default binding, not a forced one. With the experiment enabled, the visual-model resolver (`resolveVisualModel`) returns the configured recipe and the media-tools registrar consults it; future agent tools that perform visual inspection can advertise a `model` parameter (accepting the symbolic values `"visual"` / `"primary"`) the same way `Agent` / `AgentSwarm` advertise their secondary-model choice. + +This feature is experimental and disabled by default. Enable it with `KIMI_CODE_EXPERIMENTAL_VISUAL_MODEL=1`, or the master `KIMI_CODE_EXPERIMENTAL_FLAG=1`. It takes effect in every launch mode, including the interactive TUI. + +| Field | Type | Default | Description | +| --- | --- | --- | --- | +| `model` | `string` | — | The alias of a configured [`[models]`](#models) entry, e.g. `kimi-code/kimi-vision` (any provider, not limited to Kimi models). Should be a vision-capable entry (`image_in` and/or `video_in` listed in its `capabilities`) | +| `default_effort` | `string` | — | Thinking effort applied when visual tasks bind to the visual model. Unset, the effort resolves naturally (global `[thinking]` config → the bound model's default effort) instead of inheriting the caller's effort. Follows the main model's thinking-effort semantics: models with strict effort validation (e.g. Kimi models) fall back to their default effort for unsupported values; other providers receive the value as-is | +| Other fields | — | — | Accepts every field of [`[models."".overrides]`](#models) (`max_context_size`, `max_output_size`, `support_efforts`, …) as a model patch applied only to visual tasks | + +Every field besides `model` forms a patch: when at least one patch field is set, the runtime synthesizes a derived model entry in memory (a copy of the pointed entry with the patch merged into its overrides, patch winning conflicts) and visual tasks bind that derived entry; with no patch fields, visual tasks bind the pointed entry directly. The derived entry lives only in memory (never written back to `config.toml`) and is hidden from model-selection lists. + +```toml +[visual_model] +model = "kimi-code/kimi-vision" +default_effort = "low" +max_output_size = 8192 +``` + +`model` / `default_effort` can be overridden by the `KIMI_VISUAL_MODEL` / `KIMI_VISUAL_EFFORT` environment variables, which take higher priority than `config.toml`. + +When the experiment is enabled, the configuration is validated as the session starts: an unresolvable `model` produces a startup warning. The check is advisory — a broken visual model still fails at use time, with the same source hint attached to the error. + + ## `thinking` `thinking` sets the global default behavior for Thinking mode. diff --git a/docs/zh/configuration/config-files.md b/docs/zh/configuration/config-files.md index f102efba0f8..5a2d140d462 100644 --- a/docs/zh/configuration/config-files.md +++ b/docs/zh/configuration/config-files.md @@ -221,6 +221,34 @@ max_output_size = 8192 实验功能启用后,会话启动时会校验该配置:`model` 无法解析,或 `default_effort` 不在(应用补丁后的)模型 effort 列表中时,会在启动时显示警告(并通过会话警告 API 返回)。该检查仅为提示——配置有误的次主力模型仍会在派生子 Agent 时失败,派生错误中同样附带配置来源提示。 +## `visual_model` + +视觉模型是为**视觉类任务**单独配置的伴生模型——通常是一个具备视觉能力的模型,用来在主编码模型是纯文本时仍然能执行图像 / 截图 / 视频检查。它目前的消费者是 `ReadMediaFile`:设置后,当调用方模型无法处理图像或视频输入时,媒体工具会按照视觉模型的能力和 requester 注册,LLM 因此仍然可以调用 `ReadMediaFile` 进行视觉检查,而不会因为主模型是纯文本就静默丢失该工具。未设置时行为不变(工具仅在调用方模型具备视觉能力时注册,与之前一致)。 + +这是默认绑定而非强制。实验功能启用后,视觉模型解析器(`resolveVisualModel`)会返回已配置的 recipe,媒体工具注册器会读取它;后续执行视觉检查的 agent 工具可以像 `Agent` / `AgentSwarm` 暴露次主力模型选择那样,在描述里暴露 `model` 参数(仅接受 `"visual"` / `"primary"` 两个符号值)。 + +该功能目前是实验功能,默认关闭。通过 `KIMI_CODE_EXPERIMENTAL_VISUAL_MODEL=1` 启用,或使用 master `KIMI_CODE_EXPERIMENTAL_FLAG=1`。它在包括交互式 TUI 在内的所有启动方式下生效。 + +| 字段 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | +| `model` | `string` | — | [`[models]`](#models) 中已配置条目的别名,如 `kimi-code/kimi-vision`(不限 kimi 模型,可用任意供应商)。应选择具备视觉能力的条目(其 `capabilities` 列出 `image_in` 和/或 `video_in`) | +| `default_effort` | `string` | — | 视觉任务绑定视觉模型时使用的 thinking effort。未设置时按"全局 `[thinking]` 配置 → 模型默认 effort"的链路解析,不再继承主 Agent 的 effort。与主模型的 thinking effort 语义一致:严格校验 effort 的模型(如 kimi 模型)在不支持该取值时回退到模型默认 effort,其他供应商的模型按原样发送给后端 | +| 其他字段 | — | — | 接受 [`[models."".overrides]`](#models) 的全部字段(`max_context_size`、`max_output_size`、`support_efforts` 等),作为仅对视觉任务生效的模型补丁 | + +`model` 之外的字段构成补丁:存在补丁字段时,运行时会在内存中合成一个派生模型条目(被指向条目的拷贝,补丁并入其 overrides 且补丁优先),视觉任务实际绑定该派生条目;没有补丁字段时,视觉任务直接绑定 `model` 指向的条目。派生条目只存在于内存中(不写回 `config.toml`),也不会出现在模型选择列表里。 + +```toml +[visual_model] +model = "kimi-code/kimi-vision" +default_effort = "low" +max_output_size = 8192 +``` + +`model` / `default_effort` 可被环境变量 `KIMI_VISUAL_MODEL` / `KIMI_VISUAL_EFFORT` 覆盖,优先级均高于配置文件。 + +实验功能启用后,会话启动时会校验该配置:`model` 无法解析时会在启动时显示警告。该检查仅为提示——配置有误的视觉模型仍会在使用时失败,错误中同样附带配置来源提示。 + + ## `thinking` `thinking` 设置 Thinking 模式的全局默认行为。 diff --git a/packages/agent-core-v2/docs/config-manifest.toml b/packages/agent-core-v2/docs/config-manifest.toml index a6387283992..23b8890f689 100644 --- a/packages/agent-core-v2/docs/config-manifest.toml +++ b/packages/agent-core-v2/docs/config-manifest.toml @@ -8,7 +8,7 @@ # commented "# field: type" lines describe the remaining schema fields. # Values resolve as: default -> config.toml -> env overlay -> memory. -# Index (25 sections · 3 overlay(s)) +# Index (26 sections · 4 overlay(s)) # background src/agent/task/configSection.ts # builtinProductSkills src/app/skillCatalog/configSection.ts # cron src/app/cron/configSection.ts @@ -34,9 +34,11 @@ # thinking src/app/kosongConfig/configSection.ts # tokenCounting src/agent/tokenCounting/configSection.ts # tools src/agent/toolPolicy/configSection.ts +# visualModel src/app/kosongConfig/configSection.ts # (overlay) servicesCredentialEnvOverlay src/app/auth/configSection.ts # (overlay) kimiModelEnvOverlay src/app/kosongConfig/envOverlay.ts # (overlay) secondaryModelOverlay src/app/kosongConfig/secondaryModelOverlay.ts +# (overlay) visualModelOverlay src/app/kosongConfig/visualModelOverlay.ts # ########################################################################## # background @@ -439,3 +441,26 @@ strategy = "measured+estimated" [tools] # enabled: string[] # disabled: string[] + +# ########################################################################## +# visualModel (config.toml: visual_model) +# owner: src/app/kosongConfig/configSection.ts +# scope: core +# hooks: stripEnv +# env: +# model <- KIMI_VISUAL_MODEL (custom parse) +# default_effort <- KIMI_VISUAL_EFFORT (custom parse) +# ########################################################################## + +[visual_model] +# max_context_size: integer +# max_input_size: integer +# max_output_size: integer +# capabilities: string[] +# display_name: string +# reasoning_key: string +# adaptive_thinking: boolean +# support_efforts: string[] +# default_effort: string +# off_effort: string +# model: string diff --git a/packages/agent-core-v2/src/agent/media/mediaToolsRegistrar.ts b/packages/agent-core-v2/src/agent/media/mediaToolsRegistrar.ts index df70906c632..d7e575f9826 100644 --- a/packages/agent-core-v2/src/agent/media/mediaToolsRegistrar.ts +++ b/packages/agent-core-v2/src/agent/media/mediaToolsRegistrar.ts @@ -32,7 +32,9 @@ import { LifecycleScope } from '#/app/scopes'; import { ScopeActivation, registerScopedService } from '#/_base/di/scope'; import { defineState } from '#/_base/state/stateRegistry'; import { IAgentStateService } from '#/agent/state/agentState'; +import { IConfigService } from '#/app/config/config'; import { IEventBus } from '#/app/event/eventBus'; +import { IFlagService } from '#/app/flag/flag'; import { ITelemetryService } from '#/app/telemetry/telemetry'; import { IModelCatalog, type Model } from '#/kosong/model/catalog'; import { type ModelRequester } from '#/kosong/model/modelRequester'; @@ -43,6 +45,8 @@ import { ISessionWorkspaceContext } from '#/session/workspaceContext/workspaceCo import { IAgentProfileService } from '#/agent/profile/profile'; import { IAgentToolRegistryService } from '#/agent/toolRegistry/toolRegistry'; import { extendWorkspaceWithSkillRoots } from '#/tool/path-access'; +import { resolveVisualModel } from '#/session/visual/configSection'; +import type { ModelCapability } from '#/kosong/contract/capability'; import { IAgentMediaToolsRegistrar } from './mediaTools'; import { createVideoUploader, registerMediaTools } from './registerMediaTools'; @@ -61,6 +65,8 @@ export class AgentMediaToolsRegistrar extends Service implements IAgentMediaTool @IAgentToolRegistryService private readonly toolRegistry: IAgentToolRegistryService, @IAgentProfileService private readonly profile: IAgentProfileService, @IModelCatalog private readonly modelCatalog: IModelCatalog, + @IConfigService private readonly appConfig: IConfigService, + @IFlagService private readonly flags: IFlagService, @IEventBus eventBus: IEventBus, @IHostFileSystem private readonly fs: IHostFileSystem, @IHostEnvironment private readonly env: IHostEnvironment, @@ -85,11 +91,43 @@ export class AgentMediaToolsRegistrar extends Service implements IAgentMediaTool } private refresh(): void { - const capabilities = this.profile.getModelCapabilities(); + const callerCapabilities = this.profile.getModelCapabilities(); + const callerModelAlias = this.profile.getModel(); + + // Visual-model companion: when configured (and the experiment is on), + // a vision-capable visual model lets the media tools register even if + // the caller's model is text-only. The visual model's capabilities and + // requester are used in that case so the tool's mime / compression + // decisions match the model that will actually consume the payload. + // When the visual model is unset, behavior is unchanged. + const visualRecipe = resolveVisualModel(this.appConfig, this.flags); + const visualModelAlias = visualRecipe?.model; + let visualRequester: ModelRequester | undefined; + let visualModel: Model | undefined; + if (visualModelAlias !== undefined) { + try { + visualRequester = this.modelCatalog.getRequester(visualModelAlias); + visualModel = visualRequester.model; + } catch { + // dangling pointer — the secondary-model-style warning service is + // responsible for surfacing it; the registrar just falls back to + // the caller's model. + } + } + const visualCapabilities: ModelCapability | undefined = visualModel?.capabilities; + const callerHasMedia = callerCapabilities.image_in || callerCapabilities.video_in; + const visualHasMedia = visualCapabilities !== undefined + && (visualCapabilities.image_in || visualCapabilities.video_in); + const useVisual = !callerHasMedia && visualHasMedia; + const capabilities = useVisual ? visualCapabilities! : callerCapabilities; + const key = [ - this.profile.getModel(), - String(capabilities.image_in), - String(capabilities.video_in), + callerModelAlias, + String(callerCapabilities.image_in), + String(callerCapabilities.video_in), + visualModelAlias ?? '', + String(visualCapabilities?.image_in ?? false), + String(visualCapabilities?.video_in ?? false), ].join('|'); if (key === this.registeredKey) return; this.registeredKey = key; @@ -97,12 +135,18 @@ export class AgentMediaToolsRegistrar extends Service implements IAgentMediaTool const workspaceCtx = this.workspaceCtx; const skillCatalog = this.skillCatalog; const env = this.env; - const modelAlias = this.profile.getModel(); + const boundModelAlias = useVisual ? visualModelAlias! : callerModelAlias; + const boundRequester = useVisual ? visualRequester : undefined; let requester: ModelRequester | undefined; let model: Model | undefined; - if (modelAlias !== '') { - requester = this.modelCatalog.getRequester(modelAlias); - model = requester.model; + if (boundModelAlias !== '') { + try { + requester = boundRequester ?? this.modelCatalog.getRequester(boundModelAlias); + model = requester.model; + } catch { + requester = undefined; + model = undefined; + } } this.registration = registerMediaTools(this.toolRegistry, { fs: this.fs, @@ -123,7 +167,7 @@ export class AgentMediaToolsRegistrar extends Service implements IAgentMediaTool videoUploader: createVideoUploader(requester, { client: this.telemetry, props: { - model: modelAlias, + model: boundModelAlias, provider_type: model?.providerType ?? model?.protocol, protocol: model?.protocol, }, diff --git a/packages/agent-core-v2/src/app/kosongConfig/configSection.ts b/packages/agent-core-v2/src/app/kosongConfig/configSection.ts index 7af26196b34..0450faff53e 100644 --- a/packages/agent-core-v2/src/app/kosongConfig/configSection.ts +++ b/packages/agent-core-v2/src/app/kosongConfig/configSection.ts @@ -338,6 +338,28 @@ registerConfigSection(SECONDARY_MODEL_SECTION, SecondaryModelConfigSchema, { }); +export const VISUAL_MODEL_SECTION = 'visualModel'; + +export const VISUAL_MODEL_ENV = 'KIMI_VISUAL_MODEL'; +export const VISUAL_MODEL_EFFORT_ENV = 'KIMI_VISUAL_EFFORT'; + +export const VisualModelConfigSchema = ModelOverrideSchema.extend({ + model: z.string().min(1).optional(), +}); + +export type VisualModelConfig = z.infer; + +export const visualModelEnvBindings = envBindings(VisualModelConfigSchema, { + model: { env: VISUAL_MODEL_ENV, parse: parseNonEmptyEnv }, + defaultEffort: { env: VISUAL_MODEL_EFFORT_ENV, parse: parseNonEmptyEnv }, +}); + +registerConfigSection(VISUAL_MODEL_SECTION, VisualModelConfigSchema, { + env: visualModelEnvBindings, + stripEnv: stripEnvBoundFields(visualModelEnvBindings), +}); + + export const MODEL_CATALOG_SECTION = 'modelCatalog'; export const ModelCatalogConfigSchema = z.object({ diff --git a/packages/agent-core-v2/src/app/kosongConfig/visualModelOverlay.ts b/packages/agent-core-v2/src/app/kosongConfig/visualModelOverlay.ts new file mode 100644 index 00000000000..a10d087f3b3 --- /dev/null +++ b/packages/agent-core-v2/src/app/kosongConfig/visualModelOverlay.ts @@ -0,0 +1,105 @@ +/** + * `kosongConfig` domain — `[visual_model]` derived-entry overlay. + * + * Visual-model mirror of {@link secondaryModelOverlay}: when the + * visual-model recipe carries patch fields, synthesizes the derived registry + * entry ({@link VISUAL_DERIVED_MODEL_ID}) into the effective `models` view — + * a copy of the pointed entry with the patch merged into its `overrides` + * block (patch wins conflicts) and `aliases` dropped, so the derived entry + * never competes in name/alias routing. Visual-task model binding then + * resolves it by name through the standard catalog path, and the patch rides + * the same `effectiveModelConfig` merge as any `models.*.overrides` + * (including its `supportEfforts` / `defaultEffort` pruning and input + * clamping). + * + * Like the env overlay and the secondary-model overlay, the synthesized entry + * lives ONLY in the in-memory effective view: `strip` removes it from + * `models` writes so it never reaches `config.toml`, and the persistence + * bridge's deep-equal guards keep the two-way sync silent. `strip` also rolls + * back a `defaultModel` pointer set to the derived id (restoring the raw + * value, mirroring the secondary-model overlay's pinned-pointer handling) — + * the pointer can never dangle on disk after the recipe is removed. Nothing + * is synthesized when the recipe has no patch fields (visual tasks bind the + * pointed entry directly), when `visual.model` is unset, or when the pointed + * entry does not exist. The id is reserved: a user-configured entry under it + * is stripped on write all the same. + * + * Self-registered at module load via `registerConfigOverlay`; it is imported + * for side effects after the secondary-model overlay so a `visual.model` + * pointing at the secondary-derived entry sees the already-applied secondary + * view, and a `secondary.model` pointing at the visual-derived entry sees + * the already-applied visual view. + */ + +import type { ConfigEffectiveOverlay } from '#/app/config/config'; +import { registerConfigOverlay } from '#/app/config/configOverlayContributions'; +import { isPlainObject } from '#/app/config/toml'; +import type { ModelOverride } from '#/kosong/model/model'; + +import { + DEFAULT_MODEL_SECTION, + MODELS_SECTION, + VISUAL_MODEL_SECTION, + type VisualModelConfig, +} from './configSection'; + +export const VISUAL_DERIVED_MODEL_ID = '__visual__'; + +export function visualModelPatch( + visual: VisualModelConfig | undefined, +): ModelOverride | undefined { + if (visual === undefined) return undefined; + const { model: _model, ...patch } = visual; + return Object.keys(patch).length > 0 ? patch : undefined; +} + +function asRecord(value: unknown): Record { + return isPlainObject(value) ? value : {}; +} + +function withoutKey(value: unknown, key: string): unknown { + if (!isPlainObject(value) || !(key in value)) return value; + const out: Record = { ...value }; + delete out[key]; + return out; +} + +export const visualModelOverlay: ConfigEffectiveOverlay = { + apply(effective, _getEnv, validate) { + const visual = effective[VISUAL_MODEL_SECTION] as VisualModelConfig | undefined; + const patch = visualModelPatch(visual); + const baseId = visual?.model; + if (patch === undefined || baseId === undefined || baseId === VISUAL_DERIVED_MODEL_ID) { + return []; + } + const models = asRecord(effective[MODELS_SECTION]); + const base = models[baseId]; + if (!isPlainObject(base)) return []; + const { overrides: baseOverrides, aliases: _aliases, ...baseFields } = base; + const derived: Record = { + ...baseFields, + overrides: { ...asRecord(baseOverrides), ...patch }, + }; + effective[MODELS_SECTION] = validate(MODELS_SECTION, { + ...models, + [VISUAL_DERIVED_MODEL_ID]: derived, + }); + return [MODELS_SECTION]; + }, + + strip(domain, value, rawSnake) { + switch (domain) { + case MODELS_SECTION: + return withoutKey(value, VISUAL_DERIVED_MODEL_ID); + case DEFAULT_MODEL_SECTION: + if (value !== VISUAL_DERIVED_MODEL_ID) return value; + return typeof rawSnake['default_model'] === 'string' + ? rawSnake['default_model'] + : undefined; + default: + return value; + } + }, +}; + +registerConfigOverlay(visualModelOverlay); diff --git a/packages/agent-core-v2/src/index.ts b/packages/agent-core-v2/src/index.ts index df9feb7ea2a..d7ac8298fd4 100644 --- a/packages/agent-core-v2/src/index.ts +++ b/packages/agent-core-v2/src/index.ts @@ -150,6 +150,7 @@ export * from '#/kosong/protocol/protocolBase'; export * from '#/kosong/protocol/protocolTrait'; import '#/app/kosongConfig/envOverlay'; import '#/app/kosongConfig/secondaryModelOverlay'; +import '#/app/kosongConfig/visualModelOverlay'; export * from '#/kosong/model/completionBudget'; export * from '#/kosong/model/hostRequestHeaders'; export * from '#/kosong/model/model'; @@ -165,12 +166,29 @@ export { ModelCatalogConfigSchema, type ModelCatalogConfig, } from '#/app/kosongConfig/configSection'; -export type { SecondaryModelConfig } from '#/app/kosongConfig/configSection'; +export type { SecondaryModelConfig, VisualModelConfig } from '#/app/kosongConfig/configSection'; +export { + SECONDARY_MODEL_SECTION, + SECONDARY_MODEL_ENV, + SECONDARY_MODEL_EFFORT_ENV, + SecondaryModelConfigSchema, + secondaryModelEnvBindings, + VISUAL_MODEL_SECTION, + VISUAL_MODEL_ENV, + VISUAL_MODEL_EFFORT_ENV, + VisualModelConfigSchema, + visualModelEnvBindings, +} from '#/app/kosongConfig/configSection'; export { SECONDARY_DERIVED_MODEL_ID, secondaryModelOverlay, secondaryModelPatch, } from '#/app/kosongConfig/secondaryModelOverlay'; +export { + VISUAL_DERIVED_MODEL_ID, + visualModelOverlay, + visualModelPatch, +} from '#/app/kosongConfig/visualModelOverlay'; export * from '#/app/kosongConfig/kosongConfig'; export * from '#/app/kosongConfig/kosongConfigService'; export * from '#/kosong/model/modelOAuth'; @@ -396,6 +414,23 @@ export * from '#/agent/tools/agent/subagent-task'; export { AGENT_RUN_PROMPT_ORIGIN } from '#/session/subagent/runAgentTurn'; export * from '#/session/subagent/mirrorAgentRun'; import '#/session/subagent/configSection'; +import '#/session/visual/flag'; +import '#/session/visual/configSection'; +export { + VISUAL_MODEL_FLAG_ID, + VISUAL_MODEL_FLAG_ENV, + visualModelFlag, +} from '#/session/visual/flag'; +export { + resolveVisualModel, + resolveVisualBinding, + visualDisplayModel, + buildVisualModelDescriptions, + stripVisualModelParameter, + wrapVisualModelError, + VISUAL_MODEL_CHOICE_SCHEMA, + type VisualModelChoice, +} from '#/session/visual/configSection'; export * from '#/agent/tools/agent/agent'; import '#/agent/tools/agent/agentTool'; export * from '#/app/workspaceLifecycle/workspaceLifecycle'; diff --git a/packages/agent-core-v2/src/session/visual/configSection.ts b/packages/agent-core-v2/src/session/visual/configSection.ts new file mode 100644 index 00000000000..1a86d0d9766 --- /dev/null +++ b/packages/agent-core-v2/src/session/visual/configSection.ts @@ -0,0 +1,225 @@ +/** + * `visual` domain — visual-model config-section resolver. + * + * Visual-model mirror of {@link ../../../session/subagent/configSection}: + * resolves which model handles image / screenshot / video inspection tasks + * when the `visual-model` experiment is enabled and `[visual_model]` is + * configured. The caller's model remains the default; the visual model is + * an opt-in override for vision-only work, parallel to how the secondary + * model is an opt-in override for subagent spawns. + * + * Resolution rules (mirror of `resolveSubagentBinding`): + * - When the experiment is disabled, or `[visual_model]` is unset, returns + * `undefined` from {@link resolveVisualModel} and the caller's own model + * from {@link resolveVisualBinding} — no behavior change. + * - When set, {@link resolveVisualModel} returns the configured recipe; a + * recipe with patch fields binds the synthesized derived entry + * ({@link VISUAL_DERIVED_MODEL_ID}, materialized by `visualModelOverlay`); + * a pointer-only recipe binds the pointed entry directly. `default_effort` + * is passed as the explicit visual-task thinking effort; without it the + * visual task resolves thinking naturally (global thinking config → the + * bound model's default effort) rather than inheriting the caller's level. + * + * The TUI / agent tool descriptions can surface the pair via + * {@link buildVisualModelDescriptions} (each line suffixed with the entry's + * resolved capability flags, so the parent can route multimodal or + * thinking-heavy visual tasks instead of guessing from the model id), and + * spawn failures are wrapped with {@link wrapVisualModelError} so a missing + * visual-model alias points back at `[visual_model].model` / + * `KIMI_VISUAL_MODEL`. While the experiment is off, the no-op `model` + * parameter (when a tool chooses to advertise one for visual selection) is + * stripped via {@link stripVisualModelParameter}. Display-facing alias + * resolution goes through {@link visualDisplayModel}: the derived entry id + * means nothing to a user, so it resolves back to the recipe's base alias — + * flag-independent on purpose, since interpreting an already-persisted + * derived binding (resume) must keep working after the experiment is + * switched off. + */ + +import { z } from 'zod'; + +import { Error2, ErrorCodes, isError2 } from '#/errors'; +import type { AgentModelPreference } from '#/app/agentProfileCatalog/agentProfileCatalog'; +import { isPlainObject } from '#/app/config/toml'; +import type { IFlagService } from '#/app/flag/flag'; +import { + VISUAL_MODEL_ENV, + VISUAL_MODEL_SECTION, +} from '#/app/kosongConfig/configSection'; +import { + VISUAL_DERIVED_MODEL_ID, + visualModelPatch, +} from '#/app/kosongConfig/visualModelOverlay'; +import { type VisualModelConfig } from '#/app/kosongConfig/configSection'; +import type { IConfigService } from '#/app/config/config'; +import type { ModelCapability } from '#/kosong/contract/capability'; +import type { IModelCatalog } from '#/kosong/model/catalog'; + +import { VISUAL_MODEL_FLAG_ID } from './flag'; + +export type VisualModelChoice = AgentModelPreference; + +export function resolveVisualModel( + config: IConfigService, + flags: IFlagService, +): VisualModelConfig | undefined { + if (!flags.enabled(VISUAL_MODEL_FLAG_ID)) return undefined; + return config.get(VISUAL_MODEL_SECTION); +} + +/** + * Resolve which model handles a visual (image / screenshot / video) + * inspection task. `own` is the caller's current model state, used when + * inheriting (visual model unset or explicit `primary` request). + * + * `requested` mirrors the subagent `model` parameter: `undefined` follows the + * default (visual model when set, caller's model otherwise); `'primary'` + * forces the caller's model even when a visual model is configured; a + * visual-model-aware tool can also accept `'visual'` to force the visual + * model when configured (returns the caller's model when no visual model is + * configured, so the symbolic choice never fails). + */ +export function resolveVisualBinding( + config: IConfigService, + flags: IFlagService, + own: { modelAlias: string; thinkingLevel: string }, + requested?: VisualModelChoice, +): { model: string; thinking?: string; displayModel: string } { + const visual = resolveVisualModel(config, flags); + if (requested !== 'primary' && visual?.model !== undefined) { + const model = + visualModelPatch(visual) === undefined ? visual.model : VISUAL_DERIVED_MODEL_ID; + return { + model, + thinking: visual.defaultEffort, + displayModel: visualDisplayModel(config, model), + }; + } + return { + model: own.modelAlias, + thinking: own.thinkingLevel, + displayModel: visualDisplayModel(config, own.modelAlias), + }; +} + +export function visualDisplayModel(config: IConfigService, boundAlias: string): string { + if (boundAlias !== VISUAL_DERIVED_MODEL_ID) return boundAlias; + return ( + config.get(VISUAL_MODEL_SECTION)?.model ?? boundAlias + ); +} + +/** + * The "Available models" block appended to visual-task tool descriptions so + * the parent model knows it can pick. `undefined` when the visual model is + * not configured or the caller's model is not bound yet. + */ +export function buildVisualModelDescriptions( + config: IConfigService, + flags: IFlagService, + callerModelAlias: string | undefined, + modelCatalog: IModelCatalog, +): string | undefined { + const visual = resolveVisualModel(config, flags); + const visualModel = visual?.model; + if (visualModel === undefined || callerModelAlias === undefined) return undefined; + const boundVisual = + visualModelPatch(visual) === undefined ? visualModel : VISUAL_DERIVED_MODEL_ID; + return [ + 'Available models for visual inspection (pass via model):', + `- visual: ${visualModel} (default) — the configured visual model; prefer it for image / screenshot / video inspection${capabilitiesSuffix(resolvedCapabilities(modelCatalog, boundVisual))}`, + `- primary: ${callerModelAlias} — the main model you are running on; use it when the caller is itself vision-capable and you want to keep the work in-process${capabilitiesSuffix(resolvedCapabilities(modelCatalog, callerModelAlias))}`, + ].join('\n'); +} + +const ADVERTISED_CAPABILITY_FLAGS = [ + 'image_in', + 'video_in', + 'audio_in', + 'thinking', + 'tool_use', + 'dynamically_loaded_tools', +] as const satisfies readonly (keyof ModelCapability)[]; + +function capabilitiesSuffix(capability: ModelCapability | undefined): string { + if (capability === undefined) return ''; + const names = ADVERTISED_CAPABILITY_FLAGS.filter((flag) => capability[flag] === true); + return `; capabilities: ${names.length === 0 ? 'none' : names.join(', ')}`; +} + +function resolvedCapabilities( + modelCatalog: IModelCatalog, + model: string, +): ModelCapability | undefined { + try { + return modelCatalog.get(model).capabilities; + } catch { + return undefined; + } +} + +/** + * Strip the `model` property from a visual-task tool's advertised JSON schema. + * While the `visual-model` experiment is off the parameter is a silent no-op, + * so the schema the model sees (and the args validator compiled from the same + * advertised schema) drops it entirely — the visual-model concept never + * enters the prompt, and a stray `model` argument is rejected instead of + * silently inheriting the caller's model. Returns the input unchanged when + * there is no `model` property; otherwise a shallow copy — the input is never + * mutated, so callers can keep both variants as shared constants. + */ +export function stripVisualModelParameter( + parameters: Record, +): Record { + const properties = parameters['properties']; + if (!isPlainObject(properties) || !('model' in properties)) return parameters; + const nextProperties = { ...properties }; + delete nextProperties['model']; + const next: Record = { ...parameters, properties: nextProperties }; + const required = parameters['required']; + if (Array.isArray(required) && required.includes('model')) { + next['required'] = required.filter((entry) => entry !== 'model'); + } + return next; +} + +/** + * Point a visual-task model resolution failure at the visual-model + * configuration when the bound model is not the caller's own — otherwise the + * parent model sees a bare "model not configured" error with no hint that it + * comes from `[visual_model]`. + */ +export function wrapVisualModelError( + error: unknown, + boundModel: string, + callerModelAlias: string | undefined, +): unknown { + if (boundModel === callerModelAlias) return error; + if (!isError2(error) || error.code !== ErrorCodes.CONFIG_INVALID) return error; + if (error.details?.['model'] !== boundModel) return error; + const displayModel = + boundModel === VISUAL_DERIVED_MODEL_ID + ? `the derived entry "${VISUAL_DERIVED_MODEL_ID}"` + : `"${boundModel}"`; + return new Error2( + error.code, + `${error.message} (visual model ${displayModel} comes from [visual_model].model / ${VISUAL_MODEL_ENV} — check that it names a valid [models] entry)`, + { + cause: error, + name: error.name, + details: { + ...error.details, + visualModel: boundModel, + visualModelConfig: { + section: 'visualModel.model', + environment: VISUAL_MODEL_ENV, + }, + }, + }, + ); +} + +// Re-export the schema symbol for tests / type-only consumers that want a +// single import surface for the visual domain. The schema itself lives next +// to the other kosong config sections (see `kosongConfig/configSection.ts`). +export const VISUAL_MODEL_CHOICE_SCHEMA = z.enum(['primary', 'visual']); diff --git a/packages/agent-core-v2/src/session/visual/flag.ts b/packages/agent-core-v2/src/session/visual/flag.ts new file mode 100644 index 00000000000..07eb0e6f6c0 --- /dev/null +++ b/packages/agent-core-v2/src/session/visual/flag.ts @@ -0,0 +1,34 @@ +/** + * `visual` domain — registers the `visual-model` experimental flag + * into `flag`. + * + * Visual-model mirror of {@link secondaryModelFlag}: gates visual-model + * selection for image / screenshot / video inspection tasks, including the + * agent-facing model choices and startup validation warning. Off by default; + * enable via `KIMI_CODE_EXPERIMENTAL_VISUAL_MODEL`, the master + * `KIMI_CODE_EXPERIMENTAL_FLAG`, or the `[experimental]` config section. + * + * Many coding models are text-only and cannot consume image content. When + * this experiment is enabled and `[visual_model]` is configured, visual + * inspection tasks (the `ReadMediaFile` tool and any future visual subagent + * spawn) consult {@link resolveVisualModel} to pick a vision-capable model + * instead of falling back to the caller's text-only model. When unset, + * behavior is unchanged (visual tasks inherit the caller's model). + */ + +import { type FlagDefinitionInput, registerFlagDefinition } from '#/app/flag/flagRegistry'; + +export const VISUAL_MODEL_FLAG_ID = 'visual-model'; +export const VISUAL_MODEL_FLAG_ENV = 'KIMI_CODE_EXPERIMENTAL_VISUAL_MODEL'; + +export const visualModelFlag: FlagDefinitionInput = { + id: VISUAL_MODEL_FLAG_ID, + title: 'Visual model for image/screenshot inspection', + description: + 'Let image / screenshot / video inspection tasks use a separately configured visual model by default, so a text-only coding model can still drive visual work via a vision-capable companion model.', + env: VISUAL_MODEL_FLAG_ENV, + default: false, + surface: 'core', +}; + +registerFlagDefinition(visualModelFlag); diff --git a/packages/agent-core-v2/test/agent/media/tools/read-media.test.ts b/packages/agent-core-v2/test/agent/media/tools/read-media.test.ts index 02c9d0f6700..c4a21792b02 100644 --- a/packages/agent-core-v2/test/agent/media/tools/read-media.test.ts +++ b/packages/agent-core-v2/test/agent/media/tools/read-media.test.ts @@ -37,6 +37,8 @@ import { type ToolExecution, } from '#/tool/toolContract'; import { EventBusService } from '#/app/event/eventBusService'; +import { StubConfigService } from '../../../kosong/stubs'; +import { stubFlag } from '../../../app/flag/stubs'; import type { IAgentProfileService } from '#/agent/profile/profile'; import type { IModelCatalog } from '#/kosong/model/catalog'; import type { ModelRequester } from '#/kosong/model/modelRequester'; @@ -848,6 +850,8 @@ describe('AgentMediaToolsRegistrar', () => { registry, profile, modelCatalog, + new StubConfigService(), + stubFlag(false), eventBus, createTestFs({}), createTestEnv(), diff --git a/packages/agent-core-v2/test/app/kosongConfig/visualModelOverlay.test.ts b/packages/agent-core-v2/test/app/kosongConfig/visualModelOverlay.test.ts new file mode 100644 index 00000000000..b1d1c9ad963 --- /dev/null +++ b/packages/agent-core-v2/test/app/kosongConfig/visualModelOverlay.test.ts @@ -0,0 +1,129 @@ +/** + * `app/kosongConfig` visualModelOverlay tests — the `[visual_model]` + * derived-entry synthesis (visual-model mirror of `secondaryModelOverlay`): + * + * - a recipe with patch fields synthesizes `VISUAL_DERIVED_MODEL_ID` + * (base copy, patch merged into `overrides` with patch winning conflicts, + * `aliases` dropped); a pointer-only recipe, a missing pointer, and a + * dangling pointer synthesize nothing; + * - `strip` keeps the synthesized entry out of `config.toml` and rolls + * back a `defaultModel` pointer at the derived id. + */ + +import { describe, expect, it } from 'vitest'; + +import { + MODELS_SECTION, + VISUAL_MODEL_SECTION, +} from '#/app/kosongConfig/configSection'; +import { + VISUAL_DERIVED_MODEL_ID, + visualModelOverlay, +} from '#/app/kosongConfig/visualModelOverlay'; + +function apply(effective: Record): readonly string[] { + return visualModelOverlay.apply(effective, () => undefined, (_domain, value) => value); +} + +const baseEntry = { + provider: 'kimi', + model: 'kimi-vision', + maxContextSize: 131072, + aliases: ['vision-latest'], + overrides: { defaultEffort: 'medium', supportEfforts: ['low', 'medium', 'high'] }, +}; + +describe('visualModelOverlay.apply', () => { + it('does nothing when no visual model is configured', () => { + const effective: Record = { [MODELS_SECTION]: { vision: baseEntry } }; + expect(apply(effective)).toEqual([]); + expect(effective[MODELS_SECTION]).toEqual({ vision: baseEntry }); + }); + + it('does nothing for a pointer-only recipe (no patch fields)', () => { + const effective: Record = { + [MODELS_SECTION]: { vision: baseEntry }, + [VISUAL_MODEL_SECTION]: { model: 'vision' }, + }; + expect(apply(effective)).toEqual([]); + expect(effective[MODELS_SECTION]).toEqual({ vision: baseEntry }); + }); + + it('synthesizes the derived entry: base copy, patch wins overrides conflicts, aliases dropped', () => { + const effective: Record = { + [MODELS_SECTION]: { vision: baseEntry }, + [VISUAL_MODEL_SECTION]: { model: 'vision', defaultEffort: 'low', maxOutputSize: 4096 }, + }; + expect(apply(effective)).toEqual([MODELS_SECTION]); + const models = effective[MODELS_SECTION] as Record; + expect(models[VISUAL_DERIVED_MODEL_ID]).toEqual({ + provider: 'kimi', + model: 'kimi-vision', + maxContextSize: 131072, + overrides: { + defaultEffort: 'low', + supportEfforts: ['low', 'medium', 'high'], + maxOutputSize: 4096, + }, + }); + expect(models['vision']).toEqual(baseEntry); + }); + + it('does nothing when the pointed entry does not exist', () => { + const effective: Record = { + [MODELS_SECTION]: { vision: baseEntry }, + [VISUAL_MODEL_SECTION]: { model: 'nope', maxOutputSize: 4096 }, + }; + expect(apply(effective)).toEqual([]); + expect(effective[MODELS_SECTION]).toEqual({ vision: baseEntry }); + }); + + it('never derives from the derived id itself', () => { + const effective: Record = { + [MODELS_SECTION]: { [VISUAL_DERIVED_MODEL_ID]: baseEntry }, + [VISUAL_MODEL_SECTION]: { model: VISUAL_DERIVED_MODEL_ID, maxOutputSize: 1 }, + }; + expect(apply(effective)).toEqual([]); + }); + + it('does not collide with the secondary-model derived entry', () => { + // The visual and secondary overlays use distinct reserved ids + // (__visual__ vs __secondary__), so both can be configured at once. + const SECONDARY_DERIVED_MODEL_ID = '__secondary__'; + const effective: Record = { + [MODELS_SECTION]: { + vision: baseEntry, + coder: { ...baseEntry, model: 'kimi-coder' }, + }, + [VISUAL_MODEL_SECTION]: { model: 'vision', maxOutputSize: 4096 }, + secondaryModel: { model: 'coder', maxOutputSize: 8192 }, + }; + apply(effective); + const models = effective[MODELS_SECTION] as Record; + expect(models[VISUAL_DERIVED_MODEL_ID]).toBeDefined(); + expect(models[VISUAL_DERIVED_MODEL_ID]).not.toBe(models[SECONDARY_DERIVED_MODEL_ID]); + }); +}); + +describe('visualModelOverlay.strip', () => { + const strip = visualModelOverlay.strip!; + + it('removes the derived entry from models writes and leaves other domains alone', () => { + const models = { vision: baseEntry, [VISUAL_DERIVED_MODEL_ID]: { ...baseEntry } }; + expect(strip(MODELS_SECTION, models, {})).toEqual({ vision: baseEntry }); + expect(strip('thinking', { effort: 'low' }, {})).toEqual({ effort: 'low' }); + }); + + it('leaves a models section without the derived entry untouched', () => { + const models = { vision: baseEntry }; + expect(strip(MODELS_SECTION, models, {})).toBe(models); + }); + + it('rolls back a defaultModel pointer set to the derived id', () => { + expect(strip('defaultModel', 'vision', {})).toBe('vision'); + expect(strip('defaultModel', VISUAL_DERIVED_MODEL_ID, { default_model: 'vision' })).toBe( + 'vision', + ); + expect(strip('defaultModel', VISUAL_DERIVED_MODEL_ID, {})).toBeUndefined(); + }); +}); diff --git a/packages/agent-core-v2/test/session/visual/configSection.test.ts b/packages/agent-core-v2/test/session/visual/configSection.test.ts new file mode 100644 index 00000000000..870a47ddaff --- /dev/null +++ b/packages/agent-core-v2/test/session/visual/configSection.test.ts @@ -0,0 +1,203 @@ +/** + * `session/visual` resolver tests — covers `resolveVisualModel` and + * `resolveVisualBinding`, including the unset-fallback path. + * + * Mirrors the shape of the subagent resolver tests but uses the visual-model + * flag + section. The StubConfigService + stubFlag helpers mirror the + * secondary-model warning tests. + */ + +import { describe, expect, it } from 'vitest'; + +import { VISUAL_MODEL_SECTION } from '#/app/kosongConfig/configSection'; +import { VISUAL_DERIVED_MODEL_ID } from '#/app/kosongConfig/visualModelOverlay'; +import { + resolveVisualBinding, + resolveVisualModel, + visualDisplayModel, + stripVisualModelParameter, + wrapVisualModelError, +} from '#/session/visual/configSection'; +import { VISUAL_MODEL_FLAG_ID } from '#/session/visual/flag'; +import { Error2, ErrorCodes } from '#/errors'; + +import { stubFlag } from '../../app/flag/stubs'; +import { StubConfigService } from '../../kosong/stubs'; + +function makeServices(configValues: Record, flagEnabled = true) { + const config = new StubConfigService(configValues); + const flags = stubFlag((id) => flagEnabled && id === VISUAL_MODEL_FLAG_ID); + return { config, flags }; +} + +const own = { modelAlias: 'caller/kimi-coder', thinkingLevel: 'medium' }; + +describe('resolveVisualModel', () => { + it('returns undefined when the visual-model flag is disabled', () => { + const { config } = makeServices({ [VISUAL_MODEL_SECTION]: { model: 'kimi/vision' } }, false); + const { flags } = makeServices({}, false); + expect(resolveVisualModel(config, flags)).toBeUndefined(); + }); + + it('returns undefined when [visual_model] is unset (no behavior change)', () => { + const { config, flags } = makeServices({}); + expect(resolveVisualModel(config, flags)).toBeUndefined(); + }); + + it('returns the configured recipe when set and the flag is on', () => { + const { config, flags } = makeServices({ + [VISUAL_MODEL_SECTION]: { model: 'kimi/vision', defaultEffort: 'low' }, + }); + expect(resolveVisualModel(config, flags)).toEqual({ + model: 'kimi/vision', + defaultEffort: 'low', + }); + }); +}); + +describe('resolveVisualBinding', () => { + it('inherits the caller model when visual model is unset (no behavior change)', () => { + const { config, flags } = makeServices({}); + expect(resolveVisualBinding(config, flags, own)).toEqual({ + model: own.modelAlias, + thinking: own.thinkingLevel, + displayModel: own.modelAlias, + }); + }); + + it('inherits the caller model when the flag is disabled even if the recipe is set', () => { + const { config } = makeServices({ [VISUAL_MODEL_SECTION]: { model: 'kimi/vision' } }); + const { flags } = makeServices({}, false); + expect(resolveVisualBinding(config, flags, own)).toEqual({ + model: own.modelAlias, + thinking: own.thinkingLevel, + displayModel: own.modelAlias, + }); + }); + + it('binds the visual model when set (pointer-only recipe)', () => { + const { config, flags } = makeServices({ + [VISUAL_MODEL_SECTION]: { model: 'kimi/vision' }, + }); + expect(resolveVisualBinding(config, flags, own)).toEqual({ + model: 'kimi/vision', + thinking: undefined, + displayModel: 'kimi/vision', + }); + }); + + it('binds the derived entry when the recipe carries patch fields', () => { + const { config, flags } = makeServices({ + [VISUAL_MODEL_SECTION]: { model: 'kimi/vision', defaultEffort: 'low', maxOutputSize: 4096 }, + }); + const binding = resolveVisualBinding(config, flags, own); + expect(binding.model).toBe(VISUAL_DERIVED_MODEL_ID); + expect(binding.thinking).toBe('low'); + // displayModel resolves the derived id back to the recipe's base alias + expect(binding.displayModel).toBe('kimi/vision'); + }); + + it('forces the caller model on explicit "primary" even when a visual model is configured', () => { + const { config, flags } = makeServices({ + [VISUAL_MODEL_SECTION]: { model: 'kimi/vision' }, + }); + expect(resolveVisualBinding(config, flags, own, 'primary')).toEqual({ + model: own.modelAlias, + thinking: own.thinkingLevel, + displayModel: own.modelAlias, + }); + }); +}); + +describe('visualDisplayModel', () => { + it('passes through any non-derived alias', () => { + const { config } = makeServices({}); + expect(visualDisplayModel(config, 'kimi/vision')).toBe('kimi/vision'); + }); + + it('resolves the derived id back to the recipe base alias', () => { + const { config } = makeServices({ + [VISUAL_MODEL_SECTION]: { model: 'kimi/vision' }, + }); + expect(visualDisplayModel(config, VISUAL_DERIVED_MODEL_ID)).toBe('kimi/vision'); + }); + + it('falls back to the derived id when the recipe has been removed', () => { + const { config } = makeServices({}); + expect(visualDisplayModel(config, VISUAL_DERIVED_MODEL_ID)).toBe(VISUAL_DERIVED_MODEL_ID); + }); +}); + +describe('stripVisualModelParameter', () => { + it('returns the input unchanged when there is no model property', () => { + const schema = { properties: { prompt: { type: 'string' } }, required: ['prompt'] }; + expect(stripVisualModelParameter(schema)).toBe(schema); + }); + + it('removes the model property and its required entry', () => { + const schema = { + properties: { prompt: { type: 'string' }, model: { type: 'string' } }, + required: ['prompt', 'model'], + }; + const next = stripVisualModelParameter(schema); + expect(next['properties']).toEqual({ prompt: { type: 'string' } }); + expect(next['required']).toEqual(['prompt']); + }); + + it('does not mutate the input', () => { + const schema = { + properties: { model: { type: 'string' } }, + required: ['model'], + }; + const next = stripVisualModelParameter(schema); + expect(next).not.toBe(schema); + expect(schema['properties']).toEqual({ model: { type: 'string' } }); + }); +}); + +describe('wrapVisualModelError', () => { + const callerModelAlias = 'caller/kimi-coder'; + + it('returns the error unchanged when the bound model is the caller own', () => { + const error = new Error2(ErrorCodes.CONFIG_INVALID, 'boom', { + details: { model: callerModelAlias }, + }); + expect(wrapVisualModelError(error, callerModelAlias, callerModelAlias)).toBe(error); + }); + + it('returns the error unchanged for non-CONFIG_INVALID errors', () => { + const error = new Error('boom'); + expect(wrapVisualModelError(error, 'kimi/vision', callerModelAlias)).toBe(error); + }); + + it('returns the error unchanged when the error details model does not match the bound model', () => { + const error = new Error2(ErrorCodes.CONFIG_INVALID, 'boom', { + details: { model: 'some/other' }, + }); + expect(wrapVisualModelError(error, 'kimi/vision', callerModelAlias)).toBe(error); + }); + + it('wraps a missing-alias failure with a hint pointing at [visual_model]', () => { + const error = new Error2(ErrorCodes.CONFIG_INVALID, 'Model "kimi/vision" is not configured.', { + details: { model: 'kimi/vision' }, + }); + const wrapped = wrapVisualModelError(error, 'kimi/vision', callerModelAlias) as Error2; + expect(wrapped).toBeInstanceOf(Error2); + expect(wrapped.message).toContain('[visual_model]'); + expect(wrapped.message).toContain('KIMI_VISUAL_MODEL'); + expect(wrapped.details).toMatchObject({ + model: 'kimi/vision', + visualModel: 'kimi/vision', + visualModelConfig: { section: 'visualModel.model', environment: 'KIMI_VISUAL_MODEL' }, + }); + }); + + it('wraps a derived-entry failure with a hint pointing at the derived id', () => { + const error = new Error2(ErrorCodes.CONFIG_INVALID, 'missing', { + details: { model: VISUAL_DERIVED_MODEL_ID }, + }); + const wrapped = wrapVisualModelError(error, VISUAL_DERIVED_MODEL_ID, callerModelAlias) as Error2; + expect(wrapped.message).toContain(VISUAL_DERIVED_MODEL_ID); + expect(wrapped.message).toContain('[visual_model]'); + }); +}); From b8a7fd7fb11cf442168396f2d454b61a57232b2b Mon Sep 17 00:00:00 2001 From: ARRRRNY Date: Tue, 18 Aug 2026 11:43:55 +0300 Subject: [PATCH 02/71] ci: add daily upstream sync workflow --- .github/workflows/sync-upstream.yml | 74 +++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .github/workflows/sync-upstream.yml diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml new file mode 100644 index 00000000000..a65bf7d5235 --- /dev/null +++ b/.github/workflows/sync-upstream.yml @@ -0,0 +1,74 @@ +# Sync upstream (MoonshotAI/kimi-code) changes into development daily. +# +# Keeps the fork's development branch up to date with upstream while +# preserving fork-owned features (visual model assignment, etc.). + +name: Sync Upstream + +on: + schedule: + - cron: "17 6 * * *" + workflow_dispatch: + +permissions: + contents: write + +jobs: + sync: + name: Sync upstream to development + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Fetch upstream + run: git fetch upstream main + + - name: Switch to development + run: git checkout development || git checkout -b development + + - name: Check if already up to date + id: check + run: | + LOCAL=$(git rev-parse HEAD) + UPSTREAM=$(git rev-parse upstream/main) + if [ "$LOCAL" = "$UPSTREAM" ]; then + echo "up_to_date=true" >> "$GITHUB_OUTPUT" + else + echo "up_to_date=false" >> "$GITHUB_OUTPUT" + fi + + - name: Merge upstream changes + if: steps.check.outputs.up_to_date == 'false' + run: | + if git merge upstream/main --no-edit -m "merge: sync upstream $(date +%Y-%m-%d)"; then + echo "Merge succeeded" + else + echo "Merge conflict — taking upstream for conflicts" + # Resolve conflicts by preferring upstream + CONFLICTS=$(git diff --name-only --diff-filter=U) + for f in $CONFLICTS; do + git checkout --theirs "$f" + git add "$f" + done + git commit --no-edit -m "merge: sync upstream $(date +%Y-%m-%d) (conflicts resolved)" + fi + + - name: Build check + if: steps.check.outputs.up_to_date == 'false' + run: | + npm install --ignore-scripts 2>/dev/null || true + npx tsc --noEmit 2>/dev/null || echo "Type check failed but continuing" + + - name: Push + if: steps.check.outputs.up_to_date == 'false' + run: git push origin development From 3f47ec625ef2913a03559fb7bf4a954594086b61 Mon Sep 17 00:00:00 2001 From: ARRRRNY Date: Tue, 18 Aug 2026 12:02:02 +0300 Subject: [PATCH 03/71] fix(ci): add upstream remote before fetch --- .github/workflows/sync-upstream.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index a65bf7d5235..707d1071cff 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -30,6 +30,9 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + - name: Add upstream remote + run: git remote add upstream https://github.com/MoonshotAI/kimi-code.git || true + - name: Fetch upstream run: git fetch upstream main @@ -54,7 +57,6 @@ jobs: echo "Merge succeeded" else echo "Merge conflict — taking upstream for conflicts" - # Resolve conflicts by preferring upstream CONFLICTS=$(git diff --name-only --diff-filter=U) for f in $CONFLICTS; do git checkout --theirs "$f" @@ -63,12 +65,6 @@ jobs: git commit --no-edit -m "merge: sync upstream $(date +%Y-%m-%d) (conflicts resolved)" fi - - name: Build check - if: steps.check.outputs.up_to_date == 'false' - run: | - npm install --ignore-scripts 2>/dev/null || true - npx tsc --noEmit 2>/dev/null || echo "Type check failed but continuing" - - name: Push if: steps.check.outputs.up_to_date == 'false' run: git push origin development From 706e0bf58f6713c9b2b6f0b91a890eb19046557d Mon Sep 17 00:00:00 2001 From: ARRRRNY Date: Tue, 18 Aug 2026 13:26:04 +0300 Subject: [PATCH 04/71] fix: remove stale secondaryModelOverlay import, add stripEnvBoundFields import - Removed deleted secondaryModelOverlay import from index.ts - Removed stale exports (SECONDARY_DERIVED_MODEL_ID, secondaryModelOverlay, secondaryModelPatch) - Added missing stripEnvBoundFields import to kosongConfig/configSection.ts - Local build now compiles and runs correctly --- package-lock.json | 6112 +++++++++++++++++ .../src/app/kosongConfig/configSection.ts | 1 + packages/agent-core-v2/src/index.ts | 6 - 3 files changed, 6113 insertions(+), 6 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000000..822f5a386c4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6112 @@ +{ + "name": "@moonshot-ai/monorepo", + "version": "0.1.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@moonshot-ai/monorepo", + "version": "0.1.1", + "hasInstallScript": true, + "license": "MIT", + "devDependencies": { + "@arethetypeswrong/cli": "0.18.2", + "@changesets/changelog-github": "0.7.0", + "@changesets/cli": "2.30.0", + "@microsoft/api-extractor": "7.58.7", + "@types/node": "^22.15.3", + "@vitest/coverage-v8": "4.1.4", + "lint-staged": "16.4.0", + "oxlint": "1.59.0", + "oxlint-tsgolint": "0.20.0", + "pkg-pr-new": "0.0.75", + "publint": "0.3.18", + "sherif": "1.11.1", + "simple-git-hooks": "2.13.1", + "tsdown": "0.22.0", + "tsx": "^4.21.0", + "typescript": "6.0.2", + "vitest": "4.1.4" + }, + "engines": { + "node": ">=24.15.0" + } + }, + "node_modules/@andrewbranch/untar.js": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@andrewbranch/untar.js/-/untar.js-1.0.4.tgz", + "integrity": "sha512-pVXSwPsLuw8IGLo2Di0EaOfsk+ntVvpkk942J/sHYIkwvtKUakEcPh7HBgZ6tuimgzKSEHgCvO4XgQ05DEbwDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@arethetypeswrong/cli": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/cli/-/cli-0.18.2.tgz", + "integrity": "sha512-PcFM20JNlevEDKBg4Re29Rtv2xvjvQZzg7ENnrWFSS0PHgdP2njibVFw+dRUhNkPgNfac9iUqO0ohAXqQL4hbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@arethetypeswrong/core": "0.18.2", + "chalk": "^4.1.2", + "cli-table3": "^0.6.3", + "commander": "^10.0.1", + "marked": "^9.1.2", + "marked-terminal": "^7.1.0", + "semver": "^7.5.4" + }, + "bin": { + "attw": "dist/index.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/core": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/@arethetypeswrong/core/-/core-0.18.2.tgz", + "integrity": "sha512-GiwTmBFOU1/+UVNqqCGzFJYfBXEytUkiI+iRZ6Qx7KmUVtLm00sYySkfe203C9QtPG11yOz1ZaMek8dT/xnlgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@andrewbranch/untar.js": "^1.0.3", + "@loaderkit/resolve": "^1.0.2", + "cjs-module-lexer": "^1.2.3", + "fflate": "^0.8.2", + "lru-cache": "^11.0.1", + "semver": "^7.5.4", + "typescript": "5.6.1-rc", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@arethetypeswrong/core/node_modules/typescript": { + "version": "5.6.1-rc", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.1-rc.tgz", + "integrity": "sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@babel/generator": { + "version": "8.0.0-rc.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0-rc.6.tgz", + "integrity": "sha512-6mIzgVK8DgEzvIapoQwhXTMnnkuE4STQmVv9H03i/tZ2ml8oev3TRvZJgTenK2Bsq0YWNtzOrFdTyNzCMFtjJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0-rc.6", + "@babel/types": "^8.0.0-rc.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", + "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/parser": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz", + "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/types": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", + "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@braidai/lang": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@braidai/lang/-/lang-1.1.2.tgz", + "integrity": "sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@changesets/apply-release-plan": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.1.1.tgz", + "integrity": "sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/config": "^3.1.4", + "@changesets/get-version-range-type": "^0.4.0", + "@changesets/git": "^3.0.4", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "detect-indent": "^6.0.0", + "fs-extra": "^7.0.1", + "lodash.startcase": "^4.4.0", + "outdent": "^0.5.0", + "prettier": "^2.7.1", + "resolve-from": "^5.0.0", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/assemble-release-plan": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.10.tgz", + "integrity": "sha512-rSDcqdJ9KbVyjpBIuCidhvZNIiVt1XaIYp73ycVQRIA5n/j6wQaEk0ChRLMUQ1vkxZe51PTQ9OIhbg6HQMW45A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.4", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/changelog-git": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz", + "integrity": "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0" + } + }, + "node_modules/@changesets/changelog-github": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-0.7.0.tgz", + "integrity": "sha512-rBsbRvc4TVn+FvFnOVM3LxlFJfTXXCp8gfVJ+0BubxWNSVnLuAzowi5j+IEraLLP52w8AAs9QfKbPS3MMiXQJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/get-github-info": "^0.8.0", + "@changesets/types": "^6.1.0", + "dotenv": "^8.1.0" + } + }, + "node_modules/@changesets/cli": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.30.0.tgz", + "integrity": "sha512-5D3Nk2JPqMI1wK25pEymeWRSlSMdo5QOGlyfrKg0AOufrUcjEE3RQgaCpHoBiM31CSNrtSgdJ0U6zL1rLDDfBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/apply-release-plan": "^7.1.0", + "@changesets/assemble-release-plan": "^6.0.9", + "@changesets/changelog-git": "^0.2.1", + "@changesets/config": "^3.1.3", + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.3", + "@changesets/get-release-plan": "^4.0.15", + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.7", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@changesets/write": "^0.4.0", + "@inquirer/external-editor": "^1.0.2", + "@manypkg/get-packages": "^1.1.3", + "ansi-colors": "^4.1.3", + "enquirer": "^2.4.1", + "fs-extra": "^7.0.1", + "mri": "^1.2.0", + "package-manager-detector": "^0.2.0", + "picocolors": "^1.1.0", + "resolve-from": "^5.0.0", + "semver": "^7.5.3", + "spawndamnit": "^3.0.1", + "term-size": "^2.1.0" + }, + "bin": { + "changeset": "bin.js" + } + }, + "node_modules/@changesets/config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@changesets/config/-/config-3.1.4.tgz", + "integrity": "sha512-pf0bvD/v6WI2cRlZ6hzpjtZdSlXDXMAJ+Iz7xfFzV4ZxJ8OGGAON+1qYc99ZPrijnt4xp3VGG7eNvAOGS24V1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.4", + "@changesets/logger": "^0.1.1", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1", + "micromatch": "^4.0.8" + } + }, + "node_modules/@changesets/errors": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz", + "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==", + "dev": true, + "license": "MIT", + "dependencies": { + "extendable-error": "^0.1.5" + } + }, + "node_modules/@changesets/get-dependents-graph": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.4.tgz", + "integrity": "sha512-ZsS00x6WvmHq3sQv8oCMwL0f/z3wbXCVuSVTJwCnnmbC/iBdNJGFx1EcbMG4PC6sXRyH69liM4A2WKXzn/kRPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "picocolors": "^1.1.0", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/get-github-info": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-0.8.0.tgz", + "integrity": "sha512-cRnC+xdF0JIik7coko3iUP9qbnfi1iJQ3sAa6dE+Tx3+ET8bjFEm63PA4WEohgjYcmsOikPHWzPsMWWiZmntOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dataloader": "^1.4.0", + "node-fetch": "^2.5.0" + } + }, + "node_modules/@changesets/get-release-plan": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.16.tgz", + "integrity": "sha512-2K5Om6CrMPm45rtvckfzWo7e9jOVCKLCnXia5eUPaURH7/LWzri7pK1TycdzAuAtehLkW7VPbWLCSExTHmiI6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/assemble-release-plan": "^6.0.10", + "@changesets/config": "^3.1.4", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.7", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" + } + }, + "node_modules/@changesets/get-version-range-type": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz", + "integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@changesets/git": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.4.tgz", + "integrity": "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@manypkg/get-packages": "^1.1.3", + "is-subdir": "^1.1.1", + "micromatch": "^4.0.8", + "spawndamnit": "^3.0.1" + } + }, + "node_modules/@changesets/logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz", + "integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.0" + } + }, + "node_modules/@changesets/parse": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.3.tgz", + "integrity": "sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "js-yaml": "^4.1.1" + } + }, + "node_modules/@changesets/pre": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz", + "integrity": "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1" + } + }, + "node_modules/@changesets/read": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.7.tgz", + "integrity": "sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/parse": "^0.4.3", + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "p-filter": "^2.1.0", + "picocolors": "^1.1.0" + } + }, + "node_modules/@changesets/should-skip-package": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz", + "integrity": "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" + } + }, + "node_modules/@changesets/types": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz", + "integrity": "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@changesets/write": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz", + "integrity": "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "human-id": "^4.1.1", + "prettier": "^2.7.1" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@loaderkit/resolve": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@loaderkit/resolve/-/resolve-1.0.6.tgz", + "integrity": "sha512-G8FdIoF5CypfwmD9rl8BXod5HDn8JqB0CCNBXDTaRZ+yRYhARrrSToX1zg1zy9jX3zLqigsELwhT4gNtkdQAUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@braidai/lang": "^1.0.0" + } + }, + "node_modules/@manypkg/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@types/node": "^12.7.1", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0" + } + }, + "node_modules/@manypkg/find-root/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/find-root/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@manypkg/get-packages": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz", + "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@changesets/types": "^4.0.1", + "@manypkg/find-root": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "^11.0.0", + "read-yaml-file": "^1.1.0" + } + }, + "node_modules/@manypkg/get-packages/node_modules/@changesets/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz", + "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/get-packages/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@microsoft/api-extractor": { + "version": "7.58.7", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.58.7.tgz", + "integrity": "sha512-yK6OycD46gIzLRpj6ueVUWPk1ACSpkN1LBo05gY1qPTylbWyUCanXfH7+VgkI5LJrJoRSQR5F04XuCffCXLOBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/api-extractor-model": "7.33.8", + "@microsoft/tsdoc": "~0.16.0", + "@microsoft/tsdoc-config": "~0.18.1", + "@rushstack/node-core-library": "5.23.1", + "@rushstack/rig-package": "0.7.3", + "@rushstack/terminal": "0.24.0", + "@rushstack/ts-command-line": "5.3.9", + "diff": "~8.0.2", + "minimatch": "10.2.3", + "resolve": "~1.22.1", + "semver": "~7.7.4", + "source-map": "~0.6.1", + "typescript": "5.9.3" + }, + "bin": { + "api-extractor": "bin/api-extractor" + } + }, + "node_modules/@microsoft/api-extractor-model": { + "version": "7.33.8", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.33.8.tgz", + "integrity": "sha512-aIcoQggPyer3B6Ze3usz0YWC/oBwUHfRH5ETUsr+oT2BRA6SfTJl7IKPcPZkX4UR+PohowzW4uMxsvjrn8vm+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "~0.16.0", + "@microsoft/tsdoc-config": "~0.18.1", + "@rushstack/node-core-library": "5.23.1" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@microsoft/api-extractor/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", + "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@microsoft/tsdoc-config": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.18.1.tgz", + "integrity": "sha512-9brPoVdfN9k9g0dcWkFeA7IH9bbcttzDJlXvkf8b2OBzd5MueR1V2wkKBL0abn0otvmkHJC6aapBOTJDDeMCZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "0.16.0", + "ajv": "~8.18.0", + "jju": "~1.4.0", + "resolve": "~1.22.2" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.144.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz", + "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxlint-tsgolint/darwin-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-0.20.0.tgz", + "integrity": "sha512-KKQcIHZHMxqpHUA1VXIbOG6chNCFkUWbQy6M+AFVtPKkA/3xAeJkJ3njoV66bfzwPHRcWQO+kcj5XqtbkjakoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint-tsgolint/darwin-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-0.20.0.tgz", + "integrity": "sha512-7HeVMuclGfG+NLZi2ybY0T4fMI7/XxO/208rJk+zEIloKkVnlh11Wd241JMGwgNFXn+MLJbOqOfojDb2Dt4L1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint-tsgolint/linux-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-0.20.0.tgz", + "integrity": "sha512-zxhUwz+WSxE6oWlZLK2z2ps9yC6ebmgoYmjAl0Oa48+GqkZ56NVgo+wb8DURNv6xrggzHStQxqQxe3mK51HZag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint-tsgolint/linux-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-0.20.0.tgz", + "integrity": "sha512-/1l6FnahC9im8PK+Ekkx/V3yetO/PzZnJegE2FXcv/iXEhbeVxP/ouiTYcUQu9shT1FWJCSNti1VJHH+21Y1dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint-tsgolint/win32-arm64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-0.20.0.tgz", + "integrity": "sha512-oPZ5Yz8sVdo7P/5q+i3IKeix31eFZ55JAPa1+RGPoe9PoaYVsdMvR6Jvib6YtrqoJnFPlg3fjEjlEPL8VBKYJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint-tsgolint/win32-x64": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-0.20.0.tgz", + "integrity": "sha512-4stx8RHj3SP9vQyRF/yZbz5igtPvYMEUR8CUoha4BVNZihi39DpCR8qkU7lpjB5Ga1DRMo2pHaA4bdTOMaY4mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.59.0.tgz", + "integrity": "sha512-etYDw/UaEv936AQUd/CRMBVd+e+XuuU6wC+VzOv1STvsTyZenLChepLWqLtnyTTp4YMlM22ypzogDDwqYxv5cg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.59.0.tgz", + "integrity": "sha512-TgLc7XVLKH2a4h8j3vn1MDjfK33i9MY60f/bKhRGWyVzbk5LCZ4X01VZG7iHrMmi5vYbAp8//Ponigx03CLsdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.59.0.tgz", + "integrity": "sha512-DXyFPf5ZKldMLloRHx/B9fsxsiTQomaw7cmEW3YIJko2HgCh+GUhp9gGYwHrqlLJPsEe3dYj9JebjX92D3j3AA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.59.0.tgz", + "integrity": "sha512-LgvrsdgVLX1qWqIEmNsSmMXJhpAWdtUQ0M+oR0CySwi+9IHWyOGuIL8w8+u/kbZNMyZr4WUyYB5i0+D+AKgkLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.59.0.tgz", + "integrity": "sha512-bOJhqX/ny4hrFuTPlyk8foSRx/vLRpxJh0jOOKN2NWW6FScXHPAA5rQbrwdQPcgGB5V8Ua51RS03fke8ssBcug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.59.0.tgz", + "integrity": "sha512-vVUXxYMF9trXCsz4m9H6U0IjehosVHxBzVgJUxly1uz4W1PdDyicaBnpC0KRXsHYretLVe+uS9pJy8iM57Kujw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.59.0.tgz", + "integrity": "sha512-TULQW8YBPGRWg5yZpFPL54HLOnJ3/HiX6VenDPi6YfxB/jlItwSMFh3/hCeSNbh+DAMaE1Py0j5MOaivHkI/9Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.59.0.tgz", + "integrity": "sha512-Gt54Y4eqSgYJ90xipm24xeyaPV854706o/kiT8oZvUt3VDY7qqxdqyGqchMaujd87ib+/MXvnl9WkK8Cc1BExg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.59.0.tgz", + "integrity": "sha512-3CtsKp7NFB3OfqQzbuAecrY7GIZeiv7AD+xutU4tefVQzlfmTI7/ygWLrvkzsDEjTlMq41rYHxgsn6Yh8tybmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.59.0.tgz", + "integrity": "sha512-K0diOpT3ncDmOfl9I1HuvpEsAuTxkts0VYwIv/w6Xiy9CdwyPBVX88Ga9l8VlGgMrwBMnSY4xIvVlVY/fkQk7Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.59.0.tgz", + "integrity": "sha512-xAU7+QDU6kTJJ7mJLOGgo7oOjtAtkKyFZ0Yjdb5cEo3DiCCPFLvyr08rWiQh6evZ7RiUTf+o65NY/bqttzJiQQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.59.0.tgz", + "integrity": "sha512-KUmZmKlTTyauOnvUNVxK7G40sSSx0+w5l1UhaGsC6KPpOYHenx2oqJTnabmpLJicok7IC+3Y6fXAUOMyexaeJQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.59.0.tgz", + "integrity": "sha512-4usRxC8gS0PGdkHnRmwJt/4zrQNZyk6vL0trCxwZSsAKM+OxhB8nKiR+mhjdBbl8lbMh2gc3bZpNN/ik8c4c2A==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.59.0.tgz", + "integrity": "sha512-s/rNE2gDmbwAOOP493xk2X7M8LZfI1LJFSSW1+yanz3vuQCFPiHkx4GY+O1HuLUDtkzGlhtMrIcxxzyYLv308w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.59.0.tgz", + "integrity": "sha512-+yYj1udJa2UvvIUmEm0IcKgc0UlPMgz0nsSTvkPL2y6n0uU5LgIHSwVu4AHhrve6j9BpVSoRksnz8c9QcvITJA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.59.0.tgz", + "integrity": "sha512-bUplUb48LYsB3hHlQXP2ZMOenpieWoOyppLAnnAhuPag3MGPnt+7caxE3w/Vl9wpQsTA3gzLntQi9rxWrs7Xqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.59.0.tgz", + "integrity": "sha512-/HLsLuz42rWl7h7ePdmMTpHm2HIDmPtcEMYgm5BBEHiEiuNOrzMaUpd2z7UnNni5LGN9obJy2YoAYBLXQwazrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.59.0.tgz", + "integrity": "sha512-rUPy+JnanpPwV/aJCPnxAD1fW50+XPI0VkWr7f0vEbqcdsS8NpB24Rw6RsS7SdpFv8Dw+8ugCwao5nCFbqOUSg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.59.0.tgz", + "integrity": "sha512-xkE7puteDS/vUyRngLXW0t8WgdWoS/tfxXjhP/P7SMqPDx+hs44SpssO3h3qmTqECYEuXBUPzcAw5257Ka+ofA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@publint/pack": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.6.tgz", + "integrity": "sha512-3uVNyGcVplhPZSLVyeIpL7+cIRn1YCSNHLG/rUIlBQMVH8YuN9++YF+5+UDIIO9RW98dujiUoTltO7RDB5bFJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyexec": "^1.2.4" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, + "node_modules/@quansync/fs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz", + "integrity": "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/@quansync/fs/node_modules/quansync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz", + "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz", + "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz", + "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz", + "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz", + "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz", + "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz", + "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz", + "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz", + "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz", + "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz", + "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz", + "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz", + "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz", + "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rushstack/node-core-library": { + "version": "5.23.1", + "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.23.1.tgz", + "integrity": "sha512-wlKmIKIYCKuCASbITvOxLZXepPbwXvrv7S6ig6XNWFchSyhL/E2txmVXspHY49Wu2dzf7nI27a2k/yV5BA3EiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "~8.18.0", + "ajv-draft-04": "~1.0.0", + "ajv-formats": "~3.0.1", + "fs-extra": "~11.3.0", + "import-lazy": "~4.0.0", + "jju": "~1.4.0", + "resolve": "~1.22.1", + "semver": "~7.7.4" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/node-core-library/node_modules/fs-extra": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@rushstack/node-core-library/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@rushstack/problem-matcher": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.2.1.tgz", + "integrity": "sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/rig-package": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.7.3.tgz", + "integrity": "sha512-aAA518n6wxxjCfnTAOjQnm7ngNE0FVHxHAw2pxKlIhxrMn0XQjGcXKF0oKWpjBgJOmsaJpVob/v+zr3zxgPWuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jju": "~1.4.0", + "resolve": "~1.22.1" + } + }, + "node_modules/@rushstack/terminal": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.24.0.tgz", + "integrity": "sha512-8ZQS4MMaGsv27EXCBiH7WMPkRZrffeDoIevs6z9TM5dzqiY6+Hn4evfK/G+gvgBTjfvfkHIZPQQmalmI2sM4TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rushstack/node-core-library": "5.23.1", + "@rushstack/problem-matcher": "0.2.1", + "supports-color": "~8.1.1" + }, + "peerDependencies": { + "@types/node": "*" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@rushstack/ts-command-line": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.3.9.tgz", + "integrity": "sha512-GIHqU+sRGQ3LGWAZu1O+9Yh++qwtyNIIGuNbcWHJjBTm2qRez0cwINUHZ+pQLR8UuzZDcMajrDaNbUYoaL/XtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rushstack/terminal": "0.24.0", + "@types/argparse": "1.0.38", + "argparse": "~1.0.9", + "string-argv": "~0.3.1" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/argparse": { + "version": "1.0.38", + "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", + "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.4.tgz", + "integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.4", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.4", + "vitest": "4.1.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", + "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", + "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", + "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.4", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", + "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.4", + "@vitest/utils": "4.1.4", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", + "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", + "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.4", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", + "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-kit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-3.0.0.tgz", + "integrity": "sha512-8OG92q3R35qjC/4i6BLBMg8IB+fClWu/1PEwg2Z9Rn+BuNaiEgJzpzn+pxWOdHJWDCAwu2JP0wCDTozAM4QirQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0", + "estree-walker": "^3.0.3", + "pathe": "^2.0.3" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-kit/node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/ast-kit/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", + "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/ast-kit/node_modules/@babel/parser": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz", + "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/ast-kit/node_modules/@babel/types": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", + "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", + "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/better-path-resolve": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz", + "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-windows": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/birpc": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-4.1.0.tgz", + "integrity": "sha512-O8L9vALWGqdEe0cG4HJckauw3WeJETlJnDRPUYpgwB7wrU43b/5NGMdVjdVcRo+4ROgd3ih2wha1glDe4HRVgw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", + "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", + "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dataloader": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz", + "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/dts-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dts-resolver/-/dts-resolver-3.0.0.tgz", + "integrity": "sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "oxc-resolver": ">=11.0.0" + }, + "peerDependenciesMeta": { + "oxc-resolver": { + "optional": true + } + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/empathic": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", + "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/extendable-error": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", + "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "5.0.0-beta.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.5.tgz", + "integrity": "sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "engines": { + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hookable": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", + "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-id": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.2.0.tgz", + "integrity": "sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA==", + "dev": true, + "license": "MIT", + "bin": { + "human-id": "dist/cli.js" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/import-without-cache": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/import-without-cache/-/import-without-cache-0.4.0.tgz", + "integrity": "sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-subdir": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", + "integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "better-path-resolve": "1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lint-staged": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", + "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.3", + "listr2": "^9.0.5", + "picomatch": "^4.0.3", + "string-argv": "^0.3.2", + "tinyexec": "^1.0.4", + "yaml": "^2.8.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", + "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/marked": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", + "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 16" + } + }, + "node_modules/marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <16" + } + }, + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/outdent": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz", + "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/oxlint": { + "version": "1.59.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.59.0.tgz", + "integrity": "sha512-0xBLeGGjP4vD9pygRo8iuOkOzEU1MqOnfiOl7KYezL/QvWL8NUg6n03zXc7ZVqltiOpUxBk2zgHI3PnRIEdAvw==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.59.0", + "@oxlint/binding-android-arm64": "1.59.0", + "@oxlint/binding-darwin-arm64": "1.59.0", + "@oxlint/binding-darwin-x64": "1.59.0", + "@oxlint/binding-freebsd-x64": "1.59.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.59.0", + "@oxlint/binding-linux-arm-musleabihf": "1.59.0", + "@oxlint/binding-linux-arm64-gnu": "1.59.0", + "@oxlint/binding-linux-arm64-musl": "1.59.0", + "@oxlint/binding-linux-ppc64-gnu": "1.59.0", + "@oxlint/binding-linux-riscv64-gnu": "1.59.0", + "@oxlint/binding-linux-riscv64-musl": "1.59.0", + "@oxlint/binding-linux-s390x-gnu": "1.59.0", + "@oxlint/binding-linux-x64-gnu": "1.59.0", + "@oxlint/binding-linux-x64-musl": "1.59.0", + "@oxlint/binding-openharmony-arm64": "1.59.0", + "@oxlint/binding-win32-arm64-msvc": "1.59.0", + "@oxlint/binding-win32-ia32-msvc": "1.59.0", + "@oxlint/binding-win32-x64-msvc": "1.59.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=0.18.0" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + } + } + }, + "node_modules/oxlint-tsgolint": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-0.20.0.tgz", + "integrity": "sha512-/Uc9TQyN1l8w9QNvXtVHYtz+SzDJHKpb5X0UnHodl0BVzijUPk0LPlDOHAvogd1UI+iy9ZSF6gQxEqfzUxCULQ==", + "dev": true, + "license": "MIT", + "bin": { + "tsgolint": "bin/tsgolint.js" + }, + "optionalDependencies": { + "@oxlint-tsgolint/darwin-arm64": "0.20.0", + "@oxlint-tsgolint/darwin-x64": "0.20.0", + "@oxlint-tsgolint/linux-arm64": "0.20.0", + "@oxlint-tsgolint/linux-x64": "0.20.0", + "@oxlint-tsgolint/win32-arm64": "0.20.0", + "@oxlint-tsgolint/win32-x64": "0.20.0" + } + }, + "node_modules/p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-map": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-manager-detector": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", + "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^0.2.7" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-pr-new": { + "version": "0.0.75", + "resolved": "https://registry.npmjs.org/pkg-pr-new/-/pkg-pr-new-0.0.75.tgz", + "integrity": "sha512-u9mdErTewKSMsr+ceCt8VcNuNP0ro5AXiPXhUVApuEyqr2Zlvt+DdCFBcm+yGWN8mhOdZJ27meIDbnoZgfzpOw==", + "dev": true, + "license": "MIT", + "bin": { + "pkg-pr-new": "bin/cli.js" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/publint": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/publint/-/publint-0.3.18.tgz", + "integrity": "sha512-JRJFeBTrfx4qLwEuGFPk+haJOJN97KnPuK01yj+4k/Wj5BgoOK5uNsivporiqBjk2JDaslg7qJOhGRnpltGeog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@publint/pack": "^0.1.4", + "package-manager-detector": "^1.6.0", + "picocolors": "^1.1.1", + "sade": "^1.8.1" + }, + "bin": { + "publint": "src/cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://bjornlu.com/sponsor" + } + }, + "node_modules/publint/node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/read-yaml-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz", + "integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.6.1", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/read-yaml-file/node_modules/js-yaml": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", + "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rolldown": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz", + "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.144.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.2.4", + "@rolldown/binding-darwin-arm64": "1.2.4", + "@rolldown/binding-darwin-x64": "1.2.4", + "@rolldown/binding-freebsd-x64": "1.2.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", + "@rolldown/binding-linux-arm64-gnu": "1.2.4", + "@rolldown/binding-linux-arm64-musl": "1.2.4", + "@rolldown/binding-linux-ppc64-gnu": "1.2.4", + "@rolldown/binding-linux-s390x-gnu": "1.2.4", + "@rolldown/binding-linux-x64-gnu": "1.2.4", + "@rolldown/binding-linux-x64-musl": "1.2.4", + "@rolldown/binding-openharmony-arm64": "1.2.4", + "@rolldown/binding-win32-arm64-msvc": "1.2.4", + "@rolldown/binding-win32-x64-msvc": "1.2.4" + } + }, + "node_modules/rolldown-plugin-dts": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/rolldown-plugin-dts/-/rolldown-plugin-dts-0.25.2.tgz", + "integrity": "sha512-nMhN/R+vmR8GM45ZW1FWMSjRTSDDn/6w4GTf8RNrEFCBdl8B1kySWrU1ixPtbwzXoRlcO+R/S88VgXuJQwfdDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "8.0.0-rc.6", + "@babel/helper-validator-identifier": "8.0.0-rc.6", + "@babel/parser": "8.0.0-rc.6", + "ast-kit": "^3.0.0-beta.1", + "birpc": "^4.0.0", + "dts-resolver": "^3.0.0", + "get-tsconfig": "5.0.0-beta.5", + "obug": "^2.1.1" + }, + "engines": { + "node": "^22.18.0 || >=24.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@ts-macro/tsc": "^0.3.6", + "@typescript/native-preview": ">=7.0.0-dev.20260325.1", + "rolldown": "^1.0.0", + "typescript": "^5.0.0 || ^6.0.0", + "vue-tsc": "~3.2.0" + }, + "peerDependenciesMeta": { + "@ts-macro/tsc": { + "optional": true + }, + "@typescript/native-preview": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/rolldown-plugin-dts/node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/rolldown-plugin-dts/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.0-rc.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.0-rc.6.tgz", + "integrity": "sha512-nVJ+1JcCgntv8d78rRo++o2wuODT0Irknx2BF8Np4Ft2CRgjLqIs4qzSZ8b66yGbBdMWGmZBO9WEZv1hhNiSpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/rolldown-plugin-dts/node_modules/@babel/parser": { + "version": "8.0.0-rc.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.0-rc.6.tgz", + "integrity": "sha512-rOS8IpdO7mQELkTPlCsTgPejO0bFuZdEDCGQJouYbYf9e1FLTym7Fei2pEjq8q7MWbX0ravcd7QQYKs1TxOuog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0-rc.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/rolldown-plugin-dts/node_modules/@babel/types": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", + "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/rolldown-plugin-dts/node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", + "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sherif": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/sherif/-/sherif-1.11.1.tgz", + "integrity": "sha512-HBFce8NGaPuWPg5NXb6+aI7hJQFjTilhtbrgo+Y/BvtGlkuJAzLnkmC8nyD+p3v7oIAq4KQeA8qySKGga28xZg==", + "dev": true, + "license": "MIT", + "bin": { + "sherif": "index.js" + }, + "optionalDependencies": { + "sherif-darwin-arm64": "1.11.1", + "sherif-darwin-x64": "1.11.1", + "sherif-linux-arm64": "1.11.1", + "sherif-linux-arm64-musl": "1.11.1", + "sherif-linux-x64": "1.11.1", + "sherif-linux-x64-musl": "1.11.1", + "sherif-windows-arm64": "1.11.1", + "sherif-windows-x64": "1.11.1" + } + }, + "node_modules/sherif-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/sherif-darwin-arm64/-/sherif-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-VoMrUv5QY6hQ2rByNa3AAhr/KGQsCb6pvAUNKa1iCh1jvnY836hQr6zNBw9hYCDkVv6t9sITFGJljwdTCQD4xw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/sherif-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/sherif-darwin-x64/-/sherif-darwin-x64-1.11.1.tgz", + "integrity": "sha512-7j3yOCBkvVbltVT3lXoiazGfG2nb36FteYT5VZPEBSf8sTn1pPTScukAQ1Fdl+MphadGyici7XlRbDrtZ/wnvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/sherif-linux-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/sherif-linux-arm64/-/sherif-linux-arm64-1.11.1.tgz", + "integrity": "sha512-vCZFS7RxhZ/8g9bdj3UPNVPTcZiKiWigW+FIlVQEUKEKfG0MfSOMBJqEWPVVUniyJa3rdIxtmZKSdWkG0e1x3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/sherif-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/sherif-linux-arm64-musl/-/sherif-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-DCf87RFqBh8ZrYgu3y+fv0x4kFn/np84m2jAEgygznwozH/VCfrXbHFVdhxW7762JCYkXbHO9dUj/ff5fkvkvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/sherif-linux-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/sherif-linux-x64/-/sherif-linux-x64-1.11.1.tgz", + "integrity": "sha512-9t+p1X3SyhU75BrJNHBbj9i/aQxHC/sF+Mdkf17V5AlokCznFgYKQUXq5EVmcmRDDhDl69RMzCTLD95EBqUSYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/sherif-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/sherif-linux-x64-musl/-/sherif-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-f8xitqXdHObUFPZo4QVbz3o30Y4+gHA3B5ZobsOWocnSfJBaUGutBzJsUsjG6w2tccSRn6+mugiMUGKIbIPZmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/sherif-windows-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/sherif-windows-arm64/-/sherif-windows-arm64-1.11.1.tgz", + "integrity": "sha512-Dnffgcyz9zLq/8UTY2REchJzRJWcWAuMWo5Vl5O17IZGkhl71dwa7/Vi2wC3EQd8WAVK/O82yArOYggWA0dj5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/sherif-windows-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/sherif-windows-x64/-/sherif-windows-x64-1.11.1.tgz", + "integrity": "sha512-xjfYUL/IQ65DwHkRsWIxiZWtglKtL5/E3UHpnLwOui3jqW1V2K88SMct415dnlBQiL3U9VEIVUo1i+KmToOBgQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-git-hooks": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/simple-git-hooks/-/simple-git-hooks-2.13.1.tgz", + "integrity": "sha512-WszCLXwT4h2k1ufIXAgsbiTOazqqevFCIncOuUBZJ91DdvWcC5+OFkluWRQPrcuSYd8fjq+o2y1QfWqYMoAToQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "simple-git-hooks": "cli.js" + } + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawndamnit": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz", + "integrity": "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "cross-spawn": "^7.0.5", + "signal-exit": "^4.0.1" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tsdown": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/tsdown/-/tsdown-0.22.0.tgz", + "integrity": "sha512-FgW0hHb27nGQA/+F3d5+U9wKXkfilk9DVkc5+7x/ZqF03g+Hoz/eeApT32jqxATt9eRoR+1jxk7MUMON+O4CXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.2.0", + "cac": "^7.0.0", + "defu": "^6.1.7", + "empathic": "^2.0.0", + "hookable": "^6.1.1", + "import-without-cache": "^0.4.0", + "obug": "^2.1.1", + "picomatch": "^4.0.4", + "rolldown": "^1.0.0", + "rolldown-plugin-dts": "^0.25.0", + "semver": "^7.7.4", + "tinyexec": "^1.1.2", + "tinyglobby": "^0.2.16", + "tree-kill": "^1.2.2", + "unconfig-core": "^7.5.0" + }, + "bin": { + "tsdown": "dist/run.mjs" + }, + "engines": { + "node": "^22.18.0 || >=24.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@arethetypeswrong/core": "^0.18.1", + "@tsdown/css": "0.22.0", + "@tsdown/exe": "0.22.0", + "@vitejs/devtools": "*", + "publint": "^0.3.8", + "tsx": "*", + "typescript": "^5.0.0 || ^6.0.0", + "unplugin-unused": "^0.5.0", + "unrun": "*" + }, + "peerDependenciesMeta": { + "@arethetypeswrong/core": { + "optional": true + }, + "@tsdown/css": { + "optional": true + }, + "@tsdown/exe": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "publint": { + "optional": true + }, + "tsx": { + "optional": true + }, + "typescript": { + "optional": true + }, + "unplugin-unused": { + "optional": true + }, + "unrun": { + "optional": true + } + } + }, + "node_modules/tsx": { + "version": "4.23.12", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", + "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unconfig-core": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.5.0.tgz", + "integrity": "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@quansync/fs": "^1.0.0", + "quansync": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unconfig-core/node_modules/quansync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz", + "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vite": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz", + "integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.25", + "rolldown": "~1.2.1", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", + "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.4", + "@vitest/mocker": "4.1.4", + "@vitest/pretty-format": "4.1.4", + "@vitest/runner": "4.1.4", + "@vitest/snapshot": "4.1.4", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.4", + "@vitest/browser-preview": "4.1.4", + "@vitest/browser-webdriverio": "4.1.4", + "@vitest/coverage-istanbul": "4.1.4", + "@vitest/coverage-v8": "4.1.4", + "@vitest/ui": "4.1.4", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + } + } +} diff --git a/packages/agent-core-v2/src/app/kosongConfig/configSection.ts b/packages/agent-core-v2/src/app/kosongConfig/configSection.ts index c0c15056e22..0b338ade38e 100644 --- a/packages/agent-core-v2/src/app/kosongConfig/configSection.ts +++ b/packages/agent-core-v2/src/app/kosongConfig/configSection.ts @@ -3,6 +3,7 @@ import { z } from 'zod'; import { type ConfigStripEnv, envBindings, + stripEnvBoundFields, } from '#/app/config/config'; import { registerConfigSection } from '#/app/config/configSectionContributions'; import { diff --git a/packages/agent-core-v2/src/index.ts b/packages/agent-core-v2/src/index.ts index 5796ad62f5f..f43a68f46e1 100644 --- a/packages/agent-core-v2/src/index.ts +++ b/packages/agent-core-v2/src/index.ts @@ -172,7 +172,6 @@ export * from '#/kosong/protocol/protocol'; export * from '#/kosong/protocol/protocolBase'; export * from '#/kosong/protocol/protocolTrait'; import '#/app/kosongConfig/envOverlay'; -import '#/app/kosongConfig/secondaryModelOverlay'; import '#/app/kosongConfig/visualModelOverlay'; export * from '#/kosong/model/completionBudget'; export * from '#/kosong/model/hostRequestHeaders'; @@ -202,11 +201,6 @@ export { VisualModelConfigSchema, visualModelEnvBindings, } from '#/app/kosongConfig/configSection'; -export { - SECONDARY_DERIVED_MODEL_ID, - secondaryModelOverlay, - secondaryModelPatch, -} from '#/app/kosongConfig/secondaryModelOverlay'; export { VISUAL_DERIVED_MODEL_ID, visualModelOverlay, From f27c0b4145f448dbd0ecdd8174b5620cec88c54d Mon Sep 17 00:00:00 2001 From: ARRRRNY Date: Tue, 18 Aug 2026 13:32:07 +0300 Subject: [PATCH 05/71] fix: remove duplicate secondaryModel registration Upstream registers secondaryModel in session/subagent/configSection.ts. The fork's kosongConfig/configSection.ts had a stale duplicate causing 'section already registered' error at startup. --- packages/agent-core-v2/src/app/kosongConfig/configSection.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/agent-core-v2/src/app/kosongConfig/configSection.ts b/packages/agent-core-v2/src/app/kosongConfig/configSection.ts index 0b338ade38e..6c8d8f739bf 100644 --- a/packages/agent-core-v2/src/app/kosongConfig/configSection.ts +++ b/packages/agent-core-v2/src/app/kosongConfig/configSection.ts @@ -307,10 +307,7 @@ export const secondaryModelEnvBindings = envBindings(SecondaryModelConfigSchema, defaultEffort: { env: SECONDARY_MODEL_EFFORT_ENV, parse: parseNonEmptyEnv }, }); -registerConfigSection(SECONDARY_MODEL_SECTION, SecondaryModelConfigSchema, { - env: secondaryModelEnvBindings, - stripEnv: stripEnvBoundFields(secondaryModelEnvBindings), -}); +// NOTE: secondaryModel registration moved to session/subagent/configSection.ts (upstream) export const VISUAL_MODEL_SECTION = 'visualModel'; From f450b55d8db53921b8dfbf06afdfb262e9b9c4ce Mon Sep 17 00:00:00 2001 From: ARRRRNY Date: Tue, 18 Aug 2026 13:42:07 +0300 Subject: [PATCH 06/71] feat: add /visual-model slash command for image inspection model selection - New /visual-model command mirrors /secondary-model UX - Picker shows available models, saves to [visual_model] config - Registered in command registry with priority 91 - Persists defaultModel via harness.setConfig() --- apps/kimi-code/src/tui/commands/config.ts | 67 +++++++++++++++++++++ apps/kimi-code/src/tui/commands/dispatch.ts | 5 ++ apps/kimi-code/src/tui/commands/registry.ts | 6 ++ 3 files changed, 78 insertions(+) diff --git a/apps/kimi-code/src/tui/commands/config.ts b/apps/kimi-code/src/tui/commands/config.ts index 18f7edb5d8e..8769c17ef23 100644 --- a/apps/kimi-code/src/tui/commands/config.ts +++ b/apps/kimi-code/src/tui/commands/config.ts @@ -297,6 +297,73 @@ export async function handleSecondaryModelCommand(host: SlashCommandHost, args: showSecondaryModelPicker(host, models, current, alias.length > 0 ? alias : undefined); } +// --------------------------------------------------------------------------- +// Visual model (`/visual-model`) — persists `[visual_model] default_model` +// --------------------------------------------------------------------------- + +function showVisualModelPicker( + host: SlashCommandHost, + models: Record, + currentValue: string, + selectedValue?: string, +): void { + host.mountEditorReplacement( + new TabbedModelSelectorComponent({ + models, + currentValue, + selectedValue, + currentThinkingEffort: 'off', + thinkingControl: false, + title: ' Select a visual model (image inspection)', + onSelect: ({ alias }) => { + host.restoreEditor(); + void performVisualModelSave(host, alias); + }, + onCancel: () => { + host.restoreEditor(); + }, + }), + ); +} + +async function performVisualModelSave(host: SlashCommandHost, alias: string): Promise { + const displayName = modelDisplayName(alias, host.state.appState.availableModels[alias]); + try { + const config = await host.harness.getConfig({ reload: true }); + const patch: { defaultModel: string } = { + defaultModel: alias, + }; + await host.harness.setConfig({ visualModel: patch }); + } catch (error) { + host.showError(`Failed to save visual model: ${formatErrorMessage(error)}`); + return; + } + host.showStatus( + `Visual model set to ${displayName}. Image inspection will use it.`, + 'success', + ); +} + +export async function handleVisualModelCommand(host: SlashCommandHost, args: string): Promise { + const alias = args.trim(); + await refreshModelsForPicker(host); + const models = pickerModelsForHost(host); + if (Object.keys(models).length === 0) { + host.showNotice( + 'No models configured', + 'Run /login to sign in to Kimi, or /provider to add another provider from a model catalog.', + ); + return; + } + if (alias.length > 0 && models[alias] === undefined) { + host.showError(`Unknown model alias: ${alias}`); + return; + } + const visual = (await host.harness.getConfig()).visualModel; + const current = visual?.defaultModel ?? visual?.model ?? ''; + showVisualModelPicker(host, models, current, alias.length > 0 ? alias : undefined); +} + export async function handleEffortCommand(host: SlashCommandHost, args: string): Promise { const alias = host.state.appState.model; const model = host.state.appState.availableModels[alias]; diff --git a/apps/kimi-code/src/tui/commands/dispatch.ts b/apps/kimi-code/src/tui/commands/dispatch.ts index 6bf367f64b0..309114d0e67 100644 --- a/apps/kimi-code/src/tui/commands/dispatch.ts +++ b/apps/kimi-code/src/tui/commands/dispatch.ts @@ -35,6 +35,7 @@ import { handleModelCommand, handlePlanCommand, handleSecondaryModelCommand, + handleVisualModelCommand, handleThemeCommand, handleYoloCommand, showExperimentsPanel, @@ -88,6 +89,7 @@ export { handleModelCommand, handlePlanCommand, handleSecondaryModelCommand, + handleVisualModelCommand, handleThemeCommand, handleYoloCommand, showModelPicker, @@ -534,6 +536,9 @@ async function handleBuiltInSlashCommand( case 'secondary-model': await handleSecondaryModelCommand(host, args); return; + case 'visual-model': + await handleVisualModelCommand(host, args); + return; case 'effort': await handleEffortCommand(host, args); return; diff --git a/apps/kimi-code/src/tui/commands/registry.ts b/apps/kimi-code/src/tui/commands/registry.ts index d87e74b75dd..8d80b83a11b 100644 --- a/apps/kimi-code/src/tui/commands/registry.ts +++ b/apps/kimi-code/src/tui/commands/registry.ts @@ -192,6 +192,12 @@ export const BUILTIN_SLASH_COMMANDS = [ availability: 'always', experimentalFlag: 'secondary-model', }, + { + name: 'visual-model', + description: 'Configure the visual model for image inspection', + priority: 91, + availability: 'always', + }, { name: 'effort', aliases: ['thinking'], From a3e3d4fbba6b81d026a5838ab6dc7a8e1e262636 Mon Sep 17 00:00:00 2001 From: ARRRRNY Date: Tue, 18 Aug 2026 15:59:17 +0300 Subject: [PATCH 07/71] ci: add fork native release workflow Builds kimi binary with fork changes (visual-model command) on every push to development and publishes as 'nightly' GitHub Release. Reuses upstream _native-build.yml for all 6 platforms. --- .github/workflows/fork-native-release.yml | 76 +++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 .github/workflows/fork-native-release.yml diff --git a/.github/workflows/fork-native-release.yml b/.github/workflows/fork-native-release.yml new file mode 100644 index 00000000000..c416c70b9f7 --- /dev/null +++ b/.github/workflows/fork-native-release.yml @@ -0,0 +1,76 @@ +# Fork-native release: builds the kimi binary (with fork changes) on every +# push to development and publishes it as a GitHub Release. +# +# This gives us a ready-to-download binary without building locally. +# The release tag is always "nightly" — updated on every run. + +name: Fork Native Release + +on: + push: + branches: + - development + workflow_dispatch: + schedule: + # Daily re-release so the binary always tracks the latest upstream sync + - cron: "41 6 * * *" + +permissions: + contents: write + +jobs: + native-artifacts: + name: Build native binaries + uses: ./.github/workflows/_native-build.yml + with: + upload-artifact-prefix: kimi-code-fork + retention-days: 3 + sign-macos: false + + publish: + name: Publish release + needs: native-artifacts + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Determine version + id: version + run: | + VER=$(node -p "require('./apps/kimi-code/package.json').version") + echo "version=$VER" >> "$GITHUB_OUTPUT" + + - name: Download native artifacts + uses: actions/download-artifact@v7 + with: + pattern: kimi-code-fork-* + path: dist-native-release + merge-multiple: true + + - name: Delete old nightly release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release delete nightly --yes --cleanup-tag 2>/dev/null || true + + - name: Create release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create nightly \ + dist-native-release/* \ + --title "kimi-code fork nightly (${{ steps.version.outputs.version }})" \ + --notes "Built from the development branch with fork changes (visual-model command). + +- Platform: darwin-x64, darwin-arm64, linux-x64, linux-arm64, win32-x64, win32-arm64 +- Source version: ${{ github.sha }} + +Install (macOS Intel): +\`\`\`bash +unzip -o kimi-code-darwin-x64.zip -d ~/.kimi-code/bin +\`\`\` +" From 83893c2ebf257e99f098b5332a06eea633f38b1b Mon Sep 17 00:00:00 2001 From: ARRRRNY Date: Tue, 18 Aug 2026 16:00:56 +0300 Subject: [PATCH 08/71] fix(ci): fix YAML in fork release workflow notes --- .github/workflows/fork-native-release.yml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/fork-native-release.yml b/.github/workflows/fork-native-release.yml index c416c70b9f7..dea0e0b9019 100644 --- a/.github/workflows/fork-native-release.yml +++ b/.github/workflows/fork-native-release.yml @@ -60,17 +60,13 @@ jobs: - name: Create release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_NOTES: | + Built from the development branch with fork changes (visual-model command). + + Platforms: darwin-x64, darwin-arm64, linux-x64, linux-arm64, win32-x64, win32-arm64 + Source commit: ${{ github.sha }} run: | gh release create nightly \ dist-native-release/* \ --title "kimi-code fork nightly (${{ steps.version.outputs.version }})" \ - --notes "Built from the development branch with fork changes (visual-model command). - -- Platform: darwin-x64, darwin-arm64, linux-x64, linux-arm64, win32-x64, win32-arm64 -- Source version: ${{ github.sha }} - -Install (macOS Intel): -\`\`\`bash -unzip -o kimi-code-darwin-x64.zip -d ~/.kimi-code/bin -\`\`\` -" + --notes "$RELEASE_NOTES" From fa2f39bc21a4b6d8346e471cb98582403f8f4e8f Mon Sep 17 00:00:00 2001 From: ARRRRNY Date: Tue, 18 Aug 2026 16:11:19 +0300 Subject: [PATCH 09/71] ci: slim fork release to darwin-x64 and linux-x64 only --- .github/workflows/fork-native-release.yml | 63 +++++++++++++++++++---- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/.github/workflows/fork-native-release.yml b/.github/workflows/fork-native-release.yml index dea0e0b9019..84b6567560f 100644 --- a/.github/workflows/fork-native-release.yml +++ b/.github/workflows/fork-native-release.yml @@ -1,8 +1,7 @@ # Fork-native release: builds the kimi binary (with fork changes) on every # push to development and publishes it as a GitHub Release. # -# This gives us a ready-to-download binary without building locally. -# The release tag is always "nightly" — updated on every run. +# Platforms: darwin-x64 (macOS Intel) and linux-x64 only. name: Fork Native Release @@ -19,17 +18,59 @@ permissions: contents: write jobs: - native-artifacts: - name: Build native binaries - uses: ./.github/workflows/_native-build.yml - with: - upload-artifact-prefix: kimi-code-fork - retention-days: 3 - sign-macos: false + native-build: + name: Native build (${{ matrix.target }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: macos-15-intel + target: darwin-x64 + - os: ubuntu-24.04 + target: linux-x64 + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup pnpm + uses: pnpm/action-setup@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build Kimi web assets + run: node apps/kimi-code/scripts/check-web-assets.mjs + + - name: Build native executable + run: pnpm --filter @moonshot-ai/kimi-code run build:native:sea + + - name: Smoke test native executable + run: pnpm --filter @moonshot-ai/kimi-code run test:native:smoke + + - name: Package native artifact + run: pnpm --filter @moonshot-ai/kimi-code run package:native + + - name: Upload native artifact + uses: actions/upload-artifact@v7 + with: + name: kimi-code-fork-${{ matrix.target }} + retention-days: 3 + path: | + apps/kimi-code/dist-native/artifacts/kimi-code-${{ matrix.target }}.zip + apps/kimi-code/dist-native/artifacts/kimi-code-${{ matrix.target }}.zip.sha256 + if-no-files-found: ignore publish: name: Publish release - needs: native-artifacts + needs: native-build runs-on: ubuntu-latest permissions: contents: write @@ -63,7 +104,7 @@ jobs: RELEASE_NOTES: | Built from the development branch with fork changes (visual-model command). - Platforms: darwin-x64, darwin-arm64, linux-x64, linux-arm64, win32-x64, win32-arm64 + Platforms: darwin-x64 (macOS Intel), linux-x64 Source commit: ${{ github.sha }} run: | gh release create nightly \ From b2264ffbc9fb98f32962a4880136243b1c3af281 Mon Sep 17 00:00:00 2001 From: ARRRRNY Date: Tue, 18 Aug 2026 18:12:09 +0300 Subject: [PATCH 10/71] fix: add missing aliases to /visual-model slash command registration --- .changeset/fix-visual-model-slash-crash.md | 5 +++++ apps/kimi-code/src/tui/commands/registry.ts | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/fix-visual-model-slash-crash.md diff --git a/.changeset/fix-visual-model-slash-crash.md b/.changeset/fix-visual-model-slash-crash.md new file mode 100644 index 00000000000..19f4a68c0bf --- /dev/null +++ b/.changeset/fix-visual-model-slash-crash.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Fix a crash that closed the CLI when running any slash command (such as /sessions) while the visual-model experiment is enabled. diff --git a/apps/kimi-code/src/tui/commands/registry.ts b/apps/kimi-code/src/tui/commands/registry.ts index 8d80b83a11b..7f13adc47fd 100644 --- a/apps/kimi-code/src/tui/commands/registry.ts +++ b/apps/kimi-code/src/tui/commands/registry.ts @@ -194,6 +194,7 @@ export const BUILTIN_SLASH_COMMANDS = [ }, { name: 'visual-model', + aliases: [], description: 'Configure the visual model for image inspection', priority: 91, availability: 'always', From ee53e072d27be2c621c8b3af1828e2b063bab663 Mon Sep 17 00:00:00 2001 From: ARRRRNY Date: Thu, 20 Aug 2026 11:34:28 +0300 Subject: [PATCH 11/71] feat: add proxy URL per provider and multiple API keys support - Add proxy_url configuration per provider in config.toml - Implement SSE stream parsing for OpenAI legacy and responses APIs - Use undici.ProxyAgent with fetch dispatcher for proxy support - Add multiple named API keys per provider with active key selection - Update provider manager UI for managing multiple keys - Add visual-model experiment configuration - Add substitute-model experiment for rate limit fallback - Update AGENTS.md files across packages - Fix visual-model slash command crash Changes include: * packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.ts * packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.ts * packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.contrib.ts * packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.contrib.ts * packages/agent-core-v2/src/app/kosongConfig/configSection.ts * apps/kimi-code/src/tui/commands/provider.ts * apps/kimi-code/src/tui/components/dialogs/provider-manager.ts * packages/agent-core/src/session/provider-manager.ts * packages/agent-core/src/config/schema.ts * packages/klient/src/contract/global/providers.ts * packages/agent-core-v2/src/session/visual/configSection.ts * packages/agent-core-v2/src/agent/goal/goalService.ts * Multiple AGENTS.md updates across packages * Config schema updates for proxy_url and multiple API keys --- .changeset/substitute-model.md | 5 + AGENTS.md | 52 +++++ apps/kimi-code/AGENTS.md | 29 +++ apps/kimi-code/src/tui/commands/config.ts | 67 ++++++ apps/kimi-code/src/tui/commands/dispatch.ts | 5 + apps/kimi-code/src/tui/commands/prompts.ts | 41 ++++ apps/kimi-code/src/tui/commands/provider.ts | 198 +++++++++++++++++ apps/kimi-code/src/tui/commands/registry.ts | 8 + .../components/dialogs/provider-manager.ts | 183 +++++++++++++--- .../dialogs/provider-manager.test.ts | 3 + apps/kimi-inspect/AGENTS.md | 29 +++ docs/AGENTS.md | 52 +++++ package.json | 9 +- packages/agent-core-v2/AGENTS.md | 29 +++ .../src/agent/goal/goalService.ts | 131 ++++++++++++ .../src/app/kosongConfig/configSection.ts | 3 + .../agent-core-v2/src/app/telemetry/events.ts | 24 +++ packages/agent-core-v2/src/index.ts | 18 ++ .../agent-core-v2/src/kosong/model/catalog.ts | 1 + .../src/kosong/model/catalogService.ts | 1 + .../src/kosong/model/modelAuth.ts | 17 +- .../src/kosong/model/modelRequesterImpl.ts | 1 + .../src/kosong/protocol/protocol.ts | 2 + .../bases/openai/openai-legacy.contrib.ts | 1 + .../provider/bases/openai/openai-legacy.ts | 168 +++++++++++++-- .../bases/openai/openai-responses.contrib.ts | 1 + .../provider/bases/openai/openai-responses.ts | 201 +++++++++++++----- .../src/kosong/provider/provider.ts | 8 + .../src/session/substitute/configSection.ts | 75 +++++++ .../src/session/substitute/flag.ts | 16 ++ .../src/session/visual/configSection.ts | 3 +- packages/agent-core/AGENTS.md | 29 +++ packages/agent-core/src/config/schema.ts | 45 +++- packages/agent-core/src/flags/registry.ts | 9 + .../src/session/provider-manager.ts | 21 +- .../agent-core/test/config/env-model.test.ts | 4 + packages/kap-server/AGENTS.md | 29 +++ packages/klient/AGENTS.md | 29 +++ .../klient/src/contract/global/providers.ts | 8 + packages/minidb/AGENTS.md | 29 +++ packages/oauth/src/refreshProviderModels.ts | 23 +- packages/pi-tui/AGENTS.md | 29 +++ packages/transcript/AGENTS.md | 29 +++ pnpm-lock.yaml | 50 +++++ 44 files changed, 1596 insertions(+), 119 deletions(-) create mode 100644 .changeset/substitute-model.md create mode 100644 packages/agent-core-v2/src/session/substitute/configSection.ts create mode 100644 packages/agent-core-v2/src/session/substitute/flag.ts diff --git a/.changeset/substitute-model.md b/.changeset/substitute-model.md new file mode 100644 index 00000000000..8fe426f4b93 --- /dev/null +++ b/.changeset/substitute-model.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": minor +--- + +Add the /substitute-model command to configure automatic model fallback when the primary model hits a rate limit. Enable via [experimental] substitute_model = true in config.toml or KIMI_CODE_EXPERIMENTAL_SUBSTITUTE_MODEL=1. diff --git a/AGENTS.md b/AGENTS.md index 99cc7230f3f..ccaf7615271 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,9 +1,61 @@ +# ⚠️ LANGUAGE RULE — MANDATORY + +**ALL responses MUST be in English ONLY. Never respond in Turkish, Chinese, or any other language, regardless of the user's locale or the presence of non-English content in the codebase. This is a hard rule with no exceptions.** + +--- + + + # Repository-level Agent Guide Reply in the same language as the user. This is a TypeScript monorepo built for agent-assisted development. Keep the root `AGENTS.md` limited to hot-path rules: the project map, hard constraints, and workflow requirements — things every task needs to know. +## 🔍 Code Search — MANDATORY FIRST STEP + +**STOP. Before using `grep`, `find`, `rg`, `ripgrep`, or ANY shell-based search, you MUST use semantic search first.** + +```bash +# THIS is how you search code — ALWAYS FIRST: +mcp__claude_context__search_code(query="what you're looking for", path="/absolute/path/to/repo") +``` + +**Why?** Semantic search understands code relationships, finds implementations by meaning (not just text), and catches things grep misses entirely. + +**Rules:** +1. **ALWAYS** start with `mcp__claude_context__search_code` for code discovery +2. **ONLY** fall back to `grep`/`find`/`rg` when: + - You need an EXACT literal string match (e.g., a specific error message) + - The semantic search index is unavailable/broken + - You're searching for file names, not code content +3. **NEVER** use grep as your first code search tool — it's slower and less accurate + +**Indexing:** If search fails with "not indexed", run `mcp__claude_context__index_codebase(path="/absolute/path")` first, then retry. + +## 📚 Zread Wiki — Check First + +**Before diving into source code, check if a zread wiki exists for this project:** + +```bash +# Check if wiki exists: +cat .zread/wiki/current 2>/dev/null && echo "Wiki exists" || echo "No wiki" + +# If wiki exists, read the pages directly: +ls .zread/wiki/versions/$(cat .zread/wiki/current)/ + +# To regenerate wiki (if stale): +zread generate --stdio +``` + +**Why?** Zread generates comprehensive documentation from code. Reading the wiki is faster than crawling source files manually. + +**Rules:** +1. **ALWAYS** check `.zread/wiki/current` before reading source files +2. If wiki exists, read the markdown pages directly — they're already indexed +3. If wiki is missing or stale, run `zread generate --stdio` to create it +4. Wiki pages live in `.zread/wiki/versions//` — read `wiki.json` for the TOC + ## Working Principles - Think from first principles. Start from real requirements, code facts, and verification results; if the goal is unclear, discuss it with the user first. diff --git a/apps/kimi-code/AGENTS.md b/apps/kimi-code/AGENTS.md index 857dc09e930..05401df9e3a 100644 --- a/apps/kimi-code/AGENTS.md +++ b/apps/kimi-code/AGENTS.md @@ -1,3 +1,32 @@ +# ⚠️ LANGUAGE RULE — MANDATORY + +**ALL responses MUST be in English ONLY. Never respond in Turkish, Chinese, or any other language, regardless of the user's locale or the presence of non-English content in the codebase. This is a hard rule with no exceptions.** + +--- + +## 📚 Zread Wiki — Check First + +**Before diving into source code, check if a zread wiki exists for this project:** + +```bash +# Check if wiki exists: +cat .zread/wiki/current 2>/dev/null && echo "Wiki exists" || echo "No wiki" + +# If wiki exists, read the pages directly: +ls .zread/wiki/versions/$(cat .zread/wiki/current)/ + +# To regenerate wiki (if stale): +zread generate --stdio +``` + +**Why?** Zread generates comprehensive documentation from code. Reading the wiki is faster than crawling source files manually. + +**Rules:** +1. **ALWAYS** check `.zread/wiki/current` before reading source files +2. If wiki exists, read the markdown pages directly — they're already indexed +3. If wiki is missing or stale, run `zread generate --stdio` to create it +4. Wiki pages live in `.zread/wiki/versions//` — read `wiki.json` for the TOC + # apps/kimi-code Development Guide This file only contains rules local to `apps/kimi-code`. For cross-repo rules, see the root `AGENTS.md`. diff --git a/apps/kimi-code/src/tui/commands/config.ts b/apps/kimi-code/src/tui/commands/config.ts index 8769c17ef23..2e5e277066c 100644 --- a/apps/kimi-code/src/tui/commands/config.ts +++ b/apps/kimi-code/src/tui/commands/config.ts @@ -364,6 +364,73 @@ export async function handleVisualModelCommand(host: SlashCommandHost, args: str showVisualModelPicker(host, models, current, alias.length > 0 ? alias : undefined); } +// --------------------------------------------------------------------------- +// Substitute model (`/substitute-model`) — persists `[substitute_model] default_model` +// --------------------------------------------------------------------------- + +function showSubstituteModelPicker( + host: SlashCommandHost, + models: Record, + currentValue: string, + selectedValue?: string, +): void { + host.mountEditorReplacement( + new TabbedModelSelectorComponent({ + models, + currentValue, + selectedValue, + currentThinkingEffort: 'off', + thinkingControl: false, + title: ' Select a substitute model (rate-limit fallback)', + onSelect: ({ alias }) => { + host.restoreEditor(); + void performSubstituteModelSave(host, alias); + }, + onCancel: () => { + host.restoreEditor(); + }, + }), + ); +} + +async function performSubstituteModelSave(host: SlashCommandHost, alias: string): Promise { + const displayName = modelDisplayName(alias, host.state.appState.availableModels[alias]); + try { + const config = await host.harness.getConfig({ reload: true }); + const patch: { defaultModel: string } = { + defaultModel: alias, + }; + await host.harness.setConfig({ substituteModel: patch }); + } catch (error) { + host.showError(`Failed to save substitute model: ${formatErrorMessage(error)}`); + return; + } + host.showStatus( + `Substitute model set to ${displayName}. It will be used when the primary model hits a rate limit.`, + 'success', + ); +} + +export async function handleSubstituteModelCommand(host: SlashCommandHost, args: string): Promise { + const alias = args.trim(); + await refreshModelsForPicker(host); + const models = pickerModelsForHost(host); + if (Object.keys(models).length === 0) { + host.showNotice( + 'No models configured', + 'Run /login to sign in to Kimi, or /provider to add another provider from a model catalog.', + ); + return; + } + if (alias.length > 0 && models[alias] === undefined) { + host.showError(`Unknown model alias: ${alias}`); + return; + } + const substitute = (await host.harness.getConfig()).substituteModel; + const current = substitute?.defaultModel ?? ''; + showSubstituteModelPicker(host, models, current, alias.length > 0 ? alias : undefined); +} + export async function handleEffortCommand(host: SlashCommandHost, args: string): Promise { const alias = host.state.appState.model; const model = host.state.appState.availableModels[alias]; diff --git a/apps/kimi-code/src/tui/commands/dispatch.ts b/apps/kimi-code/src/tui/commands/dispatch.ts index 309114d0e67..9cdf5a22039 100644 --- a/apps/kimi-code/src/tui/commands/dispatch.ts +++ b/apps/kimi-code/src/tui/commands/dispatch.ts @@ -35,6 +35,7 @@ import { handleModelCommand, handlePlanCommand, handleSecondaryModelCommand, + handleSubstituteModelCommand, handleVisualModelCommand, handleThemeCommand, handleYoloCommand, @@ -89,6 +90,7 @@ export { handleModelCommand, handlePlanCommand, handleSecondaryModelCommand, + handleSubstituteModelCommand, handleVisualModelCommand, handleThemeCommand, handleYoloCommand, @@ -539,6 +541,9 @@ async function handleBuiltInSlashCommand( case 'visual-model': await handleVisualModelCommand(host, args); return; + case 'substitute-model': + await handleSubstituteModelCommand(host, args); + return; case 'effort': await handleEffortCommand(host, args); return; diff --git a/apps/kimi-code/src/tui/commands/prompts.ts b/apps/kimi-code/src/tui/commands/prompts.ts index cbfc33072f5..c79cd12c59e 100644 --- a/apps/kimi-code/src/tui/commands/prompts.ts +++ b/apps/kimi-code/src/tui/commands/prompts.ts @@ -128,6 +128,28 @@ export function promptApiKey( }); } +export function promptKeyName( + host: SlashCommandHost, + title: string = 'Enter a name for this API key', +): Promise { + return new Promise((resolve) => { + const dialog = new ApiKeyInputDialogComponent( + title, + ['This name helps you identify the key in the provider manager.'], + (result: ApiKeyInputResult) => { + host.restoreEditor(); + resolve(result.kind === 'ok' ? result.value.trim() : undefined); + }, + { + title: 'API Key Name', + mask: false, + emptyHint: 'Name cannot be empty.', + }, + ); + host.mountEditorReplacement(dialog); + }); +} + /** * Asks for the provider endpoint the catalog did not declare (or declared * only as an env placeholder) — required for catalog imports whose protocol @@ -224,6 +246,25 @@ export async function promptModelSelectionForCatalog( return model ? { model, thinking: selection.thinking } : undefined; } +export function promptProxyUrl(host: SlashCommandHost, providerName: string): Promise { + return new Promise((resolve) => { + const dialog = new ApiKeyInputDialogComponent( + providerName, + ['Enter proxy URL for this provider (e.g. http://localhost:8080). Leave empty to disable.'], + (result: ApiKeyInputResult) => { + host.restoreEditor(); + resolve(result.kind === 'ok' ? (result.value.trim() || undefined) : undefined); + }, + { + title: `Enter proxy URL for ${providerName}`, + mask: false, + emptyHint: 'Proxy URL can be empty to disable.', + }, + ); + host.mountEditorReplacement(dialog); + }); +} + export function runModelSelector( host: SlashCommandHost, modelDict: Record, diff --git a/apps/kimi-code/src/tui/commands/provider.ts b/apps/kimi-code/src/tui/commands/provider.ts index 5e0d84b0e63..2fc3931a7b0 100644 --- a/apps/kimi-code/src/tui/commands/provider.ts +++ b/apps/kimi-code/src/tui/commands/provider.ts @@ -13,6 +13,7 @@ import { resolveCatalogImport, SECONDARY_DERIVED_MODEL_ALIAS, type Catalog, + type ProviderConfig, type ThinkingEffort, } from '@moonshot-ai/kimi-code-sdk'; @@ -37,6 +38,8 @@ import { promptApiKey, promptBaseUrl, promptCatalogProviderSelection, + promptKeyName, + promptProxyUrl, } from './prompts'; import type { SlashCommandHost } from './dispatch'; @@ -66,6 +69,26 @@ function buildProviderManagerOptions(host: SlashCommandHost): ProviderManagerOpt host.showError(`Remove provider failed: ${formatErrorMessage(error)}`); }); }, + onAddKey: (providerId) => { + void handleProviderKeyAdd(host, providerId).catch((error: unknown) => { + host.showError(`Add API key failed: ${formatErrorMessage(error)}`); + }); + }, + onRemoveKey: (providerId, keyId) => { + void handleProviderKeyRemove(host, providerId, keyId).catch((error: unknown) => { + host.showError(`Remove API key failed: ${formatErrorMessage(error)}`); + }); + }, + onSetActiveKey: (providerId, keyId) => { + void handleProviderKeySetActive(host, providerId, keyId).catch((error: unknown) => { + host.showError(`Set active API key failed: ${formatErrorMessage(error)}`); + }); + }, + onSetProxyUrl: (providerId) => { + void handleProviderProxyUrl(host, providerId).catch((error: unknown) => { + host.showError(`Set proxy URL failed: ${formatErrorMessage(error)}`); + }); + }, onClose: () => { host.restoreEditor(); }, @@ -113,6 +136,142 @@ async function handleProviderDelete(host: SlashCommandHost, providerId: string): } } +// --------------------------------------------------------------------------- +// API Key management for providers +// --------------------------------------------------------------------------- + +async function handleProviderKeyAdd(host: SlashCommandHost, providerId: string): Promise { + const provider = host.state.appState.availableProviders[providerId]; + if (!provider) { + host.showError(`Provider ${providerId} not found`); + return; + } + + // Prompt for key name + const name = await promptKeyName(host); + if (name === undefined) { + reopenProviderManager(host); + return; + } + + // Prompt for API key value + const apiKey = await promptApiKey(host, `API key for ${providerId}/${name}`); + if (apiKey === undefined) { + reopenProviderManager(host); + return; + } + + // Generate a unique key ID + const keyId = generateKeyId(provider); + + const config = await host.harness.getConfig(); + const providers = { ...config.providers }; + const existingProvider = providers[providerId]; + if (!existingProvider) { + host.showError(`Provider ${providerId} not found in config`); + return; + } + + const apiKeys = existingProvider.apiKeys ? { ...existingProvider.apiKeys } : {}; + apiKeys[keyId] = { key: apiKey, name }; + + providers[providerId] = { + ...existingProvider, + apiKeys, + activeApiKeyId: existingProvider.activeApiKeyId ?? keyId, // First key becomes active + }; + + // Use replaceConfigSections if available (v2) to ensure full replacement, + // otherwise fall back to setConfig (v1 deep merge). + if (host.harness.supportsAtomicSectionReplace()) { + await host.harness.replaceConfigSections({ providers }); + } else { + await host.harness.setConfig({ providers }); + } + await host.authFlow.refreshConfigAfterLogin(); + host.showStatus(`Added API key "${name}" to ${providerId}`); + reopenProviderManager(host); +} + +async function handleProviderKeyRemove(host: SlashCommandHost, providerId: string, keyId: string): Promise { + const config = await host.harness.getConfig(); + const providers = { ...config.providers }; + const provider = providers[providerId]; + if (!provider || !provider.apiKeys || !provider.apiKeys[keyId]) { + host.showError(`API key not found`); + return; + } + + const apiKeys = { ...provider.apiKeys }; + const keyEntry = apiKeys[keyId]; + if (!keyEntry) { + host.showError(`API key not found`); + return; + } + const keyName = keyEntry.name; + delete apiKeys[keyId]; + + let activeApiKeyId = provider.activeApiKeyId; + if (activeApiKeyId === keyId) { + // Select another key as active if available + const remainingKeys = Object.keys(apiKeys); + activeApiKeyId = remainingKeys.length > 0 ? remainingKeys[0] : undefined; + } + + if (Object.keys(apiKeys).length === 0) { + // No keys left, remove the apiKeys field entirely + const { apiKeys: _, activeApiKeyId: __, ...rest } = provider; + providers[providerId] = rest; + } else { + providers[providerId] = { ...provider, apiKeys, activeApiKeyId }; + } + + // Use replaceConfigSections if available (v2) to ensure full replacement, + // otherwise fall back to setConfig (v1 deep merge). + if (host.harness.supportsAtomicSectionReplace()) { + await host.harness.replaceConfigSections({ providers }); + } else { + await host.harness.setConfig({ providers }); + } + await host.authFlow.refreshConfigAfterLogin(); + host.showStatus(`Removed API key "${keyName}" from ${providerId}`); + reopenProviderManager(host); +} + +async function handleProviderKeySetActive(host: SlashCommandHost, providerId: string, keyId: string): Promise { + const config = await host.harness.getConfig(); + const providers = { ...config.providers }; + const provider = providers[providerId]; + if (!provider || !provider.apiKeys || !provider.apiKeys[keyId]) { + host.showError(`API key not found`); + return; + } + + providers[providerId] = { ...provider, activeApiKeyId: keyId }; + // Use replaceConfigSections if available (v2) to ensure full replacement, + // otherwise fall back to setConfig (v1 deep merge). + if (host.harness.supportsAtomicSectionReplace()) { + await host.harness.replaceConfigSections({ providers }); + } else { + await host.harness.setConfig({ providers }); + } + await host.authFlow.refreshConfigAfterLogin(); + const keyName = provider.apiKeys[keyId].name; + host.showStatus(`Set active API key to "${keyName}" for ${providerId}`); + reopenProviderManager(host); +} + +function generateKeyId(provider: ProviderConfig): string { + const existingKeys = provider.apiKeys ? Object.keys(provider.apiKeys) : []; + let counter = 1; + let keyId = `key${counter}`; + while (existingKeys.includes(keyId)) { + counter++; + keyId = `key${counter}`; + } + return keyId; +} + async function handleProviderAdd(host: SlashCommandHost): Promise { const source = await promptProviderAddSource(host); if (source === undefined) { @@ -423,3 +582,42 @@ function promptCustomRegistryImport( host.mountEditorReplacement(dialog); }); } + +async function handleProviderProxyUrl(host: SlashCommandHost, providerId: string): Promise { + const provider = host.state.appState.availableProviders[providerId]; + if (!provider) { + host.showError(`Provider ${providerId} not found`); + return; + } + + const proxyUrl = await promptProxyUrl(host, providerId); + if (proxyUrl === undefined) { + reopenProviderManager(host); + return; + } + + const config = await host.harness.getConfig(); + const providers = { ...config.providers }; + const existingProvider = providers[providerId]; + if (!existingProvider) { + host.showError(`Provider ${providerId} not found in config`); + return; + } + + providers[providerId] = { + ...existingProvider, + proxyUrl, + }; + + // Use replaceConfigSections if available (v2) to ensure full replacement, + // otherwise fall back to setConfig (v1 deep merge). + if (host.harness.supportsAtomicSectionReplace()) { + await host.harness.replaceConfigSections({ providers }); + } else { + await host.harness.setConfig({ providers }); + } + await host.authFlow.refreshConfigAfterLogin(); + const display = proxyUrl ? proxyUrl : 'disabled'; + host.showStatus(`Proxy for ${providerId} set to ${display}`); + reopenProviderManager(host); +} diff --git a/apps/kimi-code/src/tui/commands/registry.ts b/apps/kimi-code/src/tui/commands/registry.ts index 7f13adc47fd..8f7809705ed 100644 --- a/apps/kimi-code/src/tui/commands/registry.ts +++ b/apps/kimi-code/src/tui/commands/registry.ts @@ -199,6 +199,14 @@ export const BUILTIN_SLASH_COMMANDS = [ priority: 91, availability: 'always', }, + { + name: 'substitute-model', + aliases: [], + description: 'Configure the substitute model for rate-limit fallback', + priority: 91, + availability: 'always', + experimentalFlag: 'substitute-model', + }, { name: 'effort', aliases: ['thinking'], diff --git a/apps/kimi-code/src/tui/components/dialogs/provider-manager.ts b/apps/kimi-code/src/tui/components/dialogs/provider-manager.ts index 7ae0da03d20..3a6e3266a7d 100644 --- a/apps/kimi-code/src/tui/components/dialogs/provider-manager.ts +++ b/apps/kimi-code/src/tui/components/dialogs/provider-manager.ts @@ -55,6 +55,17 @@ interface ConfirmState { readonly providerIds: readonly string[]; } +/** Key row for a named API key within a provider. */ +interface KeyRow { + readonly kind: 'key'; + readonly id: string; // `${providerId}:${keyId}` + readonly providerId: string; + readonly keyId: string; + readonly label: string; // key name + readonly preview: string; // masked key preview + readonly isActive: boolean; +} + export interface ProviderManagerOptions { /** All currently configured providers (`config.providers`). */ readonly providers: Record; @@ -65,6 +76,14 @@ export interface ProviderManagerOptions { * fetch / standalone). Passed the full provider-id list so the host * doesn't have to re-derive the source grouping. */ readonly onDeleteSource: (providerIds: readonly string[]) => void; + /** Add a new named API key to a provider. */ + readonly onAddKey: (providerId: string) => void; + /** Remove a named API key from a provider. */ + readonly onRemoveKey: (providerId: string, keyId: string) => void; + /** Set the active API key for a provider. */ + readonly onSetActiveKey: (providerId: string, keyId: string) => void; + /** Set proxy URL for a provider. */ + readonly onSetProxyUrl: (providerId: string) => void; readonly onClose: () => void; } @@ -78,6 +97,8 @@ interface SourceRow { readonly hasActive: boolean; /** Optional base URL extracted from the provider config. */ readonly baseUrl?: string; + /** Child key rows for this provider (if it has multiple named keys). */ + readonly keyRows: readonly KeyRow[]; } /** Synthetic `[ Add New Platform ]` action row pinned to the bottom. */ @@ -87,11 +108,11 @@ interface AddRow { readonly label: string; } -type Row = SourceRow | AddRow; +type Row = SourceRow | AddRow | KeyRow; const ADD_ROW_LABEL = '[ Add New Platform ]'; const PAGE_SIZE = 8; -const HEADER_HINT = '↑↓ navigate · D delete · Esc cancel'; +const HEADER_HINT = '↑↓ navigate · D delete · A add key · S set active · P proxy · Esc cancel'; // Narrows a `ProviderConfig` blob to a `CustomRegistrySource` payload. // Mirrors `readCustomRegistrySource` in `kimi-tui.ts`. We can't import @@ -135,13 +156,15 @@ function sourceUrlLabel(url: string): string { * - `cfg.source.kind === 'apiJson'` → one source per `{url, apiKey}` * pair, label = hostname + pathname. * - Anything else → 1 source per provider, label = provider id. + * - Providers with multiple named API keys (`apiKeys`) get child key rows. */ function buildRows(opts: ProviderManagerOptions): readonly Row[] { - const sources: SourceRow[] = []; + const rows: Row[] = []; // Map from `${url}${apiKey}` → index into `sources`, so we can // append further providers into the same group. const customRegistryIndex = new Map(); + const sourceRows: SourceRow[] = []; for (const [id, cfg] of Object.entries(opts.providers)) { if (id === DEFAULT_OAUTH_PROVIDER_NAME) continue; @@ -150,12 +173,13 @@ function buildRows(opts: ProviderManagerOptions): readonly Row[] { if (isOpenPlatformId(id)) { const platform = getOpenPlatformById(id); - sources.push({ + sourceRows.push({ kind: 'source', id: `open:${id}`, label: platform?.name ?? id, providerIds: [id], hasActive: isActive, + keyRows: [], }); continue; } @@ -170,42 +194,78 @@ function buildRows(opts: ProviderManagerOptions): readonly Row[] { const key = `${customSource.url}${customSource.apiKey}`; const existingIdx = customRegistryIndex.get(key); if (existingIdx !== undefined) { - const existing = sources[existingIdx]; + const existing = sourceRows[existingIdx]; if (existing !== undefined && existing.kind === 'source') { - sources[existingIdx] = { + sourceRows[existingIdx] = { kind: 'source', id: existing.id, label: existing.label, providerIds: [...existing.providerIds, id], hasActive: existing.hasActive || isActive, baseUrl: existing.baseUrl, + keyRows: existing.keyRows, }; } continue; } - customRegistryIndex.set(key, sources.length); - sources.push({ + customRegistryIndex.set(key, sourceRows.length); + sourceRows.push({ kind: 'source', id: `custom:${key}`, label: sourceUrlLabel(customSource.url), providerIds: [id], hasActive: isActive, baseUrl, + keyRows: [], }); continue; } - sources.push({ + // Build key rows for providers with multiple named API keys + const keyRows = buildKeyRows(id, cfg as ProviderConfig); + + sourceRows.push({ kind: 'source', id: `provider:${id}`, label: id, providerIds: [id], hasActive: isActive, baseUrl, + keyRows, }); } - return [...sources, { kind: 'add', id: '__add__', label: ADD_ROW_LABEL }]; + // Flatten: source rows followed by their key rows + for (const source of sourceRows) { + rows.push(source); + for (const keyRow of source.keyRows) { + rows.push(keyRow); + } + } + + rows.push({ kind: 'add', id: '__add__', label: ADD_ROW_LABEL }); + return rows; +} + +function buildKeyRows(providerId: string, provider: ProviderConfig): readonly KeyRow[] { + const apiKeys = provider.apiKeys; + if (!apiKeys || Object.keys(apiKeys).length === 0) return []; + + const activeKeyId = provider.activeApiKeyId; + return Object.entries(apiKeys).map(([keyId, keyInfo]) => ({ + kind: 'key' as const, + id: `${providerId}:${keyId}`, + providerId, + keyId, + label: keyInfo.name, + preview: maskApiKey(keyInfo.key), + isActive: keyId === activeKeyId, + })); +} + +function maskApiKey(key: string): string { + if (key.length <= 12) return '*'.repeat(key.length); + return `${key.slice(0, 8)}...${key.slice(-4)}`; } export class ProviderManagerComponent extends Container implements Focusable { @@ -312,24 +372,63 @@ export class ProviderManagerComponent extends Container implements Focusable { return; } - // Delete the highlighted provider with the D key. const ch = printableChar(data); - if (ch === 'd' || ch === 'D') { - this.armDeleteConfirm(); + const selected = rows[this.selectedIndex]; + + // Key row actions: A=add key, S=set active, D=delete key + if (selected?.kind === 'key') { + if (ch === 'a' || ch === 'A') { + this.opts.onAddKey(selected.providerId); + return; + } + if (ch === 's' || ch === 'S') { + this.opts.onSetActiveKey(selected.providerId, selected.keyId); + return; + } + if (ch === 'd' || ch === 'D') { + this.armDeleteKeyConfirm(selected); + return; + } + } + + // Source row actions: A=add key (if provider supports it), D=delete provider, P=proxy + if (selected?.kind === 'source') { + if ((ch === 'a' || ch === 'A') && selected.providerIds.length === 1) { + // Only allow adding keys to standalone providers (not grouped ones) + const providerId = selected.providerIds[0]; + if (providerId) this.opts.onAddKey(providerId); + return; + } + if ((ch === 'p' || ch === 'P') && selected.providerIds.length === 1) { + // Allow setting proxy URL for standalone providers + const providerId = selected.providerIds[0]; + if (providerId) this.opts.onSetProxyUrl(providerId); + return; + } + if (ch === 'd' || ch === 'D') { + this.armDeleteProviderConfirm(selected); + return; + } } } - private armDeleteConfirm(): void { - const selected = this.rows[this.selectedIndex]; - if (selected === undefined || selected.kind === 'add') return; - const ids = selected.providerIds; + private armDeleteProviderConfirm(selected: SourceRow): void { const prompt = - ids.length === 1 + selected.providerIds.length === 1 ? `Delete platform "${selected.label}"?` - : `Delete platform "${selected.label}" and all ${String(ids.length)} providers?`; + : `Delete platform "${selected.label}" and all ${String(selected.providerIds.length)} providers?`; this.confirm = { label: prompt, - providerIds: ids, + providerIds: selected.providerIds, + }; + this.invalidate(); + } + + private armDeleteKeyConfirm(selected: KeyRow): void { + const prompt = `Delete API key "${selected.label}" from provider "${selected.providerId}"?`; + this.confirm = { + label: prompt, + providerIds: [`${selected.providerId}:${selected.keyId}`], // special format for key deletion }; this.invalidate(); } @@ -346,7 +445,19 @@ export class ProviderManagerComponent extends Container implements Focusable { this.confirm = undefined; this.invalidate(); if (confirm === undefined) return; - this.opts.onDeleteSource(confirm.providerIds); + // Check if it's a key deletion (format: "providerId:keyId") + const firstId = confirm.providerIds[0]; + if (!firstId) return; + if (firstId.includes(':')) { + const parts = firstId.split(':', 2); + const providerId = parts[0]; + const keyId = parts[1]; + if (providerId && keyId) { + this.opts.onRemoveKey(providerId, keyId); + } + } else { + this.opts.onDeleteSource(confirm.providerIds); + } return; } // Any other key while in the confirm substate is ignored. @@ -427,20 +538,38 @@ function renderRow( // The active provider is flagged with a trailing "← current" (success), // matching the model selector's current-item marker — see .agents/skills/write-tui/DESIGN.md. - const isActive = row.kind === 'source' && row.hasActive; - const marker = isActive ? ` ${CURRENT_MARK}` : ''; + const isActiveProvider = row.kind === 'source' && row.hasActive; + const isActiveKey = row.kind === 'key' && row.isActive; + const marker = (isActiveProvider || isActiveKey) ? ` ${CURRENT_MARK}` : ''; // Reserve 2 leading spaces + 2 for the pointer + room for the marker. const labelWidth = Math.max(0, width - 4 - visibleWidth(marker)); const labelText = truncateToWidth(row.label, labelWidth, '…'); let line = ` ${pointerStyle(`${pointer} `)}${labelStyle(labelText)}`; - if (isActive) line += currentTheme.fg('success', marker); + if (isActiveProvider || isActiveKey) line += currentTheme.fg('success', marker); const lines: string[] = [line]; - if (row.kind === 'source' && row.baseUrl !== undefined && row.baseUrl.length > 0) { - const urlText = truncateToWidth(row.baseUrl, Math.max(0, width - 6), '…'); - lines.push(currentTheme.fg('textMuted', ` ${urlText}`)); + if (row.kind === 'source') { + if (row.baseUrl !== undefined && row.baseUrl.length > 0) { + const urlText = truncateToWidth(row.baseUrl, Math.max(0, width - 6), '…'); + lines.push(currentTheme.fg('textMuted', ` ${urlText}`)); + } + // Key rows are rendered as separate entries in the flat rows array, + // so we don't render them again here. + } else if (row.kind === 'key') { + // Render key row with indentation + const keyLabelWidth = Math.max(0, width - 8 - visibleWidth(marker)); + const keyLabelText = truncateToWidth(row.label, keyLabelWidth, '…'); + const keyPointer = isSelected ? SELECT_POINTER : ' '; + const keyPointerStyle = (text: string) => + isSelected ? currentTheme.fg('primary', text) : currentTheme.fg('textDim', text); + const keyLine = ` ${keyPointerStyle(`${keyPointer} `)}${currentTheme.fg('text', keyLabelText)} ${currentTheme.fg('textMuted', row.preview)}`; + if (row.isActive) { + lines.push(currentTheme.fg('success', `${keyLine} ${CURRENT_MARK}`)); + } else { + lines.push(keyLine); + } } return lines; diff --git a/apps/kimi-code/test/tui/components/dialogs/provider-manager.test.ts b/apps/kimi-code/test/tui/components/dialogs/provider-manager.test.ts index 6596cf705b5..04964dfa2dc 100644 --- a/apps/kimi-code/test/tui/components/dialogs/provider-manager.test.ts +++ b/apps/kimi-code/test/tui/components/dialogs/provider-manager.test.ts @@ -26,6 +26,9 @@ function makeComponent(overrides: Partial = {}): Provide providers: {} as Record, onAdd: vi.fn(), onDeleteSource: vi.fn(), + onAddKey: vi.fn(), + onRemoveKey: vi.fn(), + onSetActiveKey: vi.fn(), onClose: vi.fn(), ...overrides, }); diff --git a/apps/kimi-inspect/AGENTS.md b/apps/kimi-inspect/AGENTS.md index 15782d128da..1ad1408cf76 100644 --- a/apps/kimi-inspect/AGENTS.md +++ b/apps/kimi-inspect/AGENTS.md @@ -1,3 +1,32 @@ +# ⚠️ LANGUAGE RULE — MANDATORY + +**ALL responses MUST be in English ONLY. Never respond in Turkish, Chinese, or any other language, regardless of the user's locale or the presence of non-English content in the codebase. This is a hard rule with no exceptions.** + +--- + +## 📚 Zread Wiki — Check First + +**Before diving into source code, check if a zread wiki exists for this project:** + +```bash +# Check if wiki exists: +cat .zread/wiki/current 2>/dev/null && echo "Wiki exists" || echo "No wiki" + +# If wiki exists, read the pages directly: +ls .zread/wiki/versions/$(cat .zread/wiki/current)/ + +# To regenerate wiki (if stale): +zread generate --stdio +``` + +**Why?** Zread generates comprehensive documentation from code. Reading the wiki is faster than crawling source files manually. + +**Rules:** +1. **ALWAYS** check `.zread/wiki/current` before reading source files +2. If wiki exists, read the markdown pages directly — they're already indexed +3. If wiki is missing or stale, run `zread generate --stdio` to create it +4. Wiki pages live in `.zread/wiki/versions//` — read `wiki.json` for the TOC + # kimi-inspect Agent Guide Web inspector for the kap-server `/api/v1/debug` RPC surface — workspace/session browser, per-session chat, and Service panels (data + trigger buttons) for the Session and Agent scopes. diff --git a/docs/AGENTS.md b/docs/AGENTS.md index c03f4439110..6f95aa2dd7d 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -1,3 +1,9 @@ +# ⚠️ LANGUAGE RULE — MANDATORY + +**ALL responses MUST be in English ONLY. Never respond in Turkish, Chinese, or any other language, regardless of the user's locale or the presence of non-English content in the codebase. This is a hard rule with no exceptions.** + +--- + # Documentation Agent Guide This repository uses VitePress for the documentation site. Most user-facing pages under `docs/en/` and `docs/zh/` are fully written; New or updated content should keep both locales in sync. @@ -255,6 +261,52 @@ Diagram (optional) ::: warning Banner (deprecation, breaking change, security notice — after opening content, before first ##) + + +## 🔍 Code Search — MANDATORY FIRST STEP + +**STOP. Before using `grep`, `find`, `rg`, `ripgrep`, or ANY shell-based search, you MUST use semantic search first.** + +```bash +# THIS is how you search code — ALWAYS FIRST: +mcp__claude_context__search_code(query="what you're looking for", path="/absolute/path/to/repo") +``` + +**Why?** Semantic search understands code relationships, finds implementations by meaning (not just text), and catches things grep misses entirely. + +**Rules:** +1. **ALWAYS** start with `mcp__claude_context__search_code` for code discovery +2. **ONLY** fall back to `grep`/`find`/`rg` when: + - You need an EXACT literal string match (e.g., a specific error message) + - The semantic search index is unavailable/broken + - You're searching for file names, not code content +3. **NEVER** use grep as your first code search tool — it's slower and less accurate + +**Indexing:** If search fails with "not indexed", run `mcp__claude_context__index_codebase(path="/absolute/path")` first, then retry. + +## 📚 Zread Wiki — Check First + +**Before diving into source code, check if a zread wiki exists for this project:** + +```bash +# Check if wiki exists: +cat .zread/wiki/current 2>/dev/null && echo "Wiki exists" || echo "No wiki" + +# If wiki exists, read the pages directly: +ls .zread/wiki/versions/$(cat .zread/wiki/current)/ + +# To regenerate wiki (if stale): +zread generate --stdio +``` + +**Why?** Zread generates comprehensive documentation from code. Reading the wiki is faster than crawling source files manually. + +**Rules:** +1. **ALWAYS** check `.zread/wiki/current` before reading source files +2. If wiki exists, read the markdown pages directly — they're already indexed +3. If wiki is missing or stale, run `zread generate --stdio` to create it +4. Wiki pages live in `.zread/wiki/versions//` — read `wiki.json` for the TOC + ## First section Body… diff --git a/package.json b/package.json index 5cbcad953de..33f302e2eac 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,7 @@ "typescript": "6.0.2", "vitest": "4.1.4" }, - "simple-git-hooks": { - }, + "simple-git-hooks": {}, "lint-staged": { "*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}": [ "oxlint --fix --quiet", @@ -62,5 +61,9 @@ "engines": { "node": ">=24.15.0" }, - "packageManager": "pnpm@10.33.0" + "packageManager": "pnpm@10.33.0", + "dependencies": { + "http-proxy-agent": "^9.1.0", + "https-proxy-agent": "^9.1.0" + } } diff --git a/packages/agent-core-v2/AGENTS.md b/packages/agent-core-v2/AGENTS.md index e9cd9b80925..808235cc41e 100644 --- a/packages/agent-core-v2/AGENTS.md +++ b/packages/agent-core-v2/AGENTS.md @@ -1,3 +1,32 @@ +# ⚠️ LANGUAGE RULE — MANDATORY + +**ALL responses MUST be in English ONLY. Never respond in Turkish, Chinese, or any other language, regardless of the user's locale or the presence of non-English content in the codebase. This is a hard rule with no exceptions.** + +--- + +## 📚 Zread Wiki — Check First + +**Before diving into source code, check if a zread wiki exists for this project:** + +```bash +# Check if wiki exists: +cat .zread/wiki/current 2>/dev/null && echo "Wiki exists" || echo "No wiki" + +# If wiki exists, read the pages directly: +ls .zread/wiki/versions/$(cat .zread/wiki/current)/ + +# To regenerate wiki (if stale): +zread generate --stdio +``` + +**Why?** Zread generates comprehensive documentation from code. Reading the wiki is faster than crawling source files manually. + +**Rules:** +1. **ALWAYS** check `.zread/wiki/current` before reading source files +2. If wiki exists, read the markdown pages directly — they're already indexed +3. If wiki is missing or stale, run `zread generate --stdio` to create it +4. Wiki pages live in `.zread/wiki/versions//` — read `wiki.json` for the TOC + # agent-core-v2 Agent Guide > New agent engine built on the DI Scope architecture — work-in-progress port of `packages/agent-core`. Design: `plan/PLAN.md`. Porting status: `GAP_ANALYSIS.md`. diff --git a/packages/agent-core-v2/src/agent/goal/goalService.ts b/packages/agent-core-v2/src/agent/goal/goalService.ts index 3d336f1c654..2600292114a 100644 --- a/packages/agent-core-v2/src/agent/goal/goalService.ts +++ b/packages/agent-core-v2/src/agent/goal/goalService.ts @@ -39,6 +39,11 @@ import type { GoalBudgetProperties } from '#/app/telemetry/events'; import { ITelemetryService } from '#/app/telemetry/telemetry'; import { IConfigService } from '#/app/config/config'; import { IFlagService } from '#/app/flag/flag'; +import { IAgentProfileService } from '#/agent/profile/profile'; +import { + resolveSubstituteModelAlias, + resolveSubstituteCooldownMs, +} from '#/session/substitute/configSection'; import { ErrorCodes, Error2, @@ -253,10 +258,24 @@ export const goalResumeContinuationKey = defineState undefined as ResumeContinuation | undefined, ); +export const goalSubstituteModelActiveKey = defineState( + 'goal.substituteModelActive', + () => false, +); +export const goalSubstituteModelOriginalKey = defineState( + 'goal.substituteModelOriginal', + () => undefined as string | undefined, +); +export const goalSubstituteModelActivatedAtKey = defineState( + 'goal.substituteModelActivatedAt', + () => undefined as number | undefined, +); + export class AgentGoalService extends Disposable implements IAgentGoalService { declare readonly _serviceBrand: undefined; private readonly wallClockDeadline = this._register(new MutableDisposable()); + private readonly substituteRecoveryTimer = this._register(new MutableDisposable()); private pendingContinuation?: PendingContinuation; constructor( @@ -274,6 +293,7 @@ export class AgentGoalService extends Disposable implements IAgentGoalService { @IAgentUsageService usageService: IAgentUsageService, @IConfigService private readonly config: IConfigService, @IFlagService private readonly flags: IFlagService, + @IAgentProfileService private readonly profile: IAgentProfileService, @IGoalDeadlineScheduler private readonly deadlineScheduler: IGoalDeadlineScheduler, @IAgentScopeContext private readonly agentContext: IAgentScopeContext, @IAgentStateService private readonly states: IAgentStateService, @@ -293,6 +313,9 @@ export class AgentGoalService extends Disposable implements IAgentGoalService { this.states.contributeState(goalExhaustedTurnBudgetGoalsKey); this.states.contributeState(goalLiveWallClockStartedAtKey); this.states.contributeState(goalResumeContinuationKey); + this.states.contributeState(goalSubstituteModelActiveKey); + this.states.contributeState(goalSubstituteModelOriginalKey); + this.states.contributeState(goalSubstituteModelActivatedAtKey); if (!this.isSupportedAgent) return; this._register( new GoalInjection( @@ -450,6 +473,30 @@ export class AgentGoalService extends Disposable implements IAgentGoalService { this.states.set(goalResumeContinuationKey, value); } + private get substituteModelActive(): boolean { + return this.states.get(goalSubstituteModelActiveKey); + } + + private set substituteModelActive(value: boolean) { + this.states.set(goalSubstituteModelActiveKey, value); + } + + private get substituteModelOriginal(): string | undefined { + return this.states.get(goalSubstituteModelOriginalKey); + } + + private set substituteModelOriginal(value: string | undefined) { + this.states.set(goalSubstituteModelOriginalKey, value); + } + + private get substituteModelActivatedAt(): number | undefined { + return this.states.get(goalSubstituteModelActivatedAtKey); + } + + private set substituteModelActivatedAt(value: number | undefined) { + this.states.set(goalSubstituteModelActivatedAtKey, value); + } + private get isSupportedAgent(): boolean { return this.agentContext.agentId === 'main'; } @@ -883,12 +930,85 @@ export class AgentGoalService extends Disposable implements IAgentGoalService { return true; } if (result.reason === 'failed') { + if (isRateLimitError(result.error) && this.canSwitchToSubstituteModel()) { + await this.switchToSubstituteModel(goalId); + return false; + } await this.pauseActiveGoal({ reason: goalFailurePauseReason(result.error) }); return true; } return false; } + private canSwitchToSubstituteModel(): boolean { + if (this.substituteModelActive) return false; + const substituteAlias = resolveSubstituteModelAlias(this.config, this.flags); + return substituteAlias !== undefined; + } + + private async switchToSubstituteModel(goalId: string): Promise { + const substituteAlias = resolveSubstituteModelAlias(this.config, this.flags); + if (substituteAlias === undefined) return; + const originalModel = this.profile.getModel(); + this.substituteModelOriginal = originalModel; + this.substituteModelActive = true; + this.substituteModelActivatedAt = Date.now(); + try { + await this.profile.setModel(substituteAlias); + } catch { + this.substituteModelActive = false; + this.substituteModelOriginal = undefined; + this.substituteModelActivatedAt = undefined; + await this.pauseActiveGoal({ + reason: `Paused after provider rate limit (substitute model "${substituteAlias}" could not be applied)`, + }); + return; + } + const cooldownMs = resolveSubstituteCooldownMs(this.config, this.flags); + this.reminders.appendSystemReminder( + `Primary model hit a rate limit. Switched to substitute model "${substituteAlias}" for ~${Math.round(cooldownMs / 60_000)} minutes until the primary recovers.`, + { kind: 'injection', variant: 'substitute_model_activated' }, + ); + this.telemetry.track2('substitute_model_activated', { + original_model: originalModel, + substitute_model: substituteAlias, + }); + this.scheduleSubstituteRecovery(goalId, cooldownMs); + } + + private scheduleSubstituteRecovery(goalId: string, cooldownMs: number): void { + this.substituteRecoveryTimer.value = this.deadlineScheduler.schedule(cooldownMs, () => { + void this.switchBackToOriginalModel(goalId); + }); + } + + private async switchBackToOriginalModel(goalId: string): Promise { + if (!this.substituteModelActive) return; + if (!this.isActiveGoal(goalId)) { + this.clearSubstituteState(); + return; + } + const originalModel = this.substituteModelOriginal; + this.clearSubstituteState(); + if (originalModel === undefined) return; + try { + await this.profile.setModel(originalModel); + this.reminders.appendSystemReminder( + `Primary model recovered. Switched back from substitute model to "${originalModel}".`, + { kind: 'injection', variant: 'substitute_model_deactivated' }, + ); + this.telemetry.track2('substitute_model_deactivated', { + original_model: originalModel, + }); + } catch {} + } + + private clearSubstituteState(): void { + this.substituteModelActive = false; + this.substituteModelOriginal = undefined; + this.substituteModelActivatedAt = undefined; + } + private async settleGoalAfterContinuationFailure( error: unknown, goalId: string | undefined, @@ -990,6 +1110,8 @@ export class AgentGoalService extends Disposable implements IAgentGoalService { this.appendForkClearedReminder(); this.wallClockDeadline.clear(); this.liveWallClockStartedAt = undefined; + this.substituteRecoveryTimer.clear(); + this.clearSubstituteState(); const state = this.goalState; if (state === null) return; if (state.status === 'complete') { @@ -1027,6 +1149,10 @@ export class AgentGoalService extends Disposable implements IAgentGoalService { this.cancelPendingContinuation(opts.preserveLiveContinuation === true); this.wallClockDeadline.clear(); this.liveWallClockStartedAt = undefined; + this.substituteRecoveryTimer.clear(); + if (this.substituteModelActive) { + void this.switchBackToOriginalModel(this.goalState.goalId); + } void this.dispatcher.dispatch(new GoalClear({})); if (opts.emit !== false) this.emitGoalUpdated(null); if (opts.track !== false) this.telemetry.track2('goal_cleared', { actor }); @@ -1278,6 +1404,11 @@ function isMaxStepsTurnFailure(result: Pick): boo ); } +function isRateLimitError(error: unknown): boolean { + const payload = normalizeGoalErrorPayload(error); + return payload.code === ErrorCodes.PROVIDER_RATE_LIMIT; +} + function goalFailurePauseReason(error: unknown): string { const payload = normalizeGoalErrorPayload(error); switch (payload.code) { diff --git a/packages/agent-core-v2/src/app/kosongConfig/configSection.ts b/packages/agent-core-v2/src/app/kosongConfig/configSection.ts index 6c8d8f739bf..3efcaa6178f 100644 --- a/packages/agent-core-v2/src/app/kosongConfig/configSection.ts +++ b/packages/agent-core-v2/src/app/kosongConfig/configSection.ts @@ -43,11 +43,14 @@ export const ProviderConfigSchema = z.object({ modelSource: ModelSourceSchema.optional(), baseUrl: z.string().optional(), + proxyUrl: z.string().optional(), customHeaders: StringRecordSchema.optional(), defaultModel: z.string().optional(), type: ProviderTypeSchema.optional(), apiKey: z.string().optional(), + apiKeys: z.record(z.string(), z.object({ key: z.string(), name: z.string() })).optional(), + activeApiKeyId: z.string().optional(), oauth: OAuthRefSchema.optional(), env: StringRecordSchema.optional(), source: z.record(z.string(), z.unknown()).optional(), diff --git a/packages/agent-core-v2/src/app/telemetry/events.ts b/packages/agent-core-v2/src/app/telemetry/events.ts index 6d608bb92d3..f917301c2e8 100644 --- a/packages/agent-core-v2/src/app/telemetry/events.ts +++ b/packages/agent-core-v2/src/app/telemetry/events.ts @@ -296,6 +296,15 @@ export interface GoalStatusChangedEvent extends GoalBudgetProperties { wall_clock_ms: number; } +export interface SubstituteModelActivatedEvent { + original_model: string; + substitute_model: string; +} + +export interface SubstituteModelDeactivatedEvent { + original_model: string; +} + export interface ToolCallDedupDetectedEvent { turn_id?: number; step_no: number; @@ -777,6 +786,21 @@ export const telemetryEventDefinitions = { has_wall_clock_budget: 'Whether a wall-clock budget was set', }, }), + substitute_model_activated: defineAgentTelemetryEvent({ + owner: 'kimi-code', + comment: 'Substitute model activated after primary hit rate limit.', + properties: { + original_model: 'The primary model that was rate-limited', + substitute_model: 'The substitute model that was activated', + }, + }), + substitute_model_deactivated: defineAgentTelemetryEvent({ + owner: 'kimi-code', + comment: 'Substitute model deactivated, primary model recovered.', + properties: { + original_model: 'The primary model that was restored', + }, + }), tool_call_dedup_detected: defineAgentTelemetryEvent({ owner: 'kimi-code', comment: 'A duplicate tool call is detected.', diff --git a/packages/agent-core-v2/src/index.ts b/packages/agent-core-v2/src/index.ts index 2c412673b54..aaa02b73cbb 100644 --- a/packages/agent-core-v2/src/index.ts +++ b/packages/agent-core-v2/src/index.ts @@ -474,6 +474,8 @@ export * from '#/session/subagent/mirrorAgentRun'; import '#/session/subagent/configSection'; import '#/session/visual/flag'; import '#/session/visual/configSection'; +import '#/session/substitute/flag'; +import '#/session/substitute/configSection'; export { VISUAL_MODEL_FLAG_ID, VISUAL_MODEL_FLAG_ENV, @@ -489,6 +491,22 @@ export { VISUAL_MODEL_CHOICE_SCHEMA, type VisualModelChoice, } from '#/session/visual/configSection'; +export { + SUBSTITUTE_MODEL_FLAG_ID, + SUBSTITUTE_MODEL_FLAG_ENV, + substituteModelFlag, +} from '#/session/substitute/flag'; +export { + resolveSubstituteModel, + resolveSubstituteModelAlias, + resolveSubstituteCooldownMs, + SUBSTITUTE_MODEL_SECTION, + SUBSTITUTE_MODEL_ENV, + SUBSTITUTE_MODEL_COOLDOWN_ENV, + SubstituteModelConfigSchema, + substituteModelEnvBindings, + type SubstituteModelConfig, +} from '#/session/substitute/configSection'; export * from '#/agent/tools/agent/agent'; import '#/agent/tools/agent/agentTool'; export * from '#/app/sessionManager/sessionLookup'; diff --git a/packages/agent-core-v2/src/kosong/model/catalog.ts b/packages/agent-core-v2/src/kosong/model/catalog.ts index 1ebf394b93c..6d702f3a3f5 100644 --- a/packages/agent-core-v2/src/kosong/model/catalog.ts +++ b/packages/agent-core-v2/src/kosong/model/catalog.ts @@ -35,6 +35,7 @@ export interface Model { readonly aliases: readonly string[]; readonly protocol: Protocol; readonly baseUrl?: string; + readonly proxyUrl?: string; readonly headers: Readonly>; readonly capabilities: ModelCapability; diff --git a/packages/agent-core-v2/src/kosong/model/catalogService.ts b/packages/agent-core-v2/src/kosong/model/catalogService.ts index eec2673161c..713d2856c0f 100644 --- a/packages/agent-core-v2/src/kosong/model/catalogService.ts +++ b/packages/agent-core-v2/src/kosong/model/catalogService.ts @@ -350,6 +350,7 @@ export class ModelCatalog extends Disposable implements IModelCatalog { aliases: model.aliases ?? [], protocol, baseUrl: resolvedBaseUrl, + proxyUrl: providerConfig?.proxyUrl, headers: resolveOutboundHeaders( providerConfig?.type, providerConfig?.customHeaders, diff --git a/packages/agent-core-v2/src/kosong/model/modelAuth.ts b/packages/agent-core-v2/src/kosong/model/modelAuth.ts index b53d9a0213a..23c981683cd 100644 --- a/packages/agent-core-v2/src/kosong/model/modelAuth.ts +++ b/packages/agent-core-v2/src/kosong/model/modelAuth.ts @@ -14,6 +14,17 @@ import type { ModelRecord } from './model'; import type { ResolvedModelAuthMaterial } from './model.types'; import { drivesThinkingThroughTraits } from './thinking'; +function getActiveProviderApiKey(provider: ProviderConfig | undefined): string | undefined { + if (!provider) return undefined; + // 1. Named keys with active selection + if (provider.apiKeys && provider.activeApiKeyId) { + const active = provider.apiKeys[provider.activeApiKeyId]; + if (active) return active.key; + } + // 2. Legacy single key + return provider.apiKey; +} + export function resolveModelAuthMaterial( args: { readonly modelId: string; @@ -44,15 +55,15 @@ export function resolveModelAuthMaterial( providerAuthType === undefined ? {} : explainProviderEndpoint(providerAuthType, args.provider?.env ?? {}); - const providerApiKey = nonEmpty(args.provider?.apiKey) ?? nonEmpty(providerEndpoint.apiKey); + const providerApiKey = getActiveProviderApiKey(args.provider) ?? nonEmpty(providerEndpoint.apiKey); if (providerApiKey !== undefined && args.provider?.oauth !== undefined) { throw authConflictError('Provider', args.providerName); } if (providerApiKey !== undefined) { trace?.record( 'resolved.auth', - nonEmpty(args.provider?.apiKey) !== undefined - ? { kind: 'config', detail: `provider '${args.providerName}' apiKey` } + getActiveProviderApiKey(args.provider) !== undefined + ? { kind: 'config', detail: `provider '${args.providerName}' apiKey (active: ${args.provider?.activeApiKeyId ?? 'legacy'})` } : { kind: 'env', detail: `${providerEndpoint.apiKeyEnvName ?? '?'} (provider '${args.providerName}' env bag)`, diff --git a/packages/agent-core-v2/src/kosong/model/modelRequesterImpl.ts b/packages/agent-core-v2/src/kosong/model/modelRequesterImpl.ts index 2cc41870ed0..1bfb378ad38 100644 --- a/packages/agent-core-v2/src/kosong/model/modelRequesterImpl.ts +++ b/packages/agent-core-v2/src/kosong/model/modelRequesterImpl.ts @@ -36,6 +36,7 @@ export class ModelRequesterImpl implements ModelRequester { protocol: model.protocol, providerType: model.providerType, baseUrl: model.baseUrl, + proxyUrl: model.proxyUrl, modelName: model.name, defaultHeaders: model.headers, providerOptions: model.providerOptions, diff --git a/packages/agent-core-v2/src/kosong/protocol/protocol.ts b/packages/agent-core-v2/src/kosong/protocol/protocol.ts index b0c38f90ac8..3b345478ca5 100644 --- a/packages/agent-core-v2/src/kosong/protocol/protocol.ts +++ b/packages/agent-core-v2/src/kosong/protocol/protocol.ts @@ -27,12 +27,14 @@ export interface ProtocolProviderOptions { readonly vertexai?: boolean; readonly project?: string; readonly location?: string; + readonly proxyUrl?: string; } export interface ProtocolAdapterConfig { readonly protocol: Protocol; readonly providerType?: string; readonly baseUrl?: string; + readonly proxyUrl?: string; readonly modelName: string; readonly apiKey?: string; readonly defaultHeaders?: Readonly>; diff --git a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.contrib.ts b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.contrib.ts index d89429a8ebd..011484c7acf 100644 --- a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.contrib.ts +++ b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.contrib.ts @@ -27,6 +27,7 @@ registerProtocolBase({ (endpoint === undefined ? undefined : ''), baseUrl: config.baseUrl ?? firstProcessEnv(endpoint?.baseUrlEnv) ?? endpoint?.defaultBaseUrl, + proxyUrl: config.proxyUrl, defaultHeaders: traitDefaultHeaders(traits), maxTokens: config.providerOptions?.defaultMaxTokens, reasoningKey: config.providerOptions?.reasoningKey, diff --git a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.ts b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.ts index f2b36111cab..915d0eb993c 100644 --- a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.ts +++ b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-legacy.ts @@ -1,3 +1,5 @@ +import { ProxyAgent } from 'undici'; +import type { Dispatcher } from 'undici'; import OpenAI from 'openai'; import { parseTraceId, type ChatProviderError } from '#/kosong/contract/errors'; @@ -92,6 +94,7 @@ export interface OpenAIChatCompletionsHooks { export interface OpenAILegacyOptions { apiKey?: string | undefined; baseUrl?: string | undefined; + proxyUrl?: string | undefined; model: string; stream?: boolean | undefined; maxTokens?: number | undefined; @@ -491,6 +494,7 @@ export class OpenAILegacyChatProvider implements ChatProvider { private readonly _stream: boolean; private readonly _apiKey: string | undefined; private readonly _baseUrl: string | undefined; + private readonly _proxyUrl: string | undefined; private readonly _defaultHeaders: Record | undefined; private readonly _reasoningKeyDialect: ReasoningKeyDialect; private readonly _offEffort: string | undefined; @@ -502,6 +506,8 @@ export class OpenAILegacyChatProvider implements ChatProvider { private readonly _clientFactory: ((auth: ProviderRequestAuth) => OpenAI) | undefined; private readonly _hooks: OpenAIChatCompletionsHooks | undefined; + private _proxyDispatcher: Dispatcher | undefined; + readonly uploadVideo?: ( input: string | VideoUploadInput, options?: GenerateOptions, @@ -511,6 +517,7 @@ export class OpenAILegacyChatProvider implements ChatProvider { const apiKey = options.apiKey ?? process.env['OPENAI_API_KEY']; this._apiKey = apiKey === undefined || apiKey.length === 0 ? undefined : apiKey; this._baseUrl = options.baseUrl ?? 'https://api.openai.com/v1'; + this._proxyUrl = options.proxyUrl; this._defaultHeaders = options.defaultHeaders; this._model = options.model; this._stream = options.stream ?? true; @@ -531,6 +538,15 @@ export class OpenAILegacyChatProvider implements ChatProvider { this._httpClient = options.httpClient; this._clientFactory = options.clientFactory; + // Create proxy dispatcher if proxy URL is configured + if (this._proxyUrl !== undefined && this._proxyUrl.length > 0) { + try { + this._proxyDispatcher = new ProxyAgent(this._proxyUrl); + } catch (err) { + console.error('[OpenAILegacyChatProvider] Proxy agent error:', err); + } + } + this._client = this._apiKey === undefined ? undefined : this._buildClient(this._apiKey); const uploadVideo = this._hooks?.uploadVideo; @@ -620,24 +636,56 @@ export class OpenAILegacyChatProvider implements ChatProvider { const finalParams = builtParams ?? createParams; try { - const client = this._createClient(options?.auth); options?.onRequestSent?.(); - const { data, response } = await client.chat.completions - .create( - finalParams as unknown as OpenAI.Chat.ChatCompletionCreateParamsNonStreaming, - options?.signal ? { signal: options.signal } : undefined, - ) - .withResponse(); - return new OpenAILegacyStreamedMessage( - data as unknown as - | OpenAI.Chat.ChatCompletion - | AsyncIterable, - this._stream, - this._reasoningKeyDialect, - parseTraceId(response.headers), - this._hooks?.extractUsage, - this._hooks?.convertError, - ); + + // Use fetch with proxy dispatcher instead of OpenAI SDK + const apiKey = this._apiKey ?? (options?.auth?.apiKey); + if (!apiKey) { + throw new Error('API key is required'); + } + + const response = await this._fetchWithProxy('/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(finalParams), + signal: options?.signal, + }, options?.auth); + + const traceId = response.headers.get('x-request-id') ?? null; + + if (!response.ok) { + const errorData = await response.json(); + throw convertOpenAIError({ + status: response.status, + message: JSON.stringify(errorData), + }, this._hooks?.convertError); + } + + if (this._stream) { + // Parse SSE stream for streaming responses + const streamIterable = this._parseSSEStream(response); + return new OpenAILegacyStreamedMessage( + streamIterable, + this._stream, + this._reasoningKeyDialect, + traceId, + this._hooks?.extractUsage, + this._hooks?.convertError, + ); + } else { + // Parse JSON for non-streaming responses + const data = await response.json(); + return new OpenAILegacyStreamedMessage( + data as unknown as OpenAI.Chat.ChatCompletion, + this._stream, + this._reasoningKeyDialect, + traceId, + this._hooks?.extractUsage, + this._hooks?.convertError, + ); + } } catch (error: unknown) { throw convertOpenAIError(error, this._hooks?.convertError); } @@ -747,6 +795,92 @@ export class OpenAILegacyChatProvider implements ChatProvider { } return new OpenAI(clientOpts as ConstructorParameters[0]); } + + private async _fetchWithProxy( + path: string, + options: RequestInit, + auth?: ProviderRequestAuth + ): Promise { + const apiKey = auth?.apiKey ?? this._apiKey; + if (!apiKey) { + throw new Error('API key is required'); + } + + const headers = new Headers(options.headers); + headers.set('Authorization', `Bearer ${apiKey}`); + headers.set('Content-Type', 'application/json'); + + if (this._defaultHeaders) { + for (const [key, value] of Object.entries(this._defaultHeaders)) { + headers.set(key, value); + } + if (auth?.headers) { + for (const [key, value] of Object.entries(auth.headers)) { + headers.set(key, value); + } + } + } + + return fetch(`${this._baseUrl}${path}`, { + ...options, + headers, + dispatcher: this._proxyDispatcher as any, + }); + } + + private async *_parseSSEStream(response: Response): AsyncGenerator { + const reader = response.body?.getReader(); + if (!reader) { + throw new Error('Response body is null'); + } + + const decoder = new TextDecoder(); + let buffer = ''; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; + + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.startsWith('data: ')) { + const data = trimmed.slice(6); // Remove 'data: ' prefix + if (data === '[DONE]') { + return; + } + try { + const chunk = JSON.parse(data) as OpenAI.Chat.ChatCompletionChunk; + yield chunk; + } catch (e) { + // Ignore parse errors for malformed chunks + console.warn('[OpenAILegacyChatProvider] Failed to parse SSE chunk:', data); + } + } + } + } + + // Process any remaining buffer + if (buffer.trim().startsWith('data: ')) { + const data = buffer.trim().slice(6); + if (data !== '[DONE]') { + try { + const chunk = JSON.parse(data) as OpenAI.Chat.ChatCompletionChunk; + yield chunk; + } catch (e) { + console.warn('[OpenAILegacyChatProvider] Failed to parse final SSE chunk:', data); + } + } + } + } finally { + reader.releaseLock(); + } + } } export function getOpenAILegacyModelCapability(modelName: string) { diff --git a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.contrib.ts b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.contrib.ts index 6a12e180290..e17dcdf71e3 100644 --- a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.contrib.ts +++ b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.contrib.ts @@ -21,6 +21,7 @@ registerProtocolBase({ (endpoint === undefined ? undefined : ''), baseUrl: config.baseUrl ?? firstProcessEnv(endpoint?.baseUrlEnv) ?? endpoint?.defaultBaseUrl, + proxyUrl: config.proxyUrl, defaultHeaders: traitDefaultHeaders(traits), maxOutputTokens: config.providerOptions?.defaultMaxTokens, offEffort: config.providerOptions?.offEffort, diff --git a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.ts b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.ts index 19808bc2f1e..97524a61849 100644 --- a/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.ts +++ b/packages/agent-core-v2/src/kosong/provider/bases/openai/openai-responses.ts @@ -1,4 +1,6 @@ import OpenAI from 'openai'; +import { ProxyAgent } from 'undici'; +import type { Dispatcher } from 'undici'; import { Error2 } from '#/_base/errors/errors'; import { @@ -42,11 +44,6 @@ import { TOOL_RESULT_MEDIA_PROMPT, type ToolMessageConversion, } from './openai-common'; -import { - mergeRequestHeaders, - requireProviderApiKey, - resolveAuthBackedClient, -} from '../request-auth'; import { normalizeToolCallIdsForProvider, sanitizeOpenAIResponsesCallId } from '../tool-call-id'; function normalizeResponsesFinishReason( @@ -357,6 +354,7 @@ function formatResponsesFailedResponse(response: RawObject): string { export interface OpenAIResponsesOptions { apiKey?: string | undefined; baseUrl?: string | undefined; + proxyUrl?: string | undefined; model: string; maxOutputTokens?: number | undefined; offEffort?: string | undefined; @@ -1017,6 +1015,7 @@ export class OpenAIResponsesChatProvider implements ChatProvider { private readonly _stream: boolean; private readonly _apiKey: string | undefined; private readonly _baseUrl: string | undefined; + private readonly _proxyUrl: string | undefined; private readonly _defaultHeaders: Record | undefined; private readonly _thinkingEffort: ThinkingEffort | undefined; private readonly _offEffort: string | undefined; @@ -1027,10 +1026,13 @@ export class OpenAIResponsesChatProvider implements ChatProvider { private readonly _clientFactory: ((auth: ProviderRequestAuth) => OpenAI) | undefined; private readonly _convertErrorHook: ((error: unknown) => ChatProviderError | undefined) | undefined; + private _proxyDispatcher: Dispatcher | undefined; + constructor(options: OpenAIResponsesOptions) { const apiKey = options.apiKey ?? process.env['OPENAI_API_KEY']; this._apiKey = apiKey === undefined || apiKey.length === 0 ? undefined : apiKey; this._baseUrl = options.baseUrl ?? 'https://api.openai.com/v1'; + this._proxyUrl = options.proxyUrl; this._defaultHeaders = options.defaultHeaders; this._model = options.model; this._stream = true; @@ -1042,11 +1044,18 @@ export class OpenAIResponsesChatProvider implements ChatProvider { this._clientFactory = options.clientFactory; this._convertErrorHook = options.convertError; + // Create proxy dispatcher if proxy URL is configured + if (this._proxyUrl !== undefined && this._proxyUrl.length > 0) { + try { + this._proxyDispatcher = new ProxyAgent(this._proxyUrl); + } catch (err) { + console.error('[OpenAIResponsesChatProvider] Proxy agent error:', err); + } + } + if (options.maxOutputTokens !== undefined) { this._generationKwargs.max_output_tokens = options.maxOutputTokens; } - - this._client = this._apiKey === undefined ? undefined : this._buildClient(this._apiKey); } get modelName(): string { @@ -1131,71 +1140,147 @@ export class OpenAIResponsesChatProvider implements ChatProvider { } } - try { - const client = this._createClient(options?.auth); - const createParams: Record = { - model: this._model, - input, - tools: tools.map((t) => convertTool(t)), - store: false, - stream: this._stream, - ...kwargs, + const createParams: Record = { + model: this._model, + input, + tools: tools.map((t) => convertTool(t)), + store: false, + stream: this._stream, + ...kwargs, + }; + if (systemPrompt) { + createParams['instructions'] = systemPrompt; + } + if (options?.responseFormat !== undefined) { + createParams['text'] = { + ...asRawObject(createParams['text']), + ...responseFormatToResponsesText(options.responseFormat), }; - if (systemPrompt) { - createParams['instructions'] = systemPrompt; - } - if (options?.responseFormat !== undefined) { - createParams['text'] = { - ...asRawObject(createParams['text']), - ...responseFormatToResponsesText(options.responseFormat), - }; + } + + try { + options?.onRequestSent?.(); + + const apiKey = this._apiKey ?? (options?.auth?.apiKey); + if (!apiKey) { + throw new Error('API key is required'); } - if ( - !('responses' in client) || - typeof (client as { responses?: { create?: unknown } }).responses?.create !== 'function' - ) { - throw new Error2( - ProtocolErrors.codes.PROVIDER_API_ERROR, - 'OpenAI SDK version does not support Responses API. Upgrade to >=4.x with responses support.', - ); + const response = await this._fetchWithProxy('/responses', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(createParams), + signal: options?.signal, + }, options?.auth); + + if (!response.ok) { + const errorData = await response.json(); + throw convertOpenAIError({ + status: response.status, + message: JSON.stringify(errorData), + }, this._convertErrorHook); } - options?.onRequestSent?.(); - const response = await ( - client.responses as { - create(params: unknown, opts?: unknown): Promise; - } - ).create(createParams, options?.signal ? { signal: options.signal } : undefined); - return new OpenAIResponsesStreamedMessage(response, this._stream, this._convertErrorHook); + if (this._stream) { + // Parse SSE stream for streaming responses + const streamIterable = this._parseSSEStream(response); + return new OpenAIResponsesStreamedMessage(streamIterable, this._stream, this._convertErrorHook); + } else { + // Parse JSON for non-streaming responses + const data = await response.json(); + return new OpenAIResponsesStreamedMessage(data, this._stream, this._convertErrorHook); + } } catch (error: unknown) { throw convertOpenAIError(error, this._convertErrorHook); } } - private _createClient(auth: ProviderRequestAuth | undefined): OpenAI { - return resolveAuthBackedClient( - { cachedClient: this._client, clientFactory: this._clientFactory }, - auth, - (a) => - this._buildClient(requireProviderApiKey('OpenAIResponsesChatProvider', a, this._apiKey), a), - ); + private async _fetchWithProxy( + path: string, + options: RequestInit, + auth?: ProviderRequestAuth + ): Promise { + const apiKey = auth?.apiKey ?? this._apiKey; + if (!apiKey) { + throw new Error('API key is required'); + } + + const headers = new Headers(options.headers); + headers.set('Authorization', `Bearer ${apiKey}`); + headers.set('Content-Type', 'application/json'); + + if (this._defaultHeaders) { + for (const [key, value] of Object.entries(this._defaultHeaders)) { + headers.set(key, value); + } + if (auth?.headers) { + for (const [key, value] of Object.entries(auth.headers)) { + headers.set(key, value); + } + } + } + + return fetch(`${this._baseUrl}${path}`, { + ...options, + headers, + dispatcher: this._proxyDispatcher as any, + }); } - private _buildClient(apiKey: string, auth?: ProviderRequestAuth): OpenAI { - const clientOpts: Record = { - apiKey, - baseURL: this._baseUrl, - maxRetries: 0, - }; - const defaultHeaders = mergeRequestHeaders(this._defaultHeaders, auth?.headers); - if (defaultHeaders !== undefined) { - clientOpts['defaultHeaders'] = defaultHeaders; + private async *_parseSSEStream(response: Response): AsyncGenerator { + const reader = response.body?.getReader(); + if (!reader) { + throw new Error('Response body is null'); } - if (this._httpClient !== undefined) { - clientOpts['httpClient'] = this._httpClient; + + const decoder = new TextDecoder(); + let buffer = ''; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; + + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.startsWith('data: ')) { + const data = trimmed.slice(6); // Remove 'data: ' prefix + if (data === '[DONE]') { + return; + } + try { + const chunk = JSON.parse(data) as RawObject; + yield chunk; + } catch (e) { + // Ignore parse errors for malformed chunks + console.warn('[OpenAIResponsesChatProvider] Failed to parse SSE chunk:', data); + } + } + } + } + + // Process any remaining buffer + if (buffer.trim().startsWith('data: ')) { + const data = buffer.trim().slice(6); + if (data !== '[DONE]') { + try { + const chunk = JSON.parse(data) as RawObject; + yield chunk; + } catch (e) { + console.warn('[OpenAIResponsesChatProvider] Failed to parse final SSE chunk:', data); + } + } + } + } finally { + reader.releaseLock(); } - return new OpenAI(clientOpts as ConstructorParameters[0]); } } diff --git a/packages/agent-core-v2/src/kosong/provider/provider.ts b/packages/agent-core-v2/src/kosong/provider/provider.ts index e6436506d2a..2b1735b1cca 100644 --- a/packages/agent-core-v2/src/kosong/provider/provider.ts +++ b/packages/agent-core-v2/src/kosong/provider/provider.ts @@ -11,15 +11,23 @@ export interface OAuthRef { export type ModelSource = 'static' | 'discover' | 'oauth-catalog'; +export interface ProviderApiKey { + key: string; + name: string; +} + export interface ProviderConfig { modelSource?: ModelSource; baseUrl?: string; + proxyUrl?: string; customHeaders?: Record; defaultModel?: string; type?: ProviderType; apiKey?: string; + apiKeys?: Record; + activeApiKeyId?: string; oauth?: OAuthRef; env?: Record; source?: Record; diff --git a/packages/agent-core-v2/src/session/substitute/configSection.ts b/packages/agent-core-v2/src/session/substitute/configSection.ts new file mode 100644 index 00000000000..31ab14c86ec --- /dev/null +++ b/packages/agent-core-v2/src/session/substitute/configSection.ts @@ -0,0 +1,75 @@ +import { z } from 'zod'; + +import { + type EnvBindings, + envBindings, + stripEnvBoundFields, + type IConfigService, +} from '#/app/config/config'; +import { registerConfigSection } from '#/app/config/configSectionContributions'; +import type { IFlagService } from '#/app/flag/flag'; + +import { SUBSTITUTE_MODEL_FLAG_ID } from './flag'; + +export const SUBSTITUTE_MODEL_SECTION = 'substituteModel'; + +export const SubstituteModelConfigSchema = z.object({ + defaultModel: z.string().min(1).optional(), + cooldownMs: z.number().int().min(0).optional(), +}); + +export type SubstituteModelConfig = z.infer; + +export const SUBSTITUTE_MODEL_ENV = 'KIMI_SUBSTITUTE_MODEL'; +export const SUBSTITUTE_MODEL_COOLDOWN_ENV = 'KIMI_SUBSTITUTE_MODEL_COOLDOWN_MS'; + +export const DEFAULT_SUBSTITUTE_MODEL_COOLDOWN_MS = 5 * 60 * 1000; + +function parseModelEnv(raw: string): string | undefined { + const t = raw.trim(); + return t.length > 0 ? t : undefined; +} + +function parseCooldownEnv(raw: string): number | undefined { + const parsed = Number(raw); + return Number.isInteger(parsed) && parsed >= 0 ? parsed : undefined; +} + +export const substituteModelEnvBindings: EnvBindings = envBindings( + SubstituteModelConfigSchema, + { + defaultModel: { env: SUBSTITUTE_MODEL_ENV, parse: parseModelEnv }, + cooldownMs: { env: SUBSTITUTE_MODEL_COOLDOWN_ENV, parse: parseCooldownEnv }, + }, +); + +export const stripSubstituteModelEnv = stripEnvBoundFields(substituteModelEnvBindings); + +registerConfigSection(SUBSTITUTE_MODEL_SECTION, SubstituteModelConfigSchema, { + env: substituteModelEnvBindings, + stripEnv: stripSubstituteModelEnv, +}); + +export function resolveSubstituteModel( + config: IConfigService, + flags: IFlagService, +): SubstituteModelConfig | undefined { + if (!flags.enabled(SUBSTITUTE_MODEL_FLAG_ID)) return undefined; + return config.get(SUBSTITUTE_MODEL_SECTION); +} + +export function resolveSubstituteModelAlias( + config: IConfigService, + flags: IFlagService, +): string | undefined { + return resolveSubstituteModel(config, flags)?.defaultModel; +} + +export function resolveSubstituteCooldownMs( + config: IConfigService, + flags: IFlagService, +): number { + return ( + resolveSubstituteModel(config, flags)?.cooldownMs ?? DEFAULT_SUBSTITUTE_MODEL_COOLDOWN_MS + ); +} diff --git a/packages/agent-core-v2/src/session/substitute/flag.ts b/packages/agent-core-v2/src/session/substitute/flag.ts new file mode 100644 index 00000000000..ee3681a1712 --- /dev/null +++ b/packages/agent-core-v2/src/session/substitute/flag.ts @@ -0,0 +1,16 @@ +import { type FlagDefinitionInput, registerFlagDefinition } from '#/app/flag/flagRegistry'; + +export const SUBSTITUTE_MODEL_FLAG_ID = 'substitute-model'; +export const SUBSTITUTE_MODEL_FLAG_ENV = 'KIMI_CODE_EXPERIMENTAL_SUBSTITUTE_MODEL'; + +export const substituteModelFlag: FlagDefinitionInput = { + id: SUBSTITUTE_MODEL_FLAG_ID, + title: 'Substitute model for rate-limit fallback', + description: + 'When the primary model hits a provider rate limit (e.g. 429 from account quota), automatically switch to a configured substitute model and continue until the primary recovers.', + env: SUBSTITUTE_MODEL_FLAG_ENV, + default: false, + surface: 'core', +}; + +registerFlagDefinition(substituteModelFlag); diff --git a/packages/agent-core-v2/src/session/visual/configSection.ts b/packages/agent-core-v2/src/session/visual/configSection.ts index 1a86d0d9766..61d57b47f25 100644 --- a/packages/agent-core-v2/src/session/visual/configSection.ts +++ b/packages/agent-core-v2/src/session/visual/configSection.ts @@ -39,7 +39,6 @@ import { z } from 'zod'; import { Error2, ErrorCodes, isError2 } from '#/errors'; -import type { AgentModelPreference } from '#/app/agentProfileCatalog/agentProfileCatalog'; import { isPlainObject } from '#/app/config/toml'; import type { IFlagService } from '#/app/flag/flag'; import { @@ -57,7 +56,7 @@ import type { IModelCatalog } from '#/kosong/model/catalog'; import { VISUAL_MODEL_FLAG_ID } from './flag'; -export type VisualModelChoice = AgentModelPreference; +export type VisualModelChoice = 'primary' | 'visual'; export function resolveVisualModel( config: IConfigService, diff --git a/packages/agent-core/AGENTS.md b/packages/agent-core/AGENTS.md index 411873145ec..bb99391ac47 100644 --- a/packages/agent-core/AGENTS.md +++ b/packages/agent-core/AGENTS.md @@ -1,3 +1,32 @@ +# ⚠️ LANGUAGE RULE — MANDATORY + +**ALL responses MUST be in English ONLY. Never respond in Turkish, Chinese, or any other language, regardless of the user's locale or the presence of non-English content in the codebase. This is a hard rule with no exceptions.** + +--- + +## 📚 Zread Wiki — Check First + +**Before diving into source code, check if a zread wiki exists for this project:** + +```bash +# Check if wiki exists: +cat .zread/wiki/current 2>/dev/null && echo "Wiki exists" || echo "No wiki" + +# If wiki exists, read the pages directly: +ls .zread/wiki/versions/$(cat .zread/wiki/current)/ + +# To regenerate wiki (if stale): +zread generate --stdio +``` + +**Why?** Zread generates comprehensive documentation from code. Reading the wiki is faster than crawling source files manually. + +**Rules:** +1. **ALWAYS** check `.zread/wiki/current` before reading source files +2. If wiki exists, read the markdown pages directly — they're already indexed +3. If wiki is missing or stale, run `zread generate --stdio` to create it +4. Wiki pages live in `.zread/wiki/versions//` — read `wiki.json` for the TOC + # agent-core Agent Guide ## Hard rules diff --git a/packages/agent-core/src/config/schema.ts b/packages/agent-core/src/config/schema.ts index 62770fde978..65e3bb94bbe 100644 --- a/packages/agent-core/src/config/schema.ts +++ b/packages/agent-core/src/config/schema.ts @@ -24,10 +24,20 @@ export type OAuthRef = z.infer; const StringRecordSchema = z.record(z.string(), z.string()); +export const ProviderApiKeySchema = z.object({ + key: z.string().min(1), + name: z.string().min(1), +}); + +export type ProviderApiKey = z.infer; + export const ProviderConfigSchema = z.object({ type: ProviderTypeSchema, apiKey: z.string().optional(), + apiKeys: z.record(z.string(), ProviderApiKeySchema).optional(), + activeApiKeyId: z.string().optional(), baseUrl: z.string().optional(), + proxyUrl: z.string().optional(), defaultModel: z.string().optional(), oauth: OAuthRefSchema.optional(), env: StringRecordSchema.optional(), @@ -114,6 +124,13 @@ export const SecondaryModelConfigSchema = ModelAliasOverrideSchema.extend({ export type SecondaryModelConfig = z.infer; +export const SubstituteModelConfigSchema = z.object({ + defaultModel: z.string().min(1).optional(), + cooldownMs: z.number().int().min(0).optional(), +}); + +export type SubstituteModelConfig = z.infer; + export const ThinkingConfigSchema = z.object({ enabled: z.boolean().optional(), effort: z.string().optional(), @@ -364,6 +381,7 @@ export const KimiConfigSchema = z.object({ background: BackgroundConfigSchema.optional(), subagent: SubagentConfigSchema.optional(), secondaryModel: SecondaryModelConfigSchema.optional(), + substituteModel: SubstituteModelConfigSchema.optional(), mcp: McpConfigSchema.optional(), image: ImageConfigSchema.optional(), modelCatalog: ModelCatalogConfigSchema.optional(), @@ -382,6 +400,7 @@ const LoopControlPatchSchema = LoopControlSchema.partial(); const BackgroundConfigPatchSchema = BackgroundConfigSchema.partial(); const SubagentConfigPatchSchema = SubagentConfigSchema.partial(); const SecondaryModelConfigPatchSchema = SecondaryModelConfigSchema.partial(); +const SubstituteModelConfigPatchSchema = SubstituteModelConfigSchema.partial(); const McpConfigPatchSchema = McpConfigSchema.partial(); const ImageConfigPatchSchema = ImageConfigSchema.partial(); const ModelCatalogConfigPatchSchema = ModelCatalogConfigSchema.partial(); @@ -413,6 +432,7 @@ export const KimiConfigPatchSchema = z background: BackgroundConfigPatchSchema.optional(), subagent: SubagentConfigPatchSchema.optional(), secondaryModel: SecondaryModelConfigPatchSchema.optional(), + substituteModel: SubstituteModelConfigPatchSchema.optional(), mcp: McpConfigPatchSchema.optional(), image: ImageConfigPatchSchema.optional(), modelCatalog: ModelCatalogConfigPatchSchema.optional(), @@ -431,7 +451,8 @@ export function getDefaultConfig(): KimiConfig { export function validateConfig(config: unknown): KimiConfig { try { - return KimiConfigSchema.parse(config); + const parsed = KimiConfigSchema.parse(config); + return migrateLegacyApiKeys(parsed); } catch (error) { throw new KimiError(ErrorCodes.CONFIG_INVALID, `Invalid configuration: ${formatConfigValidationError(error)}`, { cause: error, @@ -439,6 +460,28 @@ export function validateConfig(config: unknown): KimiConfig { } } +function migrateLegacyApiKeys(config: KimiConfig): KimiConfig { + const providers = { ...config.providers }; + let changed = false; + + for (const [providerId, provider] of Object.entries(providers)) { + // If provider has legacy apiKey but no apiKeys, migrate it + if (provider.apiKey && !provider.apiKeys) { + const keyId = 'default'; + providers[providerId] = { + ...provider, + apiKeys: { + [keyId]: { key: provider.apiKey, name: 'Default' }, + }, + activeApiKeyId: keyId, + }; + changed = true; + } + } + + return changed ? { ...config, providers } : config; +} + export function formatConfigValidationError(error: unknown): string { const missingModelContextSize = missingModelContextSizeMessage(error); if (missingModelContextSize !== undefined) return missingModelContextSize; diff --git a/packages/agent-core/src/flags/registry.ts b/packages/agent-core/src/flags/registry.ts index 55903c6d00b..b6805834254 100644 --- a/packages/agent-core/src/flags/registry.ts +++ b/packages/agent-core/src/flags/registry.ts @@ -41,6 +41,15 @@ export const FLAG_DEFINITIONS = [ default: false, surface: 'core', }, + { + id: 'substitute-model', + title: 'Substitute model for rate-limit fallback', + description: + 'When the primary model hits a provider rate limit (e.g. 429 from account quota), automatically switch to a configured substitute model and continue until the primary recovers.', + env: 'KIMI_CODE_EXPERIMENTAL_SUBSTITUTE_MODEL', + default: false, + surface: 'core', + }, ] as const satisfies readonly FlagDefinitionInput[]; /** Literal union of registered flag ids. */ diff --git a/packages/agent-core/src/session/provider-manager.ts b/packages/agent-core/src/session/provider-manager.ts index 7fb313b4619..f7691779e54 100644 --- a/packages/agent-core/src/session/provider-manager.ts +++ b/packages/agent-core/src/session/provider-manager.ts @@ -436,20 +436,31 @@ function kimiUserAgentHeader( return userAgent === undefined ? {} : { 'User-Agent': userAgent }; } +function getActiveApiKey(provider: ProviderConfig): string | undefined { + // 1. Named keys with active selection + if (provider.apiKeys && provider.activeApiKeyId) { + const active = provider.apiKeys[provider.activeApiKeyId]; + if (active) return active.key; + } + // 2. Legacy single key + return provider.apiKey; +} + function providerApiKey(provider: ProviderConfig): string | undefined { + const activeKey = getActiveApiKey(provider); switch (provider.type) { case 'anthropic': - return providerValue(provider.apiKey, provider.env, 'ANTHROPIC_API_KEY'); + return providerValue(activeKey, provider.env, 'ANTHROPIC_API_KEY'); case 'openai': case 'openai_responses': - return providerValue(provider.apiKey, provider.env, 'OPENAI_API_KEY'); + return providerValue(activeKey, provider.env, 'OPENAI_API_KEY'); case 'kimi': - return providerValue(provider.apiKey, provider.env, 'KIMI_API_KEY'); + return providerValue(activeKey, provider.env, 'KIMI_API_KEY'); case 'google-genai': - return providerValue(provider.apiKey, provider.env, 'GOOGLE_API_KEY'); + return providerValue(activeKey, provider.env, 'GOOGLE_API_KEY'); case 'vertexai': return ( - nonEmptyString(provider.apiKey) ?? + nonEmptyString(activeKey) ?? envValue(provider.env, 'VERTEXAI_API_KEY') ?? envValue(provider.env, 'GOOGLE_API_KEY') ); diff --git a/packages/agent-core/test/config/env-model.test.ts b/packages/agent-core/test/config/env-model.test.ts index b9ac80e127f..72019428612 100644 --- a/packages/agent-core/test/config/env-model.test.ts +++ b/packages/agent-core/test/config/env-model.test.ts @@ -64,6 +64,10 @@ describe('applyEnvModelConfig', () => { expect(config.providers[ENV_MODEL_PROVIDER_KEY]).toEqual({ type: 'kimi', apiKey: 'sk-test', + apiKeys: { + default: { key: 'sk-test', name: 'Default' }, + }, + activeApiKeyId: 'default', baseUrl: 'https://api.moonshot.ai/v1', }); expect(config.models?.[ENV_MODEL_ALIAS_KEY]).toEqual({ diff --git a/packages/kap-server/AGENTS.md b/packages/kap-server/AGENTS.md index d12cce00b3b..971d0f4183c 100644 --- a/packages/kap-server/AGENTS.md +++ b/packages/kap-server/AGENTS.md @@ -1,3 +1,32 @@ +# ⚠️ LANGUAGE RULE — MANDATORY + +**ALL responses MUST be in English ONLY. Never respond in Turkish, Chinese, or any other language, regardless of the user's locale or the presence of non-English content in the codebase. This is a hard rule with no exceptions.** + +--- + +## 📚 Zread Wiki — Check First + +**Before diving into source code, check if a zread wiki exists for this project:** + +```bash +# Check if wiki exists: +cat .zread/wiki/current 2>/dev/null && echo "Wiki exists" || echo "No wiki" + +# If wiki exists, read the pages directly: +ls .zread/wiki/versions/$(cat .zread/wiki/current)/ + +# To regenerate wiki (if stale): +zread generate --stdio +``` + +**Why?** Zread generates comprehensive documentation from code. Reading the wiki is faster than crawling source files manually. + +**Rules:** +1. **ALWAYS** check `.zread/wiki/current` before reading source files +2. If wiki exists, read the markdown pages directly — they're already indexed +3. If wiki is missing or stale, run `zread generate --stdio` to create it +4. Wiki pages live in `.zread/wiki/versions//` — read `wiki.json` for the TOC + # kap-server Agent Guide The Kimi Code server, backed by the DI × Scope agent engine (`@moonshot-ai/agent-core-v2` — four scopes, App/Workspace/Session/Agent). Exposes sessions over REST + WebSocket (`/api/v1` + `/api/v1/ws`); bootstrapped from `src/start.ts` and consumed by `apps/kimi-code`. diff --git a/packages/klient/AGENTS.md b/packages/klient/AGENTS.md index 127bd662e87..bdbd26d86d7 100644 --- a/packages/klient/AGENTS.md +++ b/packages/klient/AGENTS.md @@ -1,3 +1,32 @@ +# ⚠️ LANGUAGE RULE — MANDATORY + +**ALL responses MUST be in English ONLY. Never respond in Turkish, Chinese, or any other language, regardless of the user's locale or the presence of non-English content in the codebase. This is a hard rule with no exceptions.** + +--- + +## 📚 Zread Wiki — Check First + +**Before diving into source code, check if a zread wiki exists for this project:** + +```bash +# Check if wiki exists: +cat .zread/wiki/current 2>/dev/null && echo "Wiki exists" || echo "No wiki" + +# If wiki exists, read the pages directly: +ls .zread/wiki/versions/$(cat .zread/wiki/current)/ + +# To regenerate wiki (if stale): +zread generate --stdio +``` + +**Why?** Zread generates comprehensive documentation from code. Reading the wiki is faster than crawling source files manually. + +**Rules:** +1. **ALWAYS** check `.zread/wiki/current` before reading source files +2. If wiki exists, read the markdown pages directly — they're already indexed +3. If wiki is missing or stale, run `zread generate --stdio` to create it +4. Wiki pages live in `.zread/wiki/versions//` — read `wiki.json` for the TOC + # klient Agent Guide Package-local rules for `packages/klient`. diff --git a/packages/klient/src/contract/global/providers.ts b/packages/klient/src/contract/global/providers.ts index ac8940b1305..eda4564adea 100644 --- a/packages/klient/src/contract/global/providers.ts +++ b/packages/klient/src/contract/global/providers.ts @@ -23,15 +23,23 @@ const stringRecordSchema = z.record(z.string(), z.string()); const modelSourceSchema = z.enum(['static', 'discover', 'oauth-catalog']); +const providerApiKeySchema = z.object({ + key: z.string(), + name: z.string(), +}); + export const providerConfigSchema = z.object({ modelSource: modelSourceSchema.optional(), baseUrl: z.string().optional(), + proxyUrl: z.string().optional(), customHeaders: stringRecordSchema.optional(), defaultModel: z.string().optional(), type: providerTypeSchema.optional(), apiKey: z.string().optional(), + apiKeys: z.record(z.string(), providerApiKeySchema).optional(), + activeApiKeyId: z.string().optional(), oauth: oAuthRefSchema.optional(), env: stringRecordSchema.optional(), source: z.record(z.string(), z.unknown()).optional(), diff --git a/packages/minidb/AGENTS.md b/packages/minidb/AGENTS.md index 3a3a19f0541..66ad6e76969 100644 --- a/packages/minidb/AGENTS.md +++ b/packages/minidb/AGENTS.md @@ -1,3 +1,32 @@ +# ⚠️ LANGUAGE RULE — MANDATORY + +**ALL responses MUST be in English ONLY. Never respond in Turkish, Chinese, or any other language, regardless of the user's locale or the presence of non-English content in the codebase. This is a hard rule with no exceptions.** + +--- + +## 📚 Zread Wiki — Check First + +**Before diving into source code, check if a zread wiki exists for this project:** + +```bash +# Check if wiki exists: +cat .zread/wiki/current 2>/dev/null && echo "Wiki exists" || echo "No wiki" + +# If wiki exists, read the pages directly: +ls .zread/wiki/versions/$(cat .zread/wiki/current)/ + +# To regenerate wiki (if stale): +zread generate --stdio +``` + +**Why?** Zread generates comprehensive documentation from code. Reading the wiki is faster than crawling source files manually. + +**Rules:** +1. **ALWAYS** check `.zread/wiki/current` before reading source files +2. If wiki exists, read the markdown pages directly — they're already indexed +3. If wiki is missing or stale, run `zread generate --stdio` to create it +4. Wiki pages live in `.zread/wiki/versions//` — read `wiki.json` for the TOC + # minidb Agent Guide The embedded JSON document store (`MiniDb`) behind kap-server's search index — snapshot + WAL persistence with an exclusive write lock (losers open read-only and catch up from the WAL; `OpenOptions.onLockAcquired` reports the held lock token right after acquisition, before recovery work — supervisors hosting MiniDb in a worker thread need it because worker threads share the host process pid, so the pid in the lock line alone cannot drive stale reclamation), plus a larger-than-RAM full-text layer. diff --git a/packages/oauth/src/refreshProviderModels.ts b/packages/oauth/src/refreshProviderModels.ts index 49c521fd631..a6d89b373a6 100644 --- a/packages/oauth/src/refreshProviderModels.ts +++ b/packages/oauth/src/refreshProviderModels.ts @@ -76,20 +76,37 @@ interface ProviderView { readonly type?: string; readonly baseUrl?: string; readonly apiKey?: string; + readonly apiKeys?: Record; + readonly activeApiKeyId?: string; readonly oauth?: ManagedKimiOAuthRef; readonly source?: unknown; readonly env?: unknown; } +function getActiveProviderApiKey(provider: ProviderView): string | undefined { + if (!provider) return undefined; + // 1. Named keys with active selection + if (provider.apiKeys && provider.activeApiKeyId) { + const active = provider.apiKeys[provider.activeApiKeyId]; + if (active && typeof active.key === 'string' && active.key.length > 0) { + return active.key; + } + } + // 2. Legacy single key + if (typeof provider.apiKey === 'string' && provider.apiKey.length > 0) { + return provider.apiKey; + } + return undefined; +} + /** * Mirrors the runtime credential resolution for `type: 'kimi'` providers * (`providerApiKey` in agent-core's provider-manager): the inline `apiKey` * wins, with `env.KIMI_API_KEY` as the documented config-file fallback. */ function resolveProviderApiKey(provider: ProviderView): string | undefined { - if (typeof provider.apiKey === 'string' && provider.apiKey.length > 0) { - return provider.apiKey; - } + const activeKey = getActiveProviderApiKey(provider); + if (activeKey !== undefined) return activeKey; if (isRecord(provider.env)) { const fromEnv = provider.env['KIMI_API_KEY']; if (typeof fromEnv === 'string' && fromEnv.length > 0) return fromEnv; diff --git a/packages/pi-tui/AGENTS.md b/packages/pi-tui/AGENTS.md index 3ee3f9a0033..379c3e87b6c 100644 --- a/packages/pi-tui/AGENTS.md +++ b/packages/pi-tui/AGENTS.md @@ -1,3 +1,32 @@ +# ⚠️ LANGUAGE RULE — MANDATORY + +**ALL responses MUST be in English ONLY. Never respond in Turkish, Chinese, or any other language, regardless of the user's locale or the presence of non-English content in the codebase. This is a hard rule with no exceptions.** + +--- + +## 📚 Zread Wiki — Check First + +**Before diving into source code, check if a zread wiki exists for this project:** + +```bash +# Check if wiki exists: +cat .zread/wiki/current 2>/dev/null && echo "Wiki exists" || echo "No wiki" + +# If wiki exists, read the pages directly: +ls .zread/wiki/versions/$(cat .zread/wiki/current)/ + +# To regenerate wiki (if stale): +zread generate --stdio +``` + +**Why?** Zread generates comprehensive documentation from code. Reading the wiki is faster than crawling source files manually. + +**Rules:** +1. **ALWAYS** check `.zread/wiki/current` before reading source files +2. If wiki exists, read the markdown pages directly — they're already indexed +3. If wiki is missing or stale, run `zread generate --stdio` to create it +4. Wiki pages live in `.zread/wiki/versions//` — read `wiki.json` for the TOC + # pi-tui Agent Guide `packages/pi-tui` is a vendored copy of pi-tui from the upstream pi-mono project (baseline: upstream 0.80.2, see commit `7859b0af`). It is no longer patched via pnpm patches — all local fixes are applied directly to the source. The differential-rendering behavior in `src/tui.ts` matches upstream: the fork's viewport/scrollback rendering patches were reverted; the only remaining divergences are listed below. diff --git a/packages/transcript/AGENTS.md b/packages/transcript/AGENTS.md index 0e2d0765031..4dba3e51a29 100644 --- a/packages/transcript/AGENTS.md +++ b/packages/transcript/AGENTS.md @@ -1,3 +1,32 @@ +# ⚠️ LANGUAGE RULE — MANDATORY + +**ALL responses MUST be in English ONLY. Never respond in Turkish, Chinese, or any other language, regardless of the user's locale or the presence of non-English content in the codebase. This is a hard rule with no exceptions.** + +--- + +## 📚 Zread Wiki — Check First + +**Before diving into source code, check if a zread wiki exists for this project:** + +```bash +# Check if wiki exists: +cat .zread/wiki/current 2>/dev/null && echo "Wiki exists" || echo "No wiki" + +# If wiki exists, read the pages directly: +ls .zread/wiki/versions/$(cat .zread/wiki/current)/ + +# To regenerate wiki (if stale): +zread generate --stdio +``` + +**Why?** Zread generates comprehensive documentation from code. Reading the wiki is faster than crawling source files manually. + +**Rules:** +1. **ALWAYS** check `.zread/wiki/current` before reading source files +2. If wiki exists, read the markdown pages directly — they're already indexed +3. If wiki is missing or stale, run `zread generate --stdio` to create it +4. Wiki pages live in `.zread/wiki/versions//` — read `wiki.json` for the TOC + # transcript Agent Guide The isomorphic transcript rendering data layer — agent-granular L1 store, idempotent L2 operations, `off/turn/block/delta` L3 subscription granularity, framework-free L4 view registry, and turn-cursor pagination. Pure TypeScript (browser-safe, no engine imports) and the sole owner of all transcript contract types (`src/contract/`); consumed by `packages/kap-server` (engine events → transcript, REST + WS surface; live stores backfill history from the persisted per-agent wire records — main on first attach, any agent on demand, cold sessions rebuild any agent — with 0-based turn ordinals matching the engine's). diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a29a930cd8c..79ec2be71cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,13 @@ overrides: importers: .: + dependencies: + http-proxy-agent: + specifier: ^9.1.0 + version: 9.1.0 + https-proxy-agent: + specifier: ^9.1.0 + version: 9.1.0 devDependencies: '@arethetypeswrong/cli': specifier: 0.18.2 @@ -4774,6 +4781,10 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + agent-base@9.0.0: + resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==} + engines: {node: '>= 20'} + ahooks@3.9.7: resolution: {integrity: sha512-S0lvzhbdlhK36RFBkGv+RbOM/dbbweym+BIHM/bwwuWVSVN5TuVErHPMWo4w0t1NDYg5KPp2iEf7Y7E5LASYiw==} peerDependencies: @@ -6317,10 +6328,18 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + http-proxy-agent@9.1.0: + resolution: {integrity: sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==} + engines: {node: '>= 20'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + https-proxy-agent@9.1.0: + resolution: {integrity: sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==} + engines: {node: '>= 20'} + human-id@4.1.3: resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} hasBin: true @@ -7948,6 +7967,15 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-agent-negotiate@1.1.0: + resolution: {integrity: sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==} + engines: {node: '>= 20'} + peerDependencies: + kerberos: ^2.0.0 + peerDependenciesMeta: + kerberos: + optional: true + publint@0.3.18: resolution: {integrity: sha512-JRJFeBTrfx4qLwEuGFPk+haJOJN97KnPuK01yj+4k/Wj5BgoOK5uNsivporiqBjk2JDaslg7qJOhGRnpltGeog==} engines: {node: '>=18'} @@ -13582,6 +13610,8 @@ snapshots: agent-base@7.1.4: {} + agent-base@9.0.0: {} + ahooks@3.9.7(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: '@babel/runtime': 7.29.2 @@ -15393,6 +15423,15 @@ snapshots: transitivePeerDependencies: - supports-color + http-proxy-agent@9.1.0: + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@8.1.1) + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -15400,6 +15439,15 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@9.1.0: + dependencies: + agent-base: 9.0.0 + debug: 4.4.3(supports-color@8.1.1) + proxy-agent-negotiate: 1.1.0 + transitivePeerDependencies: + - kerberos + - supports-color + human-id@4.1.3: {} human-signals@2.1.0: {} @@ -17249,6 +17297,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-agent-negotiate@1.1.0: {} + publint@0.3.18: dependencies: '@publint/pack': 0.1.4 From abf5f3b8d7c58b61873f4fe4df68c77f45009cd8 Mon Sep 17 00:00:00 2001 From: Ahmet TOK <48218623+arrrrny@users.noreply.github.com> Date: Fri, 21 Aug 2026 07:44:45 +0300 Subject: [PATCH 12/71] chore: change sync schedule to 3 AM UTC --- .github/workflows/sync-upstream.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index 707d1071cff..73ef4a358f7 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -7,7 +7,7 @@ name: Sync Upstream on: schedule: - - cron: "17 6 * * *" + - cron: "0 3 * * *" workflow_dispatch: permissions: From a5947bf3a03f098a2fba4cb397e084deccc94a98 Mon Sep 17 00:00:00 2001 From: ARRRRNY Date: Fri, 21 Aug 2026 10:11:36 +0300 Subject: [PATCH 13/71] ci: reschedule fork native release cron to 03:10 UTC --- .github/workflows/fork-native-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fork-native-release.yml b/.github/workflows/fork-native-release.yml index 84b6567560f..129e5f2032c 100644 --- a/.github/workflows/fork-native-release.yml +++ b/.github/workflows/fork-native-release.yml @@ -12,7 +12,7 @@ on: workflow_dispatch: schedule: # Daily re-release so the binary always tracks the latest upstream sync - - cron: "41 6 * * *" + - cron: "10 3 * * *" permissions: contents: write From 87cb38994d5326b12615131eb1dbe6da20def156 Mon Sep 17 00:00:00 2001 From: ARRRRNY Date: Fri, 21 Aug 2026 10:12:43 +0300 Subject: [PATCH 14/71] chore: gitignore local caches and scratch dirs --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index c6f2a483e42..da7295018ff 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,10 @@ handover.md *-mockup.html *-demo.html *-demos.html + +# Local caches / generated / scratch (not committed) +.memsearch/ +.zread/ +kimi-dev +handoffs/ +specs/ From ab12c9b45359e42903047c44f43a1417553c8002 Mon Sep 17 00:00:00 2001 From: Ahmet TOK <48218623+arrrrny@users.noreply.github.com> Date: Sun, 23 Aug 2026 09:07:47 +0300 Subject: [PATCH 15/71] feat: add /update-all-session-models command (#2) * feat: add /update-all-session-models command Reuse the /model picker to apply a chosen model to every active session at once. The command shows how many sessions will change and asks for confirmation before applying, skips sessions that cannot take the model, reports per-session results, and also updates the new-session default model. * fix: apply CodeRabbit auto-fixes Fixed 2 file(s) based on 6 unresolved review comments. Co-authored-by: CodeRabbit * fix: remove dangling .specify/feature.json spec pointer --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: CodeRabbit --- .changeset/update-all-session-models.md | 5 + apps/kimi-code/src/tui/commands/config.ts | 215 ++++++++++++++++++ apps/kimi-code/src/tui/commands/dispatch.ts | 5 + apps/kimi-code/src/tui/commands/registry.ts | 7 + .../tui/components/dialogs/confirm-dialog.ts | 115 ++++++++++ .../update-all-session-models.test.ts | 178 +++++++++++++++ 6 files changed, 525 insertions(+) create mode 100644 .changeset/update-all-session-models.md create mode 100644 apps/kimi-code/src/tui/components/dialogs/confirm-dialog.ts create mode 100644 apps/kimi-code/test/tui/commands/update-all-session-models.test.ts diff --git a/.changeset/update-all-session-models.md b/.changeset/update-all-session-models.md new file mode 100644 index 00000000000..7a0608654ba --- /dev/null +++ b/.changeset/update-all-session-models.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": minor +--- + +Add the /update-all-session-models command to switch the working model for every active session at once, reusing the /model picker and asking for confirmation before applying. diff --git a/apps/kimi-code/src/tui/commands/config.ts b/apps/kimi-code/src/tui/commands/config.ts index 03e9b26aff1..d9f0b70e22c 100644 --- a/apps/kimi-code/src/tui/commands/config.ts +++ b/apps/kimi-code/src/tui/commands/config.ts @@ -6,6 +6,7 @@ import { type ModelAlias, type PermissionMode, type Session, + type SessionSummary, type ThinkingEffort, } from '@moonshot-ai/kimi-code-sdk'; @@ -21,6 +22,7 @@ import { PermissionSelectorComponent } from '../components/dialogs/permission-se import { SettingsSelectorComponent, type SettingsSelection } from '../components/dialogs/settings-selector'; import { ThemeSelectorComponent } from '../components/dialogs/theme-selector'; import { UpdatePreferenceSelectorComponent } from '../components/dialogs/update-preference-selector'; +import { ConfirmDialogComponent } from '../components/dialogs/confirm-dialog'; import { DEFAULT_TUI_CONFIG, saveTuiConfig, type TuiConfig } from '../config'; import type { ThemeName } from '#/tui/theme'; import { currentTheme, isBuiltInTheme, lightColors, loadCustomThemeMerged } from '#/tui/theme'; @@ -266,6 +268,219 @@ export async function handleModelCommand(host: SlashCommandHost, args: string): showModelPicker(host, alias); } +// --------------------------------------------------------------------------- +// Bulk session model switch (`/update-all-session-models`) +// --------------------------------------------------------------------------- + +type BulkSessionOutcome = + | { readonly id: string; readonly status: 'succeeded' } + | { readonly id: string; readonly status: 'skipped'; readonly reason: string } + | { readonly id: string; readonly status: 'failed'; readonly reason: string }; + +/** + * Enumerate the active sessions the bulk switch will target: the non-archived + * sessions the harness manages, plus the current session (which listSessions may + * or may not include). Archived/closed sessions are excluded by the listing, so + * they are never touched (FR-008); the current session is always in scope + * (FR-003). + */ +function activeSessionsForBulk(host: SlashCommandHost, listed: readonly SessionSummary[]): SessionSummary[] { + const current = host.session; + if (current === undefined) return [...listed]; + const ids = new Set(listed.map((s) => s.id)); + if (ids.has(current.id)) return [...listed]; + return [ + ...listed, + { + id: current.id, + workDir: current.workDir, + sessionDir: current.workDir, + createdAt: 0, + updatedAt: 0, + title: 'Current session', + archived: false, + }, + ]; +} + +/** A setModel rejection is a "skip" when the model itself can't be applied to + * that session (unavailable/invalid); anything else is a hard "failure". */ +function classifyModelError(error: unknown): 'skipped' | 'failed' { + const message = formatErrorMessage(error).toLowerCase(); + const modelProblem = + message.includes('model') && + (message.includes('not found') || + message.includes('unknown') || + message.includes('invalid') || + message.includes('unavailable') || + message.includes('unsupported')); + return modelProblem ? 'skipped' : 'failed'; +} + +export async function handleUpdateAllSessionModelsCommand(host: SlashCommandHost, args: string): Promise { + const alias = args.trim(); + await refreshModelsForPicker(host); + if (alias.length > 0 && host.state.appState.availableModels[alias] === undefined) { + host.showError(`Unknown model alias: ${alias}`); + return; + } + const listed = await host.harness.listSessions(); + const sessions = activeSessionsForBulk(host, listed); + if (sessions.length === 0) { + host.showNotice( + 'No active sessions', + 'There are no active sessions to update. Start or resume a session, then try again.', + ); + return; + } + showBulkModelPicker(host, alias, sessions); +} + +function showBulkModelPicker( + host: SlashCommandHost, + selectedValue: string, + sessions: readonly SessionSummary[], +): void { + const models = pickerModelsForHost(host); + const entries = Object.entries(models); + if (entries.length === 0) { + host.showNotice( + 'No models configured', + 'Run /login to sign in to Kimi, or /provider to add another provider from a model catalog.', + ); + return; + } + host.mountEditorReplacement( + new TabbedModelSelectorComponent({ + models, + currentValue: host.state.appState.model, + selectedValue, + currentThinkingEffort: host.state.appState.thinkingEffort, + warning: hasConversationHistory(host) ? MODEL_SWITCH_CACHE_WARNING : undefined, + onSelect: ({ alias }) => { + host.restoreEditor(); + showBulkConfirm(host, alias, sessions); + }, + onCancel: () => { + host.restoreEditor(); + }, + }), + ); +} + +function showBulkConfirm(host: SlashCommandHost, alias: string, sessions: readonly SessionSummary[]): void { + const count = sessions.length; + const displayName = modelDisplayName(alias, host.state.appState.availableModels[alias]); + host.mountEditorReplacement( + new ConfirmDialogComponent({ + title: `Update ${count} active session${count === 1 ? '' : 's'} to ${displayName}?`, + body: [ + `This switches the working model for every active session${ + count === 1 ? '' : ` (${String(count)})` + }.`, + 'The new-session default model will also be updated.', + 'This cannot be undone per session — choose Cancel to make no changes.', + ], + confirmLabel: 'Update all', + cancelLabel: 'Cancel', + onResolve: (confirmed) => { + host.restoreEditor(); + if (!confirmed) { + host.showStatus('Cancelled — no sessions were changed.', 'textDim'); + return; + } + void applyModelToAllSessions(host, alias, sessions); + }, + }), + ); +} + +async function applyModelToAllSessions( + host: SlashCommandHost, + alias: string, + sessions: readonly SessionSummary[], +): Promise { + const currentId = host.session?.id; + const displayName = modelDisplayName(alias, host.state.appState.availableModels[alias]); + const results: BulkSessionOutcome[] = []; + const resumedSessions = new Set(); + + try { + for (const summary of sessions) { + const id = summary.id; + let session: Session | undefined; + if (id === currentId) { + session = host.session; + } else { + session = host.harness.getSession(id); + if (session === undefined) { + try { + session = await host.harness.resumeSession({ id }); + resumedSessions.add(id); + } catch (error) { + results.push({ id, status: 'failed', reason: formatErrorMessage(error) }); + continue; + } + } + } + if (session === undefined) { + results.push({ id, status: 'failed', reason: 'session unavailable' }); + continue; + } + try { + await session.setModel(alias); + results.push({ id, status: 'succeeded' }); + } catch (error) { + results.push({ id, status: classifyModelError(error), reason: formatErrorMessage(error) }); + } + } + + if (currentId !== undefined) { + const currentResult = results.find((r) => r.id === currentId); + if (currentResult?.status === 'succeeded') { + host.setAppState({ model: alias }); + } + } + try { + await host.harness.setConfig({ defaultModel: alias }); + } catch (error) { + host.showError(`Switched sessions to ${displayName}, but failed to save default: ${formatErrorMessage(error)}`); + } + + reportBulkResult(host, displayName, results); + } finally { + for (const id of resumedSessions) { + const session = host.harness.getSession(id); + if (session !== undefined) { + await session.close().catch(() => {}); + } + } + } +} + +function reportBulkResult( + host: SlashCommandHost, + displayName: string, + results: readonly BulkSessionOutcome[], +): void { + const succeeded = results.filter((r) => r.status === 'succeeded').length; + const skipped = results.filter((r) => r.status === 'skipped').length; + const failed = results.filter((r) => r.status === 'failed').length; + + const parts = [`Updated ${String(succeeded)} session${succeeded === 1 ? '' : 's'} to ${displayName}`]; + if (skipped > 0) parts.push(`${String(skipped)} skipped`); + if (failed > 0) parts.push(`${String(failed)} failed`); + const statusColor = succeeded === 0 ? 'error' : (skipped > 0 || failed > 0) ? 'warning' : 'success'; + host.showStatus(`${parts.join(' · ')}.`, statusColor); + + if (skipped > 0 || failed > 0) { + const lines = results + .filter((r) => r.status !== 'succeeded') + .map((r) => `• ${r.id}: ${r.status} — ${r.reason}`); + host.showNotice('Some sessions were not updated', lines.join('\n')); + } +} + export async function handleSecondaryModelCommand(host: SlashCommandHost, args: string): Promise { const alias = args.trim(); await refreshModelsForPicker(host); diff --git a/apps/kimi-code/src/tui/commands/dispatch.ts b/apps/kimi-code/src/tui/commands/dispatch.ts index e8775552d95..57ec0e78001 100644 --- a/apps/kimi-code/src/tui/commands/dispatch.ts +++ b/apps/kimi-code/src/tui/commands/dispatch.ts @@ -36,6 +36,7 @@ import { handlePlanCommand, handleSecondaryModelCommand, handleSubstituteModelCommand, + handleUpdateAllSessionModelsCommand, handleVisualModelCommand, handleThemeCommand, handleYoloCommand, @@ -92,6 +93,7 @@ export { handlePlanCommand, handleSecondaryModelCommand, handleSubstituteModelCommand, + handleUpdateAllSessionModelsCommand, handleVisualModelCommand, handleThemeCommand, handleYoloCommand, @@ -539,6 +541,9 @@ async function handleBuiltInSlashCommand( case 'model': await handleModelCommand(host, args); return; + case 'update-all-session-models': + await handleUpdateAllSessionModelsCommand(host, args); + return; case 'secondary-model': await handleSecondaryModelCommand(host, args); return; diff --git a/apps/kimi-code/src/tui/commands/registry.ts b/apps/kimi-code/src/tui/commands/registry.ts index 4b173067464..d9f17cadf5a 100644 --- a/apps/kimi-code/src/tui/commands/registry.ts +++ b/apps/kimi-code/src/tui/commands/registry.ts @@ -215,6 +215,13 @@ export const BUILTIN_SLASH_COMMANDS = [ priority: 100, availability: 'always', }, + { + name: 'update-all-session-models', + aliases: ['update-models'], + description: 'Switch the model for all active sessions at once', + priority: 100, + availability: 'always', + }, { name: 'secondary-model', aliases: ['subagent-model'], diff --git a/apps/kimi-code/src/tui/components/dialogs/confirm-dialog.ts b/apps/kimi-code/src/tui/components/dialogs/confirm-dialog.ts new file mode 100644 index 00000000000..35ae39eb503 --- /dev/null +++ b/apps/kimi-code/src/tui/components/dialogs/confirm-dialog.ts @@ -0,0 +1,115 @@ +/** + * ConfirmDialogComponent — a small Yes/No confirmation mounted as an editor + * replacement. Used by commands that change more than the current session (e.g. + * `/update-all-session-models`) so the user sees the blast radius and explicitly + * confirms before anything is written. + * + * Pure presentation: it never touches session or config state; it only reports a + * boolean back through `onResolve`. + */ + +import { + Container, + Key, + matchesKey, + truncateToWidth, + visibleWidth, + type Focusable, +} from '@moonshot-ai/pi-tui'; + +import { SELECT_POINTER } from '#/tui/constant/symbols'; +import { currentTheme } from '#/tui/theme'; +import { printableChar } from '#/tui/utils/printable-key'; + +export interface ConfirmDialogOptions { + readonly title: string; + /** Supporting lines shown above the Yes/No choice. */ + readonly body: readonly string[]; + readonly confirmLabel?: string; + readonly cancelLabel?: string; + /** Always invoked exactly once: `true` on confirm, `false` on cancel. */ + readonly onResolve: (confirmed: boolean) => void; +} + +interface ConfirmChoice { + readonly label: string; + readonly value: boolean; +} + +export class ConfirmDialogComponent extends Container implements Focusable { + focused = false; + private readonly opts: ConfirmDialogOptions; + private readonly choices: readonly ConfirmChoice[]; + private selectedIndex = 0; + private resolved = false; + + constructor(opts: ConfirmDialogOptions) { + super(); + this.opts = opts; + this.choices = [ + { label: opts.confirmLabel ?? 'Yes', value: true }, + { label: opts.cancelLabel ?? 'No', value: false }, + ]; + } + + handleInput(data: string): void { + if (this.resolved) return; + if (matchesKey(data, Key.escape)) { + this.resolved = true; + this.opts.onResolve(false); + return; + } + if (matchesKey(data, Key.up)) { + this.selectedIndex = (this.selectedIndex - 1 + this.choices.length) % this.choices.length; + return; + } + if (matchesKey(data, Key.down)) { + this.selectedIndex = (this.selectedIndex + 1) % this.choices.length; + return; + } + if (matchesKey(data, Key.enter)) { + this.resolved = true; + this.opts.onResolve(this.choices[this.selectedIndex]!.value); + return; + } + const printable = printableChar(data); + if (printable === 'y' || printable === 'Y') { + this.resolved = true; + this.opts.onResolve(true); + return; + } + if (printable === 'n' || printable === 'N') { + this.resolved = true; + this.opts.onResolve(false); + } + } + + override render(width: number): string[] { + const accent = (text: string) => currentTheme.fg('primary', text); + const dim = (text: string) => currentTheme.fg('textDim', text); + const bar = '─'.repeat(width); + + const lines: string[] = [accent(bar), currentTheme.boldFg('primary', ` ${this.opts.title}`), '']; + + for (const bodyLine of this.opts.body) { + lines.push(dim(` ${bodyLine}`)); + } + lines.push(''); + + const maxLabelWidth = Math.max(...this.choices.map((c) => visibleWidth(c.label))); + this.choices.forEach((choice, index) => { + const isSelected = index === this.selectedIndex; + const pointer = isSelected ? SELECT_POINTER : ' '; + const gap = maxLabelWidth - visibleWidth(choice.label) + 3; + const label = isSelected + ? currentTheme.boldFg('primary', choice.label) + : currentTheme.fg('text', choice.label); + lines.push(` ${isSelected ? accent(pointer) : ' '} ${label}${' '.repeat(gap)}(${index === 0 ? 'Y' : 'N'})`); + }); + + lines.push(''); + lines.push(dim(' ↑↓ choose · Enter select · Y confirm · N / Esc cancel')); + lines.push(accent(bar)); + return lines.map((line) => truncateToWidth(line, width)); + } +} diff --git a/apps/kimi-code/test/tui/commands/update-all-session-models.test.ts b/apps/kimi-code/test/tui/commands/update-all-session-models.test.ts new file mode 100644 index 00000000000..b25e7fa651f --- /dev/null +++ b/apps/kimi-code/test/tui/commands/update-all-session-models.test.ts @@ -0,0 +1,178 @@ +/** + * Scenario: /update-all-session-models command behavior in the interactive TUI. + * Responsibilities: reuse /model's picker UX, show the active-session count and + * require confirmation, apply the chosen model to every active session (current + * included), skip/fail resiliently, and persist the new-session default. + * Wiring: real command + dialogs with the SDK/session boundaries stubbed by a + * small host rig. + * Run: pnpm -C apps/kimi-code exec vitest run test/tui/commands/update-all-session-models.test.ts + */ +import type { ModelAlias, Session, SessionSummary, ThinkingEffort } from '@moonshot-ai/kimi-code-sdk'; +import { describe, expect, it, vi } from 'vitest'; + +import type { SlashCommandHost } from '#/tui/commands'; +import { handleUpdateAllSessionModelsCommand } from '#/tui/commands/config'; +import { ConfirmDialogComponent } from '#/tui/components/dialogs/confirm-dialog'; +import { + TabbedModelSelectorComponent, + type TabbedModelSelectorOptions, +} from '#/tui/components/dialogs/tabbed-model-selector'; +import { type ConfirmDialogOptions } from '#/tui/components/dialogs/confirm-dialog'; + +interface RigOptions { + readonly listed?: readonly SessionSummary[]; + readonly getSessionReturns?: Session | undefined; + readonly resumeRejects?: boolean; +} + +function model(name: string): ModelAlias { + return { + provider: 'test', + model: name, + maxContextSize: 200_000, + displayName: name, + } as unknown as ModelAlias; +} + +function makeSession(id: string): Session { + return { + id, + workDir: '/tmp/work', + setModel: vi.fn(async () => {}), + } as unknown as Session; +} + +function makeHost(options?: RigOptions) { + const current = makeSession('ses-current'); + const other = makeSession('ses-other'); + const appState = { + availableModels: { + k2: model('k2'), + cheap: model('cheap'), + } as Record, + availableProviders: {}, + transcriptEntries: [], + model: 'k2', + thinkingEffort: 'off', + }; + const listed: readonly SessionSummary[] = + options?.listed ?? [ + { + id: 'ses-other', + workDir: '/tmp/work', + sessionDir: '/tmp/work', + createdAt: 0, + updatedAt: 0, + }, + ]; + const host = { + state: { appState, transcriptEntries: [] }, + session: current, + harness: { + listSessions: vi.fn(async () => listed), + getSession: vi.fn(() => options?.getSessionReturns), + resumeSession: vi.fn(async (input: { id: string }) => + options?.resumeRejects ? Promise.reject(new Error('model k2 not found')) : input.id === 'ses-other' ? other : makeSession(input.id), + ), + getConfig: vi.fn(async () => ({})), + setConfig: vi.fn(async () => ({})), + }, + authFlow: { + refreshOAuthProviderModels: vi.fn(async () => undefined), + }, + setAppState: vi.fn((patch: Record) => Object.assign(appState, patch)), + mountEditorReplacement: vi.fn(), + restoreEditor: vi.fn(), + showStatus: vi.fn(), + showError: vi.fn(), + showNotice: vi.fn(), + track: vi.fn(), + } as unknown as SlashCommandHost & { + harness: { + listSessions: ReturnType; + getSession: ReturnType; + resumeSession: ReturnType; + setConfig: ReturnType; + }; + session: Session; + mountEditorReplacement: ReturnType; + showStatus: ReturnType; + showError: ReturnType; + showNotice: ReturnType; + }; + return { host, current, other }; +} + +function picker(host: { mountEditorReplacement: ReturnType }): TabbedModelSelectorOptions { + const component = host.mountEditorReplacement.mock.calls[0]![0]; + expect(component).toBeInstanceOf(TabbedModelSelectorComponent); + return (component as unknown as { opts: TabbedModelSelectorOptions }).opts; +} + +function confirmDialog(host: { mountEditorReplacement: ReturnType }): ConfirmDialogOptions { + const component = host.mountEditorReplacement.mock.calls[1]![0]; + expect(component).toBeInstanceOf(ConfirmDialogComponent); + return (component as unknown as { opts: ConfirmDialogOptions }).opts; +} + +const SELECTION = { alias: 'cheap', thinking: 'off' as ThinkingEffort }; + +describe('handleUpdateAllSessionModelsCommand', () => { + it('opens the same model picker as /model with the active-session set', async () => { + const { host } = makeHost(); + + await handleUpdateAllSessionModelsCommand(host, ''); + + expect(host.harness.listSessions).toHaveBeenCalled(); + // Current session is folded into the listed set, so two active sessions. + expect(Object.keys(picker(host).models)).toEqual(['k2', 'cheap']); + }); + + it('requires confirmation, then applies to every session and persists the default', async () => { + const { host, current, other } = makeHost(); + + await handleUpdateAllSessionModelsCommand(host, ''); + picker(host).onSelect(SELECTION); + const dialog = confirmDialog(host); + expect(dialog.title).toContain('2 active sessions'); + + dialog.onResolve(true); + + await vi.waitFor(() => { + expect(current.setModel).toHaveBeenCalledWith('cheap'); + }); + expect(other.setModel).toHaveBeenCalledWith('cheap'); + expect(host.harness.setConfig).toHaveBeenCalledWith({ defaultModel: 'cheap' }); + expect(host.setAppState).toHaveBeenCalledWith({ model: 'cheap' }); + expect(host.showStatus).toHaveBeenCalled(); + expect(host.showError).not.toHaveBeenCalled(); + }); + + it('makes no changes when the user cancels the confirmation', async () => { + const { host, current, other } = makeHost(); + + await handleUpdateAllSessionModelsCommand(host, ''); + picker(host).onSelect(SELECTION); + confirmDialog(host).onResolve(false); + + expect(current.setModel).not.toHaveBeenCalled(); + expect(other.setModel).not.toHaveBeenCalled(); + expect(host.harness.setConfig).not.toHaveBeenCalled(); + expect(host.showStatus).toHaveBeenCalledWith('Cancelled — no sessions were changed.', 'textDim'); + }); + + it('resumes sessions not already open, and reports a skipped session without aborting the rest', async () => { + const { host, current } = makeHost({ getSessionReturns: undefined, resumeRejects: true }); + + await handleUpdateAllSessionModelsCommand(host, ''); + picker(host).onSelect(SELECTION); + confirmDialog(host).onResolve(true); + + await vi.waitFor(() => { + expect(host.showNotice).toHaveBeenCalled(); + }); + // The current session still switches; the unresolvable one is reported, not fatal. + expect(current.setModel).toHaveBeenCalledWith('cheap'); + expect(host.showStatus).toHaveBeenCalled(); + }); +}); From b6c42b0167d2f1480d3345e29e283520fcb4faf1 Mon Sep 17 00:00:00 2001 From: Ahmet TOK <48218623+arrrrny@users.noreply.github.com> Date: Sun, 23 Aug 2026 09:08:08 +0300 Subject: [PATCH 16/71] feat(agent-core-v2): add dedicated compaction model with fallback (#1) Mirror the visual/secondary-model pattern: when the compaction-model experiment is enabled and [compaction_model] is configured, context compaction uses that dedicated model instead of the current one. If the dedicated model errors or is inaccessible (an uncatalogued alias), compaction transparently falls back to the current model on the same round, so the dedicated model is never a single point of failure. Adds resolver unit tests and end-to-end fallback integration tests. --- .changeset/compaction-model-option.md | 5 + .../fullCompaction/fullCompactionService.ts | 59 ++++- .../src/agent/llmRequester/llmRequester.ts | 1 + .../agent/llmRequester/llmRequesterService.ts | 5 +- .../src/agent/profile/profile.ts | 1 + .../src/agent/profile/profileService.ts | 14 ++ .../kosongConfig/compactionModelOverlay.ts | 104 +++++++++ .../src/app/kosongConfig/configSection.ts | 22 ++ .../agent-core-v2/src/app/telemetry/events.ts | 4 + packages/agent-core-v2/src/index.ts | 25 ++- .../src/session/compaction/configSection.ts | 128 +++++++++++ .../src/session/compaction/flag.ts | 28 +++ .../fullCompaction/compaction-model.test.ts | 202 ++++++++++++++++++ .../session/compaction/configSection.test.ts | 166 ++++++++++++++ 14 files changed, 761 insertions(+), 3 deletions(-) create mode 100644 .changeset/compaction-model-option.md create mode 100644 packages/agent-core-v2/src/app/kosongConfig/compactionModelOverlay.ts create mode 100644 packages/agent-core-v2/src/session/compaction/configSection.ts create mode 100644 packages/agent-core-v2/src/session/compaction/flag.ts create mode 100644 packages/agent-core-v2/test/agent/fullCompaction/compaction-model.test.ts create mode 100644 packages/agent-core-v2/test/session/compaction/configSection.test.ts diff --git a/.changeset/compaction-model-option.md b/.changeset/compaction-model-option.md new file mode 100644 index 00000000000..e6a4fe903a8 --- /dev/null +++ b/.changeset/compaction-model-option.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/agent-core-v2": minor +--- + +Add an opt-in dedicated compaction model: when the `compaction-model` experiment is enabled and `[compaction_model]` is configured, context compaction uses that model instead of the current one, and transparently falls back to the current model if it errors or is inaccessible. diff --git a/packages/agent-core-v2/src/agent/fullCompaction/fullCompactionService.ts b/packages/agent-core-v2/src/agent/fullCompaction/fullCompactionService.ts index 4fa80358a07..014355e4f72 100644 --- a/packages/agent-core-v2/src/agent/fullCompaction/fullCompactionService.ts +++ b/packages/agent-core-v2/src/agent/fullCompaction/fullCompactionService.ts @@ -18,6 +18,13 @@ import { TurnStarted } from '#/agent/loop/turnEvents'; import { TurnEnded } from '#/agent/loop/turnOps'; import { isAbortError } from '#/_base/utils/abort'; import { IAgentProfileService, type ProfileModelContext } from '#/agent/profile/profile'; +import { IConfigService } from '#/app/config/config'; +import { IFlagService } from '#/app/flag/flag'; +import { + compactionDisplayModel, + compactionModelBindingFor, + wrapCompactionModelError, +} from '#/session/compaction/configSection'; import { agentContextOfScope, IAgentScopeContext, @@ -152,6 +159,8 @@ export class AgentFullCompactionService extends Service implements IAgentFullCom @ILogService private readonly log: ILogService, @IAgentLoopService private readonly loopService: IAgentLoopService, @IAgentStateService private readonly states: IAgentStateService, + @IConfigService private readonly configService: IConfigService, + @IFlagService private readonly flags: IFlagService, ) { super(); this.states.contributeState(fullCompactionKey); @@ -634,12 +643,41 @@ export class AgentFullCompactionService extends Service implements IAgentFullCom const resolvedModel = this.profile.resolveModelContext(); thinkingEffort = resolvedModel.thinkingLevel; const maxContextTokens = resolvedModel.modelCapabilities.max_context_tokens; + const currentModelAlias = resolvedModel.modelAlias; const defaultCompactionCap = maxContextTokens > 0 ? Math.min(maxContextTokens, DEFAULT_COMPACTION_MAX_COMPLETION_TOKENS) : undefined; const compactionMaxOutputSize = resolvedModel.maxOutputSize ?? defaultCompactionCap; + const binding = compactionModelBindingFor(this.configService, this.flags, { + modelAlias: currentModelAlias, + thinkingLevel: thinkingEffort, + }); + const dedicatedModelAlias = binding.model; + let hasDedicatedModel = dedicatedModelAlias !== currentModelAlias; + let usingFallbackModel = false; + let boundModel = resolvedModel; + if (hasDedicatedModel) { + try { + boundModel = this.profile.resolveModelContextFor(dedicatedModelAlias); + } catch (error) { + this.log.warn( + `compaction model "${dedicatedModelAlias}" is not configured; falling back to current model "${currentModelAlias}"`, + { cause: wrapCompactionModelError(error, dedicatedModelAlias) }, + ); + hasDedicatedModel = false; + boundModel = resolvedModel; + } + } + const boundMaxOutputSize = boundModel.maxOutputSize ?? defaultCompactionCap; + let compactionRequestModel = hasDedicatedModel ? dedicatedModelAlias : undefined; + let effectiveModelAlias = hasDedicatedModel ? dedicatedModelAlias : currentModelAlias; + const effectiveMaxOutputSize = Math.min( + compactionMaxOutputSize ?? DEFAULT_COMPACTION_MAX_COMPLETION_TOKENS, + boundMaxOutputSize ?? DEFAULT_COMPACTION_MAX_COMPLETION_TOKENS, + ); + const customInstruction = data.instruction?.trim() ?? ''; const instruction = renderPrompt(compactionInstructionTemplate, { custom_instruction_block: @@ -661,7 +699,8 @@ export class AgentFullCompactionService extends Service implements IAgentFullCom const request = this.llmRequester.start( { messages, - maxOutputSize: compactionMaxOutputSize, + maxOutputSize: effectiveMaxOutputSize, + model: compactionRequestModel, source: { type: 'operation', turnId: active.originTurnId, @@ -716,6 +755,22 @@ export class AgentFullCompactionService extends Service implements IAgentFullCom retryCount = 0; continue; } + if ( + hasDedicatedModel && + !usingFallbackModel && + (isRetryableGenerateError(unwrappedError) || + !(error instanceof CompactionTruncatedError)) + ) { + usingFallbackModel = true; + effectiveModelAlias = currentModelAlias; + compactionRequestModel = undefined; + this.log.warn( + `compaction model "${dedicatedModelAlias}" failed; falling back to current model "${currentModelAlias}"`, + { cause: wrapCompactionModelError(error, dedicatedModelAlias) }, + ); + retryCount = 0; + continue; + } if (!isRetryableGenerateError(unwrappedError)) { throw error; } @@ -764,6 +819,8 @@ export class AgentFullCompactionService extends Service implements IAgentFullCom round: 1, thinking_effort: thinkingEffort, trace_id: attempt.traceId, + model: effectiveModelAlias, + model_display: compactionDisplayModel(this.configService, effectiveModelAlias), ...usageTelemetry(attempt.usage), }; this.telemetry.track2('compaction_finished', properties); diff --git a/packages/agent-core-v2/src/agent/llmRequester/llmRequester.ts b/packages/agent-core-v2/src/agent/llmRequester/llmRequester.ts index dceb42ac37b..88a3fb7aea4 100644 --- a/packages/agent-core-v2/src/agent/llmRequester/llmRequester.ts +++ b/packages/agent-core-v2/src/agent/llmRequester/llmRequester.ts @@ -42,6 +42,7 @@ export interface AgentLLMRequestOverrides { systemPrompt?: string; source?: AgentLLMRequestSource; maxOutputSize?: number; + model?: string; } export interface AgentLLMRequestTask { diff --git a/packages/agent-core-v2/src/agent/llmRequester/llmRequesterService.ts b/packages/agent-core-v2/src/agent/llmRequester/llmRequesterService.ts index 45dc08dbe62..dfada1d721a 100644 --- a/packages/agent-core-v2/src/agent/llmRequester/llmRequesterService.ts +++ b/packages/agent-core-v2/src/agent/llmRequester/llmRequesterService.ts @@ -582,7 +582,10 @@ export class AgentLLMRequesterService implements IAgentLLMRequesterService { private resolveRequest(overrides: AgentLLMRequestOverrides): ResolvedLLMRequest { const turnConfig = this.resolveTurnConfig(overrides.source); - const resolved = turnConfig?.resolved ?? this.profile.resolveModelContext(); + const resolved = + overrides.model !== undefined + ? this.profile.resolveModelContextFor(overrides.model) + : turnConfig?.resolved ?? this.profile.resolveModelContext(); const baseParams = turnConfig?.params ?? this.profile.resolveRequestParams(); const budgetParams = completionBudgetParams({ budget: resolveCompletionBudget({ diff --git a/packages/agent-core-v2/src/agent/profile/profile.ts b/packages/agent-core-v2/src/agent/profile/profile.ts index 529c23e4289..f21ce33b009 100644 --- a/packages/agent-core-v2/src/agent/profile/profile.ts +++ b/packages/agent-core-v2/src/agent/profile/profile.ts @@ -127,6 +127,7 @@ export interface IAgentProfileService { data(): ProfileData; getEffectiveThinkingLevel(): ThinkingEffort; resolveModelContext(): ProfileModelContext; + resolveModelContextFor(modelAlias: string): ProfileModelContext; resolveRequestParams(): ModelRequestParams; getModelCapabilities(): ModelCapability; getMaxOutputSize(): number | undefined; diff --git a/packages/agent-core-v2/src/agent/profile/profileService.ts b/packages/agent-core-v2/src/agent/profile/profileService.ts index 2b504afa03d..08d08df5a97 100644 --- a/packages/agent-core-v2/src/agent/profile/profileService.ts +++ b/packages/agent-core-v2/src/agent/profile/profileService.ts @@ -504,6 +504,20 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ }; } + resolveModelContextFor(modelAlias: string): ProfileModelContext { + const model = this.modelCatalog.get(modelAlias); + const loopControl = this.config.get('loopControl'); + return { + modelAlias, + modelCapabilities: model.capabilities, + maxOutputSize: model.maxOutputSize, + alwaysThinking: model.alwaysThinking || undefined, + thinkingLevel: this.resolveThinkingState(model).effective, + reservedContextSize: loopControl?.reservedContextSize, + compactionTriggerRatio: loopControl?.compactionTriggerRatio, + }; + } + resolveRequestParams(): ModelRequestParams { const model = this.tryResolveRawModel(); const thinking = this.resolveThinkingState(model); diff --git a/packages/agent-core-v2/src/app/kosongConfig/compactionModelOverlay.ts b/packages/agent-core-v2/src/app/kosongConfig/compactionModelOverlay.ts new file mode 100644 index 00000000000..754b81dc5a0 --- /dev/null +++ b/packages/agent-core-v2/src/app/kosongConfig/compactionModelOverlay.ts @@ -0,0 +1,104 @@ +import type { ConfigEffectiveOverlay } from '#/app/config/config'; +import { registerConfigOverlay } from '#/app/config/configOverlayContributions'; +import { isPlainObject } from '#/app/config/toml'; +import type { ModelOverride } from '#/kosong/model/model'; + +import { + COMPACTION_MODEL_SECTION, + DEFAULT_MODEL_SECTION, + MODELS_SECTION, + type CompactionModelConfig, +} from './configSection'; + +/** + * `kosongConfig` domain — `[compaction_model]` derived-entry overlay. + * + * Compaction-model mirror of {@link visualModelOverlay}: when the + * compaction-model recipe carries patch fields, synthesizes the derived + * registry entry ({@link COMPACTION_DERIVED_MODEL_ID}) into the effective + * `models` view — a copy of the pointed entry with the patch merged into its + * `overrides` block (patch wins conflicts) and `aliases` dropped, so the + * derived entry never competes in name/alias routing. Compaction-model binding + * then resolves it by name through the standard catalog path, and the patch + * rides the same `effectiveModelConfig` merge as any `models.*.overrides` + * (including its `supportEfforts` / `defaultEffort` pruning and input + * clamping). + * + * The synthesized entry lives ONLY in the in-memory effective view: `strip` + * removes it from `models` writes so it never reaches `config.toml`, and the + * persistence bridge's deep-equal guards keep the two-way sync silent. `strip` + * also rolls back a `defaultModel` pointer set to the derived id (restoring the + * raw value). Nothing is synthesized when the recipe has no patch fields (when + * `compaction.model` is unset), or when the pointed entry does not exist. The + * id is reserved: a user-configured entry under it is stripped on write all the + * same. + * + * Self-registered at module load via `registerConfigOverlay`; it is imported + * for side effects after the visual-model overlay. + */ +export const COMPACTION_DERIVED_MODEL_ID = '__compaction__'; + +export function compactionModelPatch( + compaction: CompactionModelConfig | undefined, +): ModelOverride | undefined { + if (compaction === undefined) return undefined; + const { model: _model, ...patch } = compaction; + return Object.keys(patch).length > 0 ? patch : undefined; +} + +function asRecord(value: unknown): Record { + return isPlainObject(value) ? value : {}; +} + +function withoutKey(value: unknown, key: string): unknown { + if (!isPlainObject(value) || !(key in value)) return value; + const out: Record = { ...value }; + delete out[key]; + return out; +} + +export const compactionModelOverlay: ConfigEffectiveOverlay = { + apply(effective, _getEnv, validate) { + const compaction = effective[COMPACTION_MODEL_SECTION] as + | CompactionModelConfig + | undefined; + const patch = compactionModelPatch(compaction); + const baseId = compaction?.model; + if ( + patch === undefined || + baseId === undefined || + baseId === COMPACTION_DERIVED_MODEL_ID + ) { + return []; + } + const models = asRecord(effective[MODELS_SECTION]); + const base = models[baseId]; + if (!isPlainObject(base)) return []; + const { overrides: baseOverrides, aliases: _aliases, ...baseFields } = base; + const derived: Record = { + ...baseFields, + overrides: { ...asRecord(baseOverrides), ...patch }, + }; + effective[MODELS_SECTION] = validate(MODELS_SECTION, { + ...models, + [COMPACTION_DERIVED_MODEL_ID]: derived, + }); + return [MODELS_SECTION]; + }, + + strip(domain, value, rawSnake) { + switch (domain) { + case MODELS_SECTION: + return withoutKey(value, COMPACTION_DERIVED_MODEL_ID); + case DEFAULT_MODEL_SECTION: + if (value !== COMPACTION_DERIVED_MODEL_ID) return value; + return typeof rawSnake['default_model'] === 'string' + ? rawSnake['default_model'] + : undefined; + default: + return value; + } + }, +}; + +registerConfigOverlay(compactionModelOverlay); diff --git a/packages/agent-core-v2/src/app/kosongConfig/configSection.ts b/packages/agent-core-v2/src/app/kosongConfig/configSection.ts index 3efcaa6178f..dc76536ea3d 100644 --- a/packages/agent-core-v2/src/app/kosongConfig/configSection.ts +++ b/packages/agent-core-v2/src/app/kosongConfig/configSection.ts @@ -335,6 +335,28 @@ registerConfigSection(VISUAL_MODEL_SECTION, VisualModelConfigSchema, { }); +export const COMPACTION_MODEL_SECTION = 'compactionModel'; + +export const COMPACTION_MODEL_ENV = 'KIMI_COMPACTION_MODEL'; +export const COMPACTION_MODEL_EFFORT_ENV = 'KIMI_COMPACTION_EFFORT'; + +export const CompactionModelConfigSchema = ModelOverrideSchema.extend({ + model: z.string().min(1).optional(), +}); + +export type CompactionModelConfig = z.infer; + +export const compactionModelEnvBindings = envBindings(CompactionModelConfigSchema, { + model: { env: COMPACTION_MODEL_ENV, parse: parseNonEmptyEnv }, + defaultEffort: { env: COMPACTION_MODEL_EFFORT_ENV, parse: parseNonEmptyEnv }, +}); + +registerConfigSection(COMPACTION_MODEL_SECTION, CompactionModelConfigSchema, { + env: compactionModelEnvBindings, + stripEnv: stripEnvBoundFields(compactionModelEnvBindings), +}); + + export const MODEL_CATALOG_SECTION = 'modelCatalog'; export const ModelCatalogConfigSchema = z.object({ diff --git a/packages/agent-core-v2/src/app/telemetry/events.ts b/packages/agent-core-v2/src/app/telemetry/events.ts index 4756af76512..a9fe1fbc365 100644 --- a/packages/agent-core-v2/src/app/telemetry/events.ts +++ b/packages/agent-core-v2/src/app/telemetry/events.ts @@ -208,6 +208,8 @@ export interface CompactionFinishedEvent { input_cache_read?: number; input_cache_creation?: number; trace_id?: string; + model?: string; + model_display?: string; } export interface CompactionFailedEvent { @@ -676,6 +678,8 @@ export const telemetryEventDefinitions = { input_cache_creation: 'Cache-creation input tokens', trace_id: 'Trace id of the final compaction request round; absent for non-Kimi protocols', + model: 'Model alias that produced the compaction summary (dedicated compaction model when configured, otherwise the active conversation model)', + model_display: 'User-facing model alias for the compaction summary producer', }, }), compaction_failed: defineAgentTelemetryEvent({ diff --git a/packages/agent-core-v2/src/index.ts b/packages/agent-core-v2/src/index.ts index 66e21d6e3a6..efdd316650a 100644 --- a/packages/agent-core-v2/src/index.ts +++ b/packages/agent-core-v2/src/index.ts @@ -175,6 +175,7 @@ export * from '#/kosong/protocol/protocolBase'; export * from '#/kosong/protocol/protocolTrait'; import '#/app/kosongConfig/envOverlay'; import '#/app/kosongConfig/visualModelOverlay'; +import '#/app/kosongConfig/compactionModelOverlay'; export * from '#/kosong/model/completionBudget'; export * from '#/kosong/model/hostRequestHeaders'; export * from '#/kosong/model/model'; @@ -190,7 +191,7 @@ export { ModelCatalogConfigSchema, type ModelCatalogConfig, } from '#/app/kosongConfig/configSection'; -export type { SecondaryModelConfig, VisualModelConfig } from '#/app/kosongConfig/configSection'; +export type { SecondaryModelConfig, VisualModelConfig, CompactionModelConfig } from '#/app/kosongConfig/configSection'; export { SECONDARY_MODEL_SECTION, SECONDARY_MODEL_ENV, @@ -202,12 +203,22 @@ export { VISUAL_MODEL_EFFORT_ENV, VisualModelConfigSchema, visualModelEnvBindings, + COMPACTION_MODEL_SECTION, + COMPACTION_MODEL_ENV, + COMPACTION_MODEL_EFFORT_ENV, + CompactionModelConfigSchema, + compactionModelEnvBindings, } from '#/app/kosongConfig/configSection'; export { VISUAL_DERIVED_MODEL_ID, visualModelOverlay, visualModelPatch, } from '#/app/kosongConfig/visualModelOverlay'; +export { + COMPACTION_DERIVED_MODEL_ID, + compactionModelOverlay, + compactionModelPatch, +} from '#/app/kosongConfig/compactionModelOverlay'; export * from '#/app/kosongConfig/kosongConfig'; export * from '#/app/kosongConfig/kosongConfigService'; export * from '#/kosong/model/modelOAuth'; @@ -492,6 +503,8 @@ export * from '#/session/subagent/mirrorAgentRun'; import '#/session/subagent/configSection'; import '#/session/visual/flag'; import '#/session/visual/configSection'; +import '#/session/compaction/flag'; +import '#/session/compaction/configSection'; import '#/session/substitute/flag'; import '#/session/substitute/configSection'; export { @@ -509,6 +522,16 @@ export { VISUAL_MODEL_CHOICE_SCHEMA, type VisualModelChoice, } from '#/session/visual/configSection'; +export { + COMPACTION_MODEL_FLAG_ID, + COMPACTION_MODEL_FLAG_ENV, + compactionModelFlag, +} from '#/session/compaction/flag'; +export { + resolveCompactionModel, + resolveCompactionBinding, + compactionModelBindingFor, +} from '#/session/compaction/configSection'; export { SUBSTITUTE_MODEL_FLAG_ID, SUBSTITUTE_MODEL_FLAG_ENV, diff --git a/packages/agent-core-v2/src/session/compaction/configSection.ts b/packages/agent-core-v2/src/session/compaction/configSection.ts new file mode 100644 index 00000000000..a1933caa29f --- /dev/null +++ b/packages/agent-core-v2/src/session/compaction/configSection.ts @@ -0,0 +1,128 @@ +import type { IConfigService } from '#/app/config/config'; +import type { IFlagService } from '#/app/flag/flag'; +import { + COMPACTION_MODEL_ENV, + COMPACTION_MODEL_SECTION, + type CompactionModelConfig, +} from '#/app/kosongConfig/configSection'; +import { + COMPACTION_DERIVED_MODEL_ID, + compactionModelPatch, +} from '#/app/kosongConfig/compactionModelOverlay'; + +import { COMPACTION_MODEL_FLAG_ID } from './flag'; + +export { COMPACTION_DERIVED_MODEL_ID }; + +/** + * `compaction` domain — compaction-model config-section resolver. + * + * Compaction-model mirror of {@link ../../../session/visual/configSection}: + * resolves which model handles context compaction when the `compaction-model` + * experiment is enabled and `[compaction_model]` is configured. The active + * conversation model remains the default; the compaction model is an opt-in + * override for the summarization/compaction step, parallel to how the visual + * model is an opt-in override for visual inspection tasks. + * + * Resolution rules (mirror of `resolveVisualModel` / `resolveVisualBinding`): + * - When the experiment is disabled, or `[compaction_model]` is unset, returns + * `undefined` from {@link resolveCompactionModel} and the caller's own model + * from {@link resolveCompactionBinding} — no behavior change. + * - When set, {@link resolveCompactionModel} returns the configured recipe; a + * recipe with patch fields binds the synthesized derived entry + * ({@link COMPACTION_DERIVED_MODEL_ID}, materialized by + * `compactionModelOverlay`); a pointer-only recipe binds the pointed entry + * directly. `default_effort` is passed as the explicit compaction thinking + * effort; without it the compaction step resolves thinking naturally (global + * thinking config → the bound model's default effort) rather than inheriting + * the caller's level. + * + * The caller resolves a binding via {@link compactionModelBindingFor}: a helper + * that returns the dedicated compaction model when configured, or the caller's + * own model otherwise. When the dedicated model errors or is inaccessible, the + * caller transparently retries the same round on its own model — the dedicated + * model is a best-effort override, never a hard dependency. Display-facing + * alias resolution goes through {@link compactionDisplayModel}: the derived + * entry id means nothing to a user, so it resolves back to the recipe's base + * alias. + */ +export interface CompactionBinding { + readonly model: string; + readonly thinking?: string; + readonly displayModel: string; +} + +export function resolveCompactionModel( + config: IConfigService, + flags: IFlagService, +): CompactionModelConfig | undefined { + if (!flags.enabled(COMPACTION_MODEL_FLAG_ID)) return undefined; + return config.get(COMPACTION_MODEL_SECTION); +} + +/** + * Resolve which model handles a compaction round. `own` is the caller's current + * model state, used when inheriting (compaction model unset). Returns the + * dedicated compaction model when configured, otherwise the caller's own model. + */ +export function resolveCompactionBinding( + config: IConfigService, + flags: IFlagService, + own: { modelAlias: string; thinkingLevel: string }, +): CompactionBinding { + const compaction = resolveCompactionModel(config, flags); + if (compaction?.model !== undefined) { + const model = + compactionModelPatch(compaction) === undefined + ? compaction.model + : COMPACTION_DERIVED_MODEL_ID; + return { + model, + thinking: compaction.defaultEffort, + displayModel: compactionDisplayModel(config, model), + }; + } + return { + model: own.modelAlias, + thinking: own.thinkingLevel, + displayModel: compactionDisplayModel(config, own.modelAlias), + }; +} + +/** + * Convenience wrapper around {@link resolveCompactionBinding} that fails back to + * the caller's own model when the compaction model is not configured. The + * dedicated model is never a hard requirement: callers treat the returned + * binding as a best-effort override and retry on their own model on error. + */ +export function compactionModelBindingFor( + config: IConfigService, + flags: IFlagService, + own: { modelAlias: string; thinkingLevel: string }, +): CompactionBinding { + return resolveCompactionBinding(config, flags, own); +} + +export function compactionDisplayModel(config: IConfigService, boundAlias: string): string { + if (boundAlias !== COMPACTION_DERIVED_MODEL_ID) return boundAlias; + return ( + config.get(COMPACTION_MODEL_SECTION)?.model ?? boundAlias + ); +} + +/** + * Point a compaction-model resolution failure at `[compaction_model]` when the + * bound model is not the caller's own — otherwise the caller sees a bare + * "model not configured" error with no hint that it comes from the compaction + * model configuration. Used by callers to wrap a dedicated-model error before + * falling back to the current model. + */ +export function wrapCompactionModelError(error: unknown, boundModel: string): unknown { + if (boundModel === COMPACTION_DERIVED_MODEL_ID) { + return new Error( + `Compaction model "${boundModel}" from [compaction_model] / ${COMPACTION_MODEL_ENV} is not a valid [models] entry`, + { cause: error }, + ); + } + return error; +} diff --git a/packages/agent-core-v2/src/session/compaction/flag.ts b/packages/agent-core-v2/src/session/compaction/flag.ts new file mode 100644 index 00000000000..e16498f65a0 --- /dev/null +++ b/packages/agent-core-v2/src/session/compaction/flag.ts @@ -0,0 +1,28 @@ +import { type FlagDefinitionInput, registerFlagDefinition } from '#/app/flag/flagRegistry'; + +/** + * `compaction` domain — registers the `compaction-model` experimental flag + * into `flag`. + * + * Compaction-model mirror of {@link visualModelFlag}: gates dedicated-model + * selection for context compaction. When this experiment is enabled and + * `[compaction_model]` is configured, the full-compaction routine asks a + * separately configured model to summarize/compact context instead of using + * the active conversation model. When unset, behavior is unchanged (compaction + * inherits the caller's model). If the dedicated model errors or is + * inaccessible, compaction transparently falls back to the current model. + */ +export const COMPACTION_MODEL_FLAG_ID = 'compaction-model'; +export const COMPACTION_MODEL_FLAG_ENV = 'KIMI_CODE_EXPERIMENTAL_COMPACTION_MODEL'; + +export const compactionModelFlag: FlagDefinitionInput = { + id: COMPACTION_MODEL_FLAG_ID, + title: 'Dedicated model for context compaction', + description: + 'Let context compaction use a separately configured model by default, so a less capable or more expensive conversation model can offload summarization to a dedicated compaction model.', + env: COMPACTION_MODEL_FLAG_ENV, + default: false, + surface: 'core', +}; + +registerFlagDefinition(compactionModelFlag); diff --git a/packages/agent-core-v2/test/agent/fullCompaction/compaction-model.test.ts b/packages/agent-core-v2/test/agent/fullCompaction/compaction-model.test.ts new file mode 100644 index 00000000000..05ae86d903c --- /dev/null +++ b/packages/agent-core-v2/test/agent/fullCompaction/compaction-model.test.ts @@ -0,0 +1,202 @@ +/** + * `agent/fullCompaction` — dedicated compaction model integration tests. + * + * Exercises the end-to-end wiring of the `[compaction_model]` experiment inside + * `AgentFullCompactionService`: + * - US1: when the flag is on and `[compaction_model]` points at a valid model, + * compaction uses that model (telemetry `model` reflects it). + * - US2: when the dedicated model errors or is inaccessible, compaction + * transparently falls back to the current model on the same round + * (telemetry `model` reflects the current model, the round still completes). + * - US3: when the flag is off or `[compaction_model]` is unset, compaction uses + * the current model with no behavior change (no-regression). + * + * Mirrors the manual-compaction flow from `fullCompaction.test.ts` but drives + * the model selection through the experimental flag + config section. + */ + +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { APIConnectionError } from '#/kosong/contract/errors'; +import type { Message } from '#/kosong/contract/message'; +import { COMPACTION_MODEL_FLAG_ENV } from '#/session/compaction/flag'; +import { recordingTelemetry, type TelemetryRecord } from '../../app/telemetry/stubs'; +import { llmGenerateServices, testAgent, type TestAgentOptions } from '../../harness'; + +type GenerateFn = NonNullable; + +const PROVIDER = { + type: 'kimi', + apiKey: 'test-key', + baseUrl: 'https://api.example.test/v1', + model: 'kimi-code', +} as const; + +const MODEL_CAPABILITIES = { + image_in: false, + video_in: false, + audio_in: false, + thinking: true, + tool_use: true, + max_context_tokens: 256_000, +} as const; + +const DEDICATED_MODEL = { + provider: 'test-provider', + model: 'compaction-model', + maxContextSize: 256_000, + capabilities: ['thinking', 'tool_use'], +} as const; + +function compactionFinished(records: readonly TelemetryRecord[]): TelemetryRecord | undefined { + return records.find((record) => record.event === 'compaction_finished'); +} + +function makeAgent(options: { + readonly initialConfig?: Record; + readonly generate?: GenerateFn; +} = {}) { + const records: TelemetryRecord[] = []; + const ctx = testAgent( + ...(options.generate !== undefined ? [llmGenerateServices(options.generate)] : []), + { + telemetry: recordingTelemetry(records), + initialConfig: { + providers: {}, + models: { + 'kimi/compaction': DEDICATED_MODEL, + }, + ...options.initialConfig, + }, + }, + ); + ctx.configure({ provider: PROVIDER, modelCapabilities: MODEL_CAPABILITIES }); + return { ctx, records }; +} + +function seedHistory(ctx: ReturnType['ctx']): void { + ctx.appendExchange(1, 'old user one', 'old assistant one', 20); + ctx.appendExchange(2, 'old user two', 'old assistant two', 40); + ctx.appendExchange(3, 'recent user three', 'recent assistant three', 120); +} + +async function runManualCompaction( + ctx: ReturnType['ctx'], + records: readonly TelemetryRecord[], + text = 'Compacted summary.', +): Promise { + const completed = ctx.once('compaction.completed'); + ctx.mockNextResponse({ type: 'text', text }); + await ctx.rpc.beginCompaction({ instruction: 'Keep the important test facts.' }); + await Promise.race([ + completed, + new Promise((_, reject) => + setTimeout( + () => { + reject( + new Error( + `timeout; events=${JSON.stringify(records.map((r) => r.event))}`, + ), + ); + }, + 8000, + ), + ), + ]); +} + +describe('FullCompaction — dedicated compaction model', () => { + afterEach(() => { + vi.unstubAllEnvs(); + }); + + it('uses the dedicated model when the flag is on and [compaction_model] is set', async () => { + vi.stubEnv(COMPACTION_MODEL_FLAG_ENV, 'true'); + const { ctx, records } = makeAgent({ initialConfig: { compactionModel: { model: 'kimi/compaction' } } }); + seedHistory(ctx); + + await runManualCompaction(ctx, records); + + const finished = compactionFinished(records); + expect(finished).toBeDefined(); + expect(finished?.properties?.['model']).toBe('kimi/compaction'); + expect(finished?.properties?.['model_display']).toBe('kimi/compaction'); + }); + + it('falls back to the current model when the dedicated model is inaccessible', async () => { + vi.stubEnv(COMPACTION_MODEL_FLAG_ENV, 'true'); + const { ctx, records } = makeAgent({ + initialConfig: { compactionModel: { model: 'kimi/ghost' } }, + }); + seedHistory(ctx); + + await runManualCompaction(ctx, records); + + const finished = compactionFinished(records); + expect(finished).toBeDefined(); + expect(finished?.properties?.['model']).toBe('kimi-code'); + }); + + it('falls back to the current model when the dedicated model errors on the first call', async () => { + vi.stubEnv(COMPACTION_MODEL_FLAG_ENV, 'true'); + let callCount = 0; + const generate: GenerateFn = async (_chat, _systemPrompt, _tools, _history, _callbacks, options) => { + options?.signal?.throwIfAborted(); + callCount += 1; + if (callCount === 1) { + throw new APIConnectionError('simulated connection failure'); + } + const message: Message = { + role: 'assistant', + content: [{ type: 'text', text: 'Compacted summary.' }], + toolCalls: [], + }; + options?.onStreamEnd?.(); + return { + id: 'mock-fallback', + message, + usage: { inputOther: 0, output: 0, inputCacheRead: 0, inputCacheCreation: 0 }, + finishReason: 'completed', + rawFinishReason: 'stop', + traceId: null, + }; + }; + const { ctx, records } = makeAgent({ + generate, + initialConfig: { compactionModel: { model: 'kimi/compaction' } }, + }); + seedHistory(ctx); + + await runManualCompaction(ctx, records); + + expect(callCount).toBe(2); + const finished = compactionFinished(records); + expect(finished).toBeDefined(); + expect(finished?.properties?.['model']).toBe('kimi-code'); + }); + + it('uses the current model when the flag is off (no behavior change)', async () => { + const { ctx, records } = makeAgent({ + initialConfig: { compactionModel: { model: 'kimi/compaction' } }, + }); + seedHistory(ctx); + + await runManualCompaction(ctx, records); + + const finished = compactionFinished(records); + expect(finished).toBeDefined(); + expect(finished?.properties?.['model']).toBe('kimi-code'); + }); + + it('uses the current model when the flag is on but [compaction_model] is unset', async () => { + vi.stubEnv(COMPACTION_MODEL_FLAG_ENV, 'true'); + const { ctx, records } = makeAgent(); + seedHistory(ctx); + + await runManualCompaction(ctx, records); + + const finished = compactionFinished(records); + expect(finished).toBeDefined(); + expect(finished?.properties?.['model']).toBe('kimi-code'); + }); +}); diff --git a/packages/agent-core-v2/test/session/compaction/configSection.test.ts b/packages/agent-core-v2/test/session/compaction/configSection.test.ts new file mode 100644 index 00000000000..0f48e15c422 --- /dev/null +++ b/packages/agent-core-v2/test/session/compaction/configSection.test.ts @@ -0,0 +1,166 @@ +/** + * `session/compaction` resolver tests — covers `resolveCompactionModel`, + * `resolveCompactionBinding`, `compactionModelBindingFor`, `compactionDisplayModel`, + * and `wrapCompactionModelError`, including the unset-fallback path. + * + * Mirror of the `session/visual` resolver tests: the compaction model is an + * opt-in override for the compaction step, parallel to how the visual model is + * an opt-in override for visual inspection. Uses the StubConfigService + + * stubFlag helpers. + */ + +import { describe, expect, it } from 'vitest'; + +import { COMPACTION_MODEL_SECTION } from '#/app/kosongConfig/configSection'; +import { COMPACTION_DERIVED_MODEL_ID } from '#/app/kosongConfig/compactionModelOverlay'; +import { + compactionDisplayModel, + compactionModelBindingFor, + resolveCompactionBinding, + resolveCompactionModel, + wrapCompactionModelError, +} from '#/session/compaction/configSection'; +import { COMPACTION_MODEL_FLAG_ID } from '#/session/compaction/flag'; +import { Error2, ErrorCodes } from '#/errors'; + +import { stubFlag } from '../../app/flag/stubs'; +import { StubConfigService } from '../../kosong/stubs'; + +function makeServices(configValues: Record, flagEnabled = true) { + const config = new StubConfigService(configValues); + const flags = stubFlag((id) => flagEnabled && id === COMPACTION_MODEL_FLAG_ID); + return { config, flags }; +} + +const own = { modelAlias: 'caller/kimi-coder', thinkingLevel: 'medium' }; + +describe('resolveCompactionModel', () => { + it('returns undefined when the compaction-model flag is disabled', () => { + const { config } = makeServices({ [COMPACTION_MODEL_SECTION]: { model: 'kimi/compaction' } }, false); + const { flags } = makeServices({}, false); + expect(resolveCompactionModel(config, flags)).toBeUndefined(); + }); + + it('returns undefined when [compaction_model] is unset (no behavior change)', () => { + const { config, flags } = makeServices({}); + expect(resolveCompactionModel(config, flags)).toBeUndefined(); + }); + + it('returns the configured recipe when set and the flag is on', () => { + const { config, flags } = makeServices({ + [COMPACTION_MODEL_SECTION]: { model: 'kimi/compaction', defaultEffort: 'low' }, + }); + expect(resolveCompactionModel(config, flags)).toEqual({ + model: 'kimi/compaction', + defaultEffort: 'low', + }); + }); +}); + +describe('resolveCompactionBinding', () => { + it('inherits the caller model when compaction model is unset (no behavior change)', () => { + const { config, flags } = makeServices({}); + expect(resolveCompactionBinding(config, flags, own)).toEqual({ + model: own.modelAlias, + thinking: own.thinkingLevel, + displayModel: own.modelAlias, + }); + }); + + it('inherits the caller model when the flag is disabled even if the recipe is set', () => { + const { config } = makeServices({ [COMPACTION_MODEL_SECTION]: { model: 'kimi/compaction' } }); + const { flags } = makeServices({}, false); + expect(resolveCompactionBinding(config, flags, own)).toEqual({ + model: own.modelAlias, + thinking: own.thinkingLevel, + displayModel: own.modelAlias, + }); + }); + + it('binds the compaction model when set (pointer-only recipe)', () => { + const { config, flags } = makeServices({ + [COMPACTION_MODEL_SECTION]: { model: 'kimi/compaction' }, + }); + expect(resolveCompactionBinding(config, flags, own)).toEqual({ + model: 'kimi/compaction', + thinking: undefined, + displayModel: 'kimi/compaction', + }); + }); + + it('binds the derived entry when the recipe carries patch fields', () => { + const { config, flags } = makeServices({ + [COMPACTION_MODEL_SECTION]: { model: 'kimi/compaction', defaultEffort: 'low', maxOutputSize: 4096 }, + }); + const binding = resolveCompactionBinding(config, flags, own); + expect(binding.model).toBe(COMPACTION_DERIVED_MODEL_ID); + expect(binding.thinking).toBe('low'); + // displayModel resolves the derived id back to the recipe's base alias + expect(binding.displayModel).toBe('kimi/compaction'); + }); +}); + +describe('compactionModelBindingFor', () => { + it('mirrors resolveCompactionBinding (inherits caller when unset)', () => { + const { config, flags } = makeServices({}); + expect(compactionModelBindingFor(config, flags, own)).toEqual({ + model: own.modelAlias, + thinking: own.thinkingLevel, + displayModel: own.modelAlias, + }); + }); + + it('binds the compaction model when set (pointer-only recipe)', () => { + const { config, flags } = makeServices({ + [COMPACTION_MODEL_SECTION]: { model: 'kimi/compaction' }, + }); + expect(compactionModelBindingFor(config, flags, own)).toEqual({ + model: 'kimi/compaction', + thinking: undefined, + displayModel: 'kimi/compaction', + }); + }); +}); + +describe('compactionDisplayModel', () => { + it('passes through any non-derived alias', () => { + const { config } = makeServices({}); + expect(compactionDisplayModel(config, 'kimi/compaction')).toBe('kimi/compaction'); + }); + + it('resolves the derived id back to the recipe base alias', () => { + const { config } = makeServices({ + [COMPACTION_MODEL_SECTION]: { model: 'kimi/compaction' }, + }); + expect(compactionDisplayModel(config, COMPACTION_DERIVED_MODEL_ID)).toBe('kimi/compaction'); + }); + + it('falls back to the derived id when the recipe has been removed', () => { + const { config } = makeServices({}); + expect(compactionDisplayModel(config, COMPACTION_DERIVED_MODEL_ID)).toBe(COMPACTION_DERIVED_MODEL_ID); + }); +}); + +describe('wrapCompactionModelError', () => { + const callerModelAlias = 'caller/kimi-coder'; + + it('returns the error unchanged when the bound model is the caller own', () => { + const error = new Error('boom'); + expect(wrapCompactionModelError(error, callerModelAlias)).toBe(error); + }); + + it('returns the error unchanged when the bound model is a pointer-only alias', () => { + const error = new Error('boom'); + expect(wrapCompactionModelError(error, 'kimi/compaction')).toBe(error); + }); + + it('wraps a failure with a hint pointing at [compaction_model] for the derived id', () => { + const error = new Error2(ErrorCodes.CONFIG_INVALID, 'Model "kimi/compaction" is not configured.', { + details: { model: 'kimi/compaction' }, + }); + const wrapped = wrapCompactionModelError(error, COMPACTION_DERIVED_MODEL_ID) as Error; + expect(wrapped).toBeInstanceOf(Error); + expect(wrapped.message).toContain(COMPACTION_DERIVED_MODEL_ID); + expect(wrapped.message).toContain('[compaction_model]'); + }); +}); From 15b67ad5841207b18048b70a48cd46f9667e73e9 Mon Sep 17 00:00:00 2001 From: Ahmet TOK <48218623+arrrrny@users.noreply.github.com> Date: Sun, 23 Aug 2026 10:53:29 +0300 Subject: [PATCH 17/71] feat: auto-compact threshold configurable per session (#3) - Widen [loop_control] compaction_trigger_ratio accepted range from 0.5-0.99 to 0.25-0.99 (both engine schemas). - Add session-scoped override via AgentProfileService setCompactionTriggerRatio (override ?? config precedence, never persisted), exposed through the node-sdk Session facade and RPC client (v2-only; v1 throws not_implemented). - Add /compact-threshold [|off] TUI slash command (v2 engine only) to show/set/clear the per-session auto-compaction threshold, mirroring the session-model override pattern. - Report the effective threshold in SessionStatus (compactionTriggerRatio / compactionTriggerRatioOverridden). - Tests: engine override semantics, config boundary cases (0.24 rejected, 0.25/0.99 accepted), TUI command behavior, and an auto-compaction integration test proving the override fires where the built-in 0.85 default does not. --- .changeset/auto-compact-per-session.md | 6 + apps/kimi-code/src/tui/commands/config.ts | 74 ++++- apps/kimi-code/src/tui/commands/dispatch.ts | 7 +- apps/kimi-code/src/tui/commands/index.ts | 3 +- apps/kimi-code/src/tui/commands/registry.ts | 13 +- .../tui/commands/compact-threshold.test.ts | 146 +++++++++ .../src/agent/loop/configSection.ts | 2 +- .../src/agent/profile/profile.ts | 41 ++- .../src/agent/profile/profileService.ts | 44 ++- .../agent-core-v2/src/app/telemetry/events.ts | 15 +- .../fullCompaction/fullCompaction.test.ts | 69 +++- .../agent/profile/compact-threshold.test.ts | 301 ++++++++++++++++++ .../test/app/config/config.test.ts | 17 +- packages/agent-core-v2/test/harness/agent.ts | 6 +- packages/agent-core/src/config/schema.ts | 2 +- .../agent-core/test/config/configs.test.ts | 24 +- packages/node-sdk/src/rpc.ts | 22 +- packages/node-sdk/src/sdk-rpc-client-v2.ts | 29 +- packages/node-sdk/src/session.ts | 14 +- packages/node-sdk/src/types.ts | 11 +- 20 files changed, 826 insertions(+), 20 deletions(-) create mode 100644 .changeset/auto-compact-per-session.md create mode 100644 apps/kimi-code/test/tui/commands/compact-threshold.test.ts create mode 100644 packages/agent-core-v2/test/agent/profile/compact-threshold.test.ts diff --git a/.changeset/auto-compact-per-session.md b/.changeset/auto-compact-per-session.md new file mode 100644 index 00000000000..acebfe08bb9 --- /dev/null +++ b/.changeset/auto-compact-per-session.md @@ -0,0 +1,6 @@ +--- +"@moonshot-ai/kimi-code": minor +"@moonshot-ai/kimi-code-sdk": minor +--- + +Add per-session auto-compaction thresholds: the accepted `[loop_control] compaction_trigger_ratio` range widens from 0.5-0.99 to 0.25-0.99, and a new `/compact-threshold [|off]` slash command (v2 engine) overrides the global config value for the current session only — like picking a session model while the default stays configured. The SDK exposes `Session.setCompactionTriggerRatio(ratio?)` and new optional `SessionStatus.compactionTriggerRatio` / `compactionTriggerRatioOverridden` read-back fields. \ No newline at end of file diff --git a/apps/kimi-code/src/tui/commands/config.ts b/apps/kimi-code/src/tui/commands/config.ts index d9f0b70e22c..b0ca2c2aa9b 100644 --- a/apps/kimi-code/src/tui/commands/config.ts +++ b/apps/kimi-code/src/tui/commands/config.ts @@ -229,6 +229,78 @@ export async function handleCompactCommand(host: SlashCommandHost, args: string) await session.compact({ instruction: customInstruction }); } +/** Accepted range for `/compact-threshold`; the engine re-validates authoritatively. */ +const COMPACT_THRESHOLD_MIN = 0.25; +const COMPACT_THRESHOLD_MAX = 0.99; +/** Built-in auto-compaction trigger ratio used when neither override nor config sets one. */ +const COMPACT_THRESHOLD_DEFAULT = 0.85; +const COMPACT_THRESHOLD_USAGE = + 'Usage: /compact-threshold [|off] — with no argument, shows the current value.'; + +export async function handleCompactThresholdCommand( + host: SlashCommandHost, + args: string, +): Promise { + const session = host.session; + if (session === undefined) { + host.showError(NO_ACTIVE_SESSION_MESSAGE); + return; + } + + const value = args.trim(); + + // No argument: read-only display of the effective threshold and its source. + if (value.length === 0) { + try { + const status = await session.getStatus(); + const effective = status.compactionTriggerRatio; + const source = + status.compactionTriggerRatioOverridden === true + ? 'session override — /compact-threshold off clears it' + : effective !== undefined + ? 'from config.toml [loop_control] compaction_trigger_ratio' + : 'built-in default (config.toml key not set)'; + host.showNotice(`Auto-compact threshold: ${effective ?? COMPACT_THRESHOLD_DEFAULT}`, source); + } catch (error) { + host.showError(`Failed to read compaction threshold: ${formatErrorMessage(error)}`); + } + return; + } + + // "off" clears the session override and returns to the global value. + if (value === 'off' || value === 'reset' || value === 'clear') { + try { + await session.setCompactionTriggerRatio(undefined); + host.showNotice( + 'Auto-compact threshold override cleared', + 'The config.toml [loop_control] compaction_trigger_ratio value (or built-in default) applies again.', + ); + } catch (error) { + host.showError(`Failed to clear compaction threshold: ${formatErrorMessage(error)}`); + } + return; + } + + const ratio = Number(value); + if (!Number.isFinite(ratio) || ratio < COMPACT_THRESHOLD_MIN || ratio > COMPACT_THRESHOLD_MAX) { + host.showError( + `Invalid threshold "${value}": must be between ${COMPACT_THRESHOLD_MIN} and ${COMPACT_THRESHOLD_MAX}.`, + ); + host.showStatus(COMPACT_THRESHOLD_USAGE); + return; + } + + try { + await session.setCompactionTriggerRatio(ratio); + host.showNotice( + `Auto-compact threshold set to ${ratio} for this session`, + 'Overrides config.toml [loop_control] compaction_trigger_ratio until the session ends.', + ); + } catch (error) { + host.showError(`Failed to set compaction threshold: ${formatErrorMessage(error)}`); + } +} + export async function handleEditorCommand(host: SlashCommandHost, args: string): Promise { const command = args.trim(); if (command.length === 0) { @@ -1274,4 +1346,4 @@ function handleSettingsSelection(host: SlashCommandHost, value: SettingsSelectio case 'upgrade': showUpdatePreferencePicker(host); return; case 'usage': void showUsage(host); return; } -} +} \ No newline at end of file diff --git a/apps/kimi-code/src/tui/commands/dispatch.ts b/apps/kimi-code/src/tui/commands/dispatch.ts index 57ec0e78001..02d1a9ae9d8 100644 --- a/apps/kimi-code/src/tui/commands/dispatch.ts +++ b/apps/kimi-code/src/tui/commands/dispatch.ts @@ -30,6 +30,7 @@ import { handleCopyCommand } from './copy'; import { handleAutoCommand, handleCompactCommand, + handleCompactThresholdCommand, handleEditorCommand, handleEffortCommand, handleModelCommand, @@ -87,6 +88,7 @@ export { handleAddDirCommand } from './add-dir'; export { handleAutoCommand, handleCompactCommand, + handleCompactThresholdCommand, handleEditorCommand, handleEffortCommand, handleModelCommand, @@ -598,6 +600,9 @@ async function handleBuiltInSlashCommand( case 'compact': await handleCompactCommand(host, args); return; + case 'compact-threshold': + await handleCompactThresholdCommand(host, args); + return; case 'goal': await handleGoalCommand(host, args); return; @@ -632,4 +637,4 @@ async function handleBuiltInSlashCommand( host.showError(`Unknown slash command: /${String(name)}`); return; } -} +} \ No newline at end of file diff --git a/apps/kimi-code/src/tui/commands/index.ts b/apps/kimi-code/src/tui/commands/index.ts index 91d8836b332..ce4cef73142 100644 --- a/apps/kimi-code/src/tui/commands/index.ts +++ b/apps/kimi-code/src/tui/commands/index.ts @@ -12,6 +12,7 @@ export { handleBtwCommand } from './btw'; export { handleCopyCommand } from './copy'; export { handleCompactCommand, + handleCompactThresholdCommand, handleEditorCommand, handleModelCommand, handlePlanCommand, @@ -41,4 +42,4 @@ export { promptModelSelectionForOpenPlatform, promptPlatformSelection, runModelSelector, -} from './prompts'; +} from './prompts'; \ No newline at end of file diff --git a/apps/kimi-code/src/tui/commands/registry.ts b/apps/kimi-code/src/tui/commands/registry.ts index d9f17cadf5a..6d30f47b5e9 100644 --- a/apps/kimi-code/src/tui/commands/registry.ts +++ b/apps/kimi-code/src/tui/commands/registry.ts @@ -343,6 +343,17 @@ export const BUILTIN_SLASH_COMMANDS = [ priority: 80, argumentHint: '', }, + { + name: 'compact-threshold', + aliases: [], + description: 'Show or set the per-session auto-compaction trigger ratio (0.25-0.99)', + priority: 80, + argumentHint: '[|off]', + // Reading the effective threshold is always safe; changing it mid-turn + // would re-aim compaction under the running turn, so mutations wait for idle. + availability: (args) => (args.trim() === '' ? 'always' : 'idle-only'), + requiresEngineV2: true, + }, { name: 'goal', aliases: [], @@ -495,4 +506,4 @@ export function sortSlashCommands(commands: readonly KimiSlashCommand[]): KimiSl return [...commands].toSorted( (a, b) => (b.priority ?? 0) - (a.priority ?? 0) || a.name.localeCompare(b.name), ); -} +} \ No newline at end of file diff --git a/apps/kimi-code/test/tui/commands/compact-threshold.test.ts b/apps/kimi-code/test/tui/commands/compact-threshold.test.ts new file mode 100644 index 00000000000..b13c0803a17 --- /dev/null +++ b/apps/kimi-code/test/tui/commands/compact-threshold.test.ts @@ -0,0 +1,146 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { handleCompactThresholdCommand } from '#/tui/commands/index'; +import type { SlashCommandHost } from '#/tui/commands/dispatch'; + +type SessionStatusLike = { + compactionTriggerRatio?: number; + compactionTriggerRatioOverridden?: boolean; +}; + +function makeHost(options: { hasSession?: boolean; status?: SessionStatusLike } = {}) { + const session = { + setCompactionTriggerRatio: vi.fn(async () => {}), + getStatus: vi.fn(async () => ({ + model: 'kimi-model', + thinkingEffort: 'high', + permission: 'auto', + planMode: false, + swarmMode: false, + towerMode: false, + contextTokens: 0, + maxContextTokens: 1000, + contextUsage: 0, + ...(options.status ?? {}), + })), + }; + const hasSession = options.hasSession ?? true; + const host = { + state: { appState: {} }, + session: hasSession ? session : undefined, + showError: vi.fn(), + showStatus: vi.fn(), + showNotice: vi.fn(), + setAppState: vi.fn(), + } as unknown as SlashCommandHost; + return { host, session }; +} + +describe('handleCompactThresholdCommand', () => { + it('errors when no session is active', async () => { + const { host, session } = makeHost({ hasSession: false }); + + await handleCompactThresholdCommand(host, '0.3'); + + expect(host.showError).toHaveBeenCalledTimes(1); + expect(session.setCompactionTriggerRatio).not.toHaveBeenCalled(); + }); + + it('shows the effective threshold with its source when called without arguments', async () => { + const { host } = makeHost({ + status: { compactionTriggerRatio: 0.3, compactionTriggerRatioOverridden: true }, + }); + + await handleCompactThresholdCommand(host, ''); + + expect(host.showNotice).toHaveBeenCalledWith('Auto-compact threshold: 0.3', expect.any(String)); + const detail = (host.showNotice as ReturnType).mock.calls[0]?.[1] as string; + expect(detail).toContain('session override'); + }); + + it('reports the config source when the value comes from config.toml', async () => { + const { host } = makeHost({ status: { compactionTriggerRatio: 0.7 } }); + + await handleCompactThresholdCommand(host, ' '); + + expect(host.showNotice).toHaveBeenCalledWith('Auto-compact threshold: 0.7', expect.any(String)); + const detail = (host.showNotice as ReturnType).mock.calls[0]?.[1] as string; + expect(detail).toContain('config.toml'); + }); + + it('reports the built-in default when neither override nor config is set', async () => { + const { host } = makeHost({ status: {} }); + + await handleCompactThresholdCommand(host, ''); + + expect(host.showNotice).toHaveBeenCalledWith( + 'Auto-compact threshold: 0.85', + expect.any(String), + ); + }); + + it('sets a valid in-range ratio as the session override', async () => { + const { host, session } = makeHost(); + + await handleCompactThresholdCommand(host, '0.3'); + + expect(session.setCompactionTriggerRatio).toHaveBeenCalledWith(0.3); + expect(host.showNotice).toHaveBeenCalledTimes(1); + expect(host.showError).not.toHaveBeenCalled(); + }); + + it('accepts the new 0.25 minimum boundary', async () => { + const { host, session } = makeHost(); + + await handleCompactThresholdCommand(host, '0.25'); + + expect(session.setCompactionTriggerRatio).toHaveBeenCalledWith(0.25); + }); + + it('rejects values below 0.25 without touching the session', async () => { + const { host, session } = makeHost(); + + await handleCompactThresholdCommand(host, '0.1'); + + expect(session.setCompactionTriggerRatio).not.toHaveBeenCalled(); + expect(host.showError).toHaveBeenCalledTimes(1); + }); + + it('rejects values above 0.99 without touching the session', async () => { + const { host, session } = makeHost(); + + await handleCompactThresholdCommand(host, '1.5'); + + expect(session.setCompactionTriggerRatio).not.toHaveBeenCalled(); + expect(host.showError).toHaveBeenCalledTimes(1); + }); + + it('rejects non-numeric values without touching the session', async () => { + const { host, session } = makeHost(); + + await handleCompactThresholdCommand(host, 'abc'); + + expect(session.setCompactionTriggerRatio).not.toHaveBeenCalled(); + expect(host.showError).toHaveBeenCalledTimes(1); + }); + + it('clears the override with "off"', async () => { + const { host, session } = makeHost(); + + await handleCompactThresholdCommand(host, 'off'); + + expect(session.setCompactionTriggerRatio).toHaveBeenCalledWith(undefined); + expect(host.showNotice).toHaveBeenCalledTimes(1); + }); + + it('surfaces engine errors when setting fails', async () => { + const { host, session } = makeHost(); + (session.setCompactionTriggerRatio as ReturnType).mockRejectedValue( + new Error('engine says no'), + ); + + await handleCompactThresholdCommand(host, '0.4'); + + expect(host.showError).toHaveBeenCalledTimes(1); + }); +}); \ No newline at end of file diff --git a/packages/agent-core-v2/src/agent/loop/configSection.ts b/packages/agent-core-v2/src/agent/loop/configSection.ts index b04d4e4cc4b..2cbd4621cda 100644 --- a/packages/agent-core-v2/src/agent/loop/configSection.ts +++ b/packages/agent-core-v2/src/agent/loop/configSection.ts @@ -16,7 +16,7 @@ export const LoopControlSchema = z.object({ maxAttemptsPerStep: z.number().int().min(0).optional(), maxRalphIterations: z.number().int().min(-1).optional(), reservedContextSize: z.number().int().min(0).optional(), - compactionTriggerRatio: z.number().min(0.5).max(0.99).optional(), + compactionTriggerRatio: z.number().min(0.25).max(0.99).optional(), }); export type LoopControl = z.infer; diff --git a/packages/agent-core-v2/src/agent/profile/profile.ts b/packages/agent-core-v2/src/agent/profile/profile.ts index f21ce33b009..cf050819471 100644 --- a/packages/agent-core-v2/src/agent/profile/profile.ts +++ b/packages/agent-core-v2/src/agent/profile/profile.ts @@ -87,6 +87,27 @@ export interface ApplyProfileOptions { readonly additionalDirs?: readonly string[]; } +/** + * Lowest accepted auto-compaction trigger ratio (fraction of the context + * window at which auto-compaction triggers). Applies both to the global + * `[loop_control] compaction_trigger_ratio` config value and to the + * session-scoped override set through `setCompactionTriggerRatio`. + */ +export const COMPACTION_TRIGGER_RATIO_MIN = 0.25; + +/** Highest accepted auto-compaction trigger ratio. */ +export const COMPACTION_TRIGGER_RATIO_MAX = 0.99; + +/** Validates a compaction trigger ratio; returns a human error message when invalid. */ +export function compactionTriggerRatioError( + ratio: number, +): string | undefined { + if (!Number.isFinite(ratio) || ratio < COMPACTION_TRIGGER_RATIO_MIN || ratio > COMPACTION_TRIGGER_RATIO_MAX) { + return `Invalid compaction trigger ratio "${String(ratio)}": must be between ${COMPACTION_TRIGGER_RATIO_MIN} and ${COMPACTION_TRIGGER_RATIO_MAX}.`; + } + return undefined; +} + export interface ProfileModelContext { readonly modelAlias: string; readonly modelCapabilities: ModelCapability; @@ -118,6 +139,24 @@ export interface IAgentProfileService { bind(input: BindAgentInput): Promise; setModel(model: string): Promise; setThinking(level: string): void; + /** + * Set (or clear, when `ratio` is undefined) the session-scoped auto-compaction + * trigger ratio override. Takes precedence over the global + * `[loop_control] compaction_trigger_ratio` config value for the rest of the + * session; it is never persisted. Throws `ProfileError` for values outside + * [COMPACTION_TRIGGER_RATIO_MIN, COMPACTION_TRIGGER_RATIO_MAX]. + */ + setCompactionTriggerRatio(ratio: number | undefined): void; + /** The session-scoped override set via {@link setCompactionTriggerRatio}, or undefined. */ + getCompactionTriggerRatioOverride(): number | undefined; + /** + * The effective auto-compaction trigger ratio — session override when set, + * otherwise the global `[loop_control] compaction_trigger_ratio` config + * value, otherwise undefined (the engine default applies). Unlike + * {@link resolveModelContext} this never requires a bound model, so it is + * safe to call on model-less sessions (e.g. from getStatus). + */ + getEffectiveCompactionTriggerRatio(): number | undefined; republishStatus(): void; getModel(): string; useProfile(profile: ResolvedAgentProfile, context: SystemPromptContext): void; @@ -140,4 +179,4 @@ export interface IAgentProfileService { removeActiveTool(name: string): void; } -export const IAgentProfileService = createDecorator('agentProfileService'); +export const IAgentProfileService = createDecorator('agentProfileService'); \ No newline at end of file diff --git a/packages/agent-core-v2/src/agent/profile/profileService.ts b/packages/agent-core-v2/src/agent/profile/profileService.ts index 08d08df5a97..535c6e508ab 100644 --- a/packages/agent-core-v2/src/agent/profile/profileService.ts +++ b/packages/agent-core-v2/src/agent/profile/profileService.ts @@ -62,7 +62,13 @@ import type { ProfileSetModelResult, ProfileUpdateData, } from './profile'; -import { IAgentProfileService, ProfileError, ProfileErrors } from './profile'; +import { + COMPACTION_TRIGGER_RATIO_MAX, + COMPACTION_TRIGGER_RATIO_MIN, + IAgentProfileService, + ProfileError, + ProfileErrors, +} from './profile'; import { TOOLS_SECTION, type ToolsConfig } from '#/agent/toolPolicy/configSection'; import { isToolActiveComposed, findInactiveToolPatterns, literalToolNames, type InactiveToolPattern } from '#/agent/toolPolicy/evaluate'; import { IAgentToolRegistryService } from '#/agent/toolRegistry/toolRegistry'; @@ -140,6 +146,9 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ private activeProfile: ResolvedAgentProfile | undefined; + /** Session-scoped auto-compaction trigger ratio override; undefined = use config. */ + private compactionTriggerRatioOverride: number | undefined; + private frozenSkillListing: string | undefined; private frozenPluginSections: string | undefined; @@ -385,6 +394,35 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ } } + setCompactionTriggerRatio(ratio: number | undefined): void { + if (ratio === undefined) { + this.compactionTriggerRatioOverride = undefined; + this.telemetry.track2('compaction_threshold_override', { action: 'clear' }); + return; + } + if ( + !Number.isFinite(ratio) || + ratio < COMPACTION_TRIGGER_RATIO_MIN || + ratio > COMPACTION_TRIGGER_RATIO_MAX + ) { + throw new ProfileError( + ProfileErrors.codes.MODEL_CONFIG_INVALID, + `Invalid compaction trigger ratio "${String(ratio)}": must be between ${COMPACTION_TRIGGER_RATIO_MIN} and ${COMPACTION_TRIGGER_RATIO_MAX}.`, + ); + } + this.compactionTriggerRatioOverride = ratio; + this.telemetry.track2('compaction_threshold_override', { ratio, action: 'set' }); + } + + getCompactionTriggerRatioOverride(): number | undefined { + return this.compactionTriggerRatioOverride; + } + + getEffectiveCompactionTriggerRatio(): number | undefined { + const loopControl = this.config.get('loopControl'); + return this.compactionTriggerRatioOverride ?? loopControl?.compactionTriggerRatio; + } + private assertThinkingEffortSupported( requested: string, model: Model | undefined, @@ -500,7 +538,7 @@ export class AgentProfileService extends Disposable implements IAgentProfileServ alwaysThinking: model.alwaysThinking || undefined, thinkingLevel: this.resolveThinkingState(model).effective, reservedContextSize: loopControl?.reservedContextSize, - compactionTriggerRatio: loopControl?.compactionTriggerRatio, + compactionTriggerRatio: this.getEffectiveCompactionTriggerRatio(), }; } @@ -985,4 +1023,4 @@ registerScopedService( AgentProfileService, ScopeActivation.OnScopeCreated, 'profile', -); +); \ No newline at end of file diff --git a/packages/agent-core-v2/src/app/telemetry/events.ts b/packages/agent-core-v2/src/app/telemetry/events.ts index a9fe1fbc365..41f0fd76374 100644 --- a/packages/agent-core-v2/src/app/telemetry/events.ts +++ b/packages/agent-core-v2/src/app/telemetry/events.ts @@ -266,6 +266,11 @@ export interface ThinkingToggleEvent { from: string; } +export interface CompactionThresholdOverrideEvent { + action: 'set' | 'clear'; + ratio?: number; +} + export interface QuestionDismissedEvent { trace_id?: string; } @@ -757,6 +762,14 @@ export const telemetryEventDefinitions = { from: 'Previous thinking effort level', }, }), + compaction_threshold_override: defineAgentTelemetryEvent({ + owner: 'kimi-code', + comment: 'The session-scoped auto-compaction trigger ratio override is set or cleared.', + properties: { + action: 'Whether the override was set or cleared', + ratio: 'The new trigger ratio; absent when clearing', + }, + }), question_dismissed: defineAgentTelemetryEvent({ owner: 'kimi-code', comment: 'A user question prompt is dismissed.', @@ -1036,4 +1049,4 @@ export type TelemetryEventPayload = export type TelemetryEventProperties = TelemetryEventRegistry[K] extends TelemetryEventDefinition ? P & (C extends 'agent' ? AgentTelemetryEventContext : object) - : never; + : never; \ No newline at end of file diff --git a/packages/agent-core-v2/test/agent/fullCompaction/fullCompaction.test.ts b/packages/agent-core-v2/test/agent/fullCompaction/fullCompaction.test.ts index 4a3ab2934eb..7a8aaba3871 100644 --- a/packages/agent-core-v2/test/agent/fullCompaction/fullCompaction.test.ts +++ b/packages/agent-core-v2/test/agent/fullCompaction/fullCompaction.test.ts @@ -2122,6 +2122,73 @@ describe('FullCompaction', () => { await ctx.expectResumeMatches(); }); + it('auto-compacts at a session override threshold instead of the built-in default', async () => { + const ctx = testAgent(); + ctx.configure({ + provider: CATALOGUED_PROVIDER, + modelCapabilities: { + ...CATALOGUED_MODEL_CAPABILITIES, + max_context_tokens: 1_000_000, + }, + }); + // ~30% used (300k of a 1M window) plus the ~30k-token pending prompt + // crosses a 0.3 session override, but stays far below the built-in + // 0.85 default and the 0.85 block ratio — only the override can fire, + // and the answer runs before the (non-blocking) compaction. + ctx.appendExchange(1, 'old user one', 'old assistant one', 300_000); + const pendingPrompt = `override-pending-verbatim:${'x'.repeat(120_000)}`; + + await ctx.rpc.setCompactionTriggerRatio({ ratio: 0.3 }); + + ctx.mockNextResponse({ type: 'text', text: 'I can answer the override pending prompt.' }); + ctx.mockNextResponse({ type: 'text', text: 'Override compacted summary.' }); + await ctx.rpc.prompt({ input: [{ type: 'text', text: pendingPrompt }] }); + const events = await ctx.untilTurnEnd(); + + // The 0.3 override fired an auto compaction the built-in 0.85 default + // would never trigger at this context size (see the control test below). + expect(events).toContainEqual( + expect.objectContaining({ + event: 'compaction.started', + args: expect.objectContaining({ trigger: 'auto' }), + }), + ); + // Answer first (non-blocking path), then the compaction request — which + // must summarize the oversized pending prompt. + expect(ctx.llmCalls).toHaveLength(2); + const [answerCall, compactionCall] = ctx.llmCalls; + expect( + answerCall?.history.map(messageText).some((text) => text.includes('override-pending-verbatim')), + ).toBe(true); + expect( + compactionCall?.history.map(messageText).some((text) => text.includes('override-pending-verbatim')), + ).toBe(true); + }); + + it('does not auto-compact at that context size without the session override (control)', async () => { + const ctx = testAgent(); + ctx.configure({ + provider: CATALOGUED_PROVIDER, + modelCapabilities: { + ...CATALOGUED_MODEL_CAPABILITIES, + max_context_tokens: 1_000_000, + }, + }); + // Same context size as the override test above, no override: 330k of a + // 1M window is far below the built-in 0.85 default, so nothing compacts. + ctx.appendExchange(1, 'old user one', 'old assistant one', 300_000); + const pendingPrompt = `control-pending-verbatim:${'x'.repeat(120_000)}`; + + ctx.mockNextResponse({ type: 'text', text: 'I can answer the control pending prompt.' }); + await ctx.rpc.prompt({ input: [{ type: 'text', text: pendingPrompt }] }); + const events = await ctx.untilTurnEnd(); + + expect(ctx.llmCalls).toHaveLength(1); + expect(events).not.toContainEqual( + expect.objectContaining({ event: 'compaction.started' }), + ); + }); + it('compacts and retries when the provider reports context overflow', async () => { let callCount = 0; const inputs: string[][] = []; @@ -3438,4 +3505,4 @@ describe('goal reminder re-injection after full compaction', () => { expect(goalReminderCount(turnRequest)).toBeGreaterThanOrEqual(1); expect(turnRequest.some((text) => text.includes('Compacted summary.'))).toBe(true); }); -}); +}); \ No newline at end of file diff --git a/packages/agent-core-v2/test/agent/profile/compact-threshold.test.ts b/packages/agent-core-v2/test/agent/profile/compact-threshold.test.ts new file mode 100644 index 00000000000..4366280eeb8 --- /dev/null +++ b/packages/agent-core-v2/test/agent/profile/compact-threshold.test.ts @@ -0,0 +1,301 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +import { SyncDescriptor } from '#/_base/di/descriptors'; +import { DisposableStore } from '#/_base/di/lifecycle'; +import { TestInstantiationService } from '#/_base/di/test'; +import { Event } from '#/_base/event'; +import { IAgentProfileService, ProfileError } from '#/agent/profile/profile'; +import { AgentProfileService } from '#/agent/profile/profileService'; +import { IAgentAgentsMdReminderService } from '#/agent/agentsMdReminder/agentsMdReminder'; +import { ISessionAgentProfileCatalog } from '#/session/sessionAgentProfileCatalog/sessionAgentProfileCatalog'; +import { IBootstrapService } from '#/app/bootstrap/bootstrap'; +import { IConfigService } from '#/app/config/config'; +import { IModelCatalog, type Model } from '#/kosong/model/catalog'; +import { IProtocolAdapterRegistry, type Protocol } from '#/kosong/protocol/protocol'; +import { ITelemetryService } from '#/app/telemetry/telemetry'; +import { IAgentTelemetryContextService } from '#/app/telemetry/agentTelemetryContext'; +import { AgentTelemetryContextService } from '#/app/telemetry/agentTelemetryContextService'; +import { IAgentScopeContext, makeAgentScopeContext } from '#/agent/scopeContext/scopeContext'; +import { IAgentStateService } from '#/agent/state/agentState'; +import { AgentStateService } from '#/agent/state/agentStateService'; +import { IHostEnvironment } from '#/os/interface/hostEnvironment'; +import { IHostFileSystem } from '#/os/interface/hostFileSystem'; +import { AppendLogStore } from '#/persistence/backends/node-fs/appendLogStore'; +import { InMemoryStorageService } from '#/persistence/backends/memory/inMemoryStorageService'; +import { IAppendLogStore } from '#/persistence/interface/appendLogStore'; +import { IFileSystemStorageService } from '#/persistence/interface/storage'; +import { ISessionContext } from '#/session/sessionContext/sessionContext'; +import { ISessionSkillCatalog } from '#/session/sessionSkillCatalog/skillCatalog'; +import { ISessionInstructionsProvider } from '#/session/sessionInstructions/instructionsProvider'; +import { ISessionToolPolicy } from '#/session/sessionToolPolicy/sessionToolPolicy'; +import { ISessionWorkspaceContext } from '#/session/workspaceContext/workspaceContext'; +import { IEventDispatcher } from '#/state/eventDispatcher'; + +import '#/kosong/provider/providers/kimi/kimi.contrib'; + +import { registerTestAgentWire, registerTestEventDispatcher, testWireScope } from '../../wire/stubs'; + +const SCOPE = 'wire'; +const KEY = 'profile-compact-threshold-test'; +const MOCK_MODEL = 'kimi-code'; + +function createTelemetryStub(): ITelemetryService { + return { + _serviceBrand: undefined, + track: () => undefined, + track2: () => undefined, + } as unknown as ITelemetryService; +} + +function createConfigStub(): IConfigService { + return { + _serviceBrand: undefined, + onDidSectionChange: () => ({ dispose: () => {} }), + get: ((key: string) => configValues[key]) as unknown as IConfigService['get'], + } as unknown as IConfigService; +} + +function createTestModel(): Model { + return { + id: MOCK_MODEL, + name: 'kimi-for-coding', + aliases: [], + protocol: 'openai', + baseUrl: 'https://example.test/v1', + headers: {}, + capabilities: { + image_in: false, + video_in: false, + audio_in: false, + thinking: true, + tool_use: false, + max_context_tokens: 1000, + }, + maxContextSize: 1000, + alwaysThinking: false, + providerType: 'kimi', + providerName: 'kimi', + authProvider: { getAuth: async () => undefined }, + }; +} + +function createModelCatalogStub(model: Model): IModelCatalog { + return { + _serviceBrand: undefined, + get: (id) => { + if (id !== model.id) throw new Error(`Unknown model: ${String(id)}`); + return model; + }, + getRequester: () => { + throw new Error('not exercised'); + }, + inspect: () => { + throw new Error('not exercised'); + }, + ping: () => { + throw new Error('not exercised'); + }, + findByName: () => [], + listModels: () => { + throw new Error('not exercised'); + }, + listProviders: () => { + throw new Error('not exercised'); + }, + getProvider: () => { + throw new Error('not exercised'); + }, + setDefaultModel: () => { + throw new Error('not exercised'); + }, + }; +} + +function createProtocolRegistryStub(): IProtocolAdapterRegistry { + return { + _serviceBrand: undefined, + supportedProtocols: () => ['anthropic', 'openai', 'openai_responses', 'google-genai'], + resolveAdapterIdentity: (protocol: Protocol, providerType?: string) => ({ + baseId: protocol, + traits: + providerType === 'kimi' && protocol === 'openai' + ? [ + { + trait: { withThinking: () => undefined, strictThinkingValidation: true }, + context: {}, + }, + ] + : [], + }), + resolveProviderBaseId: (protocol: Protocol) => protocol, + resolveCapability: () => { + throw new Error('not exercised'); + }, + createChatProvider: () => { + throw new Error('not exercised'); + }, + } as unknown as IProtocolAdapterRegistry; +} + +function stubUnused(): T { + return { _serviceBrand: undefined } as unknown as T; +} + +function createSessionContextStub(): ISessionContext { + return { + _serviceBrand: undefined, + sessionId: 'session-test', + workspaceId: 'workspace-test', + sessionDir: '/tmp/session-test', + metaScope: 'sessions/workspace-test/session-test', + cwd: '/tmp', + scope: (subKey?: string) => + subKey === undefined || subKey.length === 0 + ? 'sessions/workspace-test/session-test' + : `sessions/workspace-test/session-test/${subKey}`, + }; +} + +let disposables: DisposableStore; +let svc: IAgentProfileService; +let configValues: Record; + +function buildHost(key: string): IAgentProfileService { + const host = disposables.add(new TestInstantiationService()); + host.stub(IFileSystemStorageService, new InMemoryStorageService()); + host.set(IAppendLogStore, new SyncDescriptor(AppendLogStore)); + host.stub(ITelemetryService, createTelemetryStub()); + host.stub(IAgentScopeContext, makeAgentScopeContext({ agentId: 'main', agentScope: '' })); + host.stub(IAgentTelemetryContextService, new AgentTelemetryContextService()); + host.stub(IConfigService, createConfigStub()); + host.stub(IModelCatalog, createModelCatalogStub(createTestModel())); + host.stub(IProtocolAdapterRegistry, createProtocolRegistryStub()); + host.stub(IHostEnvironment, stubUnused()); + host.stub(IHostFileSystem, stubUnused()); + host.stub(IBootstrapService, stubUnused()); + host.stub(ISessionContext, createSessionContextStub()); + host.stub(ISessionWorkspaceContext, stubUnused()); + host.stub(ISessionAgentProfileCatalog, { + _serviceBrand: undefined, + ready: Promise.resolve(), + get: () => undefined, + getDefault: () => { + throw new Error('catalog resolution is not exercised'); + }, + list: () => [], + load: async () => {}, + reload: async () => {}, + onDidChange: () => ({ dispose: () => {} }), + }); + host.stub(ISessionSkillCatalog, { + _serviceBrand: undefined, + onDidChange: () => ({ dispose: () => {} }), + }); + host.stub(ISessionInstructionsProvider, { + _serviceBrand: undefined, + ready: Promise.resolve(), + agentsMd: undefined, + agentsMdWarning: undefined, + agentsMdPaths: undefined, + onDidChange: Event.None as Event, + } satisfies ISessionInstructionsProvider); + host.stub(IAgentAgentsMdReminderService, { + _serviceBrand: undefined, + seedInjected: () => {}, + }); + host.stub(ISessionToolPolicy, { + _serviceBrand: undefined, + ready: Promise.resolve(), + onDidChange: () => ({ dispose: () => {} }), + disabledTools: () => [], + setDisabledTools: () => Promise.resolve(), + }); + host.set(IAgentStateService, new AgentStateService()); + host.set(IAgentProfileService, new SyncDescriptor(AgentProfileService)); + registerTestAgentWire(host, testWireScope(SCOPE, key), { + log: host.get(IAppendLogStore), + }); + registerTestEventDispatcher(host); + return host.get(IAgentProfileService); +} + +beforeEach(() => { + disposables = new DisposableStore(); + configValues = {}; + svc = buildHost(KEY); +}); + +afterEach(() => disposables.dispose()); + +describe('AgentProfileService.setCompactionTriggerRatio', () => { + it('rejects values below 0.25 (the widened minimum)', () => { + expect(() => svc.setCompactionTriggerRatio(0.24)).toThrow(ProfileError); + expect(() => svc.setCompactionTriggerRatio(0.2499)).toThrow(ProfileError); + }); + + it('rejects values above 0.99 and non-finite values', () => { + expect(() => svc.setCompactionTriggerRatio(1)).toThrow(ProfileError); + expect(() => svc.setCompactionTriggerRatio(Number.NaN)).toThrow(ProfileError); + expect(() => svc.setCompactionTriggerRatio(Number.POSITIVE_INFINITY)).toThrow(ProfileError); + }); + + it('accepts the boundary values 0.25 and 0.99', () => { + svc.setCompactionTriggerRatio(0.25); + expect(svc.getCompactionTriggerRatioOverride()).toBe(0.25); + svc.setCompactionTriggerRatio(0.99); + expect(svc.getCompactionTriggerRatioOverride()).toBe(0.99); + }); + + it('clears the override when called with undefined', () => { + svc.setCompactionTriggerRatio(0.3); + expect(svc.getCompactionTriggerRatioOverride()).toBe(0.3); + svc.setCompactionTriggerRatio(undefined); + expect(svc.getCompactionTriggerRatioOverride()).toBeUndefined(); + }); + + it('getEffectiveCompactionTriggerRatio resolves precedence without a bound model', () => { + // getStatus reads this accessor on model-less sessions; it must not throw. + configValues['loopControl'] = { compactionTriggerRatio: 0.7 }; + expect(svc.getEffectiveCompactionTriggerRatio()).toBe(0.7); + svc.setCompactionTriggerRatio(0.3); + expect(svc.getEffectiveCompactionTriggerRatio()).toBe(0.3); + svc.setCompactionTriggerRatio(undefined); + expect(svc.getEffectiveCompactionTriggerRatio()).toBe(0.7); + }); +}); + +describe('AgentProfileService compaction trigger ratio precedence', () => { + beforeEach(() => { + // resolveModelContext requires a model to be configured; a plain state + // update is enough (no full bind / system-prompt rendering needed). + svc.update({ modelAlias: MOCK_MODEL }); + }); + + it('uses the config value when no override is set', () => { + configValues['loopControl'] = { compactionTriggerRatio: 0.7 }; + expect(svc.resolveModelContext().compactionTriggerRatio).toBe(0.7); + }); + + it('the session override takes precedence over the config value', () => { + configValues['loopControl'] = { compactionTriggerRatio: 0.7 }; + svc.setCompactionTriggerRatio(0.3); + expect(svc.resolveModelContext().compactionTriggerRatio).toBe(0.3); + }); + + it('returns undefined when neither override nor config sets a value', () => { + expect(svc.resolveModelContext().compactionTriggerRatio).toBeUndefined(); + }); + + it('falls back to the config value after the override is cleared', () => { + configValues['loopControl'] = { compactionTriggerRatio: 0.7 }; + svc.setCompactionTriggerRatio(0.3); + svc.setCompactionTriggerRatio(undefined); + expect(svc.resolveModelContext().compactionTriggerRatio).toBe(0.7); + expect(svc.getCompactionTriggerRatioOverride()).toBeUndefined(); + }); + + it('respects a config value at the new 0.25 minimum', () => { + configValues['loopControl'] = { compactionTriggerRatio: 0.25 }; + expect(svc.resolveModelContext().compactionTriggerRatio).toBe(0.25); + }); +}); \ No newline at end of file diff --git a/packages/agent-core-v2/test/app/config/config.test.ts b/packages/agent-core-v2/test/app/config/config.test.ts index 1824769d2cd..17ce9e45e50 100644 --- a/packages/agent-core-v2/test/app/config/config.test.ts +++ b/packages/agent-core-v2/test/app/config/config.test.ts @@ -901,6 +901,21 @@ describe('loopControl config section', () => { expect(() => registry.validate(LOOP_CONTROL_SECTION, { maxAttemptsPerStep: 1.5 })).toThrow(); }); + it('accepts compactionTriggerRatio within 0.25-0.99 and rejects values outside the range', () => { + const registry = new ConfigRegistry(); + + expect(registry.validate(LOOP_CONTROL_SECTION, { compactionTriggerRatio: 0.25 })).toEqual({ + compactionTriggerRatio: 0.25, + }); + expect(registry.validate(LOOP_CONTROL_SECTION, { compactionTriggerRatio: 0.99 })).toEqual({ + compactionTriggerRatio: 0.99, + }); + expect(() => + registry.validate(LOOP_CONTROL_SECTION, { compactionTriggerRatio: 0.24 }), + ).toThrow(); + expect(() => registry.validate(LOOP_CONTROL_SECTION, { compactionTriggerRatio: 1 })).toThrow(); + }); + it('re-applies loopControl env bindings on every get() and ignores invalid env', async () => { const env: Record = {}; const disposables = new DisposableStore(); @@ -2792,4 +2807,4 @@ describe('ConfigService persistence guards', () => { disposables.dispose(); }); -}); +}); \ No newline at end of file diff --git a/packages/agent-core-v2/test/harness/agent.ts b/packages/agent-core-v2/test/harness/agent.ts index a79cea64558..a0e8d64ef96 100644 --- a/packages/agent-core-v2/test/harness/agent.ts +++ b/packages/agent-core-v2/test/harness/agent.ts @@ -82,6 +82,7 @@ interface SetActiveToolsPayload { readonly names: readonly string[] } interface SetModelPayload { readonly model: string } interface SetPermissionPayload { readonly mode: PermissionMode } interface SetThinkingPayload { readonly level: string } +interface SetCompactionTriggerRatioPayload { readonly ratio?: number | undefined } interface StopTaskPayload { readonly taskId: string; readonly reason?: string } interface UndoHistoryPayload { readonly count: number } interface UnregisterToolPayload { readonly name: string } @@ -383,6 +384,7 @@ interface AgentRpcPassthroughAPI { runShellCommand: (payload: RunShellCommandPayload) => Promisable; cancelShellCommand: (payload: CancelShellCommandPayload) => void; setThinking: (payload: SetThinkingPayload) => void; + setCompactionTriggerRatio: (payload: SetCompactionTriggerRatioPayload) => void; setModel: (payload: SetModelPayload) => Promisable; getModel: (payload: EmptyPayload) => string; enterPlan: (payload: EmptyPayload) => Promisable; @@ -2274,6 +2276,8 @@ export class AgentTestContext { cancelShellCommand: (payload) => this.get(IAgentShellCommandService).cancel(payload.commandId), setThinking: (payload) => this.get(IAgentProfileService).setThinking(payload.level), + setCompactionTriggerRatio: (payload) => + this.get(IAgentProfileService).setCompactionTriggerRatio(payload.ratio), setModel: (payload) => this.get(IAgentProfileService).setModel(payload.model), getModel: () => this.get(IAgentProfileService).getModel(), enterPlan: () => this.get(IAgentPlanService).enter(), @@ -3061,4 +3065,4 @@ function withMetadata(events: readonly WireRecord[]): readonly WireRecord[] { }, ...events, ]; -} +} \ No newline at end of file diff --git a/packages/agent-core/src/config/schema.ts b/packages/agent-core/src/config/schema.ts index 65e3bb94bbe..492ee223bd4 100644 --- a/packages/agent-core/src/config/schema.ts +++ b/packages/agent-core/src/config/schema.ts @@ -172,7 +172,7 @@ export const LoopControlSchema = z.object({ maxRetriesPerStep: z.number().int().min(0).optional(), maxRalphIterations: z.number().int().min(-1).optional(), reservedContextSize: z.number().int().min(0).optional(), - compactionTriggerRatio: z.number().min(0.5).max(0.99).optional(), + compactionTriggerRatio: z.number().min(0.25).max(0.99).optional(), }); export type LoopControl = z.infer; diff --git a/packages/agent-core/test/config/configs.test.ts b/packages/agent-core/test/config/configs.test.ts index 39612d53aa7..fdda211e453 100644 --- a/packages/agent-core/test/config/configs.test.ts +++ b/packages/agent-core/test/config/configs.test.ts @@ -463,6 +463,28 @@ pattern = "Bash(rm *" ); }); + it('accepts compaction_trigger_ratio down to 0.25 and rejects values below it', () => { + const atMinimum = parseConfigString( + ` +[loop_control] +compaction_trigger_ratio = 0.25 +`, + 'threshold.toml', + ); + expect(atMinimum.loopControl).toMatchObject({ compactionTriggerRatio: 0.25 }); + expectKimiErrorCode( + () => + parseConfigString( + ` +[loop_control] +compaction_trigger_ratio = 0.24 +`, + 'threshold.toml', + ), + ErrorCodes.CONFIG_INVALID, + ); + }); + it('parses hooks config from TOML arrays of tables', () => { const config = parseConfigString( ` @@ -1072,4 +1094,4 @@ describe('migrateThinkingEffortMaxToHigh', () => { await expect(readFile(join(home, 'migrations-effort.json'), 'utf-8')).rejects.toThrow(); }); -}); +}); \ No newline at end of file diff --git a/packages/node-sdk/src/rpc.ts b/packages/node-sdk/src/rpc.ts index 08e7bcfcc18..937553f0d69 100644 --- a/packages/node-sdk/src/rpc.ts +++ b/packages/node-sdk/src/rpc.ts @@ -124,6 +124,11 @@ export interface SetSessionThinkingRpcInput extends SessionIdRpcInput { readonly effort: string; } +export interface SetSessionCompactionTriggerRatioRpcInput extends SessionIdRpcInput { + /** New session-scoped auto-compaction trigger ratio; `undefined` clears the override. */ + readonly ratio?: number | undefined; +} + export interface SetSessionPermissionRpcInput extends SessionIdRpcInput { readonly mode: PermissionMode; } @@ -648,6 +653,21 @@ export abstract class SDKRpcClientBase { }); } + /** + * Set (or clear, when `ratio` is undefined) a session-scoped override for + * the auto-compaction trigger ratio. Only the v2 client implements this + * (through the agent scope's `IAgentProfileService`); the v1 engine has no + * per-session compaction threshold and throws `not_implemented`. + */ + setCompactionTriggerRatio( + _input: SetSessionCompactionTriggerRatioRpcInput, + ): Promise { + throw new KimiError( + ErrorCodes.NOT_IMPLEMENTED, + 'This SDK client does not support setting the compaction trigger ratio.', + ); + } + async setPermission(input: SetSessionPermissionRpcInput): Promise { const rpc = await this.getRpc(); return rpc.setPermission({ @@ -1206,4 +1226,4 @@ export class ClientAPI implements SDKAPI { function errorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error); -} +} \ No newline at end of file diff --git a/packages/node-sdk/src/sdk-rpc-client-v2.ts b/packages/node-sdk/src/sdk-rpc-client-v2.ts index 301e7872b24..a0d4c400b9b 100644 --- a/packages/node-sdk/src/sdk-rpc-client-v2.ts +++ b/packages/node-sdk/src/sdk-rpc-client-v2.ts @@ -258,6 +258,7 @@ import { type SessionPromptWithSkillsRpcInput, type SetSessionModelRpcInput, type SetSessionModelRpcResult, + type SetSessionCompactionTriggerRatioRpcInput, type SetSessionPermissionRpcInput, type SetSessionPlanModeRpcInput, type SetSessionSwarmModeRpcInput, @@ -1659,6 +1660,18 @@ export class SDKRpcClientV2 extends SDKRpcClientBase { agent.accessor.get(IAgentProfileService).setThinking(input.effort); } + /** + * Through the agent scope (`IAgentProfileService.setCompactionTriggerRatio`), + * same shape as `setThinking`: no klient facade exists. The profile service + * validates the [0.25, 0.99] range and rejects with `model.config_invalid`. + */ + override async setCompactionTriggerRatio( + input: SetSessionCompactionTriggerRatioRpcInput, + ): Promise { + const agent = await this.agentScope(input.sessionId); + agent.accessor.get(IAgentProfileService).setCompactionTriggerRatio(input.ratio); + } + override async setPermission(input: SetSessionPermissionRpcInput): Promise { const agent = await this.agentFacade(input.sessionId); return agent.setPermission(input.mode); @@ -1739,7 +1752,8 @@ export class SDKRpcClientV2 extends SDKRpcClientBase { facade.getPlan(), facade.getUsage(), ]); - const profile = agent.accessor.get(IAgentProfileService).data(); + const profileService = agent.accessor.get(IAgentProfileService); + const profile = profileService.data(); const capability = profile.modelCapabilities; const maxContextTokens = capability.max_input_tokens ?? capability.max_context_tokens; const contextTokens = context.tokenCount; @@ -1755,6 +1769,17 @@ export class SDKRpcClientV2 extends SDKRpcClientBase { planMode: plan !== null, swarmMode: agent.accessor.get(IAgentSwarmService).isActive, towerMode: agent.accessor.get(IAgentTowerService).isActive, + // Effective auto-compaction trigger ratio (override ?? config). Both new + // fields are spread conditionally so a session with neither override nor + // config keeps the exact v1 status shape — strict-equality getStatus + // parity tests must not see extra keys. Reads through the model-less + // accessors so getStatus keeps working on model-less sessions. + ...(profileService.getEffectiveCompactionTriggerRatio() !== undefined + ? { compactionTriggerRatio: profileService.getEffectiveCompactionTriggerRatio() } + : {}), + ...(profileService.getCompactionTriggerRatioOverride() !== undefined + ? { compactionTriggerRatioOverridden: true } + : {}), contextTokens, maxContextTokens, contextUsage, @@ -2707,4 +2732,4 @@ function describeWorkspaceMcpServer( }; } return { name, transport: config.transport, url: config.url }; -} +} \ No newline at end of file diff --git a/packages/node-sdk/src/session.ts b/packages/node-sdk/src/session.ts index 48aee009600..4936eb486dc 100644 --- a/packages/node-sdk/src/session.ts +++ b/packages/node-sdk/src/session.ts @@ -280,6 +280,18 @@ export class Session { await this.rpc.setThinking({ sessionId: this.id, effort: normalized }); } + /** + * Set a session-scoped override for the auto-compaction trigger ratio + * (context-utilization fraction at which auto-compaction triggers), or clear + * it by omitting `ratio`. The override takes precedence over the global + * `[loop_control] compaction_trigger_ratio` config value for the rest of the + * session and is never persisted. Only the v2 engine supports this. + */ + async setCompactionTriggerRatio(ratio?: number): Promise { + this.ensureOpen(); + await this.rpc.setCompactionTriggerRatio({ sessionId: this.id, ratio }); + } + async setPermission(mode: PermissionMode): Promise { this.ensureOpen(); if (!isPermissionMode(mode)) { @@ -861,4 +873,4 @@ function hasResumeState( typeof (summary as { readonly agents?: unknown }).agents === 'object' && (summary as { readonly agents?: unknown }).agents !== null ); -} +} \ No newline at end of file diff --git a/packages/node-sdk/src/types.ts b/packages/node-sdk/src/types.ts index 540fcafbdd8..c18870e8f0b 100644 --- a/packages/node-sdk/src/types.ts +++ b/packages/node-sdk/src/types.ts @@ -339,6 +339,15 @@ export interface SessionStatus { readonly planMode: boolean; readonly swarmMode?: boolean; readonly towerMode?: boolean; + /** + * Effective auto-compaction trigger ratio (session override when set, + * otherwise the global `[loop_control] compaction_trigger_ratio` config + * value, otherwise undefined — the engine default applies). Populated by + * the v2 engine only. + */ + readonly compactionTriggerRatio?: number; + /** True when a session-scoped override (e.g. `/compact-threshold`) is active. */ + readonly compactionTriggerRatioOverridden?: boolean; readonly contextTokens: number; readonly maxContextTokens: number; readonly contextUsage: number; @@ -380,4 +389,4 @@ export interface AddAdditionalDirResult { export type ResumedSessionState = Pick; -export interface ResumedSessionSummary extends SessionSummary, ResumedSessionState { } +export interface ResumedSessionSummary extends SessionSummary, ResumedSessionState { } \ No newline at end of file From cefb12079ae23800469334eb9e78ca8aeadcf637 Mon Sep 17 00:00:00 2001 From: Ahmet TOK <48218623+arrrrny@users.noreply.github.com> Date: Sun, 23 Aug 2026 12:16:01 +0300 Subject: [PATCH 18/71] fix(tui): surface the compaction model (slash command + started indicator) (#4) * not verified * fix(tui): surface compaction model in started event and complete /compaction-model wiring Verification pass over the compaction-model TUI fix (commit 955fed70), which shipped unverified. Corrections: - compaction.started now carries snake_case model_display so the field survives klient/protocol event validation on the way to clients (camelCase modelDisplay was stripped, so the TUI never saw it). - AgentFullCompactionService.begin() reads profile.data() (non-throwing) instead of resolveModelContext(), which threw model.not_configured on model-less sessions; drop the no-op ternary on binding.model. - kap-server compactionStartedEventSchema gains model/model_display so the fields are not stripped on the server WS path. - Indicators prefer the user-facing display name (model_display) and fall back to the raw alias: TUI session-event-handler, acp-adapter session, vscode event-adapter. - node-sdk config-mapper: include visualModel, substituteModel, and compactionModel in KIMI_CONFIG_DOMAINS so getConfig returns them on the v2 engine (the /compaction-model, /visual-model, and /substitute-model pickers can now show the configured value); empty materialized section defaults are omitted to keep v1/v2 getConfig parity. - Fix compactionStartedModel tests: configure [compaction_model] via initialConfig + the flag env (the harness config stub ignores section env bindings), assert the snake_case fields, and add a display-name-preference case to the session-event-handler test. - Add the missing changeset. --- .changeset/compaction-model-tui-missing.md | 5 + .kimi-code/skills/speckit-analyze/SKILL.md | 259 ++++++++++++ .kimi-code/skills/speckit-bug-assess/SKILL.md | 1 + .kimi-code/skills/speckit-bug-fetch/SKILL.md | 1 + .kimi-code/skills/speckit-bug-fix/SKILL.md | 1 + .kimi-code/skills/speckit-bug-issue/SKILL.md | 1 + .kimi-code/skills/speckit-bug-pr/SKILL.md | 1 + .kimi-code/skills/speckit-bug-test/SKILL.md | 1 + .kimi-code/skills/speckit-checklist/SKILL.md | 383 ++++++++++++++++++ .kimi-code/skills/speckit-clarify/SKILL.md | 291 +++++++++++++ .../skills/speckit-constitution/SKILL.md | 177 ++++++++ .kimi-code/skills/speckit-converge/SKILL.md | 277 +++++++++++++ .kimi-code/skills/speckit-implement/SKILL.md | 226 +++++++++++ .kimi-code/skills/speckit-plan/SKILL.md | 166 ++++++++ .kimi-code/skills/speckit-specify/SKILL.md | 345 ++++++++++++++++ .kimi-code/skills/speckit-tasks/SKILL.md | 214 ++++++++++ .../skills/speckit-taskstoissues/SKILL.md | 109 +++++ .../skills/speckit-worktrees-clean/SKILL.md | 1 + .../skills/speckit-worktrees-create/SKILL.md | 1 + .../skills/speckit-worktrees-list/SKILL.md | 1 + .../skills/speckit-worktrees-specify/SKILL.md | 1 + apps/kimi-code/src/tui/commands/config.ts | 67 +++ apps/kimi-code/src/tui/commands/dispatch.ts | 5 + apps/kimi-code/src/tui/commands/registry.ts | 7 + .../src/tui/components/dialogs/compaction.ts | 8 +- .../tui/controllers/session-event-handler.ts | 2 +- .../src/tui/controllers/streaming-ui.ts | 4 +- apps/kimi-code/src/tui/kimi-tui.ts | 2 +- apps/kimi-code/src/tui/types.ts | 1 + .../tui/commands/compaction-model.test.ts | 166 ++++++++ .../tui/components/dialogs/compaction.test.ts | 25 ++ .../session-event-handler-compaction.test.ts | 53 +++ apps/vscode/shared/legacy-sdk.ts | 2 +- apps/vscode/src/runtime/event-adapter.ts | 4 +- .../src/components/CompactionCard.tsx | 9 +- .../webview-ui/src/stores/chat.store.ts | 5 + .../webview-ui/src/stores/event-handlers.ts | 3 +- packages/acp-adapter/src/session.ts | 8 +- .../src/agent/fullCompaction/compactionOps.ts | 10 +- .../fullCompaction/fullCompactionService.ts | 16 +- .../src/agent/fullCompaction/types.ts | 2 + .../compactionStartedModel.test.ts | 101 +++++ .../agent-core/src/agent/compaction/full.ts | 2 + packages/agent-core/src/config/schema.ts | 18 + .../kap-server/src/protocol/events-zod.ts | 4 +- packages/klient/src/contract/agent/events.ts | 2 + packages/node-sdk/src/v2/config-mapper.ts | 30 +- packages/protocol/src/events.ts | 4 + 48 files changed, 3002 insertions(+), 20 deletions(-) create mode 100644 .changeset/compaction-model-tui-missing.md create mode 100644 .kimi-code/skills/speckit-analyze/SKILL.md create mode 120000 .kimi-code/skills/speckit-bug-assess/SKILL.md create mode 120000 .kimi-code/skills/speckit-bug-fetch/SKILL.md create mode 120000 .kimi-code/skills/speckit-bug-fix/SKILL.md create mode 120000 .kimi-code/skills/speckit-bug-issue/SKILL.md create mode 120000 .kimi-code/skills/speckit-bug-pr/SKILL.md create mode 120000 .kimi-code/skills/speckit-bug-test/SKILL.md create mode 100644 .kimi-code/skills/speckit-checklist/SKILL.md create mode 100644 .kimi-code/skills/speckit-clarify/SKILL.md create mode 100644 .kimi-code/skills/speckit-constitution/SKILL.md create mode 100644 .kimi-code/skills/speckit-converge/SKILL.md create mode 100644 .kimi-code/skills/speckit-implement/SKILL.md create mode 100644 .kimi-code/skills/speckit-plan/SKILL.md create mode 100644 .kimi-code/skills/speckit-specify/SKILL.md create mode 100644 .kimi-code/skills/speckit-tasks/SKILL.md create mode 100644 .kimi-code/skills/speckit-taskstoissues/SKILL.md create mode 120000 .kimi-code/skills/speckit-worktrees-clean/SKILL.md create mode 120000 .kimi-code/skills/speckit-worktrees-create/SKILL.md create mode 120000 .kimi-code/skills/speckit-worktrees-list/SKILL.md create mode 120000 .kimi-code/skills/speckit-worktrees-specify/SKILL.md create mode 100644 apps/kimi-code/test/tui/commands/compaction-model.test.ts create mode 100644 packages/agent-core-v2/test/agent/fullCompaction/compactionStartedModel.test.ts diff --git a/.changeset/compaction-model-tui-missing.md b/.changeset/compaction-model-tui-missing.md new file mode 100644 index 00000000000..90bdf85bc04 --- /dev/null +++ b/.changeset/compaction-model-tui-missing.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": minor +--- + +Surface the dedicated compaction model in the TUI: add a `/compaction-model` slash command that picks and persists `[compaction_model]` (mirroring `/visual-model`), and show which model performs compaction — the in-progress indicator now reads "Compacting context using ..." for manual and automatic compaction, in the terminal TUI, the ACP adapter, and the VS Code extension. The `compaction.started` event carries `model` and `model_display`, and `/visual-model`, `/substitute-model`, and `/compaction-model` now read their configured value back on the v2 engine. \ No newline at end of file diff --git a/.kimi-code/skills/speckit-analyze/SKILL.md b/.kimi-code/skills/speckit-analyze/SKILL.md new file mode 100644 index 00000000000..2557ab146ca --- /dev/null +++ b/.kimi-code/skills/speckit-analyze/SKILL.md @@ -0,0 +1,259 @@ +--- +name: "speckit-analyze" +description: "Perform a non-destructive cross-artifact consistency and quality analysis across spec.md, plan.md, and tasks.md after task generation." +compatibility: "Requires spec-kit project structure with .specify/ directory" +metadata: + author: "github-spec-kit" + source: "templates/commands/analyze.md" +--- + + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +## Pre-Execution Checks + +**Check for extension hooks (before analysis)**: +- Check if `.specify/extensions.yml` exists in the project root. +- If it exists, read it and look for entries under the `hooks.before_analyze` key +- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally +- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. +- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: + - If the hook has no `condition` field, or it is null/empty, treat the hook as executable + - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation +- When constructing command invocations from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/skill:speckit-git-commit`. +- For each executable hook, output the following based on its `optional` flag: + - **Optional hook** (`optional: true`): + ``` + ## Extension Hooks + + **Optional Pre-Hook**: {extension} + Command: `/{command}` + Description: {description} + + Prompt: {prompt} + To execute: `/{command}` + ``` + - **Mandatory hook** (`optional: false`): + ``` + ## Extension Hooks + + **Automatic Pre-Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} + + Wait for the result of the hook command before proceeding to the Goal. + ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. +- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently + +## Goal + +Identify inconsistencies, duplications, ambiguities, and underspecified items across the three core artifacts (`spec.md`, `plan.md`, `tasks.md`) before implementation. This command MUST run only after `/skill:speckit-tasks` has successfully produced a complete `tasks.md`. + +## Operating Constraints + +**STRICTLY READ-ONLY**: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually). + +**Constitution Authority**: The project constitution (`.specify/memory/constitution.md`) is **non-negotiable** within this analysis scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, or tasks—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/skill:speckit-analyze`. + +## Execution Steps + +### 1. Initialize Analysis Context + +Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths: + +- SPEC = FEATURE_DIR/spec.md +- PLAN = FEATURE_DIR/plan.md +- TASKS = FEATURE_DIR/tasks.md + +Abort with an error message if any required file is missing (instruct the user to run missing prerequisite command). +For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). + +### 2. Load Artifacts (Progressive Disclosure) + +Load only the minimal necessary context from each artifact: + +**From spec.md:** + +- Overview/Context +- Functional Requirements +- Success Criteria (measurable outcomes — e.g., performance, security, availability, user success, business impact) +- User Stories +- Edge Cases (if present) + +**From plan.md:** + +- Architecture/stack choices +- Data Model references +- Phases +- Technical constraints + +**From tasks.md:** + +- Task IDs +- Descriptions +- Phase grouping +- Parallel markers [P] +- Referenced file paths + +**From constitution:** + +- Load `.specify/memory/constitution.md` for principle validation + +### 3. Build Semantic Models + +Create internal representations (do not include raw artifacts in output): + +- **Requirements inventory**: For each Functional Requirement (FR-###) and Success Criterion (SC-###), record a stable key. Use the explicit FR-/SC- identifier as the primary key when present, and optionally also derive an imperative-phrase slug for readability (e.g., "User can upload file" → `user-can-upload-file`). Include only Success Criteria items that require buildable work (e.g., load-testing infrastructure, security audit tooling), and exclude post-launch outcome metrics and business KPIs (e.g., "Reduce support tickets by 50%"). +- **User story/action inventory**: Discrete user actions with acceptance criteria +- **Task coverage mapping**: Map each task to one or more requirements or stories (inference by keyword / explicit reference patterns like IDs or key phrases) +- **Constitution rule set**: Extract principle names and MUST/SHOULD normative statements + +### 4. Detection Passes (Token-Efficient Analysis) + +Focus on high-signal findings. Limit to 50 findings total; aggregate remainder in overflow summary. + +#### A. Duplication Detection + +- Identify near-duplicate requirements +- Mark lower-quality phrasing for consolidation + +#### B. Ambiguity Detection + +- Flag vague adjectives (fast, scalable, secure, intuitive, robust) lacking measurable criteria +- Flag unresolved placeholders (TODO, TKTK, ???, ``, etc.) + +#### C. Underspecification + +- Requirements with verbs but missing object or measurable outcome +- User stories missing acceptance criteria alignment +- Tasks referencing files or components not defined in spec/plan + +#### D. Constitution Alignment + +- Any requirement or plan element conflicting with a MUST principle +- Missing mandated sections or quality gates from constitution + +#### E. Coverage Gaps + +- Requirements with zero associated tasks +- Tasks with no mapped requirement/story +- Success Criteria requiring buildable work (performance, security, availability) not reflected in tasks + +#### F. Inconsistency + +- Terminology drift (same concept named differently across files) +- Data entities referenced in plan but absent in spec (or vice versa) +- Task ordering contradictions (e.g., integration tasks before foundational setup tasks without dependency note) +- Conflicting requirements (e.g., one requires Next.js while other specifies Vue) + +### 5. Severity Assignment + +Use this heuristic to prioritize findings: + +- **CRITICAL**: Violates constitution MUST, missing core spec artifact, or requirement with zero coverage that blocks baseline functionality +- **HIGH**: Duplicate or conflicting requirement, ambiguous security/performance attribute, untestable acceptance criterion +- **MEDIUM**: Terminology drift, missing non-functional task coverage, underspecified edge case +- **LOW**: Style/wording improvements, minor redundancy not affecting execution order + +### 6. Produce Compact Analysis Report + +Output a Markdown report (no file writes) with the following structure: + +## Specification Analysis Report + +| ID | Category | Severity | Location(s) | Summary | Recommendation | +|----|----------|----------|-------------|---------|----------------| +| A1 | Duplication | HIGH | spec.md:L120-134 | Two similar requirements ... | Merge phrasing; keep clearer version | + +(Add one row per finding; generate stable IDs prefixed by category initial.) + +**Coverage Summary Table:** + +| Requirement Key | Has Task? | Task IDs | Notes | +|-----------------|-----------|----------|-------| + +**Constitution Alignment Issues:** (if any) + +**Unmapped Tasks:** (if any) + +**Metrics:** + +- Total Requirements +- Total Tasks +- Coverage % (requirements with >=1 task) +- Ambiguity Count +- Duplication Count +- Critical Issues Count + +### 7. Provide Next Actions + +At end of report, output a concise Next Actions block: + +- If CRITICAL issues exist: Recommend resolving before `/skill:speckit-implement` +- If only LOW/MEDIUM: User may proceed, but provide improvement suggestions +- Provide explicit command suggestions: e.g., "Run /skill:speckit-specify with refinement", "Run /skill:speckit-plan to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'" + +### 8. Offer Remediation + +Ask the user: "Would you like me to suggest concrete remediation edits for the top N issues?" (Do NOT apply them automatically.) + +### 9. Check for extension hooks + +After reporting, check if `.specify/extensions.yml` exists in the project root. +- If it exists, read it and look for entries under the `hooks.after_analyze` key +- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally +- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. +- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: + - If the hook has no `condition` field, or it is null/empty, treat the hook as executable + - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation +- When constructing command invocations from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/skill:speckit-git-commit`. +- For each executable hook, output the following based on its `optional` flag: + - **Optional hook** (`optional: true`): + ``` + ## Extension Hooks + + **Optional Hook**: {extension} + Command: `/{command}` + Description: {description} + + Prompt: {prompt} + To execute: `/{command}` + ``` + - **Mandatory hook** (`optional: false`): + ``` + ## Extension Hooks + + **Automatic Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} + ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. +- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently + +## Operating Principles + +### Context Efficiency + +- **Minimal high-signal tokens**: Focus on actionable findings, not exhaustive documentation +- **Progressive disclosure**: Load artifacts incrementally; don't dump all content into analysis +- **Token-efficient output**: Limit findings table to 50 rows; summarize overflow +- **Deterministic results**: Rerunning without changes should produce consistent IDs and counts + +### Analysis Guidelines + +- **NEVER modify files** (this is read-only analysis) +- **NEVER hallucinate missing sections** (if absent, report them accurately) +- **Prioritize constitution violations** (these are always CRITICAL) +- **Use examples over exhaustive rules** (cite specific instances, not generic patterns) +- **Report zero issues gracefully** (emit success report with coverage statistics) + +## Context + +$ARGUMENTS diff --git a/.kimi-code/skills/speckit-bug-assess/SKILL.md b/.kimi-code/skills/speckit-bug-assess/SKILL.md new file mode 120000 index 00000000000..412e88628a6 --- /dev/null +++ b/.kimi-code/skills/speckit-bug-assess/SKILL.md @@ -0,0 +1 @@ +../../../.specify/extensions/bug/.specify-dev/agent-commands/kimi/speckit-bug-assess/SKILL.md \ No newline at end of file diff --git a/.kimi-code/skills/speckit-bug-fetch/SKILL.md b/.kimi-code/skills/speckit-bug-fetch/SKILL.md new file mode 120000 index 00000000000..9ae77d005ab --- /dev/null +++ b/.kimi-code/skills/speckit-bug-fetch/SKILL.md @@ -0,0 +1 @@ +../../../.specify/extensions/bug/.specify-dev/agent-commands/kimi/speckit-bug-fetch/SKILL.md \ No newline at end of file diff --git a/.kimi-code/skills/speckit-bug-fix/SKILL.md b/.kimi-code/skills/speckit-bug-fix/SKILL.md new file mode 120000 index 00000000000..9200f11da36 --- /dev/null +++ b/.kimi-code/skills/speckit-bug-fix/SKILL.md @@ -0,0 +1 @@ +../../../.specify/extensions/bug/.specify-dev/agent-commands/kimi/speckit-bug-fix/SKILL.md \ No newline at end of file diff --git a/.kimi-code/skills/speckit-bug-issue/SKILL.md b/.kimi-code/skills/speckit-bug-issue/SKILL.md new file mode 120000 index 00000000000..9d638d7ba65 --- /dev/null +++ b/.kimi-code/skills/speckit-bug-issue/SKILL.md @@ -0,0 +1 @@ +../../../.specify/extensions/bug/.specify-dev/agent-commands/kimi/speckit-bug-issue/SKILL.md \ No newline at end of file diff --git a/.kimi-code/skills/speckit-bug-pr/SKILL.md b/.kimi-code/skills/speckit-bug-pr/SKILL.md new file mode 120000 index 00000000000..a75281b12e1 --- /dev/null +++ b/.kimi-code/skills/speckit-bug-pr/SKILL.md @@ -0,0 +1 @@ +../../../.specify/extensions/bug/.specify-dev/agent-commands/kimi/speckit-bug-pr/SKILL.md \ No newline at end of file diff --git a/.kimi-code/skills/speckit-bug-test/SKILL.md b/.kimi-code/skills/speckit-bug-test/SKILL.md new file mode 120000 index 00000000000..ac994f89519 --- /dev/null +++ b/.kimi-code/skills/speckit-bug-test/SKILL.md @@ -0,0 +1 @@ +../../../.specify/extensions/bug/.specify-dev/agent-commands/kimi/speckit-bug-test/SKILL.md \ No newline at end of file diff --git a/.kimi-code/skills/speckit-checklist/SKILL.md b/.kimi-code/skills/speckit-checklist/SKILL.md new file mode 100644 index 00000000000..249c149e136 --- /dev/null +++ b/.kimi-code/skills/speckit-checklist/SKILL.md @@ -0,0 +1,383 @@ +--- +name: "speckit-checklist" +description: "Generate a custom checklist for the current feature based on user requirements." +compatibility: "Requires spec-kit project structure with .specify/ directory" +metadata: + author: "github-spec-kit" + source: "templates/commands/checklist.md" +--- + + +## Checklist Purpose: "Unit Tests for English" + +**CRITICAL CONCEPT**: Checklists are **UNIT TESTS FOR REQUIREMENTS WRITING** - they validate the quality, clarity, and completeness of requirements in a given domain. + +**NOT for verification/testing**: + +- ❌ NOT "Verify the button clicks correctly" +- ❌ NOT "Test error handling works" +- ❌ NOT "Confirm the API returns 200" +- ❌ NOT checking if code/implementation matches the spec + +**FOR requirements quality validation**: + +- ✅ "Are visual hierarchy requirements defined for all card types?" (completeness) +- ✅ "Is 'prominent display' quantified with specific sizing/positioning?" (clarity) +- ✅ "Are hover state requirements consistent across all interactive elements?" (consistency) +- ✅ "Are accessibility requirements defined for keyboard navigation?" (coverage) +- ✅ "Does the spec define what happens when logo image fails to load?" (edge cases) + +**Metaphor**: If your spec is code written in English, the checklist is its unit test suite. You're testing whether the requirements are well-written, complete, unambiguous, and ready for implementation - NOT whether the implementation works. + +**Ownership and checkbox lifecycle**: + +- Custom checklists generated by this command are reviewer-owned requirements-quality review artifacts. +- `[x]` means the reviewer determined the requirements-quality criterion is satisfied. +- `[x]` does NOT mean implementation work is complete. +- This command generates or appends checklist items; it MUST NOT mark generated items `[x]`. +- An agent may assist with evaluating items only when explicitly asked by the reviewer. +- `checklists/requirements.md` is a separate built-in spec-quality checklist maintained by `/skill:speckit-specify` and `/skill:speckit-clarify`; do not treat that exception as applying to custom checklists generated here. + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +## Pre-Execution Checks + +**Check for extension hooks (before checklist generation)**: +- Check if `.specify/extensions.yml` exists in the project root. +- If it exists, read it and look for entries under the `hooks.before_checklist` key +- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally +- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. +- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: + - If the hook has no `condition` field, or it is null/empty, treat the hook as executable + - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation +- When constructing command invocations from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/skill:speckit-git-commit`. +- For each executable hook, output the following based on its `optional` flag: + - **Optional hook** (`optional: true`): + ``` + ## Extension Hooks + + **Optional Pre-Hook**: {extension} + Command: `/{command}` + Description: {description} + + Prompt: {prompt} + To execute: `/{command}` + ``` + - **Mandatory hook** (`optional: false`): + ``` + ## Extension Hooks + + **Automatic Pre-Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} + + Wait for the result of the hook command before proceeding to the Execution Steps. + ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. +- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently + +## Execution Steps + +1. **Setup**: Run `.specify/scripts/bash/check-prerequisites.sh --json --template checklist-template` from repo root and parse JSON for FEATURE_DIR, AVAILABLE_DOCS list, and TEMPLATE_CONTENT. + - All file paths must be absolute. + - For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). + +2. **IF EXISTS**: Load `.specify/memory/constitution.md` for project principles and governance constraints. + +3. **Clarify intent (dynamic)**: Derive up to THREE initial contextual clarifying questions (no pre-baked catalog). They MUST: + - Be generated from the user's phrasing + extracted signals from spec/plan/tasks + - Only ask about information that materially changes checklist content + - Be skipped individually if already unambiguous in `$ARGUMENTS` + - Prefer precision over breadth + + Generation algorithm: + 1. Extract signals: feature domain keywords (e.g., auth, latency, UX, API), risk indicators ("critical", "must", "compliance"), stakeholder hints ("QA", "review", "security team"), and explicit deliverables ("a11y", "rollback", "contracts"). + 2. Cluster signals into candidate focus areas (max 4) ranked by relevance. + 3. Identify probable audience & timing (author, reviewer, QA, release) if not explicit. + 4. Detect missing dimensions: scope breadth, depth/rigor, risk emphasis, exclusion boundaries, measurable acceptance criteria. + 5. Formulate questions chosen from these archetypes: + - Scope refinement (e.g., "Should this include integration touchpoints with X and Y or stay limited to local module correctness?") + - Risk prioritization (e.g., "Which of these potential risk areas should receive mandatory gating checks?") + - Depth calibration (e.g., "Is this a lightweight pre-commit sanity list or a formal release gate?") + - Audience framing (e.g., "Will this be used by the author only or peers during PR review?") + - Boundary exclusion (e.g., "Should we explicitly exclude performance tuning items this round?") + - Scenario class gap (e.g., "No recovery flows detected—are rollback / partial failure paths in scope?") + + Question formatting rules: + - If presenting options, generate a compact table with columns: Option | Candidate | Why It Matters + - Limit to A–E options maximum; omit table if a free-form answer is clearer + - Never ask the user to restate what they already said + - Avoid speculative categories (no hallucination). If uncertain, ask explicitly: "Confirm whether X belongs in scope." + + Defaults when interaction impossible: + - Depth: Standard + - Audience: Reviewer (PR) if code-related; Author otherwise + - Focus: Top 2 relevance clusters + + Output the questions (label Q1/Q2/Q3). After answers: if ≥2 scenario classes (Alternate / Exception / Recovery / Non-Functional domain) remain unclear, you MAY ask up to TWO more targeted follow‑ups (Q4/Q5) with a one-line justification each (e.g., "Unresolved recovery path risk"). Do not exceed five total questions. Skip escalation if user explicitly declines more. + +4. **Understand user request**: Combine `$ARGUMENTS` + clarifying answers: + - Derive checklist theme (e.g., security, review, deploy, ux) + - Consolidate explicit must-have items mentioned by user + - Map focus selections to category scaffolding + - Infer any missing context from spec/plan/tasks (do NOT hallucinate) + +5. **Load feature context**: Read from FEATURE_DIR: + - spec.md: Feature requirements and scope + - plan.md (if exists): Technical details, dependencies + - tasks.md (if exists): Implementation tasks + + **Context Loading Strategy**: + - Load only necessary portions relevant to active focus areas (avoid full-file dumping) + - Prefer summarizing long sections into concise scenario/requirement bullets + - Use progressive disclosure: add follow-on retrieval only if gaps detected + - If source docs are large, generate interim summary items instead of embedding raw text + +6. **Generate checklist** - Use TEMPLATE_CONTENT as the structural template and create "Unit Tests for Requirements": + - Create `FEATURE_DIR/checklists/` directory if it doesn't exist + - Generate unique checklist filename: + - Use short, descriptive name based on domain (e.g., `ux.md`, `api.md`, `security.md`) + - Format: `[domain].md` + - File handling behavior: + - If file does NOT exist: Create new file and number items starting from CHK001 + - If file exists: Append new items to existing file, continuing from the last CHK ID (e.g., if last item is CHK015, start new items at CHK016) + - Never delete or replace existing checklist content - always preserve and append + - Leave every newly generated item unchecked (`[ ]`); checkbox state belongs to the reviewer + + **CORE PRINCIPLE - Test the Requirements, Not the Implementation**: + Every checklist item MUST evaluate the REQUIREMENTS THEMSELVES for: + - **Completeness**: Are all necessary requirements present? + - **Clarity**: Are requirements unambiguous and specific? + - **Consistency**: Do requirements align with each other? + - **Measurability**: Can requirements be objectively verified? + - **Coverage**: Are all scenarios/edge cases addressed? + + **Category Structure** - Group items by requirement quality dimensions: + - **Requirement Completeness** (Are all necessary requirements documented?) + - **Requirement Clarity** (Are requirements specific and unambiguous?) + - **Requirement Consistency** (Do requirements align without conflicts?) + - **Acceptance Criteria Quality** (Are success criteria measurable?) + - **Scenario Coverage** (Are all flows/cases addressed?) + - **Edge Case Coverage** (Are boundary conditions defined?) + - **Non-Functional Requirements** (Performance, Security, Accessibility, etc. - are they specified?) + - **Dependencies & Assumptions** (Are they documented and validated?) + - **Ambiguities & Conflicts** (What needs clarification?) + + **HOW TO WRITE CHECKLIST ITEMS - "Unit Tests for English"**: + + ❌ **WRONG** (Testing implementation): + - "Verify landing page displays 3 episode cards" + - "Test hover states work on desktop" + - "Confirm logo click navigates home" + + ✅ **CORRECT** (Testing requirements quality): + - "Are the exact number and layout of featured episodes specified?" [Completeness] + - "Is 'prominent display' quantified with specific sizing/positioning?" [Clarity] + - "Are hover state requirements consistent across all interactive elements?" [Consistency] + - "Are keyboard navigation requirements defined for all interactive UI?" [Coverage] + - "Is the fallback behavior specified when logo image fails to load?" [Edge Cases] + - "Are loading states defined for asynchronous episode data?" [Completeness] + - "Does the spec define visual hierarchy for competing UI elements?" [Clarity] + + **ITEM STRUCTURE**: + Each item should follow this pattern: + - Question format asking about requirement quality + - Focus on what's WRITTEN (or not written) in the spec/plan + - Include quality dimension in brackets [Completeness/Clarity/Consistency/etc.] + - Reference spec section `[Spec §X.Y]` when checking existing requirements + - Use `[Gap]` marker when checking for missing requirements + + **EXAMPLES BY QUALITY DIMENSION**: + + Completeness: + - "Are error handling requirements defined for all API failure modes? [Gap]" + - "Are accessibility requirements specified for all interactive elements? [Completeness]" + - "Are mobile breakpoint requirements defined for responsive layouts? [Gap]" + + Clarity: + - "Is 'fast loading' quantified with specific timing thresholds? [Clarity, Spec §NFR-2]" + - "Are 'related episodes' selection criteria explicitly defined? [Clarity, Spec §FR-5]" + - "Is 'prominent' defined with measurable visual properties? [Ambiguity, Spec §FR-4]" + + Consistency: + - "Do navigation requirements align across all pages? [Consistency, Spec §FR-10]" + - "Are card component requirements consistent between landing and detail pages? [Consistency]" + + Coverage: + - "Are requirements defined for zero-state scenarios (no episodes)? [Coverage, Edge Case]" + - "Are concurrent user interaction scenarios addressed? [Coverage, Gap]" + - "Are requirements specified for partial data loading failures? [Coverage, Exception Flow]" + + Measurability: + - "Are visual hierarchy requirements measurable/testable? [Acceptance Criteria, Spec §FR-1]" + - "Can 'balanced visual weight' be objectively verified? [Measurability, Spec §FR-2]" + + **Scenario Classification & Coverage** (Requirements Quality Focus): + - Check if requirements exist for: Primary, Alternate, Exception/Error, Recovery, Non-Functional scenarios + - For each scenario class, ask: "Are [scenario type] requirements complete, clear, and consistent?" + - If scenario class missing: "Are [scenario type] requirements intentionally excluded or missing? [Gap]" + - Include resilience/rollback when state mutation occurs: "Are rollback requirements defined for migration failures? [Gap]" + + **Traceability Requirements**: + - MINIMUM: ≥80% of items MUST include at least one traceability reference + - Each item should reference: spec section `[Spec §X.Y]`, or use markers: `[Gap]`, `[Ambiguity]`, `[Conflict]`, `[Assumption]` + - If no ID system exists: "Is a requirement & acceptance criteria ID scheme established? [Traceability]" + + **Surface & Resolve Issues** (Requirements Quality Problems): + Ask questions about the requirements themselves: + - Ambiguities: "Is the term 'fast' quantified with specific metrics? [Ambiguity, Spec §NFR-1]" + - Conflicts: "Do navigation requirements conflict between §FR-10 and §FR-10a? [Conflict]" + - Assumptions: "Is the assumption of 'always available podcast API' validated? [Assumption]" + - Dependencies: "Are external podcast API requirements documented? [Dependency, Gap]" + - Missing definitions: "Is 'visual hierarchy' defined with measurable criteria? [Gap]" + + **Content Consolidation**: + - Soft cap: If raw candidate items > 40, prioritize by risk/impact + - Merge near-duplicates checking the same requirement aspect + - If >5 low-impact edge cases, create one item: "Are edge cases X, Y, Z addressed in requirements? [Coverage]" + + **🚫 ABSOLUTELY PROHIBITED** - These make it an implementation test, not a requirements test: + - ❌ Any item starting with "Verify", "Test", "Confirm", "Check" + implementation behavior + - ❌ References to code execution, user actions, system behavior + - ❌ "Displays correctly", "works properly", "functions as expected" + - ❌ "Click", "navigate", "render", "load", "execute" + - ❌ Test cases, test plans, QA procedures + - ❌ Implementation details (frameworks, APIs, algorithms) + + **✅ REQUIRED PATTERNS** - These test requirements quality: + - ✅ "Are [requirement type] defined/specified/documented for [scenario]?" + - ✅ "Is [vague term] quantified/clarified with specific criteria?" + - ✅ "Are requirements consistent between [section A] and [section B]?" + - ✅ "Can [requirement] be objectively measured/verified?" + - ✅ "Are [edge cases/scenarios] addressed in requirements?" + - ✅ "Does the spec define [missing aspect]?" + +7. **Structure Reference**: Generate the checklist following the canonical template in `.specify/templates/checklist-template.md` for title, meta section, category headings, ownership note, notes section, and ID formatting. If template is unavailable, use: H1 title, purpose/created meta lines, an ownership note explaining that `[x]` means reviewer approval of requirements quality, `##` category sections containing `- [ ] CHK### ` lines with globally incrementing IDs starting at CHK001, and notes that `/skill:speckit-implement` reads checklist state but does not modify markers. + +8. **Report**: Output full path to checklist file, item count, and summarize whether the run created a new file or appended to an existing one. Summarize: + - Focus areas selected + - Depth level + - Actor/timing + - Any explicit user-specified must-have items incorporated + +**Important**: Each `/skill:speckit-checklist` command invocation uses a short, descriptive checklist filename and either creates a new file or appends to an existing one. This allows: + +- Multiple checklists of different types (e.g., `ux.md`, `test.md`, `security.md`) +- Simple, memorable filenames that indicate checklist purpose +- Easy identification and navigation in the `checklists/` folder + +To avoid clutter, use descriptive types and clean up obsolete checklists when done. + +## Example Checklist Types & Sample Items + +**UX Requirements Quality:** `ux.md` + +Sample items (testing the requirements, NOT the implementation): + +- "Are visual hierarchy requirements defined with measurable criteria? [Clarity, Spec §FR-1]" +- "Is the number and positioning of UI elements explicitly specified? [Completeness, Spec §FR-1]" +- "Are interaction state requirements (hover, focus, active) consistently defined? [Consistency]" +- "Are accessibility requirements specified for all interactive elements? [Coverage, Gap]" +- "Is fallback behavior defined when images fail to load? [Edge Case, Gap]" +- "Can 'prominent display' be objectively measured? [Measurability, Spec §FR-4]" + +**API Requirements Quality:** `api.md` + +Sample items: + +- "Are error response formats specified for all failure scenarios? [Completeness]" +- "Are rate limiting requirements quantified with specific thresholds? [Clarity]" +- "Are authentication requirements consistent across all endpoints? [Consistency]" +- "Are retry/timeout requirements defined for external dependencies? [Coverage, Gap]" +- "Is versioning strategy documented in requirements? [Gap]" + +**Performance Requirements Quality:** `performance.md` + +Sample items: + +- "Are performance requirements quantified with specific metrics? [Clarity]" +- "Are performance targets defined for all critical user journeys? [Coverage]" +- "Are performance requirements under different load conditions specified? [Completeness]" +- "Can performance requirements be objectively measured? [Measurability]" +- "Are degradation requirements defined for high-load scenarios? [Edge Case, Gap]" + +**Security Requirements Quality:** `security.md` + +Sample items: + +- "Are authentication requirements specified for all protected resources? [Coverage]" +- "Are data protection requirements defined for sensitive information? [Completeness]" +- "Is the threat model documented and requirements aligned to it? [Traceability]" +- "Are security requirements consistent with compliance obligations? [Consistency]" +- "Are security failure/breach response requirements defined? [Gap, Exception Flow]" + +## Anti-Examples: What NOT To Do + +**❌ WRONG - These test implementation, not requirements:** + +```markdown +- [ ] CHK001 - Verify landing page displays 3 episode cards [Spec §FR-001] +- [ ] CHK002 - Test hover states work correctly on desktop [Spec §FR-003] +- [ ] CHK003 - Confirm logo click navigates to home page [Spec §FR-010] +- [ ] CHK004 - Check that related episodes section shows 3-5 items [Spec §FR-005] +``` + +**✅ CORRECT - These test requirements quality:** + +```markdown +- [ ] CHK001 - Are the number and layout of featured episodes explicitly specified? [Completeness, Spec §FR-001] +- [ ] CHK002 - Are hover state requirements consistently defined for all interactive elements? [Consistency, Spec §FR-003] +- [ ] CHK003 - Are navigation requirements clear for all clickable brand elements? [Clarity, Spec §FR-010] +- [ ] CHK004 - Is the selection criteria for related episodes documented? [Gap, Spec §FR-005] +- [ ] CHK005 - Are loading state requirements defined for asynchronous episode data? [Gap] +- [ ] CHK006 - Can "visual hierarchy" requirements be objectively measured? [Measurability, Spec §FR-001] +``` + +**Key Differences:** + +- Wrong: Tests if the system works correctly +- Correct: Tests if the requirements are written correctly +- Wrong: Verification of behavior +- Correct: Validation of requirement quality +- Wrong: "Does it do X?" +- Correct: "Is X clearly specified?" + +## Post-Execution Checks + +**Check for extension hooks (after checklist generation)**: +Check if `.specify/extensions.yml` exists in the project root. +- If it exists, read it and look for entries under the `hooks.after_checklist` key +- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally +- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. +- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: + - If the hook has no `condition` field, or it is null/empty, treat the hook as executable + - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation +- When constructing command invocations from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/skill:speckit-git-commit`. +- For each executable hook, output the following based on its `optional` flag: + - **Optional hook** (`optional: true`): + ``` + ## Extension Hooks + + **Optional Hook**: {extension} + Command: `/{command}` + Description: {description} + + Prompt: {prompt} + To execute: `/{command}` + ``` + - **Mandatory hook** (`optional: false`): + ``` + ## Extension Hooks + + **Automatic Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} + ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. +- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently diff --git a/.kimi-code/skills/speckit-clarify/SKILL.md b/.kimi-code/skills/speckit-clarify/SKILL.md new file mode 100644 index 00000000000..2ef36d7e375 --- /dev/null +++ b/.kimi-code/skills/speckit-clarify/SKILL.md @@ -0,0 +1,291 @@ +--- +name: "speckit-clarify" +description: "Identify underspecified areas in the current feature spec by asking up to 5 highly targeted clarification questions and encoding answers back into the spec." +compatibility: "Requires spec-kit project structure with .specify/ directory" +metadata: + author: "github-spec-kit" + source: "templates/commands/clarify.md" +--- + + +## User Input + +```text +$ARGUMENTS +``` + +You **MUST** consider the user input before proceeding (if not empty). + +## Pre-Execution Checks + +**Check for extension hooks (before clarification)**: +- Check if `.specify/extensions.yml` exists in the project root. +- If it exists, read it and look for entries under the `hooks.before_clarify` key +- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally +- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default. +- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions: + - If the hook has no `condition` field, or it is null/empty, treat the hook as executable + - If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation +- When constructing command invocations from hook command names, replace dots (`.`) with hyphens (`-`). For example, `speckit.git.commit` → `/skill:speckit-git-commit`. +- For each executable hook, output the following based on its `optional` flag: + - **Optional hook** (`optional: true`): + ``` + ## Extension Hooks + + **Optional Pre-Hook**: {extension} + Command: `/{command}` + Description: {description} + + Prompt: {prompt} + To execute: `/{command}` + ``` + - **Mandatory hook** (`optional: false`): + ``` + ## Extension Hooks + + **Automatic Pre-Hook**: {extension} + Executing: `/{command}` + EXECUTE_COMMAND: {command} + + Wait for the result of the hook command before proceeding to the Outline. + ``` + After emitting the block above you MUST actually invoke the hook and wait for it to finish before continuing. Run it the same way you would run the command yourself in this agent/session (the invocation may differ from the literal `{command}` id shown above, e.g. a skills-mode agent runs it as `/skill:speckit-...` or `$speckit-...`). Emitting the block alone does not run the hook. +- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently + +## Outline + +Goal: Detect and reduce ambiguity or missing decision points in the active feature specification and record the clarifications directly in the spec file. + +Note: This clarification workflow is expected to run (and be completed) BEFORE invoking `/skill:speckit-plan`. If the user explicitly states they are skipping clarification (e.g., exploratory spike), you may proceed, but must warn that downstream rework risk increases. + +Execution steps: + +1. Run `.specify/scripts/bash/check-prerequisites.sh --json --paths-only` from repo root **once** (combined `--json --paths-only` mode / `-Json -PathsOnly`). Parse minimal JSON payload fields: + - `FEATURE_DIR` + - `FEATURE_SPEC` + - (Optionally capture `IMPL_PLAN`, `TASKS` for future chained flows.) + - If JSON parsing fails, abort and instruct user to re-run `/skill:speckit-specify` or verify feature branch environment. + - For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot"). + +2. **IF EXISTS**: Load `.specify/memory/constitution.md` for project principles and governance constraints. + +3. Load the current spec file. Perform a structured ambiguity & coverage scan using this taxonomy. For each category, mark status: Clear / Partial / Missing. Produce an internal coverage map used for prioritization (do not output raw map unless no questions will be asked). + + Functional Scope & Behavior: + - Core user goals & success criteria + - Explicit out-of-scope declarations + - User roles / personas differentiation + + Domain & Data Model: + - Entities, attributes, relationships + - Identity & uniqueness rules + - Lifecycle/state transitions + - Data volume / scale assumptions + + Interaction & UX Flow: + - Critical user journeys / sequences + - Error/empty/loading states + - Accessibility or localization notes + + Non-Functional Quality Attributes: + - Performance (latency, throughput targets) + - Scalability (horizontal/vertical, limits) + - Reliability & availability (uptime, recovery expectations) + - Observability (logging, metrics, tracing signals) + - Security & privacy (authN/Z, data protection, threat assumptions) + - Compliance / regulatory constraints (if any) + + Integration & External Dependencies: + - External services/APIs and failure modes + - Data import/export formats + - Protocol/versioning assumptions + + Edge Cases & Failure Handling: + - Negative scenarios + - Rate limiting / throttling + - Conflict resolution (e.g., concurrent edits) + + Constraints & Tradeoffs: + - Technical constraints (language, storage, hosting) + - Explicit tradeoffs or rejected alternatives + + Terminology & Consistency: + - Canonical glossary terms + - Avoided synonyms / deprecated terms + + Completion Signals: + - Acceptance criteria testability + - Measurable Definition of Done style indicators + + Misc / Placeholders: + - TODO markers / unresolved decisions + - Ambiguous adjectives ("robust", "intuitive") lacking quantification + + For each category with Partial or Missing status, add a candidate question opportunity unless: + - Clarification would not materially change implementation or validation strategy + - Information is better deferred to planning phase (note internally) + +4. Generate (internally) a prioritized queue of candidate clarification questions (maximum 5). Do NOT output them all at once. Apply these constraints: + - Maximum of 5 total questions across the whole session. + - Each question must be answerable with EITHER: + - A short multiple‑choice selection (2–5 distinct, mutually exclusive options), OR + - A one-word / short‑phrase answer (explicitly constrain: "Answer in <=5 words"). + - Only include questions whose answers materially impact architecture, data modeling, task decomposition, test design, UX behavior, operational readiness, or compliance validation. + - Ensure category coverage balance: attempt to cover the highest impact unresolved categories first; avoid asking two low-impact questions when a single high-impact area (e.g., security posture) is unresolved. + - Exclude questions already answered, trivial stylistic preferences, or plan-level execution details (unless blocking correctness). + - Favor clarifications that reduce downstream rework risk or prevent misaligned acceptance tests. + - If more than 5 categories remain unresolved, select the top 5 by (Impact * Uncertainty) heuristic. + +5. Sequential questioning loop (interactive): + - Present EXACTLY ONE question at a time. + - **Question writing quality (applies to every question, MC or short-answer):** + - Lead with `**Question:**` followed by a full interrogative that ends with `?`. The question text before the `?` must make sense on its own. + - NEVER use a topic label, section heading, or requirement id as the question itself. For example, `Acceptance device/runtime matrix (FR-023)` is INVALID — it is a label, not a question. + - After the `?`, the only permitted suffix is an optional parenthesized requirement/question id. Exact format: `**Question:** ?` or `**Question:** ? (FR-023)`. Never put the id before the `?`, and never use the id (alone or with a topic label) as the whole prompt. + - Immediately after the question line, add one plain-language "Why it matters" sentence (the stake for acceptance or shipping) before the recommendation/options. + - Use everyday wording; introduce jargon only if defined in the same sentence. Self-check: a reader who does not know Spec Kit must be able to answer from the Question line alone. Terse is fine; cryptic labels are not. + - For multiple‑choice questions: + - **Analyze all options** and determine the **most suitable option** based on: + - Best practices for the project type + - Common patterns in similar implementations + - Risk reduction (security, performance, maintainability) + - Alignment with any explicit project goals or constraints visible in the spec + - Present your **recommended option prominently** at the top with clear reasoning (1-2 sentences explaining why this is the best choice). + - Format as: `**Recommended:** Option [X] - ` + - Then render all options as a Markdown table: + + | Option | Description | + |--------|-------------| + | A |