fix(vscode): restore speech input availability

This commit is contained in:
marius-kilocode
2026-06-16 14:12:47 +02:00
parent 327eddb196
commit 2fa0890928
49 changed files with 460 additions and 165 deletions
@@ -0,0 +1,7 @@
---
"@kilocode/cli": patch
"@kilocode/sdk": patch
"kilo-code": patch
---
Restore speech input when profile details are unavailable, move transcription model selection to the Models tab, and default transcription to Whisper Large V3 Turbo.
@@ -41,7 +41,7 @@ Enable and sign in to the Kilo provider to use voice input in prompt fields. Req
## Choose a model
You can optionally choose a transcription model in **Settings** > **Experimental** > **Speech to Text Model**. Kilo stores this choice as `experimental.speech_to_text_model` in your global Kilo CLI config (`~/.config/kilo/kilo.jsonc`).
You can optionally choose a transcription model in **Settings** > **Models** > **Speech to Text Model**. Kilo stores this choice as `experimental.speech_to_text_model` in your global Kilo CLI config (`~/.config/kilo/kilo.jsonc`).
---
@@ -47,6 +47,18 @@ Use **Local Config** or **Global Config** in the Settings header to open the mat
If you check config files into version control, make sure they do not contain API keys or other secrets (e.g., `provider.*.options.apiKey`). Use environment variables for credentials instead.
{% /callout %}
### Voice Transcription Model
When the Kilo provider is enabled and you are signed in, choose the transcription model under **Models** > **Speech to Text Model**. This stores `experimental.speech_to_text_model` in your global Kilo CLI config:
```json
{
"experimental": {
"speech_to_text_model": "openai/whisper-large-v3-turbo"
}
}
```
### Reasoning Blocks
Reasoning blocks stay expanded by default in the VS Code chat UI. Enable **Auto-Collapse Reasoning** in the Display tab, or set `auto_collapse_reasoning` in `kilo.jsonc`, to collapse them after the agent finishes writing them:
@@ -198,20 +210,9 @@ Available experimental settings include:
- **Share mode** - `manual`, `auto`, or `disabled` session sharing
- **LSP integration** - expose language server diagnostics to the agent
- **Paste summary** - summarize large clipboard pastes before including them
- **Speech to Text Model** - optionally select the transcription model
- **Batch tool** - allow the agent to batch multiple tool calls in one step
- **OpenTelemetry** - enable Kilo telemetry and optional OTLP export when configured
Voice input appears automatically when the Kilo provider is enabled and you are signed in. Choosing **Speech to Text Model** stores `experimental.speech_to_text_model` in your global Kilo CLI config (`~/.config/kilo/kilo.jsonc`):
```json
{
"experimental": {
"speech_to_text_model": "openai/gpt-4o-mini-transcribe"
}
}
```
Advanced options not exposed in the UI can be configured via the `experimental` key in `kilo.jsonc`:
```json
-4
View File
@@ -902,10 +902,6 @@
"default": false,
"description": "Enable chat textarea autocomplete"
},
"kilo-code.new.speechToText.model": {
"type": "string",
"description": "Model to use for experimental speech-to-text voice input. Requires Kilo Gateway."
},
"kilo-code.new.claudeCodeCompat": {
"type": "boolean",
"default": false,
+13 -3
View File
@@ -9,7 +9,7 @@ import {
sanitizeCustomProviderConfig,
withCustomProviderDeletions,
} from "./shared/custom-provider"
import { isCustomProviderPackage, KILO_AUTO, parseModelString } from "./shared/provider-model"
import { isCustomProviderPackage, KILO_AUTO, KILO_PROVIDER_ID, parseModelString } from "./shared/provider-model"
import { configFeatures } from "./features"
/**
@@ -50,7 +50,7 @@ function same(a: unknown, b: unknown): boolean {
return akeys.every((key, index) => key === bkeys[index] && same(a[key], b[key]))
}
/** Fetch auth methods alongside the provider list. Auth states default to empty (endpoint not yet available). */
/** Fetch provider availability and authentication state without exposing stored credentials. */
export async function fetchProviderData(client: KiloClient, dir: string) {
const authRequest =
typeof client.provider.auth === "function"
@@ -59,10 +59,18 @@ export async function fetchProviderData(client: KiloClient, dir: string) {
.then((r) => r.data ?? {})
.catch(() => ({}))
: Promise.resolve({})
const kiloRequest =
typeof client.kilo?.authStatus === "function"
? client.kilo
.authStatus({ directory: dir }, { throwOnError: true })
.then((r) => (r.data?.authenticated ? (r.data.type ?? null) : null))
.catch(() => null)
: Promise.resolve(null)
const [{ data: response }, authMethods] = await Promise.all([
const [{ data: response }, authMethods, kiloAuth] = await Promise.all([
client.provider.list({ directory: dir }, { throwOnError: true }),
authRequest,
kiloRequest,
])
const authStates: Record<string, AuthState> = {}
const storedKeys: Record<string, StoredProviderKey> = {}
@@ -83,6 +91,8 @@ export async function fetchProviderData(client: KiloClient, dir: string) {
delete next.key
return next as (typeof response.all)[number]
})
delete authStates[KILO_PROVIDER_ID]
if (kiloAuth) authStates[KILO_PROVIDER_ID] = kiloAuth
return { response: { ...response, all }, authMethods, authStates, storedKeys }
}
@@ -6,6 +6,11 @@ export interface SpeechToTextModelDef {
}
const models: SpeechToTextModelDef[] = [
{
id: "openai/whisper-large-v3-turbo",
label: "Whisper Large V3 Turbo",
provider: "OpenAI-compatible",
},
{
id: "openai/gpt-4o-mini-transcribe",
label: "GPT-4o Mini Transcribe",
@@ -23,11 +28,6 @@ const models: SpeechToTextModelDef[] = [
label: "Whisper 1",
provider: "OpenAI",
},
{
id: "openai/whisper-large-v3-turbo",
label: "Whisper Large V3 Turbo",
provider: "OpenAI-compatible",
},
{
id: "openai/whisper-large-v3",
label: "Whisper Large V3",
@@ -133,6 +133,13 @@ test("settings and mode editing expose distinct model field purposes", async ({
/Override the default model for specific modes/,
)
await load(page, "settings--models-speech-to-text")
const speech = page.getByRole("button", { name: "Speech to Text Model: Chirp 3" })
await expect(speech).toBeEnabled()
await speech.click()
await page.getByRole("option", { name: "GPT-4o Mini Transcribe (OpenAI)" }).click()
await expect(page.getByRole("button", { name: "Speech to Text Model: GPT-4o Mini Transcribe" })).toBeVisible()
await load(page, "settings--mode-edit-export")
await expect(page.getByRole("button", { name: /Model Override:/ })).toHaveAccessibleDescription(
"Override the default model for this agent",
@@ -444,6 +444,50 @@ describe("fetchProviderData", () => {
expect("key" in item).toBe(false)
})
it("uses local Kilo auth status instead of profile availability", async () => {
const client = {
provider: {
list: async () => ({
data: {
all: [{ id: "kilo", name: "Kilo Gateway", source: "custom", env: [], models: {} }],
connected: ["kilo"],
default: { kilo: "kilo-auto/frontier" },
},
}),
auth: async () => ({ data: {} }),
},
kilo: {
authStatus: async () => ({ data: { authenticated: true, type: "oauth" } }),
},
} as unknown as Parameters<typeof fetchProviderData>[0]
const result = await fetchProviderData(client, "/tmp")
expect(result.authStates).toEqual({ kilo: "oauth" })
})
it("does not infer Kilo speech access without stored Gateway auth", async () => {
const client = {
provider: {
list: async () => ({
data: {
all: [{ id: "kilo", name: "Kilo Gateway", source: "config", key: "configured", env: [], models: {} }],
connected: ["kilo"],
default: { kilo: "kilo-auto/frontier" },
},
}),
auth: async () => ({ data: {} }),
},
kilo: {
authStatus: async () => ({ data: { authenticated: false } }),
},
} as unknown as Parameters<typeof fetchProviderData>[0]
const result = await fetchProviderData(client, "/tmp")
expect(result.authStates).toEqual({})
})
it("retains stripped keys for providers with a configured baseURL", async () => {
const client = {
provider: {
@@ -6,17 +6,20 @@ import {
import { DEFAULT_SPEECH_TO_TEXT_MODEL } from "../../src/speech-to-text/models"
describe("speech-to-text availability", () => {
const providers = ["kilo"]
const profile = {}
it("shows speech input by default when Kilo access exists", () => {
expect(canUseSpeechToText({}, providers, profile)).toBe(true)
it("shows speech input for stored Kilo credentials", () => {
expect(canUseSpeechToText({}, { kilo: "oauth" })).toBe(true)
expect(canUseSpeechToText({}, { kilo: "api" })).toBe(true)
})
it("hides speech input without a signed-in, enabled Kilo provider", () => {
expect(canUseSpeechToText({}, [], profile)).toBe(false)
expect(canUseSpeechToText({}, providers, null)).toBe(false)
expect(canUseSpeechToText({ disabled_providers: ["kilo"] }, providers, profile)).toBe(false)
it("hides speech input without usable Kilo credentials", () => {
expect(canUseSpeechToText({}, {})).toBe(false)
expect(canUseSpeechToText({}, { kilo: "wellknown" })).toBe(false)
})
it("honors enabled and disabled provider configuration", () => {
expect(canUseSpeechToText({ disabled_providers: ["kilo"] }, { kilo: "oauth" })).toBe(false)
expect(canUseSpeechToText({ enabled_providers: ["openai"] }, { kilo: "oauth" })).toBe(false)
expect(canUseSpeechToText({ enabled_providers: ["kilo"] }, { kilo: "oauth" })).toBe(true)
})
it("normalizes configured and unknown transcription models", () => {
@@ -6,7 +6,8 @@ import {
} from "../../src/speech-to-text/models"
describe("speech-to-text model catalog", () => {
it("keeps the first speech model as the fallback default", () => {
it("uses Whisper Large V3 Turbo as the fallback default", () => {
expect(DEFAULT_SPEECH_TO_TEXT_MODEL.id).toBe("openai/whisper-large-v3-turbo")
expect(DEFAULT_SPEECH_TO_TEXT_MODEL.id).toBe(SPEECH_TO_TEXT_MODELS[0]?.id)
})
@@ -2,8 +2,13 @@ import { describe, expect, it, mock } from "bun:test"
import { createRoot } from "solid-js"
import type { ExtensionMessage, WebviewMessage } from "../../webview-ui/src/types/messages"
type Toast = {
actions?: Array<{ onClick: string | (() => void) }>
}
const toasts: Toast[] = []
mock.module("@kilocode/kilo-ui/toast", () => ({
showToast: () => undefined,
showToast: (toast: Toast) => toasts.push(toast),
}))
const { useSpeechToText } = await import("../../webview-ui/src/components/speech-to-text/useSpeechToText")
@@ -11,6 +16,8 @@ const { useSpeechToText } = await import("../../webview-ui/src/components/speech
function setup() {
const sent: WebviewMessage[] = []
let handler: ((message: ExtensionMessage) => void) | undefined
let logins = 0
toasts.length = 0
const root = createRoot((dispose) => ({
dispose,
@@ -24,16 +31,37 @@ function setup() {
}
},
},
{ profileData: () => ({}), goToLogin: () => {} },
{ goToLogin: () => logins++ },
{ t: (key) => key },
),
}))
const fire = (message: ExtensionMessage) => handler?.(message)
return { ...root, fire, sent }
return { ...root, fire, sent, logins: () => logins }
}
describe("useSpeechToText", () => {
it("offers sign-in when stored credentials stop authenticating", () => {
const ctx = setup()
ctx.speech.start({ model: "scribe", insert: () => {} })
const start = ctx.sent[0]
if (start?.type !== "speechToTextStart") throw new Error("speech start message missing")
ctx.fire({
type: "speechToTextError",
requestId: start.requestId,
error: "Unauthorized",
code: "not_authenticated",
})
const action = toasts[0]?.actions?.find((item) => typeof item.onClick === "function")
if (typeof action?.onClick === "function") action.onClick()
expect(ctx.logins()).toBe(1)
expect(ctx.speech.error()).toBe("speechToText.error.loginRequired")
ctx.dispose()
})
it("runs the stop completion after inserting a transcript", () => {
const ctx = setup()
const text: string[] = []
@@ -92,7 +92,7 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
const provider = useProvider()
const { config } = useConfig()
const speech = useSpeechToText(vscode, server, { t })
const canUseSpeech = () => canUseSpeechToText(config(), provider.connected(), server.profileData())
const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates())
const speechModel = () => selectedSpeechToTextModel(config())
const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent)
const sendAllKeybind = () =>
@@ -103,7 +103,7 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
const [highlightedIndex, setHighlightedIndex] = createSignal(0)
const [variant, setVariant] = createSignal<string | undefined>(session.currentVariant())
const speech = useSpeechToText(vscode, server, { t })
const canUseSpeech = () => canUseSpeechToText(config(), provider.connected(), server.profileData())
const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates())
const speechModel = () => selectedSpeechToTextModel(config())
// Variant list for the currently selected model
@@ -96,7 +96,7 @@ export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (props) =>
const provider = useProvider()
const { config } = useConfig()
const speech = useSpeechToText(vscode, server, { t })
const canUseSpeech = () => canUseSpeechToText(config(), provider.connected(), server.profileData())
const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates())
const speechModel = () => selectedSpeechToTextModel(config())
const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent)
const sendAllKeybind = () =>
@@ -295,7 +295,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const isBusy = () =>
isPromptBusy(session.status(), !!props.suggesting?.(), !!props.questioning?.(), session.submitting())
const isDisabled = () => !server.isConnected()
const canUseSpeech = () => canUseSpeechToText(config(), provider.connected(), server.profileData())
const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates())
const speechModel = () => selectedSpeechToTextModel(config())
const hasInput = () => text().trim().length > 0 || imageAttach.images().length > 0 || reviewComments().length > 0
const canSend = () =>
@@ -1,19 +1,13 @@
import { Component, For, Show, createMemo, createSignal, onCleanup, onMount } from "solid-js"
import { Switch } from "@kilocode/kilo-ui/switch"
import { Tooltip } from "@kilocode/kilo-ui/tooltip"
import { Select } from "@kilocode/kilo-ui/select"
import { TextField } from "@kilocode/kilo-ui/text-field"
import { Card } from "@kilocode/kilo-ui/card"
import { useConfig } from "../../context/config"
import { useLanguage } from "../../context/language"
import { useProvider } from "../../context/provider"
import { useServer } from "../../context/server"
import { useVSCode } from "../../context/vscode"
import type { ExtensionMessage } from "../../types/messages"
import SettingsRow from "./SettingsRow"
import { DEFAULT_SPEECH_TO_TEXT_MODEL } from "../../../../src/speech-to-text/models"
import { hasSpeechToTextAccess, selectedSpeechToTextModel } from "../speech-to-text/availability"
import { SPEECH_TO_TEXT_MODEL_OPTIONS } from "../speech-to-text/model-selector"
interface ShareOption {
value: string
@@ -29,8 +23,6 @@ const SHARE_OPTIONS: ShareOption[] = [
const ExperimentalTab: Component = () => {
const { config, updateConfig } = useConfig()
const language = useLanguage()
const provider = useProvider()
const server = useServer()
const vscode = useVSCode()
const [active, setActive] = createSignal(false)
@@ -47,8 +39,6 @@ const ExperimentalTab: Component = () => {
})
const experimental = createMemo(() => config().experimental ?? {})
const kiloReady = createMemo(() => hasSpeechToTextAccess(config(), provider.connected(), server.profileData()))
const speechModel = createMemo(() => selectedSpeechToTextModel(config()))
const updateExperimental = (key: string, value: unknown) => {
updateConfig({
@@ -164,36 +154,6 @@ const ExperimentalTab: Component = () => {
</Switch>
</SettingsRow>
<SettingsRow
title={language.t("settings.experimental.speechToTextModel.title")}
description={
kiloReady()
? language.t("settings.experimental.speechToTextModel.description")
: language.t("settings.experimental.speechToText.disabledDescription")
}
>
<Tooltip
value={language.t("settings.experimental.speechToText.disabledDescription")}
placement="top"
inactive={kiloReady()}
>
<Select
options={SPEECH_TO_TEXT_MODEL_OPTIONS}
current={SPEECH_TO_TEXT_MODEL_OPTIONS.find((item) => item.value === speechModel())}
value={(item) => item.value}
label={(item) => `${item.label} (${item.provider})`}
onSelect={(item) =>
updateExperimental("speech_to_text_model", item?.value ?? DEFAULT_SPEECH_TO_TEXT_MODEL.id)
}
variant="secondary"
size="small"
triggerVariant="settings"
disabled={!kiloReady()}
placeholder={DEFAULT_SPEECH_TO_TEXT_MODEL.label}
/>
</Tooltip>
</SettingsRow>
<SettingsRow
title={language.t("settings.experimental.continueOnDeny.title")}
description={language.t("settings.experimental.continueOnDeny.description")}
@@ -1,6 +1,8 @@
import { Component, For, Show, createMemo } from "solid-js"
import { Card } from "@kilocode/kilo-ui/card"
import { Select } from "@kilocode/kilo-ui/select"
import { Switch } from "@kilocode/kilo-ui/switch"
import { Tooltip } from "@kilocode/kilo-ui/tooltip"
import { useConfig } from "../../context/config"
import { useLanguage } from "../../context/language"
import { useProvider } from "../../context/provider"
@@ -9,6 +11,9 @@ import { parseModelString } from "../../../../src/shared/provider-model"
import { ModelSelectorBase } from "../shared/ModelSelector"
import { ThinkingSelectorBase } from "../shared/ThinkingSelector"
import SettingsRow from "./SettingsRow"
import { DEFAULT_SPEECH_TO_TEXT_MODEL } from "../../../../src/speech-to-text/models"
import { hasSpeechToTextAccess, selectedSpeechToTextModel } from "../speech-to-text/availability"
import { SPEECH_TO_TEXT_MODEL_OPTIONS } from "../speech-to-text/model-selector"
import { AUTOCOMPLETE_SELECTOR_MODELS, getAutocompleteSelection } from "./autocomplete-model-selector"
const ModelsTab: Component = () => {
@@ -37,6 +42,11 @@ const ModelsTab: Component = () => {
}
const subagentModel = createMemo(() => parseModelString(config().subagent_model ?? undefined))
const speechModel = createMemo(() => selectedSpeechToTextModel(config()))
const speechOption = createMemo(
() => SPEECH_TO_TEXT_MODEL_OPTIONS.find((item) => item.value === speechModel()) ?? SPEECH_TO_TEXT_MODEL_OPTIONS[0],
)
const kiloReady = createMemo(() => hasSpeechToTextAccess(config(), provider.authStates()))
const variantKey = createMemo(() => config().subagent_model ?? undefined)
const subagentVariants = createMemo(() => Object.keys(provider.findModel(subagentModel())?.variants ?? {}))
const subagentVariant = createMemo(() => {
@@ -168,6 +178,43 @@ const ModelsTab: Component = () => {
description={language.t("settings.autocomplete.model.description")}
/>
</SettingsRow>
<SettingsRow
title={language.t("settings.models.speechToTextModel.title")}
description={
kiloReady()
? language.t("settings.models.speechToTextModel.description")
: language.t("settings.models.speechToText.disabledDescription")
}
>
<Tooltip
value={language.t("settings.models.speechToText.disabledDescription")}
placement="top"
inactive={kiloReady()}
>
<Select
options={SPEECH_TO_TEXT_MODEL_OPTIONS}
current={speechOption()}
value={(item) => item.value}
label={(item) => `${item.label} (${item.provider})`}
onSelect={(item) =>
updateConfig({
experimental: {
...config().experimental,
speech_to_text_model: item?.value ?? DEFAULT_SPEECH_TO_TEXT_MODEL.id,
},
})
}
variant="secondary"
size="small"
triggerVariant="settings"
triggerProps={{
"aria-label": `${language.t("settings.models.speechToTextModel.title")}: ${speechOption()?.label}`,
}}
disabled={!kiloReady()}
placeholder={DEFAULT_SPEECH_TO_TEXT_MODEL.label}
/>
</Tooltip>
</SettingsRow>
<SettingsRow
title={language.t("settings.models.hidePromptTraining.title")}
description={language.t("settings.models.hidePromptTraining.description")}
@@ -35,7 +35,7 @@ const ProvidersTab: Component = () => {
onCleanup(action.dispose)
const kiloLoggedIn = createMemo(() => !!server.profileData())
const kiloLoggedIn = createMemo(() => !!provider.authStates()[KILO_PROVIDER_ID])
const connectedProviders = createMemo(() => {
const ids = visibleConnectedIds(provider.connected(), provider.authStates())
@@ -2,18 +2,23 @@ import { KILO_PROVIDER_ID } from "../../../../src/shared/provider-model"
import { getSpeechToTextModel } from "../../../../src/speech-to-text/models"
type Cfg = {
enabled_providers?: string[]
disabled_providers?: string[]
experimental?: {
speech_to_text_model?: string
}
}
export function hasSpeechToTextAccess(cfg: Cfg, providers: readonly string[], profile: unknown | null): boolean {
return providers.includes(KILO_PROVIDER_ID) && !cfg.disabled_providers?.includes(KILO_PROVIDER_ID) && !!profile
type AuthState = "api" | "oauth" | "wellknown"
export function hasSpeechToTextAccess(cfg: Cfg, auth: Readonly<Record<string, AuthState>>): boolean {
const enabled = !cfg.enabled_providers || cfg.enabled_providers.includes(KILO_PROVIDER_ID)
const type = auth[KILO_PROVIDER_ID]
return enabled && !cfg.disabled_providers?.includes(KILO_PROVIDER_ID) && (type === "api" || type === "oauth")
}
export function canUseSpeechToText(cfg: Cfg, providers: readonly string[], profile: unknown | null): boolean {
return hasSpeechToTextAccess(cfg, providers, profile)
export function canUseSpeechToText(cfg: Cfg, auth: Readonly<Record<string, AuthState>>): boolean {
return hasSpeechToTextAccess(cfg, auth)
}
export function selectedSpeechToTextModel(cfg: Cfg): string {
@@ -9,7 +9,6 @@ type VSCode = {
}
type Server = {
profileData: Accessor<unknown | null>
goToLogin: () => void
}
@@ -70,6 +69,10 @@ export function useSpeechToText(vscode: VSCode, server: Server, lang: Lang): Spe
}
if (msg.type === "speechToTextError") {
if (msg.code === "not_authenticated") {
login()
return
}
fail(msg.error)
return
}
@@ -98,19 +101,6 @@ export function useSpeechToText(vscode: VSCode, server: Server, lang: Lang): Spe
insert = opts.insert
setError(undefined)
if (!server.profileData()) {
showToast({
variant: "error",
title: lang.t("speechToText.error.loginRequired"),
actions: [
{ label: lang.t("common.signIn"), onClick: server.goToLogin },
{ label: lang.t("common.dismiss"), onClick: "dismiss" },
],
})
fail(lang.t("speechToText.error.loginRequired"), false)
return
}
counter++
request = `${prefix}-${counter}`
setState("recording")
@@ -144,6 +134,19 @@ export function useSpeechToText(vscode: VSCode, server: Server, lang: Lang): Spe
setError(undefined)
}
function login() {
const message = lang.t("speechToText.error.loginRequired")
showToast({
variant: "error",
title: message,
actions: [
{ label: lang.t("common.signIn"), onClick: server.goToLogin },
{ label: lang.t("common.dismiss"), onClick: "dismiss" },
],
})
fail(message, false)
}
function fail(message: string, toast = true) {
cleanup()
setState("error")
+3 -3
View File
@@ -1298,10 +1298,10 @@ export const dict = {
"settings.experimental.speechToText.title": "تحويل الصوت إلى نص",
"settings.experimental.speechToText.description":
"تمكين الإدخال الصوتي في حقول المطالبة باستخدام حساب Kilo الخاص بك من خلال Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"قم بتمكين وتسجيل الدخول إلى مزود Kilo لاستخدام Speech to Text. ميزة Speech to Text مدعومة حاليًا فقط مع Kilo Gateway.",
"settings.experimental.speechToTextModel.title": "نموذج تحويل الصوت إلى نص",
"settings.experimental.speechToTextModel.description": "اختر نموذج نسخ Kilo Gateway للإدخال الصوتي.",
"settings.models.speechToTextModel.title": "نموذج تحويل الصوت إلى نص",
"settings.models.speechToTextModel.description": "اختر نموذج نسخ Kilo Gateway للإدخال الصوتي.",
"settings.experimental.continueOnDeny.title": "المتابعة عند الرفض",
"settings.experimental.continueOnDeny.description": "متابعة حلقة الوكيل عند رفض الإذن",
"settings.experimental.mcpTimeout.title": "مهلة MCP (مللي ثانية)",
+3 -3
View File
@@ -1329,10 +1329,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Fala para texto",
"settings.experimental.speechToText.description":
"Ative a entrada de voz nos campos de prompt usando sua conta do Kilo por meio do Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Ative e faça login no provedor Kilo para usar o Speech to Text. Atualmente, o Speech to Text é compatível apenas com o Kilo Gateway.",
"settings.experimental.speechToTextModel.title": "Modelo de fala para texto",
"settings.experimental.speechToTextModel.description":
"settings.models.speechToTextModel.title": "Modelo de fala para texto",
"settings.models.speechToTextModel.description":
"Escolha o modelo de transcrição do Kilo Gateway para entrada de voz.",
"settings.experimental.continueOnDeny.title": "Continuar ao negar",
"settings.experimental.continueOnDeny.description": "Continuar o loop do agente quando uma permissão é negada",
+3 -4
View File
@@ -1328,11 +1328,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Govor u tekst",
"settings.experimental.speechToText.description":
"Omogućite glasovni unos u poljima za promptove koristeći vaš Kilo račun preko Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Omogućite i prijavite se na Kilo provajder da biste koristili Speech to Text. Speech to Text je trenutno podržan samo uz Kilo Gateway.",
"settings.experimental.speechToTextModel.title": "Model govora u tekst",
"settings.experimental.speechToTextModel.description":
"Odaberite Kilo Gateway model za transkripciju za glasovni unos.",
"settings.models.speechToTextModel.title": "Model govora u tekst",
"settings.models.speechToTextModel.description": "Odaberite Kilo Gateway model za transkripciju za glasovni unos.",
"settings.experimental.continueOnDeny.title": "Nastavi pri odbijanju",
"settings.experimental.continueOnDeny.description": "Nastavi petlju agenta kada je dozvola odbijena",
"settings.experimental.mcpTimeout.title": "MCP istek vremena (ms)",
+3 -3
View File
@@ -1322,10 +1322,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Tale til tekst",
"settings.experimental.speechToText.description":
"Aktivér stemmeinput i prompt-felter ved hjælp af din Kilo-konto gennem Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Aktivér og log ind på Kilo-udbyderen for at bruge Speech to Text. Speech to Text understøttes i øjeblikket kun med Kilo Gateway.",
"settings.experimental.speechToTextModel.title": "Model til tale til tekst",
"settings.experimental.speechToTextModel.description": "Vælg Kilo Gateway-transskriptionsmodellen til stemmeinput.",
"settings.models.speechToTextModel.title": "Model til tale til tekst",
"settings.models.speechToTextModel.description": "Vælg Kilo Gateway-transskriptionsmodellen til stemmeinput.",
"settings.experimental.continueOnDeny.title": "Fortsæt ved afvisning",
"settings.experimental.continueOnDeny.description": "Fortsæt agentløkken, når en tilladelse afvises",
"settings.experimental.mcpTimeout.title": "MCP-timeout (ms)",
+3 -3
View File
@@ -1345,10 +1345,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Sprache zu Text",
"settings.experimental.speechToText.description":
"Aktivieren Sie die Spracheingabe in Prompt-Feldern mit Ihrem Kilo-Konto über Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Aktivieren Sie den Kilo-Anbieter und melden Sie sich an, um Speech to Text zu verwenden. Speech to Text wird derzeit nur mit Kilo Gateway unterstützt.",
"settings.experimental.speechToTextModel.title": "Sprache-zu-Text-Modell",
"settings.experimental.speechToTextModel.description":
"settings.models.speechToTextModel.title": "Sprache-zu-Text-Modell",
"settings.models.speechToTextModel.description":
"Wählen Sie das Kilo Gateway-Transkriptionsmodell für die Spracheingabe.",
"settings.experimental.continueOnDeny.title": "Bei Ablehnung fortfahren",
"settings.experimental.continueOnDeny.description":
@@ -1305,10 +1305,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Speech to Text",
"settings.experimental.speechToText.description":
"Enable voice input in prompt fields using your Kilo account through Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Enable and sign in to the Kilo provider to use Speech to Text. Speech to Text is currently only supported through Kilo Gateway.",
"settings.experimental.speechToTextModel.title": "Speech to Text Model",
"settings.experimental.speechToTextModel.description": "Choose the Kilo Gateway transcription model for voice input.",
"settings.models.speechToTextModel.title": "Speech to Text Model",
"settings.models.speechToTextModel.description": "Choose the Kilo Gateway transcription model for voice input.",
"settings.experimental.continueOnDeny.title": "Continue on Deny",
"settings.experimental.continueOnDeny.description": "Continue the agent loop when a permission is denied",
"settings.experimental.mcpTimeout.title": "MCP Timeout (ms)",
+3 -3
View File
@@ -1337,10 +1337,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Voz a texto",
"settings.experimental.speechToText.description":
"Habilita la entrada de voz en los campos de prompt usando tu cuenta de Kilo a través de Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Habilita e inicia sesión en el proveedor Kilo para usar Speech to Text. Actualmente, Speech to Text solo es compatible con Kilo Gateway.",
"settings.experimental.speechToTextModel.title": "Modelo de voz a texto",
"settings.experimental.speechToTextModel.description":
"settings.models.speechToTextModel.title": "Modelo de voz a texto",
"settings.models.speechToTextModel.description":
"Elige el modelo de transcripción de Kilo Gateway para la entrada de voz.",
"settings.experimental.continueOnDeny.title": "Continuar al denegar",
"settings.experimental.continueOnDeny.description": "Continuar el bucle del agente cuando se deniega un permiso",
+3 -3
View File
@@ -1352,10 +1352,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Transcription vocale",
"settings.experimental.speechToText.description":
"Activez la saisie vocale dans les champs de prompt en utilisant votre compte Kilo via Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Activez et connectez-vous au fournisseur Kilo pour utiliser Speech to Text. Speech to Text n'est actuellement pris en charge qu'avec Kilo Gateway.",
"settings.experimental.speechToTextModel.title": "Modèle de transcription vocale",
"settings.experimental.speechToTextModel.description":
"settings.models.speechToTextModel.title": "Modèle de transcription vocale",
"settings.models.speechToTextModel.description":
"Choisissez le modèle de transcription Kilo Gateway pour la saisie vocale.",
"settings.experimental.continueOnDeny.title": "Continuer en cas de refus",
"settings.experimental.continueOnDeny.description":
+3 -4
View File
@@ -1535,11 +1535,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Da voce a testo",
"settings.experimental.speechToText.description":
"Abilita input vocale nei campi prompt usando il tuo account Kilo tramite Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Abilita e accedi al provider Kilo per usare Da voce a testo. Da voce a testo è attualmente supportato solo tramite Kilo Gateway.",
"settings.experimental.speechToTextModel.title": "Modello Da voce a testo",
"settings.experimental.speechToTextModel.description":
"Scegli il modello di trascrizione Kilo Gateway per l'input vocale.",
"settings.models.speechToTextModel.title": "Modello Da voce a testo",
"settings.models.speechToTextModel.description": "Scegli il modello di trascrizione Kilo Gateway per l'input vocale.",
// Compaction limit
"settings.context.compactionLimit.title": "Limite compattazione automatica",
+3 -3
View File
@@ -1316,10 +1316,10 @@ export const dict = {
"settings.experimental.speechToText.title": "音声認識",
"settings.experimental.speechToText.description":
"Kilo Gateway経由でKiloアカウントを使用して、プロンプトフィールドでの音声入力を有効にします。",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Speech to Text を使用するには、Kilo プロバイダーを有効にしてサインインしてください。現在、Speech to Text は Kilo Gateway でのみサポートされています。",
"settings.experimental.speechToTextModel.title": "音声認識モデル",
"settings.experimental.speechToTextModel.description": "音声入力に使用するKilo Gateway文字起こしモデルを選択します。",
"settings.models.speechToTextModel.title": "音声認識モデル",
"settings.models.speechToTextModel.description": "音声入力に使用するKilo Gateway文字起こしモデルを選択します。",
"settings.experimental.continueOnDeny.title": "拒否時に続行",
"settings.experimental.continueOnDeny.description": "権限が拒否された場合にエージェントループを続行",
"settings.experimental.mcpTimeout.title": "MCPタイムアウト(ミリ秒)",
+3 -3
View File
@@ -1310,10 +1310,10 @@ export const dict = {
"settings.experimental.speechToText.title": "음성 텍스트 변환",
"settings.experimental.speechToText.description":
"Kilo Gateway를 통해 Kilo 계정을 사용하여 프롬프트 필드에서 음성 입력을 활성화합니다.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Speech to Text를 사용하려면 Kilo 제공자를 활성화하고 로그인하세요. 현재 Speech to Text는 Kilo Gateway에서만 지원됩니다.",
"settings.experimental.speechToTextModel.title": "음성 텍스트 변환 모델",
"settings.experimental.speechToTextModel.description": "음성 입력에 사용할 Kilo Gateway 변환 모델을 선택하세요.",
"settings.models.speechToTextModel.title": "음성 텍스트 변환 모델",
"settings.models.speechToTextModel.description": "음성 입력에 사용할 Kilo Gateway 변환 모델을 선택하세요.",
"settings.experimental.continueOnDeny.title": "거부 시 계속",
"settings.experimental.continueOnDeny.description": "권한이 거부되면 에이전트 루프 계속",
"settings.experimental.mcpTimeout.title": "MCP 타임아웃 (ms)",
+3 -3
View File
@@ -1323,10 +1323,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Spraak naar tekst",
"settings.experimental.speechToText.description":
"Schakel spraakinvoer in promptvelden in met uw Kilo-account via Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Schakel de Kilo-provider in en meld u aan om Speech to Text te gebruiken. Speech to Text wordt momenteel alleen ondersteund met Kilo Gateway.",
"settings.experimental.speechToTextModel.title": "Spraak-naar-tekst-model",
"settings.experimental.speechToTextModel.description": "Kies het Kilo Gateway-transcriptiemodel voor spraakinvoer.",
"settings.models.speechToTextModel.title": "Spraak-naar-tekst-model",
"settings.models.speechToTextModel.description": "Kies het Kilo Gateway-transcriptiemodel voor spraakinvoer.",
"settings.experimental.continueOnDeny.title": "Doorgaan bij weigering",
"settings.experimental.continueOnDeny.description":
"Ga door met de agent loop wanneer een toestemming wordt geweigerd",
+3 -3
View File
@@ -1287,10 +1287,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Tale til tekst",
"settings.experimental.speechToText.description":
"Aktiver taleinndata i prompt-felt ved å bruke din Kilo-konto gjennom Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Aktiver og logg på Kilo-leverandøren for å bruke Speech to Text. Speech to Text støttes for øyeblikket bare med Kilo Gateway.",
"settings.experimental.speechToTextModel.title": "Tale-til-tekst-modell",
"settings.experimental.speechToTextModel.description": "Velg Kilo Gateway-transkripsjonsmodellen for taleinndata.",
"settings.models.speechToTextModel.title": "Tale-til-tekst-modell",
"settings.models.speechToTextModel.description": "Velg Kilo Gateway-transkripsjonsmodellen for taleinndata.",
"settings.experimental.continueOnDeny.title": "Fortsett ved avvisning",
"settings.experimental.continueOnDeny.description": "Fortsett agentløkken når en tillatelse avvises",
"settings.experimental.mcpTimeout.title": "MCP-tidsavbrudd (ms)",
+3 -3
View File
@@ -1287,10 +1287,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Mowa na tekst",
"settings.experimental.speechToText.description":
"Włącz wprowadzanie głosowe w polach promptów przy użyciu konta Kilo za pośrednictwem Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Włącz i zaloguj się do dostawcy Kilo, aby korzystać ze Speech to Text. Speech to Text jest obecnie obsługiwane tylko z Kilo Gateway.",
"settings.experimental.speechToTextModel.title": "Model mowy na tekst",
"settings.experimental.speechToTextModel.description":
"settings.models.speechToTextModel.title": "Model mowy na tekst",
"settings.models.speechToTextModel.description":
"Wybierz model transkrypcji Kilo Gateway dla wprowadzania głosowego.",
"settings.experimental.continueOnDeny.title": "Kontynuuj przy odmowie",
"settings.experimental.continueOnDeny.description": "Kontynuuj pętlę agenta po odmowie uprawnienia",
+3 -4
View File
@@ -1327,11 +1327,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Речь в текст",
"settings.experimental.speechToText.description":
"Включите голосовой ввод в полях запросов, используя вашу учетную запись Kilo через Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Включите провайдер Kilo и выполните вход, чтобы использовать Speech to Text. В настоящее время Speech to Text поддерживается только с Kilo Gateway.",
"settings.experimental.speechToTextModel.title": "Модель речи в текст",
"settings.experimental.speechToTextModel.description":
"Выберите модель транскрипции Kilo Gateway для голосового ввода.",
"settings.models.speechToTextModel.title": "Модель речи в текст",
"settings.models.speechToTextModel.description": "Выберите модель транскрипции Kilo Gateway для голосового ввода.",
"settings.experimental.continueOnDeny.title": "Продолжить при отказе",
"settings.experimental.continueOnDeny.description": "Продолжить цикл агента при отказе в разрешении",
"settings.experimental.mcpTimeout.title": "Таймаут MCP (мс)",
+3 -4
View File
@@ -1306,11 +1306,10 @@ export const dict = {
"settings.experimental.speechToText.title": "แปลงเสียงเป็นข้อความ",
"settings.experimental.speechToText.description":
"เปิดใช้งานการป้อนข้อมูลด้วยเสียงในช่องพรอมต์โดยใช้บัญชี Kilo ของคุณผ่าน Kilo Gateway",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"เปิดใช้งานและลงชื่อเข้าใช้ผู้ให้บริการ Kilo เพื่อใช้ Speech to Text ปัจจุบัน Speech to Text รองรับเฉพาะกับ Kilo Gateway เท่านั้น",
"settings.experimental.speechToTextModel.title": "โมเดลแปลงเสียงเป็นข้อความ",
"settings.experimental.speechToTextModel.description":
"เลือกโมเดลการถอดเสียง Kilo Gateway สำหรับการป้อนข้อมูลด้วยเสียง",
"settings.models.speechToTextModel.title": "โมเดลแปลงเสียงเป็นข้อความ",
"settings.models.speechToTextModel.description": "เลือกโมเดลการถอดเสียง Kilo Gateway สำหรับการป้อนข้อมูลด้วยเสียง",
"settings.experimental.continueOnDeny.title": "ดำเนินต่อเมื่อถูกปฏิเสธ",
"settings.experimental.continueOnDeny.description": "ดำเนินลูปเอเจนต์ต่อเมื่อสิทธิ์ถูกปฏิเสธ",
"settings.experimental.mcpTimeout.title": "หมดเวลา MCP (มิลลิวินาที)",
+3 -3
View File
@@ -1316,10 +1316,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Sesten metne",
"settings.experimental.speechToText.description":
"Kilo Gateway üzerinden Kilo hesabınızı kullanarak komut alanlarında sesli girişi etkinleştirin.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Speech to Text kullanmak için Kilo sağlayıcısını etkinleştirin ve giriş yapın. Speech to Text şu anda yalnızca Kilo Gateway ile desteklenmektedir.",
"settings.experimental.speechToTextModel.title": "Sesten metne modeli",
"settings.experimental.speechToTextModel.description": "Sesli giriş için Kilo Gateway transkripsiyon modelini seçin.",
"settings.models.speechToTextModel.title": "Sesten metne modeli",
"settings.models.speechToTextModel.description": "Sesli giriş için Kilo Gateway transkripsiyon modelini seçin.",
"settings.experimental.continueOnDeny.title": "Reddetme Durumunda Devam Et",
"settings.experimental.continueOnDeny.description": "Bir izin reddedildiğinde ajan döngüsüne devam et",
"settings.experimental.mcpTimeout.title": "MCP Zaman Aşımı (ms)",
+3 -4
View File
@@ -1315,11 +1315,10 @@ export const dict = {
"settings.experimental.speechToText.title": "Мовлення в текст",
"settings.experimental.speechToText.description":
"Увімкніть голосове введення в полях запитів, використовуючи ваш обліковий запис Kilo через Kilo Gateway.",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"Увімкніть провайдер Kilo та виконайте вхід, щоб використовувати Speech to Text. Наразі Speech to Text підтримується лише з Kilo Gateway.",
"settings.experimental.speechToTextModel.title": "Модель мовлення в текст",
"settings.experimental.speechToTextModel.description":
"Виберіть модель транскрипції Kilo Gateway для голосового введення.",
"settings.models.speechToTextModel.title": "Модель мовлення в текст",
"settings.models.speechToTextModel.description": "Виберіть модель транскрипції Kilo Gateway для голосового введення.",
"settings.experimental.continueOnDeny.title": "Продовжувати при відхиленні",
"settings.experimental.continueOnDeny.description": "Продовжувати цикл агента, коли дозвіл відхилено",
"settings.experimental.mcpTimeout.title": "Тайм-аут MCP (мс)",
+3 -3
View File
@@ -1286,10 +1286,10 @@ export const dict = {
"settings.experimental.codebaseSearch.description": "启用 AI 驱动的自然语言代码库搜索",
"settings.experimental.speechToText.title": "语音转文本",
"settings.experimental.speechToText.description": "通过 Kilo Gateway 使用您的 Kilo 帐户在提示词字段中启用语音输入。",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"启用并登录 Kilo 提供商以使用 Speech to Text。Speech to Text 目前仅支持通过 Kilo Gateway 使用。",
"settings.experimental.speechToTextModel.title": "语音转文本模型",
"settings.experimental.speechToTextModel.description": "选择用于语音输入的 Kilo Gateway 转录模型。",
"settings.models.speechToTextModel.title": "语音转文本模型",
"settings.models.speechToTextModel.description": "选择用于语音输入的 Kilo Gateway 转录模型。",
"settings.experimental.continueOnDeny.title": "拒绝后继续",
"settings.experimental.continueOnDeny.description": "权限被拒绝时继续智能体循环",
"settings.experimental.mcpTimeout.title": "MCP 超时(毫秒)",
+3 -3
View File
@@ -1254,10 +1254,10 @@ export const dict = {
"settings.experimental.codebaseSearch.description": "啟用 AI 驅動的自然語言程式碼庫搜尋",
"settings.experimental.speechToText.title": "語音轉文字",
"settings.experimental.speechToText.description": "透過 Kilo Gateway 使用您的 Kilo 帳戶在提示詞欄位中啟用語音輸入。",
"settings.experimental.speechToText.disabledDescription":
"settings.models.speechToText.disabledDescription":
"啟用並登入 Kilo 供應商以使用 Speech to Text。Speech to Text 目前僅支援透過 Kilo Gateway 使用。",
"settings.experimental.speechToTextModel.title": "語音轉文字模型",
"settings.experimental.speechToTextModel.description": "選擇用於語音輸入的 Kilo Gateway 轉錄模型。",
"settings.models.speechToTextModel.title": "語音轉文字模型",
"settings.models.speechToTextModel.description": "選擇用於語音輸入的 Kilo Gateway 轉錄模型。",
"settings.experimental.continueOnDeny.title": "拒絕後繼續",
"settings.experimental.continueOnDeny.description": "權限被拒絕時繼續 Agent 迴圈",
"settings.experimental.mcpTimeout.title": "MCP 逾時(毫秒)",
@@ -84,6 +84,17 @@ export const ModelsAccessibleLabels: Story = {
),
}
export const ModelsSpeechToText: Story = {
name: "ModelsTab — speech-to-text model",
render: () => (
<StoryProviders kiloAuth config={{ experimental: { speech_to_text_model: "google/chirp-3" } } as any}>
<div style={{ "max-height": "700px", overflow: "auto" }}>
<ModelsTab />
</div>
</StoryProviders>
),
}
function OpenModelPicker(props: { children: any }) {
let ref: HTMLDivElement | undefined
onMount(() => {
@@ -33,6 +33,11 @@ export const ProfileWithBalance = Schema.Struct({
currentOrgId: Schema.NullOr(Schema.String),
})
export const AuthStatus = Schema.Struct({
authenticated: Schema.Boolean,
type: Schema.optional(Schema.Literals(["api", "oauth"])),
})
export const NotificationAction = Schema.Struct({
actionText: Schema.String,
actionURL: Schema.String,
@@ -241,6 +246,7 @@ export const CloudSessionData = Schema.Struct({
export const KiloGatewayPaths = {
modes: `${root}/modes`,
profile: `${root}/profile`,
authStatus: `${root}/auth-status`,
fim: `${root}/fim`,
edit: `${root}/edit`,
audioTranscriptions: `${root}/audio/transcriptions`,
@@ -268,6 +274,17 @@ export const KiloGatewayApi = HttpApi.make("kilo")
description: "Fetch user profile and organizations from Kilo Gateway",
}),
),
HttpApiEndpoint.get("authStatus", KiloGatewayPaths.authStatus, {
query: WorkspaceRoutingQuery,
success: described(AuthStatus, "Kilo authentication status"),
error: HttpApiError.BadRequest,
}).annotateMerge(
OpenApi.annotations({
identifier: "kilo.authStatus",
summary: "Get Kilo authentication status",
description: "Check whether a locally stored Kilo credential can authenticate Gateway requests",
}),
),
HttpApiEndpoint.get("modes", KiloGatewayPaths.modes, {
query: WorkspaceRoutingQuery,
success: described(OrganizationModes, "Organization modes list"),
@@ -73,6 +73,13 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo",
return { profile, balance, currentOrgId }
})
const authStatus = Effect.fn("KiloGatewayHttpApi.authStatus")(function* () {
const info = yield* auth.get("kilo").pipe(Effect.mapError(() => new HttpApiError.BadRequest({})))
const type = getToken(info) && (info?.type === "api" || info?.type === "oauth") ? info.type : undefined
if (!type) return { authenticated: false }
return { authenticated: true, type }
})
const proxyAuth = Effect.fn("KiloGatewayHttpApi.proxyAuth")(function* () {
const info = yield* auth.get("kilo").pipe(Effect.mapError(() => new HttpApiError.Unauthorized({})))
return {
@@ -504,6 +511,7 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo",
return handlers
.handle("profile", profile)
.handle("authStatus", authStatus)
.handle("modes", modes)
.handle("fim", fim)
.handle("edit", edit)
@@ -132,6 +132,11 @@ export const kiloScenarios: Scenario[] = [
http.protected.get("/indexing/models", "indexing.models").json(200, object),
http.protected.get("/indexing/warnings", "indexing.warnings").json(200, array),
http.protected.get("/kilo/profile", "kilo.profile").probe({ path: "/path" }).status(401),
http.protected.get("/kilo/auth-status", "kilo.authStatus").json(200, (body) => {
object(body)
check(body.authenticated === false, "Kilo auth status should report signed out")
check(body.type === undefined, "Kilo auth status should not expose a credential type while signed out")
}),
http.protected.get("/kilo/modes", "kilo.modes").json(200, (body) => {
object(body)
array(body.modes)
@@ -17,6 +17,7 @@ type Schema = {
items?: Schema
properties?: Record<string, Schema>
type?: string
enum?: string[]
minLength?: number
maxLength?: number
pattern?: string
@@ -165,6 +166,12 @@ describe("Kilo PublicApi OpenAPI contract", () => {
expect(profile?.balance).toEqual({ anyOf: [expect.objectContaining({ type: "object" }), { type: "null" }] })
expect(profile?.currentOrgId).toEqual({ anyOf: [{ type: "string" }, { type: "null" }] })
const auth = response(KiloGatewayPaths.authStatus)?.properties
expect(auth).toEqual({
authenticated: { type: "boolean" },
type: { type: "string", enum: ["api", "oauth"] },
})
const sessions = response(KiloGatewayPaths.cloudSessions)?.properties
expect(sessions?.cliSessions?.items?.properties?.title).toEqual({
anyOf: [{ type: "string" }, { type: "null" }],
@@ -84,6 +84,17 @@ function post(path: string, body: Record<string, unknown>) {
}
describe("Kilo gateway HttpApi statuses", () => {
it.live("reports locally stored API authentication without a Gateway request", () =>
Effect.gen(function* () {
yield* stub(() => Promise.reject(new Error("unexpected Gateway request")))
const response = yield* HttpClient.get(KiloGatewayPaths.authStatus)
expect(response.status).toBe(200)
expect(yield* response.json).toEqual({ authenticated: true, type: "api" })
}),
)
it.live("preserves cloud session list rate limits", () =>
Effect.gen(function* () {
yield* stub(() => new Response("rate limited", { status: 429 }))
+32
View File
@@ -91,6 +91,8 @@ import type {
InstanceDisposeResponses,
KiloAudioTranscriptionsErrors,
KiloAudioTranscriptionsResponses,
KiloAuthStatusErrors,
KiloAuthStatusResponses,
KiloClawChatCredentialsResponses,
KiloClawStatusErrors,
KiloClawStatusResponses,
@@ -6523,6 +6525,36 @@ export class Kilo extends HeyApiClient {
})
}
/**
* Get Kilo authentication status
*
* Check whether a locally stored Kilo credential can authenticate Gateway requests
*/
public authStatus<ThrowOnError extends boolean = false>(
parameters?: {
directory?: string
workspace?: string
},
options?: Options<never, ThrowOnError>,
) {
const params = buildClientParams(
[parameters],
[
{
args: [
{ in: "query", key: "directory" },
{ in: "query", key: "workspace" },
],
},
],
)
return (options?.client ?? this.client).get<KiloAuthStatusResponses, KiloAuthStatusErrors, ThrowOnError>({
url: "/kilo/auth-status",
...options,
...params,
})
}
/**
* Get organization custom modes
*
+31
View File
@@ -8837,6 +8837,37 @@ export type KiloProfileResponses = {
export type KiloProfileResponse = KiloProfileResponses[keyof KiloProfileResponses]
export type KiloAuthStatusData = {
body?: never
path?: never
query?: {
directory?: string
workspace?: string
}
url: "/kilo/auth-status"
}
export type KiloAuthStatusErrors = {
/**
* Bad request
*/
400: BadRequestError
}
export type KiloAuthStatusError = KiloAuthStatusErrors[keyof KiloAuthStatusErrors]
export type KiloAuthStatusResponses = {
/**
* Kilo authentication status
*/
200: {
authenticated: boolean
type?: "api" | "oauth"
}
}
export type KiloAuthStatusResponse = KiloAuthStatusResponses[keyof KiloAuthStatusResponses]
export type KiloModesData = {
body?: never
path?: never
+66
View File
@@ -11382,6 +11382,72 @@
]
}
},
"/kilo/auth-status": {
"get": {
"tags": ["kilo"],
"operationId": "kilo.authStatus",
"parameters": [
{
"name": "directory",
"in": "query",
"schema": {
"type": "string"
},
"required": false
},
{
"name": "workspace",
"in": "query",
"schema": {
"type": "string"
},
"required": false
}
],
"responses": {
"200": {
"description": "Kilo authentication status",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"authenticated": {
"type": "boolean"
},
"type": {
"type": "string",
"enum": ["api", "oauth"]
}
},
"required": ["authenticated"],
"additionalProperties": false,
"description": "Kilo authentication status"
}
}
}
},
"400": {
"description": "Bad request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BadRequestError"
}
}
}
}
},
"description": "Check whether a locally stored Kilo credential can authenticate Gateway requests",
"summary": "Get Kilo authentication status",
"x-codeSamples": [
{
"lang": "js",
"source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.kilo.authStatus({\n ...\n})"
}
]
}
},
"/kilo/modes": {
"get": {
"tags": ["kilo"],