feat(providers): add LongCat API support (#100501)

* fix(ai): honor provider reasoning compatibility

* feat(longcat): add hosted provider plugin

* chore(longcat): register package metadata

* docs(longcat): add provider setup guide

* docs(longcat): document self-hosted model routing

* refactor(longcat): externalize provider plugin

* chore(longcat): add npm release artifacts
This commit is contained in:
Vincent Koc 2026-07-06 10:07:08 -07:00 committed by GitHub
parent 8336a94ecb
commit bf8626c0e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 792 additions and 6 deletions

5
.github/labeler.yml vendored
View file

@ -448,6 +448,11 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/kimi-coding/**"
"extensions: longcat":
- changed-files:
- any-glob-to-any-file:
- "extensions/longcat/**"
- "docs/providers/longcat.md"
"extensions: minimax":
- changed-files:
- any-glob-to-any-file:

View file

@ -1481,6 +1481,7 @@
"providers/kilocode",
"providers/litellm",
"providers/lmstudio",
"providers/longcat",
"providers/minimax",
"providers/mistral",
"providers/moonshot",

View file

@ -6261,6 +6261,24 @@ Do not edit it by hand; run `pnpm docs:map:gen`.
- H2: Distribution
- H2: Surface
## plugins/reference/logbook.md
- Route: /plugins/reference/logbook
- Headings:
- H1: Logbook plugin
- H2: Distribution
- H2: Surface
- H2: Related docs
## plugins/reference/longcat.md
- Route: /plugins/reference/longcat
- Headings:
- H1: LongCat plugin
- H2: Distribution
- H2: Surface
- H2: Related docs
## plugins/reference/matrix.md
- Route: /plugins/reference/matrix
@ -7564,6 +7582,19 @@ Do not edit it by hand; run `pnpm docs:map:gen`.
- H3: Authentication errors (HTTP 401)
- H2: Related
## providers/longcat.md
- Route: /providers/longcat
- Headings:
- H2: Install plugin
- H2: Getting started
- H3: Non-interactive setup
- H2: Reasoning behavior
- H2: Pricing
- H2: Self-hosted LongCat-2.0
- H2: Troubleshooting
- H2: Related
## providers/minimax.md
- Route: /providers/minimax

View file

@ -51,7 +51,7 @@ Each entry lists the package, distribution route, and description.
## Core npm package
60 plugins
61 plugins
- **[admin-http-rpc](/plugins/reference/admin-http-rpc)** (`@openclaw/admin-http-rpc`) - included in OpenClaw. OpenClaw admin HTTP RPC endpoint.
@ -105,6 +105,8 @@ Each entry lists the package, distribution route, and description.
- **[lmstudio](/plugins/reference/lmstudio)** (`@openclaw/lmstudio-provider`) - included in OpenClaw. Adds LM Studio model provider support to OpenClaw.
- **[logbook](/plugins/reference/logbook)** (`@openclaw/logbook`) - included in OpenClaw. Automatic work journal: captures periodic screen snapshots from a paired node and turns them into a reviewable timeline of your day.
- **[memory-core](/plugins/reference/memory-core)** (`@openclaw/memory-core`) - included in OpenClaw. Adds agent-callable tools.
- **[memory-wiki](/plugins/reference/memory-wiki)** (`@openclaw/memory-wiki`) - included in OpenClaw. Persistent wiki compiler and Obsidian-friendly knowledge vault for OpenClaw.
@ -175,7 +177,7 @@ Each entry lists the package, distribution route, and description.
## Official external packages
68 plugins
69 plugins
- **[acpx](/plugins/reference/acpx)** (`@openclaw/acpx`) - npm; ClawHub. OpenClaw ACP runtime backend with plugin-owned session and transport management.
@ -247,6 +249,8 @@ Each entry lists the package, distribution route, and description.
- **[lobster](/plugins/reference/lobster)** (`@openclaw/lobster`) - npm; ClawHub. Lobster workflow tool plugin for typed pipelines and resumable approvals.
- **[longcat](/plugins/reference/longcat)** (`@openclaw/longcat-provider`) - npm; ClawHub: `clawhub:@openclaw/longcat-provider`. OpenClaw LongCat provider plugin.
- **[matrix](/plugins/reference/matrix)** (`@openclaw/matrix`) - ClawHub: `clawhub:@openclaw/matrix`; npm. OpenClaw Matrix channel plugin for rooms and direct messages.
- **[mattermost](/plugins/reference/mattermost)** (`@openclaw/mattermost`) - npm; ClawHub: `clawhub:@openclaw/mattermost`. Adds the Mattermost channel surface for sending and receiving OpenClaw messages.

View file

@ -15,5 +15,5 @@ This page is generated from `extensions/*/package.json` and
pnpm plugins:inventory:gen
```
Use [Plugin inventory](/plugins/plugin-inventory) to browse all 130
Use [Plugin inventory](/plugins/plugin-inventory) to browse all 132
generated plugin reference pages by distribution, package, and description.

View file

@ -0,0 +1,23 @@
---
summary: "Automatic work journal: captures periodic screen snapshots from a paired node and turns them into a reviewable timeline of your day."
read_when:
- You are installing, configuring, or auditing the logbook plugin
title: "Logbook plugin"
---
# Logbook plugin
Automatic work journal: captures periodic screen snapshots from a paired node and turns them into a reviewable timeline of your day.
## Distribution
- Package: `@openclaw/logbook`
- Install route: included in OpenClaw
## Surface
plugin
## Related docs
- [logbook](/plugins/logbook)

View file

@ -0,0 +1,23 @@
---
summary: "OpenClaw LongCat provider plugin."
read_when:
- You are installing, configuring, or auditing the longcat plugin
title: "LongCat plugin"
---
# LongCat plugin
OpenClaw LongCat provider plugin.
## Distribution
- Package: `@openclaw/longcat-provider`
- Install route: npm; ClawHub: `clawhub:@openclaw/longcat-provider`
## Surface
providers: longcat
## Related docs
- [longcat](/providers/longcat)

View file

@ -52,6 +52,7 @@ Looking for chat channel docs (WhatsApp/Telegram/Discord/Slack/Mattermost (plugi
- [Kilocode](/providers/kilocode)
- [LiteLLM (unified gateway)](/providers/litellm)
- [LM Studio (local models)](/providers/lmstudio)
- [LongCat](/providers/longcat)
- [MiniMax](/providers/minimax)
- [Mistral](/providers/mistral)
- [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot)

129
docs/providers/longcat.md Normal file
View file

@ -0,0 +1,129 @@
---
summary: "LongCat API setup for LongCat-2.0"
title: "LongCat"
read_when:
- You want to use LongCat-2.0 with OpenClaw
- You need the LongCat API key or model limits
---
[LongCat](https://longcat.ai) provides a hosted API for LongCat-2.0, a
reasoning model built for coding and agentic workloads. OpenClaw provides the
official `longcat` plugin for LongCat's OpenAI-compatible endpoint.
| Property | Value |
| ---------- | ---------------------------------- |
| Provider | `longcat` |
| Auth | `LONGCAT_API_KEY` |
| API | OpenAI-compatible Chat Completions |
| Base URL | `https://api.longcat.chat/openai` |
| Model | `longcat/LongCat-2.0` |
| Context | 1,048,576 tokens |
| Max output | 131,072 tokens |
| Input | Text |
## Install plugin
Install the official package, then restart Gateway:
```bash
openclaw plugins install @openclaw/longcat-provider
openclaw gateway restart
```
## Getting started
<Steps>
<Step title="Create an API key">
Sign in to the [LongCat API Platform](https://longcat.chat/platform/) and
create a key on the [API Keys](https://longcat.chat/platform/api_keys)
page.
</Step>
<Step title="Run onboarding">
```bash
openclaw onboard --auth-choice longcat-api-key
```
</Step>
<Step title="Verify the model">
```bash
openclaw models list --provider longcat
```
</Step>
</Steps>
Onboarding adds the hosted catalog and selects `longcat/LongCat-2.0` when no
primary model is already configured.
### Non-interactive setup
```bash
openclaw onboard --non-interactive \
--mode local \
--auth-choice longcat-api-key \
--longcat-api-key "$LONGCAT_API_KEY"
```
## Reasoning behavior
LongCat exposes binary thinking control. OpenClaw maps enabled thinking levels
to `thinking: { type: "enabled" }` and `/think off` to
`thinking: { type: "disabled" }`. LongCat does not currently document
`reasoning_effort`, so OpenClaw does not send it.
LongCat returns reasoning in `reasoning_content`. OpenClaw preserves that field
when replaying assistant tool-call turns so multi-turn agent sessions retain
the provider's expected message shape.
## Pricing
The built-in catalog uses LongCat's pay-as-you-go list prices in USD per million
tokens: $0.75 uncached input, $0.015 cached input, and $2.95 output. LongCat may
offer temporary discounts; the [pricing page](https://longcat.chat/platform/docs/Pricing/LongCat-2.0.html)
and your billing records are authoritative.
## Self-hosted LongCat-2.0
The `longcat` provider targets LongCat's hosted API. For the open weights on
[Hugging Face](https://huggingface.co/meituan-longcat/LongCat-2.0), serve the
model through an OpenAI-compatible runtime and use OpenClaw's existing
[vLLM](/providers/vllm) or [SGLang](/providers/sglang) provider instead.
Keep the runtime's exact model identifier in the self-hosted provider catalog;
do not route a local deployment through `longcat/LongCat-2.0`.
## Troubleshooting
<AccordionGroup>
<Accordion title="The key works in a shell but not in the Gateway">
Daemon-managed Gateway processes do not inherit every interactive shell
variable. Put `LONGCAT_API_KEY` in `~/.openclaw/.env`, configure it through
onboarding, or use an approved secret reference.
</Accordion>
<Accordion title="Requests fail with 402 or 429">
`402` means the account has insufficient token quota. `429` means the API
key hit a rate limit. Check [LongCat usage](https://longcat.chat/platform/usage)
and retry rate-limited requests after the provider's backoff window.
</Accordion>
<Accordion title="The model does not appear">
Run `openclaw plugins list` and confirm the `longcat` plugin is
enabled, then run `openclaw models list --provider longcat`.
</Accordion>
</AccordionGroup>
## Related
<CardGroup cols={2}>
<Card title="Model providers" href="/concepts/model-providers" icon="layers">
Provider configuration, model refs, and failover behavior.
</Card>
<Card title="LongCat API docs" href="https://longcat.chat/platform/docs/" icon="arrow-up-right-from-square">
Hosted API endpoints, authentication, limits, and examples.
</Card>
<Card title="LongCat-2.0 model card" href="https://huggingface.co/meituan-longcat/LongCat-2.0" icon="arrow-up-right-from-square">
Architecture, deployment guidance, and model details.
</Card>
<Card title="Secrets" href="/gateway/secrets" icon="key">
Store provider credentials without embedding plaintext in config.
</Card>
</CardGroup>

View file

@ -0,0 +1,13 @@
# OpenClaw LongCat Provider
Official OpenClaw provider plugin for LongCat-2.0 through LongCat's hosted
OpenAI-compatible API.
Install from OpenClaw:
```bash
openclaw plugins install @openclaw/longcat-provider
openclaw gateway restart
```
Configure `LONGCAT_API_KEY`, then select `longcat/LongCat-2.0`.

10
extensions/longcat/api.ts Normal file
View file

@ -0,0 +1,10 @@
// LongCat API module exposes the plugin public contract.
export {
LONGCAT_BASE_URL,
LONGCAT_DEFAULT_MODEL_ID,
LONGCAT_DEFAULT_MODEL_REF,
LONGCAT_MODEL_CATALOG,
} from "./models.js";
export { applyLongCatConfig } from "./onboard.js";
export { buildLongCatProvider } from "./provider-catalog.js";
export { createLongCatThinkingWrapper } from "./stream.js";

View file

@ -0,0 +1,183 @@
// LongCat tests cover provider registration, onboarding, and wire compatibility.
import { readFileSync } from "node:fs";
import type { StreamFn } from "openclaw/plugin-sdk/agent-core";
import {
createAssistantMessageEventStream,
type Context,
type Model,
} from "openclaw/plugin-sdk/llm";
import { registerSingleProviderPlugin } from "openclaw/plugin-sdk/plugin-test-runtime";
import { resolveAgentModelPrimaryValue } from "openclaw/plugin-sdk/provider-onboard";
import { buildOpenAICompletionsParams } from "openclaw/plugin-sdk/provider-transport-runtime";
import { describe, expect, it } from "vitest";
import plugin from "./index.js";
import { LONGCAT_DEFAULT_MODEL_REF } from "./models.js";
import { applyLongCatConfig } from "./onboard.js";
import { buildLongCatProvider } from "./provider-catalog.js";
import { createLongCatThinkingWrapper } from "./stream.js";
function readManifest() {
return JSON.parse(readFileSync(new URL("./openclaw.plugin.json", import.meta.url), "utf8")) as {
providerAuthChoices?: Array<{ choiceId?: string; optionKey?: string; cliFlag?: string }>;
setup?: { providers?: Array<{ id?: string; envVars?: string[] }> };
};
}
function requireLongCatModel(): Model<"openai-completions"> {
const model = buildLongCatProvider().models?.[0];
if (!model) {
throw new Error("LongCat catalog did not provide a model");
}
return {
...model,
api: "openai-completions",
baseUrl: "https://api.longcat.chat/openai",
provider: "longcat",
input: ["text"],
cost: { ...model.cost },
} as Model<"openai-completions">;
}
describe("LongCat provider plugin", () => {
it("registers the manifest-owned API key onboarding flow", async () => {
const provider = await registerSingleProviderPlugin(plugin);
expect(provider).toMatchObject({
id: "longcat",
aliases: ["meituan-longcat"],
envVars: ["LONGCAT_API_KEY"],
});
expect(provider.auth[0]).toMatchObject({
id: "api-key",
kind: "api_key",
wizard: { choiceId: "longcat-api-key" },
});
expect(readManifest().providerAuthChoices).toEqual([
expect.objectContaining({
choiceId: "longcat-api-key",
optionKey: "longcatApiKey",
cliFlag: "--longcat-api-key",
}),
]);
expect(readManifest().setup?.providers).toEqual([
{ id: "longcat", envVars: ["LONGCAT_API_KEY"] },
]);
});
it("exposes the hosted LongCat-2.0 catalog", () => {
expect(buildLongCatProvider()).toMatchObject({
baseUrl: "https://api.longcat.chat/openai",
api: "openai-completions",
models: [
expect.objectContaining({
id: "LongCat-2.0",
reasoning: true,
contextWindow: 1_048_576,
maxTokens: 131_072,
compat: {
supportsStore: false,
supportsDeveloperRole: false,
supportsReasoningEffort: false,
supportsUsageInStreaming: false,
supportsStrictMode: false,
maxTokensField: "max_tokens",
requiresReasoningContentOnAssistantMessages: true,
thinkingFormat: "deepseek",
},
}),
],
});
});
it("applies the LongCat catalog without replacing an existing primary model", () => {
const result = applyLongCatConfig({
agents: { defaults: { model: { primary: "openai/gpt-5.5" } } },
});
expect(resolveAgentModelPrimaryValue(result.agents?.defaults?.model)).toBe("openai/gpt-5.5");
expect(result.agents?.defaults?.models?.[LONGCAT_DEFAULT_MODEL_REF]).toEqual({
alias: "LongCat 2.0",
});
});
it("uses LongCat thinking and replay fields without reasoning_effort", () => {
const model = requireLongCatModel();
const context = {
systemPrompt: "system",
messages: [
{ role: "user", content: "read it", timestamp: 1 },
{
role: "assistant",
api: "openai-completions",
provider: "longcat",
model: "LongCat-2.0",
content: [
{
type: "thinking",
thinking: "use the read tool",
thinkingSignature: "reasoning_content",
},
{ type: "toolCall", id: "call_1", name: "read", arguments: {} },
],
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "toolUse",
timestamp: 2,
},
],
tools: [
{
name: "read",
description: "Read data",
parameters: { type: "object", properties: {} },
},
],
} as Context;
let payload: Record<string, unknown> | undefined;
const baseStreamFn: StreamFn = (streamModel, streamContext, options) => {
const params = buildOpenAICompletionsParams(
streamModel as Model<"openai-completions">,
streamContext,
{ maxTokens: 2048, reasoning: "high" } as never,
) as Record<string, unknown>;
options?.onPayload?.(params, streamModel);
const stream = createAssistantMessageEventStream();
queueMicrotask(() => stream.end());
return stream;
};
const wrappedStreamFn = createLongCatThinkingWrapper(baseStreamFn, "high");
void wrappedStreamFn(model, context, {
onPayload: (nextPayload) => {
payload = nextPayload as Record<string, unknown>;
},
});
if (!payload) {
throw new Error("LongCat payload was not captured");
}
expect(payload).toMatchObject({
max_tokens: 2048,
thinking: { type: "enabled" },
});
expect(payload).not.toHaveProperty("max_completion_tokens");
expect(payload).not.toHaveProperty("reasoning_effort");
expect(payload).not.toHaveProperty("store");
expect(payload).not.toHaveProperty("stream_options");
expect(payload.messages).toEqual(
expect.arrayContaining([
expect.objectContaining({ role: "system", content: "system" }),
expect.objectContaining({
role: "assistant",
reasoning_content: "use the read tool",
}),
]),
);
});
});

View file

@ -0,0 +1,53 @@
// LongCat plugin entrypoint registers its OpenClaw integration.
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderToolCompatFamilyHooks } from "openclaw/plugin-sdk/provider-tools";
import { LONGCAT_DEFAULT_MODEL_REF } from "./models.js";
import { applyLongCatConfig } from "./onboard.js";
import { buildLongCatProvider } from "./provider-catalog.js";
import { createLongCatThinkingWrapper } from "./stream.js";
const PROVIDER_ID = "longcat";
export default defineSingleProviderPluginEntry({
id: PROVIDER_ID,
name: "LongCat Provider",
description: "Official LongCat provider plugin",
provider: {
label: "LongCat",
docsPath: "/providers/longcat",
aliases: ["meituan-longcat"],
envVars: ["LONGCAT_API_KEY"],
auth: [
{
methodId: "api-key",
label: "LongCat API key",
hint: "API key",
optionKey: "longcatApiKey",
flagName: "--longcat-api-key",
envVar: "LONGCAT_API_KEY",
promptMessage: "Enter LongCat API key",
defaultModel: LONGCAT_DEFAULT_MODEL_REF,
applyConfig: (cfg) => applyLongCatConfig(cfg),
noteTitle: "LongCat",
noteMessage: "Manage API keys at https://longcat.chat/platform/api_keys",
wizard: {
choiceId: "longcat-api-key",
choiceLabel: "LongCat API key",
groupId: "longcat",
groupLabel: "LongCat",
groupHint: "API key",
},
},
],
catalog: {
buildProvider: buildLongCatProvider,
},
...buildProviderReplayFamilyHooks({
family: "openai-compatible",
dropReasoningFromHistory: false,
}),
...buildProviderToolCompatFamilyHooks("openai"),
wrapStreamFn: (ctx) => createLongCatThinkingWrapper(ctx.streamFn, ctx.thinkingLevel),
},
});

View file

@ -0,0 +1,14 @@
// LongCat plugin module implements models behavior.
import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared";
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
import manifest from "./openclaw.plugin.json" with { type: "json" };
const LONGCAT_MANIFEST_PROVIDER = buildManifestModelProviderConfig({
providerId: "longcat",
catalog: manifest.modelCatalog.providers.longcat,
});
export const LONGCAT_BASE_URL = LONGCAT_MANIFEST_PROVIDER.baseUrl;
export const LONGCAT_MODEL_CATALOG: ModelDefinitionConfig[] = LONGCAT_MANIFEST_PROVIDER.models;
export const LONGCAT_DEFAULT_MODEL_ID = "LongCat-2.0";
export const LONGCAT_DEFAULT_MODEL_REF = `longcat/${LONGCAT_DEFAULT_MODEL_ID}`;

12
extensions/longcat/npm-shrinkwrap.json generated Normal file
View file

@ -0,0 +1,12 @@
{
"name": "@openclaw/longcat-provider",
"version": "2026.6.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/longcat-provider",
"version": "2026.6.11"
}
}
}

View file

@ -0,0 +1,21 @@
// LongCat setup module handles plugin onboarding behavior.
import {
createModelCatalogPresetAppliers,
type OpenClawConfig,
} from "openclaw/plugin-sdk/provider-onboard";
import { LONGCAT_BASE_URL, LONGCAT_DEFAULT_MODEL_REF, LONGCAT_MODEL_CATALOG } from "./models.js";
const longCatPresetAppliers = createModelCatalogPresetAppliers({
primaryModelRef: LONGCAT_DEFAULT_MODEL_REF,
resolveParams: (_cfg: OpenClawConfig) => ({
providerId: "longcat",
api: "openai-completions",
baseUrl: LONGCAT_BASE_URL,
catalogModels: LONGCAT_MODEL_CATALOG,
aliases: [{ modelRef: LONGCAT_DEFAULT_MODEL_REF, alias: "LongCat 2.0" }],
}),
});
export function applyLongCatConfig(cfg: OpenClawConfig): OpenClawConfig {
return longCatPresetAppliers.applyConfig(cfg);
}

View file

@ -0,0 +1,82 @@
{
"id": "longcat",
"name": "LongCat",
"description": "OpenClaw LongCat provider plugin.",
"activation": {
"onStartup": false
},
"enabledByDefault": true,
"providers": ["longcat"],
"providerRequest": {
"providers": {
"longcat": {
"family": "longcat"
}
}
},
"modelCatalog": {
"providers": {
"longcat": {
"baseUrl": "https://api.longcat.chat/openai",
"api": "openai-completions",
"models": [
{
"id": "LongCat-2.0",
"name": "LongCat 2.0",
"reasoning": true,
"input": ["text"],
"contextWindow": 1048576,
"maxTokens": 131072,
"cost": {
"input": 0.75,
"output": 2.95,
"cacheRead": 0.015,
"cacheWrite": 0.75
},
"compat": {
"supportsStore": false,
"supportsDeveloperRole": false,
"supportsReasoningEffort": false,
"supportsUsageInStreaming": false,
"supportsStrictMode": false,
"maxTokensField": "max_tokens",
"requiresReasoningContentOnAssistantMessages": true,
"thinkingFormat": "deepseek"
}
}
]
}
},
"discovery": {
"longcat": "static"
}
},
"setup": {
"providers": [
{
"id": "longcat",
"envVars": ["LONGCAT_API_KEY"]
}
]
},
"providerAuthChoices": [
{
"provider": "longcat",
"method": "api-key",
"choiceId": "longcat-api-key",
"choiceLabel": "LongCat API key",
"groupId": "longcat",
"groupLabel": "LongCat",
"groupHint": "API key",
"optionKey": "longcatApiKey",
"cliFlag": "--longcat-api-key",
"cliOption": "--longcat-api-key <key>",
"cliDescription": "LongCat API key"
}
],
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {}
}
}

View file

@ -0,0 +1,35 @@
{
"name": "@openclaw/longcat-provider",
"version": "2026.6.11",
"description": "OpenClaw LongCat provider plugin.",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
},
"type": "module",
"devDependencies": {
"@openclaw/plugin-sdk": "workspace:*"
},
"openclaw": {
"extensions": [
"./index.ts"
],
"install": {
"clawhubSpec": "clawhub:@openclaw/longcat-provider",
"npmSpec": "@openclaw/longcat-provider",
"defaultChoice": "npm",
"minHostVersion": ">=2026.6.8"
},
"compat": {
"pluginApi": ">=2026.6.11"
},
"build": {
"openclawVersion": "2026.6.11",
"bundledDist": false
},
"release": {
"publishToClawHub": true,
"publishToNpm": true
}
}
}

View file

@ -0,0 +1,11 @@
// LongCat provider module implements model/runtime integration.
import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared";
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared";
import manifest from "./openclaw.plugin.json" with { type: "json" };
export function buildLongCatProvider(): ModelProviderConfig {
return buildManifestModelProviderConfig({
providerId: "longcat",
catalog: manifest.modelCatalog.providers.longcat,
});
}

View file

@ -0,0 +1,20 @@
// LongCat stream helpers apply the provider's binary thinking control.
import type { ProviderWrapStreamFnContext } from "openclaw/plugin-sdk/plugin-entry";
import { createPayloadPatchStreamWrapper } from "openclaw/plugin-sdk/provider-stream-shared";
export function createLongCatThinkingWrapper(
baseStreamFn: ProviderWrapStreamFnContext["streamFn"],
thinkingLevel: ProviderWrapStreamFnContext["thinkingLevel"],
): NonNullable<ProviderWrapStreamFnContext["streamFn"]> {
return createPayloadPatchStreamWrapper(
baseStreamFn,
({ payload }) => {
payload.thinking = { type: thinkingLevel === "off" ? "disabled" : "enabled" };
delete payload.reasoning_effort;
},
{
shouldPatch: ({ model }) =>
model.api === "openai-completions" && model.provider === "longcat",
},
);
}

View file

@ -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"
]
}

View file

@ -112,6 +112,7 @@
"!dist/extensions/line/**",
"!dist/extensions/llama-cpp/**",
"!dist/extensions/lobster/**",
"!dist/extensions/longcat/**",
"!dist/extensions/mattermost/**",
"!dist/extensions/memory-lancedb/**",
"!dist/extensions/matrix/**",

View file

@ -24,8 +24,9 @@ const mockChunksRef: {
chunks: OpenAICompatibleChatCompletionChunk[];
stream?: AsyncIterable<OpenAICompatibleChatCompletionChunk>;
} = { chunks: [] };
const mockOpenAIOptionsRef: { options: unknown[]; requests: unknown[] } = {
const mockOpenAIOptionsRef: { options: unknown[]; payloads: unknown[]; requests: unknown[] } = {
options: [],
payloads: [],
requests: [],
};
@ -37,7 +38,8 @@ vi.mock("openai", () => {
chat = {
completions: {
create: (_params: unknown, requestOptions: unknown) => {
create: (params: unknown, requestOptions: unknown) => {
mockOpenAIOptionsRef.payloads.push(params);
mockOpenAIOptionsRef.requests.push(requestOptions);
return {
withResponse: async () => {
@ -70,6 +72,7 @@ import { streamOpenAICompletions, streamSimpleOpenAICompletions } from "./openai
beforeEach(() => {
mockChunksRef.chunks = [];
mockChunksRef.stream = undefined;
mockOpenAIOptionsRef.payloads = [];
mockOpenAIOptionsRef.requests = [];
});
@ -161,6 +164,30 @@ function createNeverYieldingStream(): AsyncIterable<OpenAICompatibleChatCompleti
}
describe("OpenAI-compatible completions params", () => {
it("omits reasoning_effort when deepseek-format compatibility disables it", async () => {
mockChunksRef.chunks = [makeTextChunk("ok"), makeFinishChunk("stop")];
const compatibleModel = {
...reasoningModel,
provider: "longcat",
baseUrl: "https://api.longcat.chat/openai",
compat: {
thinkingFormat: "deepseek" as const,
supportsReasoningEffort: false,
},
} satisfies Model<"openai-completions">;
const stream = streamOpenAICompletions(compatibleModel, context, {
apiKey: "sk-test",
reasoningEffort: "high",
});
await stream.result();
expect(mockOpenAIOptionsRef.payloads[0]).toMatchObject({
thinking: { type: "enabled" },
});
expect(mockOpenAIOptionsRef.payloads[0]).not.toHaveProperty("reasoning_effort");
});
it("configures the OpenAI SDK client with the host-built model fetch", async () => {
mockOpenAIOptionsRef.options = [];
mockChunksRef.chunks = [makeTextChunk("ok"), makeFinishChunk("stop")];

View file

@ -755,7 +755,7 @@ function buildParams(
};
} else if (compat.thinkingFormat === "deepseek" && model.reasoning) {
params.thinking = { type: options?.reasoningEffort ? "enabled" : "disabled" };
if (options?.reasoningEffort) {
if (options?.reasoningEffort && compat.supportsReasoningEffort) {
params.reasoning_effort =
model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort;
}

View file

@ -67,6 +67,7 @@ describe("model catalog normalization", () => {
sendSessionIdHeader: false,
supportsEagerToolInputStreaming: false,
supportsLongCacheRetention: true,
requiresReasoningContentOnAssistantMessages: true,
supportsStore: "yes",
thinkingFormat: "together",
unknownFlag: true,
@ -163,6 +164,7 @@ describe("model catalog normalization", () => {
sendSessionIdHeader: false,
supportsEagerToolInputStreaming: false,
supportsLongCacheRetention: true,
requiresReasoningContentOnAssistantMessages: true,
thinkingFormat: "together",
},
status: "preview",

View file

@ -383,6 +383,7 @@ function normalizeModelCatalogCompat(value: unknown): ModelCatalogCompatConfig |
"requiresToolResultName",
"requiresAssistantAfterToolResult",
"requiresThinkingAsText",
"requiresReasoningContentOnAssistantMessages",
"zaiToolStream",
"sendSessionAffinityHeaders",
"sendSessionIdHeader",

View file

@ -47,6 +47,7 @@ export type ModelCatalogCompatConfig = {
requiresToolResultName?: boolean;
requiresAssistantAfterToolResult?: boolean;
requiresThinkingAsText?: boolean;
requiresReasoningContentOnAssistantMessages?: boolean;
openRouterRouting?: ModelCatalogOpenRouterRouting;
vercelGatewayRouting?: ModelCatalogVercelGatewayRouting;
zaiToolStream?: boolean;

6
pnpm-lock.yaml generated
View file

@ -969,6 +969,12 @@ importers:
extensions/lmstudio: {}
extensions/longcat:
devDependencies:
'@openclaw/plugin-sdk':
specifier: workspace:*
version: link:../../packages/plugin-sdk
extensions/lobster:
dependencies:
'@clawdbot/lobster':

View file

@ -131,6 +131,7 @@ function humanizeId(value) {
["litellm", "LiteLLM"],
["llm", "LLM"],
["lmstudio", "LM Studio"],
["longcat", "LongCat"],
["mdns", "mDNS"],
["minimax", "MiniMax"],
["modelstudio", "Model Studio"],

View file

@ -589,6 +589,49 @@
}
}
},
{
"name": "@openclaw/longcat-provider",
"description": "OpenClaw LongCat provider plugin",
"source": "official",
"kind": "provider",
"openclaw": {
"plugin": {
"id": "longcat",
"label": "LongCat"
},
"providers": [
{
"id": "longcat",
"aliases": ["meituan-longcat"],
"name": "LongCat",
"docs": "/providers/longcat",
"categories": ["cloud", "llm"],
"envVars": ["LONGCAT_API_KEY"],
"authChoices": [
{
"method": "api-key",
"choiceId": "longcat-api-key",
"choiceLabel": "LongCat API key",
"groupId": "longcat",
"groupLabel": "LongCat",
"groupHint": "OpenAI-compatible LongCat API",
"optionKey": "longcatApiKey",
"cliFlag": "--longcat-api-key",
"cliOption": "--longcat-api-key <key>",
"cliDescription": "LongCat API key",
"onboardingScopes": ["text-inference"]
}
]
}
],
"install": {
"clawhubSpec": "clawhub:@openclaw/longcat-provider",
"npmSpec": "@openclaw/longcat-provider",
"defaultChoice": "npm",
"minHostVersion": ">=2026.6.8"
}
}
},
{
"name": "@openclaw/groq-provider",
"description": "OpenClaw Groq media-understanding provider.",

View file

@ -1632,6 +1632,7 @@ describe("official external plugin catalog", () => {
["deepinfra", "@openclaw/deepinfra-provider"],
["deepseek", "@openclaw/deepseek-provider"],
["groq", "@openclaw/groq-provider"],
["longcat", "@openclaw/longcat-provider"],
["kilocode", "@openclaw/kilocode-provider"],
["kimi", "@openclaw/kimi-provider"],
["qianfan", "@openclaw/qianfan-provider"],
@ -1752,6 +1753,19 @@ describe("official external plugin catalog", () => {
});
});
it("lists LongCat as an official external provider", () => {
const longcat = expectCatalogEntry("longcat");
expect(resolveOfficialExternalPluginId(longcat)).toBe("longcat");
expect(getOfficialExternalPluginCatalogEntry("meituan-longcat")).toBe(longcat);
expect(resolveOfficialExternalPluginInstall(longcat)).toEqual({
clawhubSpec: "clawhub:@openclaw/longcat-provider",
npmSpec: "@openclaw/longcat-provider",
defaultChoice: "npm",
minHostVersion: ">=2026.6.8",
});
});
it("resolves external provider aliases beyond the primary provider id", () => {
const qwen = expectCatalogEntry("qwen");
@ -1814,6 +1828,7 @@ describe("official external plugin catalog", () => {
DEEPINFRA_API_KEY: "deepinfra-key",
DEEPSEEK_API_KEY: "deepseek-key",
GROQ_API_KEY: "groq-key",
LONGCAT_API_KEY: "longcat-key",
KILOCODE_API_KEY: "kilocode-key",
KIMICODE_API_KEY: "kimi-key",
KIMI_API_KEY: "moonshot-kimi-key",
@ -1839,6 +1854,7 @@ describe("official external plugin catalog", () => {
"groq",
"kilocode",
"kimi",
"longcat",
"moonshot",
"qianfan",
"qwen",
@ -1849,6 +1865,7 @@ describe("official external plugin catalog", () => {
"zai",
]);
expect(resolveOfficialExternalProviderPluginIdsForEnv({ GROQ_API_KEY: " " })).toEqual([]);
expect(resolveOfficialExternalProviderPluginIdsForEnv({ LONGCAT_API_KEY: " " })).toEqual([]);
});
it("keeps Tencent auth choices available through the cold-install auth catalog", () => {