diff --git a/.env.example b/.env.example index 44c30da44cd..f7ed308c713 100644 --- a/.env.example +++ b/.env.example @@ -54,6 +54,8 @@ OPENCLAW_GATEWAY_TOKEN= # Optional additional providers # ZAI_API_KEY=... # AI_GATEWAY_API_KEY=... +# TOKENHUB_API_KEY=... +# LKEAP_API_KEY=... # MINIMAX_API_KEY=... # SYNTHETIC_API_KEY=... diff --git a/.github/labeler.yml b/.github/labeler.yml index d697bcb96f1..a833b4b4d3e 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -269,6 +269,10 @@ - changed-files: - any-glob-to-any-file: - "extensions/deepseek/**" +"extensions: tencent": + - changed-files: + - any-glob-to-any-file: + - "extensions/tencent/**" "extensions: stepfun": - changed-files: - any-glob-to-any-file: diff --git a/docs/docs.json b/docs/docs.json index f19cc718e64..6fdc0576762 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1292,6 +1292,7 @@ "providers/sglang", "providers/stepfun", "providers/synthetic", + "providers/tencent", "providers/together", "providers/venice", "providers/vercel-ai-gateway", diff --git a/docs/providers/tencent.md b/docs/providers/tencent.md new file mode 100644 index 00000000000..8c3ea17d38c --- /dev/null +++ b/docs/providers/tencent.md @@ -0,0 +1,90 @@ +--- +title: "Tencent Cloud (TokenHub + Token Plan)" +summary: "Tencent Cloud TokenHub and Token Plan setup (separate keys)" +read_when: + - You want to use Tencent Hy models with OpenClaw + - You need the TokenHub API key or Token Plan (LKEAP) setup +--- + +# Tencent Cloud (TokenHub + Token Plan) + +The Tencent Cloud provider gives access to Tencent Hy models via two endpoints +with separate API keys: + +- **TokenHub** (`tencent-tokenhub`) — call Hy via Tencent TokenHub Gateway +- **Token Plan** (`tencent-token-plan`) — call Hy via the LKEAP + Token Plan endpoint + +Both providers use OpenAI-compatible APIs. + +## Quick start + +TokenHub: + +```bash +openclaw onboard --auth-choice tokenhub-api-key +``` + +Token Plan: + +```bash +openclaw onboard --auth-choice tencent-token-plan-api-key +``` + +## Non-interactive example + +```bash +# TokenHub +openclaw onboard --non-interactive \ + --mode local \ + --auth-choice tokenhub-api-key \ + --tokenhub-api-key "$TOKENHUB_API_KEY" \ + --skip-health \ + --accept-risk + +# Token Plan +openclaw onboard --non-interactive \ + --mode local \ + --auth-choice tencent-token-plan-api-key \ + --tencent-token-plan-api-key "$LKEAP_API_KEY" \ + --skip-health \ + --accept-risk +``` + +## Providers and endpoints + +| Provider | Endpoint | Use case | +| -------------------- | ------------------------------------- | ----------------------- | +| `tencent-tokenhub` | `tokenhub.tencentmaas.com/v1` | Hy via Tencent TokenHub | +| `tencent-token-plan` | `api.lkeap.cloud.tencent.com/plan/v3` | Hy via LKEAP Token Plan | + +Each provider uses its own API key. Setup registers only the selected provider. + +## Available models + +### tencent-tokenhub + +- **hy3-preview** — Hy3 preview (256K context, reasoning, default) + +### tencent-token-plan + +- **hy3-preview** — Hy3 preview (256K context, reasoning, default) + +## Notes + +- TokenHub model refs use `tencent-tokenhub/`. Token Plan model refs + use `tencent-token-plan/`. +- Override pricing and context metadata in `models.providers` if needed. + +## Environment note + +If the Gateway runs as a daemon (launchd/systemd), make sure `TOKENHUB_API_KEY` +or `LKEAP_API_KEY` is available to that process (for example, in +`~/.openclaw/.env` or via `env.shellEnv`). + +## Related documentation + +- [OpenClaw Configuration](/configuration) +- [Model Providers](/concepts/model-providers) +- [Tencent TokenHub](https://cloud.tencent.com/document/product/1823/130050) +- [Tencent Token Plan API](https://cloud.tencent.com/document/product/1823/130060) diff --git a/extensions/tencent/api.ts b/extensions/tencent/api.ts new file mode 100644 index 00000000000..62bafadda2b --- /dev/null +++ b/extensions/tencent/api.ts @@ -0,0 +1,11 @@ +export { + buildTokenHubModelDefinition, + buildTokenPlanModelDefinition, + TOKENHUB_BASE_URL, + TOKENHUB_MODEL_CATALOG, + TOKENHUB_PROVIDER_ID, + TOKEN_PLAN_BASE_URL, + TOKEN_PLAN_MODEL_CATALOG, + TOKEN_PLAN_PROVIDER_ID, +} from "./models.js"; +export { buildTokenHubProvider, buildTokenPlanProvider } from "./provider-catalog.js"; diff --git a/extensions/tencent/index.ts b/extensions/tencent/index.ts new file mode 100644 index 00000000000..2aef592d2ee --- /dev/null +++ b/extensions/tencent/index.ts @@ -0,0 +1,116 @@ +import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; +import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key"; +import { buildSingleProviderApiKeyCatalog } from "openclaw/plugin-sdk/provider-catalog-shared"; +import { + TOKENHUB_MODEL_CATALOG, + TOKENHUB_PROVIDER_ID, + TOKEN_PLAN_MODEL_CATALOG, + TOKEN_PLAN_PROVIDER_ID, +} from "./models.js"; +import { + applyTokenHubConfig, + TOKENHUB_DEFAULT_MODEL_REF, + applyTokenPlanConfig, + TOKEN_PLAN_DEFAULT_MODEL_REF, +} from "./onboard.js"; +import { buildTokenHubProvider, buildTokenPlanProvider } from "./provider-catalog.js"; + +function buildStaticCatalogEntries(providerId: string, catalog: typeof TOKENHUB_MODEL_CATALOG) { + return catalog.map((entry) => ({ + provider: providerId, + id: entry.id, + name: entry.name, + reasoning: entry.reasoning, + input: [...entry.input], + contextWindow: entry.contextWindow, + })); +} + +export default definePluginEntry({ + id: "tencent", + name: "Tencent Cloud Provider", + description: "Bundled Tencent Cloud provider plugins (TokenHub + Token Plan)", + register(api) { + // ---------- TokenHub provider ---------- + api.registerProvider({ + id: TOKENHUB_PROVIDER_ID, + label: "Tencent TokenHub", + docsPath: "/providers/tencent", + envVars: ["TOKENHUB_API_KEY"], + auth: [ + createProviderApiKeyAuthMethod({ + providerId: TOKENHUB_PROVIDER_ID, + methodId: "api-key", + label: "Tencent TokenHub", + hint: "Hy via Tencent TokenHub Gateway", + optionKey: "tokenhubApiKey", + flagName: "--tokenhub-api-key", + envVar: "TOKENHUB_API_KEY", + promptMessage: "Enter Tencent TokenHub API key", + defaultModel: TOKENHUB_DEFAULT_MODEL_REF, + expectedProviders: [TOKENHUB_PROVIDER_ID], + applyConfig: (cfg) => applyTokenHubConfig(cfg), + wizard: { + choiceId: "tokenhub-api-key", + choiceLabel: "Tencent TokenHub", + groupId: "tencent", + groupLabel: "Tencent Cloud", + groupHint: "TokenHub + Token Plan", + }, + }), + ], + catalog: { + order: "simple", + run: (ctx) => + buildSingleProviderApiKeyCatalog({ + ctx, + providerId: TOKENHUB_PROVIDER_ID, + buildProvider: buildTokenHubProvider, + }), + }, + augmentModelCatalog: () => + buildStaticCatalogEntries(TOKENHUB_PROVIDER_ID, TOKENHUB_MODEL_CATALOG), + }); + + // ---------- Token Plan provider ---------- + api.registerProvider({ + id: TOKEN_PLAN_PROVIDER_ID, + label: "Tencent Token Plan", + docsPath: "/providers/tencent", + envVars: ["LKEAP_API_KEY"], + auth: [ + createProviderApiKeyAuthMethod({ + providerId: TOKEN_PLAN_PROVIDER_ID, + methodId: "api-key", + label: "Tencent Token Plan", + hint: "Hy via Token Plan", + optionKey: "tencentTokenPlanApiKey", + flagName: "--tencent-token-plan-api-key", + envVar: "LKEAP_API_KEY", + promptMessage: "Enter Tencent Token Plan API key", + defaultModel: TOKEN_PLAN_DEFAULT_MODEL_REF, + expectedProviders: [TOKEN_PLAN_PROVIDER_ID], + applyConfig: (cfg) => applyTokenPlanConfig(cfg), + wizard: { + choiceId: "tencent-token-plan-api-key", + choiceLabel: "Tencent Token Plan", + groupId: "tencent", + groupLabel: "Tencent Cloud", + groupHint: "TokenHub + Token Plan", + }, + }), + ], + catalog: { + order: "simple", + run: (ctx) => + buildSingleProviderApiKeyCatalog({ + ctx, + providerId: TOKEN_PLAN_PROVIDER_ID, + buildProvider: buildTokenPlanProvider, + }), + }, + augmentModelCatalog: () => + buildStaticCatalogEntries(TOKEN_PLAN_PROVIDER_ID, TOKEN_PLAN_MODEL_CATALOG), + }); + }, +}); diff --git a/extensions/tencent/models.ts b/extensions/tencent/models.ts new file mode 100644 index 00000000000..131942c64b7 --- /dev/null +++ b/extensions/tencent/models.ts @@ -0,0 +1,93 @@ +import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared"; + +// ---------- TokenHub provider ---------- + +export const TOKENHUB_BASE_URL = "https://tokenhub.tencentmaas.com/v1"; +export const TOKENHUB_PROVIDER_ID = "tencent-tokenhub"; + +// Hy3 preview pricing ($ per 1M tokens), tiered by input context length. +// Flat rates mirror the first tier; tieredPricing drives actual cost calculation. +const HY3_PREVIEW_COST = { + input: 0.176, + output: 0.587, + cacheRead: 0.059, + cacheWrite: 0, + tieredPricing: [ + { + input: 0.176, + output: 0.587, + cacheRead: 0.059, + cacheWrite: 0, + range: [0, 16_000] as [number, number], + }, + { + input: 0.235, + output: 0.939, + cacheRead: 0.088, + cacheWrite: 0, + range: [16_000, 32_000] as [number, number], + }, + { + input: 0.293, + output: 1.173, + cacheRead: 0.117, + cacheWrite: 0, + range: [32_000] as [number], + }, + ], +}; + +export const TOKENHUB_MODEL_CATALOG: ModelDefinitionConfig[] = [ + { + id: "hy3-preview", + name: "Hy3 preview (TokenHub)", + reasoning: true, + input: ["text"], + contextWindow: 256_000, + maxTokens: 64_000, + cost: HY3_PREVIEW_COST, + compat: { + supportsUsageInStreaming: true, + supportsReasoningEffort: true, + }, + }, +]; + +export function buildTokenHubModelDefinition( + model: (typeof TOKENHUB_MODEL_CATALOG)[number], +): ModelDefinitionConfig { + return { + ...model, + api: "openai-completions", + }; +} + +// ---------- Token Plan provider ---------- + +export const TOKEN_PLAN_BASE_URL = "https://api.lkeap.cloud.tencent.com/plan/v3"; +export const TOKEN_PLAN_PROVIDER_ID = "tencent-token-plan"; + +export const TOKEN_PLAN_MODEL_CATALOG: ModelDefinitionConfig[] = [ + { + id: "hy3-preview", + name: "Hy3 preview (Token Plan)", + reasoning: true, + input: ["text"], + contextWindow: 256_000, + maxTokens: 64_000, + cost: HY3_PREVIEW_COST, + compat: { + supportsUsageInStreaming: true, + supportsReasoningEffort: true, + }, + }, +]; + +export function buildTokenPlanModelDefinition( + model: (typeof TOKEN_PLAN_MODEL_CATALOG)[number], +): ModelDefinitionConfig { + return { + ...model, + api: "openai-completions", + }; +} diff --git a/extensions/tencent/onboard.ts b/extensions/tencent/onboard.ts new file mode 100644 index 00000000000..1f66ad5d639 --- /dev/null +++ b/extensions/tencent/onboard.ts @@ -0,0 +1,73 @@ +import { + applyAgentDefaultModelPrimary, + applyProviderConfigWithModelCatalog, + type OpenClawConfig, +} from "openclaw/plugin-sdk/provider-onboard"; +import { + buildTokenHubModelDefinition, + buildTokenPlanModelDefinition, + TOKENHUB_BASE_URL, + TOKENHUB_MODEL_CATALOG, + TOKENHUB_PROVIDER_ID, + TOKEN_PLAN_BASE_URL, + TOKEN_PLAN_MODEL_CATALOG, + TOKEN_PLAN_PROVIDER_ID, +} from "./api.js"; + +// ---------- TokenHub ---------- + +export const TOKENHUB_DEFAULT_MODEL_REF = `${TOKENHUB_PROVIDER_ID}/hy3-preview`; + +function applyTokenHubProviderConfig(cfg: OpenClawConfig): OpenClawConfig { + const models = { ...cfg.agents?.defaults?.models }; + models[TOKENHUB_DEFAULT_MODEL_REF] = { + ...models[TOKENHUB_DEFAULT_MODEL_REF], + // Provider-specific alias to keep alias resolution deterministic when + // both Tencent providers are enabled (see buildModelAliasIndex). + alias: models[TOKENHUB_DEFAULT_MODEL_REF]?.alias ?? "Hy3 preview (TokenHub)", + }; + + return applyProviderConfigWithModelCatalog(cfg, { + agentModels: models, + providerId: TOKENHUB_PROVIDER_ID, + api: "openai-completions", + baseUrl: TOKENHUB_BASE_URL, + catalogModels: TOKENHUB_MODEL_CATALOG.map(buildTokenHubModelDefinition), + }); +} + +export function applyTokenHubConfig(cfg: OpenClawConfig): OpenClawConfig { + return applyAgentDefaultModelPrimary( + applyTokenHubProviderConfig(cfg), + TOKENHUB_DEFAULT_MODEL_REF, + ); +} + +// ---------- Token Plan ---------- + +export const TOKEN_PLAN_DEFAULT_MODEL_REF = `${TOKEN_PLAN_PROVIDER_ID}/hy3-preview`; + +function applyTokenPlanProviderConfig(cfg: OpenClawConfig): OpenClawConfig { + const models = { ...cfg.agents?.defaults?.models }; + models[TOKEN_PLAN_DEFAULT_MODEL_REF] = { + ...models[TOKEN_PLAN_DEFAULT_MODEL_REF], + // Provider-specific alias to keep alias resolution deterministic when + // both Tencent providers are enabled (see buildModelAliasIndex). + alias: models[TOKEN_PLAN_DEFAULT_MODEL_REF]?.alias ?? "Hy3 preview (Token Plan)", + }; + + return applyProviderConfigWithModelCatalog(cfg, { + agentModels: models, + providerId: TOKEN_PLAN_PROVIDER_ID, + api: "openai-completions", + baseUrl: TOKEN_PLAN_BASE_URL, + catalogModels: TOKEN_PLAN_MODEL_CATALOG.map(buildTokenPlanModelDefinition), + }); +} + +export function applyTokenPlanConfig(cfg: OpenClawConfig): OpenClawConfig { + return applyAgentDefaultModelPrimary( + applyTokenPlanProviderConfig(cfg), + TOKEN_PLAN_DEFAULT_MODEL_REF, + ); +} diff --git a/extensions/tencent/openclaw.plugin.json b/extensions/tencent/openclaw.plugin.json new file mode 100644 index 00000000000..7fa34e875b0 --- /dev/null +++ b/extensions/tencent/openclaw.plugin.json @@ -0,0 +1,42 @@ +{ + "id": "tencent", + "enabledByDefault": true, + "providers": ["tencent-tokenhub", "tencent-token-plan"], + "providerAuthEnvVars": { + "tencent-tokenhub": ["TOKENHUB_API_KEY"], + "tencent-token-plan": ["LKEAP_API_KEY"] + }, + "providerAuthChoices": [ + { + "provider": "tencent-tokenhub", + "method": "api-key", + "choiceId": "tokenhub-api-key", + "choiceLabel": "Tencent TokenHub", + "groupId": "tencent", + "groupLabel": "Tencent Cloud", + "groupHint": "TokenHub + Token Plan", + "optionKey": "tokenhubApiKey", + "cliFlag": "--tokenhub-api-key", + "cliOption": "--tokenhub-api-key ", + "cliDescription": "Tencent TokenHub API key" + }, + { + "provider": "tencent-token-plan", + "method": "api-key", + "choiceId": "tencent-token-plan-api-key", + "choiceLabel": "Tencent Token Plan", + "groupId": "tencent", + "groupLabel": "Tencent Cloud", + "groupHint": "TokenHub + Token Plan", + "optionKey": "tencentTokenPlanApiKey", + "cliFlag": "--tencent-token-plan-api-key", + "cliOption": "--tencent-token-plan-api-key ", + "cliDescription": "Tencent Token Plan API key" + } + ], + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": {} + } +} diff --git a/extensions/tencent/package.json b/extensions/tencent/package.json new file mode 100644 index 00000000000..8825dff9606 --- /dev/null +++ b/extensions/tencent/package.json @@ -0,0 +1,15 @@ +{ + "name": "@openclaw/tencent-provider", + "version": "2026.4.10", + "private": true, + "description": "OpenClaw Tencent Cloud provider plugin (TokenHub + Token Plan)", + "type": "module", + "devDependencies": { + "@openclaw/plugin-sdk": "workspace:*" + }, + "openclaw": { + "extensions": [ + "./index.ts" + ] + } +} diff --git a/extensions/tencent/provider-catalog.ts b/extensions/tencent/provider-catalog.ts new file mode 100644 index 00000000000..43fe05f1816 --- /dev/null +++ b/extensions/tencent/provider-catalog.ts @@ -0,0 +1,25 @@ +import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared"; +import { + buildTokenHubModelDefinition, + buildTokenPlanModelDefinition, + TOKENHUB_BASE_URL, + TOKENHUB_MODEL_CATALOG, + TOKEN_PLAN_BASE_URL, + TOKEN_PLAN_MODEL_CATALOG, +} from "./models.js"; + +export function buildTokenHubProvider(): ModelProviderConfig { + return { + baseUrl: TOKENHUB_BASE_URL, + api: "openai-completions", + models: TOKENHUB_MODEL_CATALOG.map(buildTokenHubModelDefinition), + }; +} + +export function buildTokenPlanProvider(): ModelProviderConfig { + return { + baseUrl: TOKEN_PLAN_BASE_URL, + api: "openai-completions", + models: TOKEN_PLAN_MODEL_CATALOG.map(buildTokenPlanModelDefinition), + }; +} diff --git a/extensions/tencent/tsconfig.json b/extensions/tencent/tsconfig.json new file mode 100644 index 00000000000..b8a85a99ac3 --- /dev/null +++ b/extensions/tencent/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../tsconfig.package-boundary.base.json", + "compilerOptions": { + "rootDir": "." + }, + "include": ["./*.ts", "./src/**/*.ts"], + "exclude": [ + "./**/*.test.ts", + "./dist/**", + "./node_modules/**", + "./src/test-support/**", + "./src/**/*test-helpers.ts", + "./src/**/*test-harness.ts", + "./src/**/*test-support.ts" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9dbd3b5c73a..83ad3526310 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1182,6 +1182,12 @@ importers: specifier: workspace:* version: link:../../packages/plugin-sdk + extensions/tencent: + devDependencies: + '@openclaw/plugin-sdk': + specifier: workspace:* + version: link:../../packages/plugin-sdk + extensions/tlon: dependencies: '@aws-sdk/client-s3':