mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
feat(tencent): add Tencent Hy3 provider (TokenHub and TokenPlan) (#99076)
Summary: - Merged feat(tencent): add Tencent Hy3 provider (TokenHub and TokenPlan) after ClawSweeper review. Automerge notes: - Ran the ClawSweeper repair loop before final review. - Included post-review commit in the final squash: fix(tencent): preserve TokenHub auth compatibility - Included post-review commit in the final squash: refactor(tencent): unify TokenPlan env/flag naming with TokenHub - Included post-review commit in the final squash: docs: refresh Tencent provider docs metadata - Included post-review commit in the final squash: fix: allow TokenPlan provider config overlays - Included post-review commit in the final squash: docs: dedupe Tencent provider glossary labels - Included post-review commit in the final squash: fix(tencent): repair TokenHub model defaults Validation: - ClawSweeper review passed for head 30c9fc130f26a038d1430c14e710eb77d99fb0d4. - Required merge gates passed before the squash merge. Prepared head SHA: 30c9fc130f26a038d1430c14e710eb77d99fb0d4 Review: https://github.com/openclaw/openclaw/pull/99076#issuecomment-4888527271 Co-authored-by: leisang <leisang@tencent.com> Co-authored-by: Mason Huang <masonxhuang@tencent.com> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: hxy91819
This commit is contained in:
parent
cfcdf2a519
commit
2b51c255f8
26 changed files with 1080 additions and 99 deletions
|
|
@ -992,8 +992,8 @@
|
|||
"target": "轨迹导出"
|
||||
},
|
||||
{
|
||||
"source": "Tencent Cloud (TokenHub)",
|
||||
"target": "腾讯云(TokenHub)"
|
||||
"source": "Tencent Cloud (TokenHub / TokenPlan)",
|
||||
"target": "腾讯云(TokenHub / TokenPlan)"
|
||||
},
|
||||
{
|
||||
"source": "Codex Harness Context Engine Port",
|
||||
|
|
|
|||
|
|
@ -7813,7 +7813,6 @@ Do not edit it by hand; run `pnpm docs:map:gen`.
|
|||
- H2: Quick start
|
||||
- H2: Non-interactive setup
|
||||
- H2: Built-in catalog
|
||||
- H2: Tiered pricing
|
||||
- H2: Advanced configuration
|
||||
- H2: Related
|
||||
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ Each entry lists the package, distribution route, and description.
|
|||
|
||||
- **[tavily](/plugins/reference/tavily)** (`@openclaw/tavily-plugin`) - npm; ClawHub: `clawhub:@openclaw/tavily-plugin`. Adds agent-callable tools. Adds web search provider support.
|
||||
|
||||
- **[tencent](/plugins/reference/tencent)** (`@openclaw/tencent-provider`) - npm; ClawHub: `clawhub:@openclaw/tencent-provider`. Adds Tencent TokenHub model provider support to OpenClaw.
|
||||
- **[tencent](/plugins/reference/tencent)** (`@openclaw/tencent-provider`) - npm; ClawHub: `clawhub:@openclaw/tencent-provider`. Adds Tencent TokenHub and TokenPlan model provider support to OpenClaw.
|
||||
|
||||
- **[tlon](/plugins/reference/tlon)** (`@openclaw/tlon`) - npm; ClawHub. OpenClaw Tlon/Urbit channel plugin for chat workflows.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
summary: "Adds Tencent TokenHub model provider support to OpenClaw."
|
||||
summary: "Adds Tencent Hy3 model provider support (TokenHub and TokenPlan) to OpenClaw."
|
||||
read_when:
|
||||
- You are installing, configuring, or auditing the tencent plugin
|
||||
title: "Tencent plugin"
|
||||
|
|
@ -7,7 +7,7 @@ title: "Tencent plugin"
|
|||
|
||||
# Tencent plugin
|
||||
|
||||
Adds Tencent TokenHub model provider support to OpenClaw.
|
||||
Adds Tencent TokenHub and TokenPlan model provider support to OpenClaw.
|
||||
|
||||
## Distribution
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ Adds Tencent TokenHub model provider support to OpenClaw.
|
|||
|
||||
## Surface
|
||||
|
||||
providers: tencent-tokenhub
|
||||
providers: tencent-tokenhub, tencent-tokenplan
|
||||
|
||||
## Related docs
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugi
|
|||
- [SGLang (local models)](/providers/sglang)
|
||||
- [StepFun](/providers/stepfun)
|
||||
- [Synthetic](/providers/synthetic)
|
||||
- [Tencent Cloud (TokenHub)](/providers/tencent)
|
||||
- [Tencent Cloud (TokenHub / TokenPlan)](/providers/tencent)
|
||||
- [Together AI](/providers/together)
|
||||
- [Venice (Venice AI, privacy-focused)](/providers/venice)
|
||||
- [Vercel AI Gateway](/providers/vercel-ai-gateway)
|
||||
|
|
|
|||
|
|
@ -1,50 +1,61 @@
|
|||
---
|
||||
summary: "Tencent Cloud TokenHub setup for Hy3 preview"
|
||||
title: "Tencent Cloud (TokenHub)"
|
||||
summary: "Tencent Cloud TokenHub and TokenPlan setup for hy3"
|
||||
title: "Tencent Cloud (TokenHub / TokenPlan)"
|
||||
read_when:
|
||||
- You want to use Tencent Hy3 preview with OpenClaw
|
||||
- You need the TokenHub API key setup
|
||||
- You want to use Tencent hy3 with OpenClaw
|
||||
- You need the TokenHub or TokenPlan API key setup
|
||||
---
|
||||
|
||||
Install the official Tencent Cloud provider plugin to access Tencent Hy3 preview through the TokenHub endpoint (`tencent-tokenhub`) using an OpenAI-compatible API.
|
||||
Install the official Tencent Cloud provider plugin to access Tencent Hy3 through two endpoints — TokenHub (`tencent-tokenhub`) and TokenPlan (`tencent-tokenplan`) — using an OpenAI-compatible API.
|
||||
|
||||
| Property | Value |
|
||||
| --------------- | ---------------------------------------- |
|
||||
| Provider id | `tencent-tokenhub` |
|
||||
| Package | `@openclaw/tencent-provider` |
|
||||
| Auth env var | `TOKENHUB_API_KEY` |
|
||||
| Onboarding flag | `--auth-choice tokenhub-api-key` |
|
||||
| Direct CLI flag | `--tokenhub-api-key <key>` |
|
||||
| API | OpenAI-compatible (`openai-completions`) |
|
||||
| Base URL | `https://tokenhub.tencentmaas.com/v1` |
|
||||
| Default model | `tencent-tokenhub/hy3-preview` |
|
||||
| Property | Value |
|
||||
| ------------------------- | ----------------------------------------------------- |
|
||||
| Provider ids | `tencent-tokenhub`, `tencent-tokenplan` |
|
||||
| Package | `@openclaw/tencent-provider` |
|
||||
| TokenHub auth env var | `TOKENHUB_API_KEY` |
|
||||
| TokenPlan auth env var | `TOKENPLAN_API_KEY` |
|
||||
| TokenHub onboarding flag | `--auth-choice tokenhub-api-key` |
|
||||
| TokenPlan onboarding flag | `--auth-choice tokenplan-api-key` |
|
||||
| TokenHub direct CLI flag | `--tokenhub-api-key <key>` |
|
||||
| TokenPlan direct CLI flag | `--tokenplan-api-key <key>` |
|
||||
| API | OpenAI-compatible (`openai-completions`) |
|
||||
| TokenHub base URL | `https://tokenhub.tencentmaas.com/v1` |
|
||||
| TokenHub global base URL | `https://tokenhub-intl.tencentmaas.com/v1` (override) |
|
||||
| TokenPlan base URL | `https://api.lkeap.cloud.tencent.com/plan/v3` |
|
||||
| Default model | `tencent-tokenhub/hy3` |
|
||||
|
||||
## Quick start
|
||||
|
||||
<Steps>
|
||||
<Step title="Install the plugin">
|
||||
```bash
|
||||
openclaw plugins install @openclaw/tencent-provider
|
||||
```
|
||||
</Step>
|
||||
<Step title="Create a TokenHub API key">
|
||||
Create an API key in Tencent Cloud TokenHub. If you choose a limited access scope for the key, include **Hy3 preview** in the allowed models.
|
||||
<Step title="Create a Tencent API key">
|
||||
Create an API key for Tencent Cloud TokenHub and TokenPlan. If you choose a limited access scope for the key, include **hy3** (and **hy3 preview** if you plan to use it on TokenHub) in the allowed models.
|
||||
</Step>
|
||||
<Step title="Run onboarding">
|
||||
<CodeGroup>
|
||||
|
||||
```bash Onboarding
|
||||
```bash TokenHub onboarding
|
||||
openclaw onboard --auth-choice tokenhub-api-key
|
||||
```
|
||||
|
||||
```bash Direct flag
|
||||
```bash TokenHub direct flag
|
||||
openclaw onboard --non-interactive \
|
||||
--auth-choice tokenhub-api-key \
|
||||
--tokenhub-api-key "$TOKENHUB_API_KEY"
|
||||
```
|
||||
|
||||
```bash TokenPlan onboarding
|
||||
openclaw onboard --auth-choice tokenplan-api-key
|
||||
```
|
||||
|
||||
```bash TokenPlan direct flag
|
||||
openclaw onboard --non-interactive \
|
||||
--auth-choice tokenplan-api-key \
|
||||
--tokenplan-api-key "$TOKENPLAN_API_KEY"
|
||||
```
|
||||
|
||||
```bash Env only
|
||||
export TOKENHUB_API_KEY=...
|
||||
export TOKENPLAN_API_KEY=...
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
|
@ -53,6 +64,7 @@ export TOKENHUB_API_KEY=...
|
|||
<Step title="Verify the model">
|
||||
```bash
|
||||
openclaw models list --provider tencent-tokenhub
|
||||
openclaw models list --provider tencent-tokenplan
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
|
@ -60,12 +72,21 @@ export TOKENHUB_API_KEY=...
|
|||
## Non-interactive setup
|
||||
|
||||
```bash
|
||||
# TokenHub
|
||||
openclaw onboard --non-interactive \
|
||||
--mode local \
|
||||
--auth-choice tokenhub-api-key \
|
||||
--tokenhub-api-key "$TOKENHUB_API_KEY" \
|
||||
--skip-health \
|
||||
--accept-risk
|
||||
|
||||
# TokenPlan
|
||||
openclaw onboard --non-interactive \
|
||||
--mode local \
|
||||
--auth-choice tokenplan-api-key \
|
||||
--tokenplan-api-key "$TOKENPLAN_API_KEY" \
|
||||
--skip-health \
|
||||
--accept-risk
|
||||
```
|
||||
|
||||
<Note>
|
||||
|
|
@ -74,28 +95,18 @@ openclaw onboard --non-interactive \
|
|||
|
||||
## Built-in catalog
|
||||
|
||||
| Model ref | Name | Input | Context | Max output | Notes |
|
||||
| ------------------------------ | ---------------------- | ----- | ------- | ---------- | -------------------------- |
|
||||
| `tencent-tokenhub/hy3-preview` | Hy3 preview (TokenHub) | text | 256,000 | 64,000 | Default; reasoning-enabled |
|
||||
| Model ref | Name | Input | Context | Max output | Notes |
|
||||
| ------------------------------ | ---------------------- | ----- | ------- | ---------- | ----------------- |
|
||||
| `tencent-tokenhub/hy3-preview` | hy3 preview (TokenHub) | text | 256,000 | 64,000 | reasoning-enabled |
|
||||
| `tencent-tokenhub/hy3` | hy3 (TokenHub) | text | 256,000 | 64,000 | reasoning-enabled |
|
||||
| `tencent-tokenplan/hy3` | hy3 (TokenPlan) | text | 256,000 | 64,000 | reasoning-enabled |
|
||||
|
||||
Hy3 preview is Tencent Hunyuan's large MoE language model for reasoning, long-context instruction following, code, and agent workflows. It supports standard chat-completions tool calling plus `reasoning_effort`.
|
||||
hy3 is Tencent Hunyuan's large MoE language model for reasoning, long-context instruction following, code, and agent workflows. Tencent's OpenAI-compatible examples use `hy3` as the model id and support standard chat-completions tool calling plus `reasoning_effort`.
|
||||
|
||||
<Tip>
|
||||
The model id is `hy3-preview`. Do not confuse it with Tencent's `HY-3D-*` models, which are 3D generation APIs and are not the OpenClaw chat model configured by this provider.
|
||||
The model id is `hy3`. Do not confuse it with Tencent's `HY-3D-*` models, which are 3D generation APIs and are not the OpenClaw chat model configured by this provider.
|
||||
</Tip>
|
||||
|
||||
## Tiered pricing
|
||||
|
||||
The provider catalog ships tiered cost metadata that scales with input window length, so cost estimates are populated without manual overrides.
|
||||
|
||||
| Input tokens range | Input rate | Output rate | Cache read |
|
||||
| ------------------ | ---------- | ----------- | ---------- |
|
||||
| 0 - 16,000 | 0.176 | 0.587 | 0.059 |
|
||||
| 16,000 - 32,000 | 0.235 | 0.939 | 0.088 |
|
||||
| 32,000+ | 0.293 | 1.173 | 0.117 |
|
||||
|
||||
Rates are per million tokens in USD as advertised by Tencent. Override pricing under `models.providers.tencent-tokenhub` only when you need a different surface.
|
||||
|
||||
## Advanced configuration
|
||||
|
||||
<AccordionGroup>
|
||||
|
|
@ -109,7 +120,7 @@ Rates are per million tokens in USD as advertised by Tencent. Override pricing u
|
|||
</Accordion>
|
||||
|
||||
<Accordion title="Environment availability for the daemon">
|
||||
If the Gateway runs as a managed service (launchd, systemd, Docker), `TOKENHUB_API_KEY` must be visible to that process. Set it in `~/.openclaw/.env` or via `env.shellEnv` so launchd, systemd, or Docker exec environments can read it.
|
||||
If the Gateway runs as a managed service (launchd, systemd, Docker), `TOKENHUB_API_KEY` and `TOKENPLAN_API_KEY` must be visible to that process. Set them in `~/.openclaw/.env` or via `env.shellEnv` so launchd, systemd, or Docker exec environments can read them.
|
||||
|
||||
<Warning>
|
||||
Keys exported only in an interactive shell are not visible to managed gateway processes. Use the env file or config seam for persistent availability.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
// Tencent API module exposes the plugin public contract.
|
||||
export {
|
||||
buildTokenHubModelDefinition,
|
||||
buildTokenPlanModelDefinition,
|
||||
TOKENHUB_BASE_URL,
|
||||
TOKENHUB_MODEL_CATALOG,
|
||||
TOKENHUB_PROVIDER_ID,
|
||||
TOKENPLAN_BASE_URL,
|
||||
TOKENPLAN_MODEL_CATALOG,
|
||||
TOKENPLAN_PROVIDER_ID,
|
||||
} from "./models.js";
|
||||
export { buildTokenHubProvider } from "./provider-catalog.js";
|
||||
export { buildTokenHubProvider, buildTokenPlanProvider } from "./provider-catalog.js";
|
||||
|
|
|
|||
112
extensions/tencent/config-compat.test.ts
Normal file
112
extensions/tencent/config-compat.test.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// Tencent tests cover config compatibility repair behavior.
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
migrateTencentTokenHubModelDefaults,
|
||||
TENCENT_TOKENHUB_DEFAULT_MODEL_REF,
|
||||
TENCENT_TOKENHUB_PREVIEW_MODEL_REF,
|
||||
} from "./config-compat.js";
|
||||
|
||||
describe("Tencent config compatibility", () => {
|
||||
it("adds the stable TokenHub model and makes it primary for old preview defaults", () => {
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: {
|
||||
primary: TENCENT_TOKENHUB_PREVIEW_MODEL_REF,
|
||||
fallbacks: ["openai/gpt-5.5"],
|
||||
},
|
||||
models: {
|
||||
[TENCENT_TOKENHUB_PREVIEW_MODEL_REF]: {
|
||||
alias: "Preview",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = migrateTencentTokenHubModelDefaults(config);
|
||||
|
||||
expect(result.changes).toEqual([
|
||||
`Updated Tencent TokenHub agent model defaults to include ${TENCENT_TOKENHUB_DEFAULT_MODEL_REF} and ${TENCENT_TOKENHUB_PREVIEW_MODEL_REF}.`,
|
||||
`Changed Tencent TokenHub primary default from ${TENCENT_TOKENHUB_PREVIEW_MODEL_REF} to ${TENCENT_TOKENHUB_DEFAULT_MODEL_REF}.`,
|
||||
]);
|
||||
expect(result.config.agents?.defaults?.model).toEqual({
|
||||
primary: TENCENT_TOKENHUB_DEFAULT_MODEL_REF,
|
||||
fallbacks: ["openai/gpt-5.5"],
|
||||
});
|
||||
expect(result.config.agents?.defaults?.models).toEqual({
|
||||
[TENCENT_TOKENHUB_PREVIEW_MODEL_REF]: {
|
||||
alias: "Preview",
|
||||
},
|
||||
[TENCENT_TOKENHUB_DEFAULT_MODEL_REF]: {
|
||||
alias: "Hy3 (TokenHub)",
|
||||
},
|
||||
});
|
||||
expect(config.agents?.defaults?.models).not.toHaveProperty(TENCENT_TOKENHUB_DEFAULT_MODEL_REF);
|
||||
});
|
||||
|
||||
it("adds the preview TokenHub model for hy3-only intermediate configs", () => {
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: TENCENT_TOKENHUB_DEFAULT_MODEL_REF,
|
||||
models: {
|
||||
[TENCENT_TOKENHUB_DEFAULT_MODEL_REF]: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = migrateTencentTokenHubModelDefaults(config);
|
||||
|
||||
expect(result.config.agents?.defaults?.model).toBe(TENCENT_TOKENHUB_DEFAULT_MODEL_REF);
|
||||
expect(result.config.agents?.defaults?.models).toEqual({
|
||||
[TENCENT_TOKENHUB_DEFAULT_MODEL_REF]: {
|
||||
alias: "Hy3 (TokenHub)",
|
||||
},
|
||||
[TENCENT_TOKENHUB_PREVIEW_MODEL_REF]: {
|
||||
alias: "Hy3 preview (TokenHub)",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("does not create a model allowlist when TokenHub models are not already configured", () => {
|
||||
const config = {
|
||||
models: {
|
||||
providers: {
|
||||
"tencent-tokenhub": {
|
||||
baseUrl: "https://tokenhub.tencentmaas.com/v1",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = migrateTencentTokenHubModelDefaults(config);
|
||||
|
||||
expect(result).toEqual({ config, changes: [] });
|
||||
});
|
||||
|
||||
it("does not report changes after TokenHub defaults are already repaired", () => {
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: TENCENT_TOKENHUB_DEFAULT_MODEL_REF },
|
||||
models: {
|
||||
[TENCENT_TOKENHUB_DEFAULT_MODEL_REF]: {
|
||||
alias: "Hy3 (TokenHub)",
|
||||
},
|
||||
[TENCENT_TOKENHUB_PREVIEW_MODEL_REF]: {
|
||||
alias: "Hy3 preview (TokenHub)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = migrateTencentTokenHubModelDefaults(config);
|
||||
|
||||
expect(result).toEqual({ config, changes: [] });
|
||||
});
|
||||
});
|
||||
120
extensions/tencent/config-compat.ts
Normal file
120
extensions/tencent/config-compat.ts
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// Tencent config compatibility repairs shipped TokenHub model allowlists.
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
|
||||
export const TENCENT_TOKENHUB_DEFAULT_MODEL_REF = "tencent-tokenhub/hy3";
|
||||
export const TENCENT_TOKENHUB_PREVIEW_MODEL_REF = "tencent-tokenhub/hy3-preview";
|
||||
|
||||
const TOKENHUB_DEFAULT_ALIAS = "Hy3 (TokenHub)";
|
||||
const TOKENHUB_PREVIEW_ALIAS = "Hy3 preview (TokenHub)";
|
||||
|
||||
type AgentDefaults = NonNullable<NonNullable<OpenClawConfig["agents"]>["defaults"]>;
|
||||
type AgentDefaultModel = AgentDefaults["model"];
|
||||
type AgentModelEntry = NonNullable<AgentDefaults["models"]>[string];
|
||||
|
||||
function isTokenHubModelMapConfigured(models: Record<string, AgentModelEntry>): boolean {
|
||||
return (
|
||||
Object.hasOwn(models, TENCENT_TOKENHUB_DEFAULT_MODEL_REF) ||
|
||||
Object.hasOwn(models, TENCENT_TOKENHUB_PREVIEW_MODEL_REF)
|
||||
);
|
||||
}
|
||||
|
||||
function withDefaultAlias(entry: AgentModelEntry | undefined, alias: string): AgentModelEntry {
|
||||
return {
|
||||
...entry,
|
||||
alias: entry?.alias ?? alias,
|
||||
};
|
||||
}
|
||||
|
||||
function needsDefaultAlias(entry: AgentModelEntry | undefined): boolean {
|
||||
return entry?.alias === undefined;
|
||||
}
|
||||
|
||||
function migrateDefaultModel(model: AgentDefaultModel): {
|
||||
model: AgentDefaultModel;
|
||||
changed: boolean;
|
||||
} {
|
||||
if (model === TENCENT_TOKENHUB_PREVIEW_MODEL_REF) {
|
||||
return {
|
||||
model: { primary: TENCENT_TOKENHUB_DEFAULT_MODEL_REF },
|
||||
changed: true,
|
||||
};
|
||||
}
|
||||
if (
|
||||
model &&
|
||||
typeof model === "object" &&
|
||||
"primary" in model &&
|
||||
model.primary === TENCENT_TOKENHUB_PREVIEW_MODEL_REF
|
||||
) {
|
||||
return {
|
||||
model: {
|
||||
...model,
|
||||
primary: TENCENT_TOKENHUB_DEFAULT_MODEL_REF,
|
||||
},
|
||||
changed: true,
|
||||
};
|
||||
}
|
||||
return { model, changed: false };
|
||||
}
|
||||
|
||||
export function migrateTencentTokenHubModelDefaults(cfg: OpenClawConfig): {
|
||||
config: OpenClawConfig;
|
||||
changes: string[];
|
||||
} {
|
||||
const existingModels = cfg.agents?.defaults?.models;
|
||||
if (!existingModels || !isTokenHubModelMapConfigured(existingModels)) {
|
||||
return { config: cfg, changes: [] };
|
||||
}
|
||||
|
||||
const needsDefaultRepair =
|
||||
!Object.hasOwn(existingModels, TENCENT_TOKENHUB_DEFAULT_MODEL_REF) ||
|
||||
needsDefaultAlias(existingModels[TENCENT_TOKENHUB_DEFAULT_MODEL_REF]);
|
||||
const needsPreviewRepair =
|
||||
!Object.hasOwn(existingModels, TENCENT_TOKENHUB_PREVIEW_MODEL_REF) ||
|
||||
needsDefaultAlias(existingModels[TENCENT_TOKENHUB_PREVIEW_MODEL_REF]);
|
||||
const migratedModel = migrateDefaultModel(cfg.agents?.defaults?.model);
|
||||
if (!needsDefaultRepair && !needsPreviewRepair && !migratedModel.changed) {
|
||||
return { config: cfg, changes: [] };
|
||||
}
|
||||
|
||||
const nextModels = {
|
||||
...existingModels,
|
||||
[TENCENT_TOKENHUB_DEFAULT_MODEL_REF]: withDefaultAlias(
|
||||
existingModels[TENCENT_TOKENHUB_DEFAULT_MODEL_REF],
|
||||
TOKENHUB_DEFAULT_ALIAS,
|
||||
),
|
||||
[TENCENT_TOKENHUB_PREVIEW_MODEL_REF]: withDefaultAlias(
|
||||
existingModels[TENCENT_TOKENHUB_PREVIEW_MODEL_REF],
|
||||
TOKENHUB_PREVIEW_ALIAS,
|
||||
),
|
||||
};
|
||||
|
||||
const nextConfig: OpenClawConfig = {
|
||||
...cfg,
|
||||
agents: {
|
||||
...cfg.agents,
|
||||
defaults: {
|
||||
...cfg.agents?.defaults,
|
||||
models: nextModels,
|
||||
...(migratedModel.model !== undefined ? { model: migratedModel.model } : undefined),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const changes = [
|
||||
`Updated Tencent TokenHub agent model defaults to include ${TENCENT_TOKENHUB_DEFAULT_MODEL_REF} and ${TENCENT_TOKENHUB_PREVIEW_MODEL_REF}.`,
|
||||
];
|
||||
if (migratedModel.changed) {
|
||||
changes.push(
|
||||
`Changed Tencent TokenHub primary default from ${TENCENT_TOKENHUB_PREVIEW_MODEL_REF} to ${TENCENT_TOKENHUB_DEFAULT_MODEL_REF}.`,
|
||||
);
|
||||
}
|
||||
|
||||
return { config: nextConfig, changes };
|
||||
}
|
||||
|
||||
export function normalizeCompatibilityConfig({ cfg }: { cfg: OpenClawConfig }): {
|
||||
config: OpenClawConfig;
|
||||
changes: string[];
|
||||
} {
|
||||
return migrateTencentTokenHubModelDefaults(cfg);
|
||||
}
|
||||
320
extensions/tencent/index.test.ts
Normal file
320
extensions/tencent/index.test.ts
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
// Tencent tests cover index plugin behavior.
|
||||
import type { StreamFn } from "openclaw/plugin-sdk/agent-core";
|
||||
import type { Context, Model } from "openclaw/plugin-sdk/llm";
|
||||
import {
|
||||
registerProviderPlugin,
|
||||
requireRegisteredProvider,
|
||||
resolveProviderPluginChoice,
|
||||
} from "openclaw/plugin-sdk/plugin-test-runtime";
|
||||
import { buildOpenAICompletionsParams } from "openclaw/plugin-sdk/provider-transport-runtime";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { runSingleProviderCatalog } from "../test-support/provider-model-test-helpers.js";
|
||||
import tencentPlugin from "./index.js";
|
||||
|
||||
type OpenAICompletionsModel = Model<"openai-completions">;
|
||||
|
||||
const registerTencentPlugin = () =>
|
||||
registerProviderPlugin({
|
||||
plugin: tencentPlugin,
|
||||
id: "tencent",
|
||||
name: "Tencent Cloud Provider",
|
||||
});
|
||||
|
||||
async function getTokenHubProvider() {
|
||||
const { providers } = await registerTencentPlugin();
|
||||
return requireRegisteredProvider(providers, "tencent-tokenhub");
|
||||
}
|
||||
|
||||
async function getTokenPlanProvider() {
|
||||
const { providers } = await registerTencentPlugin();
|
||||
return requireRegisteredProvider(providers, "tencent-tokenplan");
|
||||
}
|
||||
|
||||
function hyReasoningModel(params: {
|
||||
provider: "tencent-tokenhub" | "tencent-tokenplan";
|
||||
id: "hy3" | "hy3-preview";
|
||||
baseUrl: string;
|
||||
supportedReasoningEfforts?: string[];
|
||||
}): OpenAICompletionsModel {
|
||||
return {
|
||||
provider: params.provider,
|
||||
id: params.id,
|
||||
name: params.id,
|
||||
api: "openai-completions",
|
||||
baseUrl: params.baseUrl,
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 256_000,
|
||||
maxTokens: 64_000,
|
||||
compat: {
|
||||
supportsUsageInStreaming: true,
|
||||
supportsReasoningEffort: true,
|
||||
supportedReasoningEfforts: params.supportedReasoningEfforts ?? ["none", "high"],
|
||||
},
|
||||
} as OpenAICompletionsModel;
|
||||
}
|
||||
|
||||
function captureTencentPayload(params: {
|
||||
provider: Pick<Awaited<ReturnType<typeof getTokenHubProvider>>, "wrapStreamFn">;
|
||||
model: OpenAICompletionsModel;
|
||||
reasoning: string;
|
||||
}) {
|
||||
let captured: Record<string, unknown> | undefined;
|
||||
const baseStreamFn: StreamFn = (_model, context, options) => {
|
||||
const payload = buildOpenAICompletionsParams(_model as OpenAICompletionsModel, context, options);
|
||||
options?.onPayload?.(payload, _model);
|
||||
captured = payload;
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
const wrapped = params.provider.wrapStreamFn?.({
|
||||
streamFn: baseStreamFn,
|
||||
provider: params.model.provider,
|
||||
modelId: params.model.id,
|
||||
model: params.model,
|
||||
thinkingLevel: "high",
|
||||
});
|
||||
if (!wrapped) {
|
||||
throw new Error("expected Tencent provider stream wrapper");
|
||||
}
|
||||
void wrapped(
|
||||
params.model,
|
||||
{ messages: [] } as never,
|
||||
{
|
||||
reasoning: params.reasoning,
|
||||
} as never,
|
||||
);
|
||||
return captured;
|
||||
}
|
||||
|
||||
describe("tencent provider plugin", () => {
|
||||
it("registers Tencent TokenHub api-key auth metadata", async () => {
|
||||
const { providers } = await registerTencentPlugin();
|
||||
const provider = requireRegisteredProvider(providers, "tencent-tokenhub");
|
||||
const resolved = resolveProviderPluginChoice({
|
||||
providers,
|
||||
choice: "tokenhub-api-key",
|
||||
});
|
||||
|
||||
expect(provider.id).toBe("tencent-tokenhub");
|
||||
expect(provider.label).toBe("Tencent TokenHub");
|
||||
expect(provider.envVars).toEqual(["TOKENHUB_API_KEY"]);
|
||||
expect(provider.auth).toHaveLength(1);
|
||||
if (!resolved) {
|
||||
throw new Error("expected Tencent TokenHub api-key auth choice");
|
||||
}
|
||||
expect(resolved.provider.id).toBe("tencent-tokenhub");
|
||||
expect(resolved.method.id).toBe("api-key");
|
||||
});
|
||||
|
||||
it("registers Tencent TokenPlan api-key auth metadata", async () => {
|
||||
const { providers } = await registerTencentPlugin();
|
||||
const provider = requireRegisteredProvider(providers, "tencent-tokenplan");
|
||||
const resolved = resolveProviderPluginChoice({
|
||||
providers,
|
||||
choice: "tokenplan-api-key",
|
||||
});
|
||||
|
||||
expect(provider.id).toBe("tencent-tokenplan");
|
||||
expect(provider.label).toBe("Tencent TokenPlan");
|
||||
expect(provider.envVars).toEqual(["TOKENPLAN_API_KEY"]);
|
||||
expect(provider.auth).toHaveLength(1);
|
||||
if (!resolved) {
|
||||
throw new Error("expected Tencent TokenPlan api-key auth choice");
|
||||
}
|
||||
expect(resolved.provider.id).toBe("tencent-tokenplan");
|
||||
expect(resolved.method.id).toBe("api-key");
|
||||
});
|
||||
|
||||
it("builds the static Tencent TokenHub model catalog with reasoning flags", async () => {
|
||||
const provider = await getTokenHubProvider();
|
||||
const catalogProvider = await runSingleProviderCatalog(provider);
|
||||
|
||||
expect(catalogProvider.api).toBe("openai-completions");
|
||||
expect(catalogProvider.baseUrl).toBe("https://tokenhub.tencentmaas.com/v1");
|
||||
|
||||
const modelIds = catalogProvider.models?.map((m) => m.id);
|
||||
expect(modelIds).toContain("hy3");
|
||||
expect(modelIds).toContain("hy3-preview");
|
||||
|
||||
const hy3 = catalogProvider.models?.find((m) => m.id === "hy3");
|
||||
expect(hy3?.reasoning).toBe(true);
|
||||
expect(hy3?.compat?.supportsReasoningEffort).toBe(true);
|
||||
expect(hy3?.compat?.supportedReasoningEfforts).toEqual(["none", "high"]);
|
||||
|
||||
const hy3Preview = catalogProvider.models?.find((m) => m.id === "hy3-preview");
|
||||
expect(hy3Preview?.reasoning).toBe(true);
|
||||
expect(hy3Preview?.compat?.supportsReasoningEffort).toBe(true);
|
||||
expect(hy3Preview?.compat?.supportedReasoningEfforts).toEqual(["none", "low", "high"]);
|
||||
});
|
||||
|
||||
it("builds the static Tencent TokenPlan model catalog with reasoning flags", async () => {
|
||||
const provider = await getTokenPlanProvider();
|
||||
const catalogProvider = await runSingleProviderCatalog(provider);
|
||||
|
||||
expect(catalogProvider.api).toBe("openai-completions");
|
||||
expect(catalogProvider.baseUrl).toBe("https://api.lkeap.cloud.tencent.com/plan/v3");
|
||||
|
||||
const modelIds = catalogProvider.models?.map((m) => m.id);
|
||||
expect(modelIds).toEqual(["hy3"]);
|
||||
|
||||
const hy3 = catalogProvider.models?.find((m) => m.id === "hy3");
|
||||
expect(hy3?.reasoning).toBe(true);
|
||||
expect(hy3?.compat?.supportsReasoningEffort).toBe(true);
|
||||
expect(hy3?.compat?.supportedReasoningEfforts).toEqual(["none", "high"]);
|
||||
});
|
||||
|
||||
it("injects reasoning_effort into TokenPlan hy3 chat-completions payload", async () => {
|
||||
const model = hyReasoningModel({
|
||||
provider: "tencent-tokenplan",
|
||||
id: "hy3",
|
||||
baseUrl: "https://api.lkeap.cloud.tencent.com/plan/v3",
|
||||
});
|
||||
const context = { messages: [{ role: "user", content: "hi", timestamp: 1 }] } as Context;
|
||||
|
||||
const payload = buildOpenAICompletionsParams(model, context, {
|
||||
reasoning: "high",
|
||||
} as never);
|
||||
|
||||
expect(payload.model).toBe("hy3");
|
||||
expect(payload.reasoning_effort).toBe("high");
|
||||
});
|
||||
|
||||
it("emits reasoning_effort=high when high effort is requested for TokenHub hy3", async () => {
|
||||
const model = hyReasoningModel({
|
||||
provider: "tencent-tokenhub",
|
||||
id: "hy3",
|
||||
baseUrl: "https://tokenhub.tencentmaas.com/v1",
|
||||
});
|
||||
const context = { messages: [{ role: "user", content: "hi", timestamp: 1 }] } as Context;
|
||||
|
||||
const payload = buildOpenAICompletionsParams(model, context, {
|
||||
reasoning: "high",
|
||||
} as never);
|
||||
|
||||
expect(payload.reasoning_effort).toBe("high");
|
||||
});
|
||||
|
||||
it("emits reasoning_effort=none when none effort is requested for TokenHub hy3", async () => {
|
||||
const model = hyReasoningModel({
|
||||
provider: "tencent-tokenhub",
|
||||
id: "hy3",
|
||||
baseUrl: "https://tokenhub.tencentmaas.com/v1",
|
||||
});
|
||||
const context = { messages: [{ role: "user", content: "hi", timestamp: 1 }] } as Context;
|
||||
|
||||
const payload = buildOpenAICompletionsParams(model, context, {
|
||||
reasoning: "none",
|
||||
} as never);
|
||||
|
||||
expect(payload.reasoning_effort).toBe("none");
|
||||
});
|
||||
|
||||
it("defaults hy3-preview reasoning_effort to high when no effort is provided", async () => {
|
||||
const model = hyReasoningModel({
|
||||
provider: "tencent-tokenhub",
|
||||
id: "hy3-preview",
|
||||
baseUrl: "https://tokenhub.tencentmaas.com/v1",
|
||||
supportedReasoningEfforts: ["none", "low", "high"],
|
||||
});
|
||||
const context = { messages: [{ role: "user", content: "hi", timestamp: 1 }] } as Context;
|
||||
|
||||
const payload = buildOpenAICompletionsParams(model, context, undefined);
|
||||
|
||||
expect(payload.reasoning_effort).toBe("high");
|
||||
});
|
||||
|
||||
it("preserves low reasoning_effort for TokenHub hy3-preview", async () => {
|
||||
const provider = await getTokenHubProvider();
|
||||
const model = hyReasoningModel({
|
||||
provider: "tencent-tokenhub",
|
||||
id: "hy3-preview",
|
||||
baseUrl: "https://tokenhub.tencentmaas.com/v1",
|
||||
supportedReasoningEfforts: ["none", "low", "high"],
|
||||
});
|
||||
|
||||
const payload = captureTencentPayload({
|
||||
provider,
|
||||
model,
|
||||
reasoning: "low",
|
||||
});
|
||||
|
||||
expect(payload?.reasoning_effort).toBe("low");
|
||||
});
|
||||
|
||||
it("keeps TokenHub hy3 explicit high and none reasoning_effort unchanged", async () => {
|
||||
const provider = await getTokenHubProvider();
|
||||
const model = hyReasoningModel({
|
||||
provider: "tencent-tokenhub",
|
||||
id: "hy3",
|
||||
baseUrl: "https://tokenhub.tencentmaas.com/v1",
|
||||
});
|
||||
|
||||
const highPayload = captureTencentPayload({
|
||||
provider,
|
||||
model,
|
||||
reasoning: "high",
|
||||
});
|
||||
const nonePayload = captureTencentPayload({
|
||||
provider,
|
||||
model,
|
||||
reasoning: "none",
|
||||
});
|
||||
|
||||
expect(highPayload?.reasoning_effort).toBe("high");
|
||||
expect(nonePayload?.reasoning_effort).toBe("none");
|
||||
});
|
||||
|
||||
it("keeps minimal reasoning enabled for TokenHub and TokenPlan hy3", async () => {
|
||||
const tokenHubProvider = await getTokenHubProvider();
|
||||
const tokenPlanProvider = await getTokenPlanProvider();
|
||||
const tokenHubModel = hyReasoningModel({
|
||||
provider: "tencent-tokenhub",
|
||||
id: "hy3",
|
||||
baseUrl: "https://tokenhub.tencentmaas.com/v1",
|
||||
});
|
||||
const tokenPlanModel = hyReasoningModel({
|
||||
provider: "tencent-tokenplan",
|
||||
id: "hy3",
|
||||
baseUrl: "https://api.lkeap.cloud.tencent.com/plan/v3",
|
||||
});
|
||||
|
||||
const tokenHubPayload = captureTencentPayload({
|
||||
provider: tokenHubProvider,
|
||||
model: tokenHubModel,
|
||||
reasoning: "minimal",
|
||||
});
|
||||
const tokenPlanPayload = captureTencentPayload({
|
||||
provider: tokenPlanProvider,
|
||||
model: tokenPlanModel,
|
||||
reasoning: "minimal",
|
||||
});
|
||||
|
||||
expect(tokenHubPayload?.reasoning_effort).toBe("high");
|
||||
expect(tokenPlanPayload?.reasoning_effort).toBe("high");
|
||||
});
|
||||
|
||||
it("keeps TokenHub hy3-preview unsupported efforts on the model fallback path", async () => {
|
||||
const provider = await getTokenHubProvider();
|
||||
const model = hyReasoningModel({
|
||||
provider: "tencent-tokenhub",
|
||||
id: "hy3-preview",
|
||||
baseUrl: "https://tokenhub.tencentmaas.com/v1",
|
||||
supportedReasoningEfforts: ["none", "low", "high"],
|
||||
});
|
||||
|
||||
const minimalPayload = captureTencentPayload({
|
||||
provider,
|
||||
model,
|
||||
reasoning: "minimal",
|
||||
});
|
||||
const mediumPayload = captureTencentPayload({
|
||||
provider,
|
||||
model,
|
||||
reasoning: "medium",
|
||||
});
|
||||
|
||||
expect(minimalPayload?.reasoning_effort).toBe("low");
|
||||
expect(mediumPayload?.reasoning_effort).toBe("low");
|
||||
});
|
||||
});
|
||||
|
|
@ -2,9 +2,20 @@
|
|||
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 } from "./models.js";
|
||||
import { applyTokenHubConfig, TOKENHUB_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
import { buildTokenHubProvider } from "./provider-catalog.js";
|
||||
import {
|
||||
TOKENHUB_MODEL_CATALOG,
|
||||
TOKENHUB_PROVIDER_ID,
|
||||
TOKENPLAN_MODEL_CATALOG,
|
||||
TOKENPLAN_PROVIDER_ID,
|
||||
} from "./models.js";
|
||||
import {
|
||||
applyTokenHubConfig,
|
||||
TOKENHUB_DEFAULT_MODEL_REF,
|
||||
applyTokenPlanConfig,
|
||||
TOKENPLAN_DEFAULT_MODEL_REF,
|
||||
} from "./onboard.js";
|
||||
import { buildTokenHubProvider, buildTokenPlanProvider } from "./provider-catalog.js";
|
||||
import { wrapTencentProviderStream } from "./stream.js";
|
||||
|
||||
function buildStaticCatalogEntries(providerId: string, catalog: typeof TOKENHUB_MODEL_CATALOG) {
|
||||
return catalog.map((entry) => ({
|
||||
|
|
@ -17,38 +28,40 @@ function buildStaticCatalogEntries(providerId: string, catalog: typeof TOKENHUB_
|
|||
}));
|
||||
}
|
||||
|
||||
function createTokenHubApiKeyAuthMethod() {
|
||||
return 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: applyTokenHubConfig,
|
||||
wizard: {
|
||||
choiceId: "tokenhub-api-key",
|
||||
choiceLabel: "Tencent TokenHub",
|
||||
groupId: "tencent",
|
||||
groupLabel: "Tencent Cloud",
|
||||
groupHint: "Tencent TokenHub",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "tencent",
|
||||
name: "Tencent Cloud Provider",
|
||||
description: "Bundled Tencent Cloud provider plugin (TokenHub)",
|
||||
description: "Bundled Tencent Cloud provider plugin (TokenHub, TokenPlan)",
|
||||
register(api) {
|
||||
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: "Tencent TokenHub",
|
||||
},
|
||||
}),
|
||||
],
|
||||
auth: [createTokenHubApiKeyAuthMethod()],
|
||||
catalog: {
|
||||
order: "simple",
|
||||
run: (ctx) =>
|
||||
|
|
@ -60,6 +73,48 @@ export default definePluginEntry({
|
|||
},
|
||||
augmentModelCatalog: () =>
|
||||
buildStaticCatalogEntries(TOKENHUB_PROVIDER_ID, TOKENHUB_MODEL_CATALOG),
|
||||
wrapStreamFn: wrapTencentProviderStream,
|
||||
});
|
||||
|
||||
api.registerProvider({
|
||||
id: TOKENPLAN_PROVIDER_ID,
|
||||
label: "Tencent TokenPlan",
|
||||
docsPath: "/providers/tencent",
|
||||
envVars: ["TOKENPLAN_API_KEY"],
|
||||
auth: [
|
||||
createProviderApiKeyAuthMethod({
|
||||
providerId: TOKENPLAN_PROVIDER_ID,
|
||||
methodId: "api-key",
|
||||
label: "Tencent TokenPlan",
|
||||
hint: "Hy via Tencent TokenPlan Gateway",
|
||||
optionKey: "tokenplanApiKey",
|
||||
flagName: "--tokenplan-api-key",
|
||||
envVar: "TOKENPLAN_API_KEY",
|
||||
promptMessage: "Enter Tencent TokenPlan API key",
|
||||
defaultModel: TOKENPLAN_DEFAULT_MODEL_REF,
|
||||
expectedProviders: [TOKENPLAN_PROVIDER_ID],
|
||||
applyConfig: applyTokenPlanConfig,
|
||||
wizard: {
|
||||
choiceId: "tokenplan-api-key",
|
||||
choiceLabel: "Tencent TokenPlan",
|
||||
groupId: "tencent",
|
||||
groupLabel: "Tencent Cloud",
|
||||
groupHint: "Tencent TokenPlan",
|
||||
},
|
||||
}),
|
||||
],
|
||||
catalog: {
|
||||
order: "simple",
|
||||
run: (ctx) =>
|
||||
buildSingleProviderApiKeyCatalog({
|
||||
ctx,
|
||||
providerId: TOKENPLAN_PROVIDER_ID,
|
||||
buildProvider: buildTokenPlanProvider,
|
||||
}),
|
||||
},
|
||||
augmentModelCatalog: () =>
|
||||
buildStaticCatalogEntries(TOKENPLAN_PROVIDER_ID, TOKENPLAN_MODEL_CATALOG),
|
||||
wrapStreamFn: wrapTencentProviderStream,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,3 +24,25 @@ export function buildTokenHubModelDefinition(
|
|||
api: "openai-completions",
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- TokenPlan provider ----------
|
||||
|
||||
export const TOKENPLAN_PROVIDER_ID = "tencent-tokenplan";
|
||||
|
||||
const TOKENPLAN_MANIFEST_PROVIDER = buildManifestModelProviderConfig({
|
||||
providerId: TOKENPLAN_PROVIDER_ID,
|
||||
catalog: manifest.modelCatalog.providers[TOKENPLAN_PROVIDER_ID],
|
||||
});
|
||||
|
||||
export const TOKENPLAN_BASE_URL = TOKENPLAN_MANIFEST_PROVIDER.baseUrl;
|
||||
|
||||
export const TOKENPLAN_MODEL_CATALOG: ModelDefinitionConfig[] = TOKENPLAN_MANIFEST_PROVIDER.models;
|
||||
|
||||
export function buildTokenPlanModelDefinition(
|
||||
model: (typeof TOKENPLAN_MODEL_CATALOG)[number],
|
||||
): ModelDefinitionConfig {
|
||||
return {
|
||||
...model,
|
||||
api: "openai-completions",
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,20 +6,29 @@ import {
|
|||
} from "openclaw/plugin-sdk/provider-onboard";
|
||||
import {
|
||||
buildTokenHubModelDefinition,
|
||||
buildTokenPlanModelDefinition,
|
||||
TOKENHUB_BASE_URL,
|
||||
TOKENHUB_MODEL_CATALOG,
|
||||
TOKENHUB_PROVIDER_ID,
|
||||
TOKENPLAN_BASE_URL,
|
||||
TOKENPLAN_MODEL_CATALOG,
|
||||
TOKENPLAN_PROVIDER_ID,
|
||||
} from "./api.js";
|
||||
|
||||
// ---------- TokenHub ----------
|
||||
|
||||
export const TOKENHUB_DEFAULT_MODEL_REF = `${TOKENHUB_PROVIDER_ID}/hy3-preview`;
|
||||
export const TOKENHUB_DEFAULT_MODEL_REF = `${TOKENHUB_PROVIDER_ID}/hy3`;
|
||||
export const TOKENHUB_PREVIEW_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],
|
||||
alias: models[TOKENHUB_DEFAULT_MODEL_REF]?.alias ?? "Hy3 preview (TokenHub)",
|
||||
alias: models[TOKENHUB_DEFAULT_MODEL_REF]?.alias ?? "Hy3 (TokenHub)",
|
||||
};
|
||||
models[TOKENHUB_PREVIEW_MODEL_REF] = {
|
||||
...models[TOKENHUB_PREVIEW_MODEL_REF],
|
||||
alias: models[TOKENHUB_PREVIEW_MODEL_REF]?.alias ?? "Hy3 preview (TokenHub)",
|
||||
};
|
||||
|
||||
return applyProviderConfigWithModelCatalog(cfg, {
|
||||
|
|
@ -37,3 +46,30 @@ export function applyTokenHubConfig(cfg: OpenClawConfig): OpenClawConfig {
|
|||
TOKENHUB_DEFAULT_MODEL_REF,
|
||||
);
|
||||
}
|
||||
|
||||
// ---------- TokenPlan ----------
|
||||
|
||||
export const TOKENPLAN_DEFAULT_MODEL_REF = `${TOKENPLAN_PROVIDER_ID}/hy3`;
|
||||
|
||||
function applyTokenPlanProviderConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||
const models = { ...cfg.agents?.defaults?.models };
|
||||
models[TOKENPLAN_DEFAULT_MODEL_REF] = {
|
||||
...models[TOKENPLAN_DEFAULT_MODEL_REF],
|
||||
alias: models[TOKENPLAN_DEFAULT_MODEL_REF]?.alias ?? "Hy3 (TokenPlan)",
|
||||
};
|
||||
|
||||
return applyProviderConfigWithModelCatalog(cfg, {
|
||||
agentModels: models,
|
||||
providerId: TOKENPLAN_PROVIDER_ID,
|
||||
api: "openai-completions",
|
||||
baseUrl: TOKENPLAN_BASE_URL,
|
||||
catalogModels: TOKENPLAN_MODEL_CATALOG.map(buildTokenPlanModelDefinition),
|
||||
});
|
||||
}
|
||||
|
||||
export function applyTokenPlanConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||
return applyAgentDefaultModelPrimary(
|
||||
applyTokenPlanProviderConfig(cfg),
|
||||
TOKENPLAN_DEFAULT_MODEL_REF,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
},
|
||||
"enabledByDefault": true,
|
||||
"providerCatalogEntry": "./provider-discovery.ts",
|
||||
"providers": ["tencent-tokenhub"],
|
||||
"providers": ["tencent-tokenhub", "tencent-tokenplan"],
|
||||
"modelCatalog": {
|
||||
"providers": {
|
||||
"tencent-tokenhub": {
|
||||
|
|
@ -50,14 +50,48 @@
|
|||
},
|
||||
"compat": {
|
||||
"supportsUsageInStreaming": true,
|
||||
"supportsReasoningEffort": true
|
||||
"supportsReasoningEffort": true,
|
||||
"supportedReasoningEfforts": ["none", "low", "high"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hy3",
|
||||
"name": "Hy3 (TokenHub)",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"contextWindow": 256000,
|
||||
"maxTokens": 64000,
|
||||
"compat": {
|
||||
"supportsUsageInStreaming": true,
|
||||
"supportsReasoningEffort": true,
|
||||
"supportedReasoningEfforts": ["none", "high"]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"tencent-tokenplan": {
|
||||
"baseUrl": "https://api.lkeap.cloud.tencent.com/plan/v3",
|
||||
"api": "openai-completions",
|
||||
"models": [
|
||||
{
|
||||
"id": "hy3",
|
||||
"name": "Hy3 (TokenPlan)",
|
||||
"reasoning": true,
|
||||
"input": ["text"],
|
||||
"contextWindow": 256000,
|
||||
"maxTokens": 64000,
|
||||
"compat": {
|
||||
"supportsUsageInStreaming": true,
|
||||
"supportsReasoningEffort": true,
|
||||
"supportedReasoningEfforts": ["none", "high"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"discovery": {
|
||||
"tencent-tokenhub": "static"
|
||||
"tencent-tokenhub": "static",
|
||||
"tencent-tokenplan": "static"
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
|
|
@ -65,9 +99,20 @@
|
|||
{
|
||||
"id": "tencent-tokenhub",
|
||||
"envVars": ["TOKENHUB_API_KEY"]
|
||||
},
|
||||
{
|
||||
"id": "tencent-tokenplan",
|
||||
"envVars": ["TOKENPLAN_API_KEY"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"configContracts": {
|
||||
"compatibilityMigrationPaths": [
|
||||
"models.providers.tencent-tokenhub",
|
||||
"agents.defaults.models.tencent-tokenhub/hy3",
|
||||
"agents.defaults.models.tencent-tokenhub/hy3-preview"
|
||||
]
|
||||
},
|
||||
"providerAuthChoices": [
|
||||
{
|
||||
"provider": "tencent-tokenhub",
|
||||
|
|
@ -81,6 +126,19 @@
|
|||
"cliFlag": "--tokenhub-api-key",
|
||||
"cliOption": "--tokenhub-api-key <key>",
|
||||
"cliDescription": "Tencent TokenHub API key"
|
||||
},
|
||||
{
|
||||
"provider": "tencent-tokenplan",
|
||||
"method": "api-key",
|
||||
"choiceId": "tokenplan-api-key",
|
||||
"choiceLabel": "Tencent TokenPlan",
|
||||
"groupId": "tencent",
|
||||
"groupLabel": "Tencent Cloud",
|
||||
"groupHint": "Tencent TokenPlan",
|
||||
"optionKey": "tokenplanApiKey",
|
||||
"cliFlag": "--tokenplan-api-key",
|
||||
"cliOption": "--tokenplan-api-key <key>",
|
||||
"cliDescription": "Tencent TokenPlan API key"
|
||||
}
|
||||
],
|
||||
"configSchema": {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"extensions": [
|
||||
"./index.ts"
|
||||
],
|
||||
"setupEntry": "./setup-api.ts",
|
||||
"install": {
|
||||
"clawhubSpec": "clawhub:@openclaw/tencent-provider",
|
||||
"npmSpec": "@openclaw/tencent-provider",
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import {
|
||||
buildTokenHubModelDefinition,
|
||||
buildTokenPlanModelDefinition,
|
||||
TOKENHUB_BASE_URL,
|
||||
TOKENHUB_MODEL_CATALOG,
|
||||
TOKENPLAN_BASE_URL,
|
||||
TOKENPLAN_MODEL_CATALOG,
|
||||
} from "./models.js";
|
||||
|
||||
export function buildTokenHubProvider(): ModelProviderConfig {
|
||||
|
|
@ -13,3 +16,11 @@ export function buildTokenHubProvider(): ModelProviderConfig {
|
|||
models: TOKENHUB_MODEL_CATALOG.map(buildTokenHubModelDefinition),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildTokenPlanProvider(): ModelProviderConfig {
|
||||
return {
|
||||
baseUrl: TOKENPLAN_BASE_URL,
|
||||
api: "openai-completions",
|
||||
models: TOKENPLAN_MODEL_CATALOG.map(buildTokenPlanModelDefinition),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,32 @@
|
|||
// Tencent provider module implements model/runtime integration.
|
||||
import type { ProviderPlugin } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { buildTokenHubProvider } from "./provider-catalog.js";
|
||||
import { buildTokenHubProvider, buildTokenPlanProvider } from "./provider-catalog.js";
|
||||
|
||||
const tencentProviderDiscovery: ProviderPlugin = {
|
||||
id: "tencent-tokenhub",
|
||||
label: "Tencent TokenHub",
|
||||
docsPath: "/providers/models",
|
||||
auth: [],
|
||||
staticCatalog: {
|
||||
order: "simple",
|
||||
run: async () => ({
|
||||
provider: buildTokenHubProvider(),
|
||||
}),
|
||||
const tencentProviderDiscovery: ProviderPlugin[] = [
|
||||
{
|
||||
id: "tencent-tokenhub",
|
||||
label: "Tencent TokenHub",
|
||||
docsPath: "/providers/tencent",
|
||||
auth: [],
|
||||
staticCatalog: {
|
||||
order: "simple",
|
||||
run: async () => ({
|
||||
provider: buildTokenHubProvider(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
{
|
||||
id: "tencent-tokenplan",
|
||||
label: "Tencent TokenPlan",
|
||||
docsPath: "/providers/tencent",
|
||||
auth: [],
|
||||
staticCatalog: {
|
||||
order: "simple",
|
||||
run: async () => ({
|
||||
provider: buildTokenPlanProvider(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export default tencentProviderDiscovery;
|
||||
|
|
|
|||
12
extensions/tencent/setup-api.ts
Normal file
12
extensions/tencent/setup-api.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Tencent setup entrypoint exposes lightweight config repair hooks.
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { migrateTencentTokenHubModelDefaults } from "./config-compat.js";
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "tencent",
|
||||
name: "Tencent Cloud Provider Setup",
|
||||
description: "Lightweight Tencent Cloud provider setup hooks",
|
||||
register(api) {
|
||||
api.registerConfigMigration((config) => migrateTencentTokenHubModelDefaults(config));
|
||||
},
|
||||
});
|
||||
92
extensions/tencent/stream.ts
Normal file
92
extensions/tencent/stream.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import type { StreamFn } from "openclaw/plugin-sdk/agent-core";
|
||||
import type { ProviderWrapStreamFnContext } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
createPayloadPatchStreamWrapper,
|
||||
type OpenAICompatibleThinkingLevel,
|
||||
} from "openclaw/plugin-sdk/provider-stream-shared";
|
||||
import { TOKENHUB_PROVIDER_ID, TOKENPLAN_PROVIDER_ID } from "./models.js";
|
||||
|
||||
const TENCENT_PROVIDER_IDS: ReadonlySet<string> = new Set([
|
||||
TOKENHUB_PROVIDER_ID,
|
||||
TOKENPLAN_PROVIDER_ID,
|
||||
]);
|
||||
|
||||
type StreamModel = Parameters<StreamFn>[0];
|
||||
type StreamOptions = Parameters<StreamFn>[2];
|
||||
|
||||
const TENCENT_REASONING_EFFORT_MAP: Readonly<Record<string, string>> = Object.freeze({
|
||||
off: "none",
|
||||
none: "none",
|
||||
minimal: "high",
|
||||
low: "high",
|
||||
medium: "high",
|
||||
high: "high",
|
||||
xhigh: "high",
|
||||
});
|
||||
|
||||
const TOKENHUB_HY3_PREVIEW_REASONING_EFFORTS = new Set(["none", "low", "high"]);
|
||||
|
||||
function resolveRequestedEffort(
|
||||
thinkingLevel: OpenAICompatibleThinkingLevel,
|
||||
options: StreamOptions,
|
||||
): string | undefined {
|
||||
const withEffort = (options ?? {}) as { reasoningEffort?: unknown; reasoning?: unknown };
|
||||
const raw =
|
||||
(typeof withEffort.reasoningEffort === "string" && withEffort.reasoningEffort) ||
|
||||
(typeof withEffort.reasoning === "string" && withEffort.reasoning) ||
|
||||
(typeof thinkingLevel === "string" && thinkingLevel) ||
|
||||
undefined;
|
||||
return raw ? raw.trim().toLowerCase() : undefined;
|
||||
}
|
||||
|
||||
function mapEffortForTencent(model: StreamModel, effort: string | undefined): string | undefined {
|
||||
if (!effort) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
(model as { provider?: unknown }).provider === TOKENHUB_PROVIDER_ID &&
|
||||
(model as { id?: unknown }).id === "hy3-preview"
|
||||
) {
|
||||
if (effort === "off") {
|
||||
return "none";
|
||||
}
|
||||
// Preview supports low directly. Leave unsupported requests to the shared
|
||||
// model fallback that already normalized the underlying payload.
|
||||
return TOKENHUB_HY3_PREVIEW_REASONING_EFFORTS.has(effort) ? effort : undefined;
|
||||
}
|
||||
return TENCENT_REASONING_EFFORT_MAP[effort];
|
||||
}
|
||||
|
||||
function isTencentCompletionsCall(model: StreamModel): boolean {
|
||||
const provider = (model as { provider?: unknown }).provider;
|
||||
const api = (model as { api?: unknown }).api;
|
||||
return (
|
||||
typeof provider === "string" &&
|
||||
TENCENT_PROVIDER_IDS.has(provider) &&
|
||||
api === "openai-completions"
|
||||
);
|
||||
}
|
||||
|
||||
export function wrapTencentProviderStream(ctx: ProviderWrapStreamFnContext): StreamFn {
|
||||
return createPayloadPatchStreamWrapper(
|
||||
ctx.streamFn,
|
||||
({ payload, model, options }) => {
|
||||
const requested = resolveRequestedEffort(ctx.thinkingLevel, options);
|
||||
const mapped = mapEffortForTencent(model, requested);
|
||||
|
||||
if (mapped === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mapped === "none" || mapped === "off") {
|
||||
payload.reasoning_effort = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
payload.reasoning_effort = mapped;
|
||||
},
|
||||
{
|
||||
shouldPatch: ({ model }) => isTencentCompletionsCall(model),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -1221,6 +1221,36 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tencent-tokenplan",
|
||||
"name": "Tencent TokenPlan",
|
||||
"docs": "/providers/tencent",
|
||||
"categories": [
|
||||
"cloud",
|
||||
"llm"
|
||||
],
|
||||
"envVars": [
|
||||
"TOKENPLAN_API_KEY"
|
||||
],
|
||||
"authChoices": [
|
||||
{
|
||||
"method": "api-key",
|
||||
"choiceId": "tokenplan-api-key",
|
||||
"choiceLabel": "Tencent TokenPlan",
|
||||
"choiceHint": "Hy via Tencent TokenPlan Gateway",
|
||||
"groupId": "tencent",
|
||||
"groupLabel": "Tencent Cloud",
|
||||
"groupHint": "Tencent TokenPlan",
|
||||
"optionKey": "tokenplanApiKey",
|
||||
"cliFlag": "--tokenplan-api-key",
|
||||
"cliOption": "--tokenplan-api-key <key>",
|
||||
"cliDescription": "Tencent TokenPlan API key",
|
||||
"onboardingScopes": [
|
||||
"text-inference"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"install": {
|
||||
|
|
|
|||
|
|
@ -77,11 +77,14 @@ describe("model provider localService config", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("accepts standalone timeout overlays for Xiaomi Token Plan", () => {
|
||||
it.each([
|
||||
{ provider: "xiaomi-token-plan", name: "Xiaomi Token Plan" },
|
||||
{ provider: "tencent-tokenplan", name: "Tencent TokenPlan" },
|
||||
] as const)("accepts standalone timeout overlays for $name", ({ provider }) => {
|
||||
const result = validateConfigObjectRaw({
|
||||
models: {
|
||||
providers: {
|
||||
"xiaomi-token-plan": {
|
||||
[provider]: {
|
||||
timeoutSeconds: 600,
|
||||
},
|
||||
},
|
||||
|
|
@ -90,9 +93,9 @@ describe("model provider localService config", () => {
|
|||
|
||||
expect(result.ok).toBe(true);
|
||||
if (result.ok) {
|
||||
expect(result.config.models?.providers?.["xiaomi-token-plan"]?.timeoutSeconds).toBe(600);
|
||||
expect(result.config.models?.providers?.["xiaomi-token-plan"]?.models).toEqual([]);
|
||||
expect(result.config.models?.providers?.["xiaomi-token-plan"]?.baseUrl).toBe("");
|
||||
expect(result.config.models?.providers?.[provider]?.timeoutSeconds).toBe(600);
|
||||
expect(result.config.models?.providers?.[provider]?.models).toEqual([]);
|
||||
expect(result.config.models?.providers?.[provider]?.baseUrl).toBe("");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -497,6 +497,7 @@ const BUILT_IN_MODEL_PROVIDER_OVERLAY_IDS = new Set([
|
|||
"stepfun-plan",
|
||||
"synthetic",
|
||||
"tencent-tokenhub",
|
||||
"tencent-tokenplan",
|
||||
"together",
|
||||
"venice",
|
||||
"vercel-ai-gateway",
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ const BLOCKED_PROVIDER_AUTH_WORKSPACE_DOTENV_KEYS = [
|
|||
"TAVILY_API_KEY",
|
||||
"TOGETHER_API_KEY",
|
||||
"TOKENHUB_API_KEY",
|
||||
"TOKENPLAN_API_KEY",
|
||||
"VENICE_API_KEY",
|
||||
"VLLM_API_KEY",
|
||||
"VOLCANO_ENGINE_API_KEY",
|
||||
|
|
|
|||
|
|
@ -1823,6 +1823,7 @@ describe("official external plugin catalog", () => {
|
|||
STEPFUN_API_KEY: "stepfun-key",
|
||||
FIREWORKS_API_KEY: "fireworks-key",
|
||||
TOKENHUB_API_KEY: "tokenhub-key",
|
||||
TOKENPLAN_API_KEY: "tokenplan-key",
|
||||
VENICE_API_KEY: "venice-key",
|
||||
AI_GATEWAY_API_KEY: "gateway-key",
|
||||
ZAI_API_KEY: "zai-key",
|
||||
|
|
@ -1850,6 +1851,31 @@ describe("official external plugin catalog", () => {
|
|||
expect(resolveOfficialExternalProviderPluginIdsForEnv({ GROQ_API_KEY: " " })).toEqual([]);
|
||||
});
|
||||
|
||||
it("keeps Tencent auth choices available through the cold-install auth catalog", () => {
|
||||
const tencent = expectCatalogEntry("tencent");
|
||||
const tokenHub = tencent.openclaw?.providers?.find(
|
||||
(provider) => provider.id === "tencent-tokenhub",
|
||||
);
|
||||
const tokenPlan = tencent.openclaw?.providers?.find(
|
||||
(provider) => provider.id === "tencent-tokenplan",
|
||||
);
|
||||
|
||||
expect(tokenHub?.envVars).toEqual(["TOKENHUB_API_KEY"]);
|
||||
expect(tokenHub?.authChoices).toEqual([
|
||||
expect.objectContaining({
|
||||
choiceId: "tokenhub-api-key",
|
||||
optionKey: "tokenhubApiKey",
|
||||
cliFlag: "--tokenhub-api-key",
|
||||
}),
|
||||
]);
|
||||
expect(tokenPlan?.envVars).toEqual(["TOKENPLAN_API_KEY"]);
|
||||
expect(tokenPlan?.authChoices?.[0]).toMatchObject({
|
||||
choiceId: "tokenplan-api-key",
|
||||
optionKey: "tokenplanApiKey",
|
||||
cliFlag: "--tokenplan-api-key",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps Groq available through the cold-install auth catalog", () => {
|
||||
const groq = expectCatalogEntry("groq");
|
||||
const authChoice = groq.openclaw?.providers?.find((provider) => provider.id === "groq")
|
||||
|
|
|
|||
38
src/plugins/setup-registry.tencent.test.ts
Normal file
38
src/plugins/setup-registry.tencent.test.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Covers Tencent setup config migration registration in the plugin setup registry.
|
||||
import path from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { runPluginSetupConfigMigrations } from "./setup-registry.js";
|
||||
|
||||
describe("Tencent setup config migration", () => {
|
||||
test("repairs TokenHub model defaults through setup registry", () => {
|
||||
const result = runPluginSetupConfigMigrations({
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: path.resolve("extensions"),
|
||||
},
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "tencent-tokenhub/hy3-preview" },
|
||||
models: {
|
||||
"tencent-tokenhub/hy3-preview": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as OpenClawConfig,
|
||||
});
|
||||
|
||||
expect(result.changes).toEqual([
|
||||
"Updated Tencent TokenHub agent model defaults to include tencent-tokenhub/hy3 and tencent-tokenhub/hy3-preview.",
|
||||
"Changed Tencent TokenHub primary default from tencent-tokenhub/hy3-preview to tencent-tokenhub/hy3.",
|
||||
]);
|
||||
expect(result.config.agents?.defaults?.model).toEqual({
|
||||
primary: "tencent-tokenhub/hy3",
|
||||
});
|
||||
expect(Object.keys(result.config.agents?.defaults?.models ?? {}).toSorted()).toEqual([
|
||||
"tencent-tokenhub/hy3",
|
||||
"tencent-tokenhub/hy3-preview",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -105,4 +105,19 @@ describe("plugin npm runtime build planning", () => {
|
|||
expect(plan.packageFiles).toContain("dist/**");
|
||||
}
|
||||
});
|
||||
|
||||
it("builds Tencent setup metadata for installed-package migrations", () => {
|
||||
const plan = expectPluginNpmRuntimeBuildPlan(
|
||||
resolvePluginNpmRuntimeBuildPlan({
|
||||
repoRoot,
|
||||
packageDir: path.join(repoRoot, "extensions", "tencent"),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(plan.entry["setup-api"]).toBe(
|
||||
path.join(repoRoot, "extensions", "tencent", "setup-api.ts"),
|
||||
);
|
||||
expect(plan.runtimeSetupEntry).toBe("./dist/setup-api.js");
|
||||
expect(plan.runtimeBuildOutputs).toContain("./dist/setup-api.js");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue