Diseño de integración del proveedor Kilo Gateway

Visión general

Este documento describe el diseño para integrar “Kilo Gateway” como proveedor de primera clase en OpenClaw, siguiendo el modelo de la implementación existente de OpenRouter. Kilo Gateway utiliza una API de completions compatible con OpenAI con una URL base diferente.

Decisiones de diseño

1. Nombre del proveedor

Recomendación: kilocode

Justificación:

  • Coincide con el ejemplo de configuración del usuario (clave de proveedor kilocode)
  • Consistente con los patrones de nombres de proveedores existentes (ej. openrouter, opencode, moonshot)
  • Corto y fácil de recordar
  • Evita confusión con términos genéricos como “kilo” o “gateway”

Alternativa considerada: kilo-gateway — rechazada porque los nombres con guiones son menos comunes en el código y kilocode es más conciso.

2. Referencia de modelo por defecto

Recomendación: kilocode/anthropic/claude-opus-4.6

Justificación:

  • Basada en el ejemplo de configuración del usuario
  • Claude Opus 4.5 es un modelo por defecto capaz
  • La selección explícita de modelo evita depender del enrutamiento automático

3. Configuración de URL base

Recomendación: URL por defecto hardcodeada con posibilidad de sobreescritura en la configuración

  • URL base por defecto: https://api.kilo.ai/api/gateway/
  • Configurable: Sí, mediante models.providers.kilocode.baseUrl

Esto sigue el patrón usado por otros proveedores como Moonshot, Venice y Synthetic.

4. Escaneo de modelos

Recomendación: Sin endpoint dedicado de escaneo de modelos inicialmente

Justificación:

  • Kilo Gateway actúa como proxy de OpenRouter, por lo que los modelos son dinámicos
  • Los usuarios pueden configurar modelos manualmente en su configuración
  • Si Kilo Gateway expone un endpoint /models en el futuro, se puede agregar el escaneo

5. Manejo especial

Recomendación: Heredar el comportamiento de OpenRouter para modelos de Anthropic

Dado que Kilo Gateway actúa como proxy de OpenRouter, debe aplicarse el mismo manejo especial:

  • Elegibilidad de TTL de caché para modelos anthropic/*
  • Parámetros extra (cacheControlTtl) para modelos anthropic/*
  • La política de transcripciones sigue los patrones de OpenRouter

Archivos a modificar

Gestión de credenciales central

1. src/commands/onboard-auth.credentials.ts

Agregar:

export const KILOCODE_DEFAULT_MODEL_REF = "kilocode/anthropic/claude-opus-4.6";

export async function setKilocodeApiKey(key: string, agentDir?: string) {
  upsertAuthProfile({
    profileId: "kilocode:default",
    credential: {
      type: "api_key",
      provider: "kilocode",
      key,
    },
    agentDir: resolveAuthAgentDir(agentDir),
  });
}

2. src/agents/model-auth.ts

Agregar a envMap en resolveEnvApiKey():

const envMap: Record<string, string> = {
  // ... entradas existentes
  kilocode: "KILOCODE_API_KEY",
};

3. src/config/io.ts

Agregar a SHELL_ENV_EXPECTED_KEYS:

const SHELL_ENV_EXPECTED_KEYS = [
  // ... entradas existentes
  "KILOCODE_API_KEY",
];

Aplicación de configuración

4. src/commands/onboard-auth.config-core.ts

Agregar nuevas funciones:

export const KILOCODE_BASE_URL = "https://api.kilo.ai/api/gateway/";

export function applyKilocodeProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
  const models = { ...cfg.agents?.defaults?.models };
  models[KILOCODE_DEFAULT_MODEL_REF] = {
    ...models[KILOCODE_DEFAULT_MODEL_REF],
    alias: models[KILOCODE_DEFAULT_MODEL_REF]?.alias ?? "Kilo Gateway",
  };

  const providers = { ...cfg.models?.providers };
  const existingProvider = providers.kilocode;
  const { apiKey: existingApiKey, ...existingProviderRest } = (existingProvider ?? {}) as Record<
    string,
    unknown
  > as { apiKey?: string };
  const resolvedApiKey = typeof existingApiKey === "string" ? existingApiKey : undefined;
  const normalizedApiKey = resolvedApiKey?.trim();

  providers.kilocode = {
    ...existingProviderRest,
    baseUrl: KILOCODE_BASE_URL,
    api: "openai-completions",
    ...(normalizedApiKey ? { apiKey: normalizedApiKey } : {}),
  };

  return {
    ...cfg,
    agents: {
      ...cfg.agents,
      defaults: {
        ...cfg.agents?.defaults,
        models,
      },
    },
    models: {
      mode: cfg.models?.mode ?? "merge",
      providers,
    },
  };
}

export function applyKilocodeConfig(cfg: OpenClawConfig): OpenClawConfig {
  const next = applyKilocodeProviderConfig(cfg);
  const existingModel = next.agents?.defaults?.model;
  return {
    ...next,
    agents: {
      ...next.agents,
      defaults: {
        ...next.agents?.defaults,
        model: {
          ...(existingModel && "fallbacks" in (existingModel as Record<string, unknown>)
            ? {
                fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks,
              }
            : undefined),
          primary: KILOCODE_DEFAULT_MODEL_REF,
        },
      },
    },
  };
}

Sistema de elección de autenticación

5. src/commands/onboard-types.ts

Agregar al tipo AuthChoice:

export type AuthChoice =
  // ... opciones existentes
  "kilocode-api-key";
// ...

Agregar a OnboardOptions:

export type OnboardOptions = {
  // ... opciones existentes
  kilocodeApiKey?: string;
  // ...
};

6. src/commands/auth-choice-options.ts

Agregar a AuthChoiceGroupId:

export type AuthChoiceGroupId =
  // ... grupos existentes
  "kilocode";
// ...

Agregar a AUTH_CHOICE_GROUP_DEFS:

{
  value: "kilocode",
  label: "Kilo Gateway",
  hint: "API key (OpenRouter-compatible)",
  choices: ["kilocode-api-key"],
},

Agregar a buildAuthChoiceOptions():

options.push({
  value: "kilocode-api-key",
  label: "Kilo Gateway API key",
  hint: "OpenRouter-compatible gateway",
});

7. src/commands/auth-choice.preferred-provider.ts

Agregar mapeo:

const PREFERRED_PROVIDER_BY_AUTH_CHOICE: Partial<Record<AuthChoice, string>> = {
  // ... mapeos existentes
  "kilocode-api-key": "kilocode",
};

Aplicación de elección de autenticación

8. src/commands/auth-choice.apply.api-providers.ts

Agregar import:

import {
  // ... imports existentes
  applyKilocodeConfig,
  applyKilocodeProviderConfig,
  KILOCODE_DEFAULT_MODEL_REF,
  setKilocodeApiKey,
} from "./onboard-auth.js";

Agregar manejo para kilocode-api-key:

if (authChoice === "kilocode-api-key") {
  const store = ensureAuthProfileStore(params.agentDir, {
    allowKeychainPrompt: false,
  });
  const profileOrder = resolveAuthProfileOrder({
    cfg: nextConfig,
    store,
    provider: "kilocode",
  });
  const existingProfileId = profileOrder.find((profileId) => Boolean(store.profiles[profileId]));
  const existingCred = existingProfileId ? store.profiles[existingProfileId] : undefined;
  let profileId = "kilocode:default";
  let mode: "api_key" | "oauth" | "token" = "api_key";
  let hasCredential = false;

  if (existingProfileId && existingCred?.type) {
    profileId = existingProfileId;
    mode =
      existingCred.type === "oauth" ? "oauth" : existingCred.type === "token" ? "token" : "api_key";
    hasCredential = true;
  }

  if (!hasCredential && params.opts?.token && params.opts?.tokenProvider === "kilocode") {
    await setKilocodeApiKey(normalizeApiKeyInput(params.opts.token), params.agentDir);
    hasCredential = true;
  }

  if (!hasCredential) {
    const envKey = resolveEnvApiKey("kilocode");
    if (envKey) {
      const useExisting = await params.prompter.confirm({
        message: `Use existing KILOCODE_API_KEY (${envKey.source}, ${formatApiKeyPreview(envKey.apiKey)})?`,
        initialValue: true,
      });
      if (useExisting) {
        await setKilocodeApiKey(envKey.apiKey, params.agentDir);
        hasCredential = true;
      }
    }
  }

  if (!hasCredential) {
    const key = await params.prompter.text({
      message: "Enter Kilo Gateway API key",
      validate: validateApiKeyInput,
    });
    await setKilocodeApiKey(normalizeApiKeyInput(String(key)), params.agentDir);
    hasCredential = true;
  }

  if (hasCredential) {
    nextConfig = applyAuthProfileConfig(nextConfig, {
      profileId,
      provider: "kilocode",
      mode,
    });
  }
  {
    const applied = await applyDefaultModelChoice({
      config: nextConfig,
      setDefaultModel: params.setDefaultModel,
      defaultModel: KILOCODE_DEFAULT_MODEL_REF,
      applyDefaultConfig: applyKilocodeConfig,
      applyProviderConfig: applyKilocodeProviderConfig,
      noteDefault: KILOCODE_DEFAULT_MODEL_REF,
      noteAgentModel,
      prompter: params.prompter,
    });
    nextConfig = applied.config;
    agentModelOverride = applied.agentModelOverride ?? agentModelOverride;
  }
  return { config: nextConfig, agentModelOverride };
}

También agregar el mapeo de tokenProvider al inicio de la función:

if (params.opts.tokenProvider === "kilocode") {
  authChoice = "kilocode-api-key";
}

Registro en CLI

9. src/cli/program/register.onboard.ts

Agregar opción CLI:

.option("--kilocode-api-key <key>", "Kilo Gateway API key")

Agregar al manejador de acción:

kilocodeApiKey: opts.kilocodeApiKey as string | undefined,

Actualizar texto de ayuda de auth-choice:

.option(
  "--auth-choice <choice>",
  "Auth: setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|kilocode-api-key|ai-gateway-api-key|...",
)

Onboarding no interactivo

10. src/commands/onboard-non-interactive/local/auth-choice.ts

Agregar manejo para kilocode-api-key:

if (authChoice === "kilocode-api-key") {
  const resolved = await resolveNonInteractiveApiKey({
    provider: "kilocode",
    cfg: baseConfig,
    flagValue: opts.kilocodeApiKey,
    flagName: "--kilocode-api-key",
    envVar: "KILOCODE_API_KEY",
  });
  await setKilocodeApiKey(resolved.apiKey, agentDir);
  nextConfig = applyAuthProfileConfig(nextConfig, {
    profileId: "kilocode:default",
    provider: "kilocode",
    mode: "api_key",
  });
  // ... aplicar modelo por defecto
}

Actualización de exports

11. src/commands/onboard-auth.ts

Agregar exports:

export {
  // ... exports existentes
  applyKilocodeConfig,
  applyKilocodeProviderConfig,
  KILOCODE_BASE_URL,
} from "./onboard-auth.config-core.js";

export {
  // ... exports existentes
  KILOCODE_DEFAULT_MODEL_REF,
  setKilocodeApiKey,
} from "./onboard-auth.credentials.js";

Manejo especial (Opcional)

12. src/agents/pi-embedded-runner/cache-ttl.ts

Agregar soporte de Kilo Gateway para modelos de Anthropic:

export function isCacheTtlEligibleProvider(provider: string, modelId: string): boolean {
  const normalizedProvider = provider.toLowerCase();
  const normalizedModelId = modelId.toLowerCase();
  if (normalizedProvider === "anthropic") return true;
  if (normalizedProvider === "openrouter" && normalizedModelId.startsWith("anthropic/"))
    return true;
  if (normalizedProvider === "kilocode" && normalizedModelId.startsWith("anthropic/")) return true;
  return false;
}

13. src/agents/transcript-policy.ts

Agregar manejo de Kilo Gateway (similar a OpenRouter):

const isKilocodeGemini = provider === "kilocode" && modelId.toLowerCase().includes("gemini");

// Incluir en la verificación de needsNonImageSanitize
const needsNonImageSanitize =
  isGoogle || isAnthropic || isMistral || isOpenRouterGemini || isKilocodeGemini;

Estructura de configuración

Ejemplo de configuración del usuario

{
  "models": {
    "mode": "merge",
    "providers": {
      "kilocode": {
        "baseUrl": "https://api.kilo.ai/api/gateway/",
        "apiKey": "xxxxx",
        "api": "openai-completions",
        "models": [
          {
            "id": "anthropic/claude-opus-4.6",
            "name": "Anthropic: Claude Opus 4.6"
          },
          { "id": "minimax/minimax-m2.5:free", "name": "Minimax: Minimax M2.5" }
        ]
      }
    }
  }
}

Estructura del perfil de autenticación

{
  "profiles": {
    "kilocode:default": {
      "type": "api_key",
      "provider": "kilocode",
      "key": "xxxxx"
    }
  }
}

Consideraciones de testing

  1. Tests unitarios:

    • Verificar que setKilocodeApiKey() escribe el perfil correcto
    • Verificar que applyKilocodeConfig() establece los valores por defecto correctos
    • Verificar que resolveEnvApiKey("kilocode") devuelve la variable de entorno correcta
  2. Tests de integración:

    • Probar el flujo de onboarding con --auth-choice kilocode-api-key
    • Probar el onboarding no interactivo con --kilocode-api-key
    • Probar la selección de modelo con prefijo kilocode/
  3. Tests E2E:

    • Probar llamadas API reales a través de Kilo Gateway (tests en vivo)

Notas de migración

  • No se necesita migración para usuarios existentes
  • Los nuevos usuarios pueden usar inmediatamente la opción de auth kilocode-api-key
  • La configuración manual existente con proveedor kilocode seguirá funcionando

Consideraciones futuras

  1. Catálogo de modelos: Si Kilo Gateway expone un endpoint /models, agregar soporte de escaneo similar a scanOpenRouterModels()

  2. Soporte OAuth: Si Kilo Gateway agrega OAuth, extender el sistema de autenticación en consecuencia

  3. Limitación de tasa: Considerar agregar manejo de limitación de tasa específico para Kilo Gateway si es necesario

  4. Documentación: Agregar documentación en docs/providers/kilocode.md explicando la configuración y el uso

Resumen de cambios

ArchivoTipo de cambioDescripción
src/commands/onboard-auth.credentials.tsAgregarKILOCODE_DEFAULT_MODEL_REF, setKilocodeApiKey()
src/agents/model-auth.tsModificarAgregar kilocode a envMap
src/config/io.tsModificarAgregar KILOCODE_API_KEY a claves de entorno shell
src/commands/onboard-auth.config-core.tsAgregarapplyKilocodeProviderConfig(), applyKilocodeConfig()
src/commands/onboard-types.tsModificarAgregar kilocode-api-key a AuthChoice, agregar kilocodeApiKey a opciones
src/commands/auth-choice-options.tsModificarAgregar grupo y opción kilocode
src/commands/auth-choice.preferred-provider.tsModificarAgregar mapeo kilocode-api-key
src/commands/auth-choice.apply.api-providers.tsModificarAgregar manejo de kilocode-api-key
src/cli/program/register.onboard.tsModificarAgregar opción --kilocode-api-key
src/commands/onboard-non-interactive/local/auth-choice.tsModificarAgregar manejo no interactivo
src/commands/onboard-auth.tsModificarExportar nuevas funciones
src/agents/pi-embedded-runner/cache-ttl.tsModificarAgregar soporte para kilocode
src/agents/transcript-policy.tsModificarAgregar manejo de kilocode con Gemini