manifest-provider 5.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +50 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +7 -0
- package/dist/openclaw.plugin.json +40 -0
- package/openclaw.plugin.json +40 -0
- package/package.json +72 -0
- package/skills/manifest/SKILL.md +246 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MNFST Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# manifest-provider
|
|
2
|
+
|
|
3
|
+
Cloud provider plugin for [Manifest](https://manifest.build) — the smart LLM router for [OpenClaw](https://openclaw.ai).
|
|
4
|
+
|
|
5
|
+
Picks the best model for each request based on a multi-dimension complexity score, balancing quality, speed, and cost automatically. Supports 10+ providers with tier-based routing and fallbacks.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
openclaw plugins install manifest-provider
|
|
11
|
+
openclaw providers setup manifest-provider
|
|
12
|
+
openclaw gateway restart
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The setup wizard prompts for your API key from [app.manifest.build](https://app.manifest.build). After setup, use `manifest/auto` as your model.
|
|
16
|
+
|
|
17
|
+
You can also set the key via environment variable for CI/CD: `export MANIFEST_API_KEY=mnfst_...`
|
|
18
|
+
|
|
19
|
+
## What it does
|
|
20
|
+
|
|
21
|
+
- Registers Manifest as a provider in OpenClaw with the `auto` model
|
|
22
|
+
- Interactive auth onboarding via `openclaw providers setup manifest-provider`
|
|
23
|
+
- Agent tools: `manifest_usage`, `manifest_costs`, `manifest_health`
|
|
24
|
+
- `/manifest` status command
|
|
25
|
+
- Subscription provider discovery (detects existing OAuth providers)
|
|
26
|
+
|
|
27
|
+
## Self-hosted / Local mode
|
|
28
|
+
|
|
29
|
+
For a self-hosted server with SQLite and a local dashboard, install the full package instead:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
openclaw plugins install manifest
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
See the [manifest](https://www.npmjs.com/package/manifest) package.
|
|
36
|
+
|
|
37
|
+
## Contributing
|
|
38
|
+
|
|
39
|
+
This package lives at `packages/openclaw-plugins/manifest-provider/` in the [mnfst/manifest](https://github.com/mnfst/manifest) monorepo.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm run build --workspace=packages/openclaw-plugins/manifest-provider # esbuild bundle
|
|
43
|
+
npm test --workspace=packages/openclaw-plugins/manifest-provider # Jest tests
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Links
|
|
47
|
+
|
|
48
|
+
- [Dashboard](https://app.manifest.build)
|
|
49
|
+
- [Documentation](https://docs.manifest.build)
|
|
50
|
+
- [GitHub](https://github.com/mnfst/manifest)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/* manifest — OpenClaw LLM Router Plugin */
|
|
2
|
+
"use strict";var API_KEY_PREFIX="mnfst_";var SUBSCRIPTION_PROVIDER_CONFIGS=Object.freeze({anthropic:Object.freeze({supportsSubscription:!0,subscriptionLabel:"Claude Max / Pro subscription",subscriptionAuthMode:"token",subscriptionKeyPlaceholder:"Paste your setup-token",subscriptionCommand:"claude setup-token",subscriptionTokenPrefix:"sk-ant-oat",knownModels:Object.freeze(["claude-opus-4","claude-sonnet-4","claude-haiku-4"]),subscriptionCapabilities:Object.freeze({maxContextWindow:2e5,supportsPromptCaching:!1,supportsBatching:!1})}),openai:Object.freeze({supportsSubscription:!0,subscriptionLabel:"ChatGPT Plus/Pro/Team",subscriptionAuthMode:"popup_oauth",subscriptionOAuth:!0,knownModels:Object.freeze(["gpt-5.4","gpt-5.3-codex","gpt-5.2-codex","gpt-5.2","gpt-5.1-codex-max","gpt-5.1-codex"]),subscriptionCapabilities:Object.freeze({maxContextWindow:2e5,supportsPromptCaching:!1,supportsBatching:!1})}),minimax:Object.freeze({supportsSubscription:!0,subscriptionLabel:"MiniMax Coding Plan",subscriptionAuthMode:"device_code",knownModels:Object.freeze(["MiniMax-M2.5","MiniMax-M2.5-highspeed","MiniMax-M2.1","MiniMax-M2.1-highspeed","MiniMax-M2"]),subscriptionCapabilities:Object.freeze({maxContextWindow:2e5,supportsPromptCaching:!1,supportsBatching:!1})}),copilot:Object.freeze({supportsSubscription:!0,subscriptionLabel:"GitHub Copilot subscription",subscriptionAuthMode:"device_code",knownModels:Object.freeze(["copilot/claude-opus-4.6","copilot/claude-opus-4.6-fast","copilot/claude-sonnet-4.6","copilot/claude-haiku-4.5","copilot/gpt-5.4","copilot/gpt-5.2-codex","copilot/gpt-5-mini","copilot/gpt-4.1","copilot/gpt-4o","copilot/gpt-4o-mini","copilot/gemini-3.1-pro-preview","copilot/grok-code-fast-1"]),subscriptionCapabilities:Object.freeze({maxContextWindow:2e5,supportsPromptCaching:!1,supportsBatching:!1})})}),SUPPORTED_SUBSCRIPTION_PROVIDER_IDS=Object.freeze(Object.keys(SUBSCRIPTION_PROVIDER_CONFIGS));function normalizeProviderId(providerId){return String(providerId||"").toLowerCase()}function getSubscriptionProviderConfig(providerId){return SUBSCRIPTION_PROVIDER_CONFIGS[normalizeProviderId(providerId)]??null}function supportsSubscriptionProvider(providerId){return getSubscriptionProviderConfig(providerId)!==null}var ENV={API_KEY:"MANIFEST_API_KEY",ENDPOINT:"MANIFEST_ENDPOINT"},DEFAULTS={ENDPOINT:"https://app.manifest.build",SERVICE_NAME:"openclaw-gateway"};function isLoopback(endpoint){try{let host=new URL(endpoint).hostname;return host==="localhost"||host==="127.0.0.1"||host==="::1"||host==="[::1]"}catch{return!1}}function parseConfigWithDeprecation(raw){let obj=raw&&typeof raw=="object"&&!Array.isArray(raw)?raw:{};obj.config&&typeof obj.config=="object"&&!Array.isArray(obj.config)&&(obj=obj.config);let _deprecatedDevMode=!1,mode;obj.mode==="local"?mode="local":obj.mode==="dev"?(mode="cloud",_deprecatedDevMode=!0):mode="cloud";let apiKey=typeof obj.apiKey=="string"&&obj.apiKey.length>0?obj.apiKey:process.env[ENV.API_KEY]||"",envEndpoint=process.env[ENV.ENDPOINT],endpoint=typeof obj.endpoint=="string"&&obj.endpoint.length>0?obj.endpoint:envEndpoint&&envEndpoint.length>0?envEndpoint:DEFAULTS.ENDPOINT,port=typeof obj.port=="number"&&obj.port>0?obj.port:2099,host=typeof obj.host=="string"&&obj.host.length>0?obj.host:"127.0.0.1",devMode;return typeof obj.devMode=="boolean"?devMode=obj.devMode:_deprecatedDevMode?devMode=!0:devMode=isLoopback(endpoint)&&!apiKey.startsWith(API_KEY_PREFIX),{config:{mode,devMode,apiKey,endpoint,port,host},_deprecatedDevMode}}function validateConfig(config){return config.mode==="local"?null:config.devMode?config.endpoint.startsWith("http")?null:`Invalid endpoint URL '${config.endpoint}'. Must start with http:// or https://. Fix it via:
|
|
3
|
+
openclaw config set plugins.entries.manifest.config.endpoint http://localhost:<PORT>`:config.apiKey?config.apiKey.startsWith(API_KEY_PREFIX)?config.endpoint.startsWith("http")?null:`Invalid endpoint URL '${config.endpoint}'. Must start with http:// or https://. Fix it via:
|
|
4
|
+
openclaw config set plugins.entries.manifest.config.endpoint https://app.manifest.build
|
|
5
|
+
|
|
6
|
+
Or run the setup wizard:
|
|
7
|
+
openclaw providers setup manifest`:`Invalid apiKey format. Keys must start with '${API_KEY_PREFIX}'. Fix it via:
|
|
8
|
+
openclaw config set plugins.entries.manifest.config.apiKey ${API_KEY_PREFIX}YOUR_KEY`:`Missing apiKey. Set it via:
|
|
9
|
+
openclaw config set plugins.entries.manifest.config.apiKey ${API_KEY_PREFIX}YOUR_KEY
|
|
10
|
+
or export MANIFEST_API_KEY=${API_KEY_PREFIX}YOUR_KEY`}async function verifyConnection(config){let baseUrl=config.endpoint.replace(/\/otlp(\/v1)?\/?$/,""),result={endpointReachable:!1,authValid:!1,agentName:null,error:null};try{let healthRes=await fetch(`${baseUrl}/api/v1/health`,{signal:AbortSignal.timeout(5e3)});if(!healthRes.ok)return result.error=`Health endpoint returned ${healthRes.status}`,result;result.endpointReachable=!0}catch(err){let msg=err instanceof Error?err.message:String(err);return result.error=`Cannot reach endpoint: ${msg}`,result}try{let authHeaders=config.apiKey?{Authorization:`Bearer ${config.apiKey}`}:{},usageRes=await fetch(`${baseUrl}/api/v1/agent/usage?range=24h`,{headers:authHeaders,signal:AbortSignal.timeout(5e3)});if(usageRes.status===401||usageRes.status===403)return result.error="API key rejected. Re-run setup: openclaw providers setup manifest",result;if(!usageRes.ok)return result.error=`Usage endpoint returned ${usageRes.status}`,result;result.authValid=!0;let body=await usageRes.json();body&&typeof body.agentName=="string"&&(result.agentName=body.agentName)}catch(err){let msg=err instanceof Error?err.message:String(err);return result.error=`Auth check failed: ${msg}`,result}return result}var RANGE_MAP={today:"24h",week:"7d",month:"30d"};async function callApi(baseUrl,path,apiKey,logger){let url=`${baseUrl}${path}`;try{let headers=apiKey?{Authorization:`Bearer ${apiKey}`}:{},res=await fetch(url,{headers});if(!res.ok)return{content:[{type:"text",text:JSON.stringify({error:`API returned ${res.status}`})}]};let data=await res.json();return{content:[{type:"text",text:JSON.stringify(data)}]}}catch(err){let msg=err instanceof Error?err.message:String(err);return logger.error(`[manifest] API call failed: ${msg}`),{content:[{type:"text",text:JSON.stringify({error:msg})}]}}}function toLegacy(r){try{let parsed=JSON.parse(r.content[0].text);return parsed.error?{error:parsed.error}:{result:parsed}}catch{return{result:r.content[0].text}}}function registerTools(api,config,logger){let baseUrl=config.endpoint.replace(/\/otlp(\/v1)?\/?$/,"");api.registerTool({name:"manifest_usage",description:"Get token consumption for this agent: total, input, output, cache-read tokens, and action count. Use when the user asks about token usage or consumption.",parameters:{type:"object",properties:{period:{type:"string",enum:["today","week","month"],default:"today",description:"Time period"}}},async handler(params){let range=RANGE_MAP[params.period||"today"]||"24h";return toLegacy(await callApi(baseUrl,`/api/v1/agent/usage?range=${range}`,config.apiKey,logger))},async execute(_id,params){let range=RANGE_MAP[params.period||"today"]||"24h";return callApi(baseUrl,`/api/v1/agent/usage?range=${range}`,config.apiKey,logger)}},{optional:!0}),api.registerTool({name:"manifest_costs",description:"Get cost breakdown for this agent in USD, grouped by model. Use when the user asks about costs, spending, or money burned.",parameters:{type:"object",properties:{period:{type:"string",enum:["today","week","month"],default:"week",description:"Time period"}}},async handler(params){let range=RANGE_MAP[params.period||"week"]||"7d";return toLegacy(await callApi(baseUrl,`/api/v1/agent/costs?range=${range}`,config.apiKey,logger))},async execute(_id,params){let range=RANGE_MAP[params.period||"week"]||"7d";return callApi(baseUrl,`/api/v1/agent/costs?range=${range}`,config.apiKey,logger)}},{optional:!0}),api.registerTool({name:"manifest_health",description:"Check whether Manifest routing is connected and working. Use when the user asks if routing is set up or wants a connectivity test.",parameters:{type:"object",properties:{}},async handler(){let check=await verifyConnection(config);return check.error?{error:check.error}:{result:{endpointReachable:check.endpointReachable,authValid:check.authValid,agentName:check.agentName,status:"ok"}}},async execute(){let check=await verifyConnection(config);return check.error?{content:[{type:"text",text:JSON.stringify({error:check.error})}]}:{content:[{type:"text",text:JSON.stringify({endpointReachable:check.endpointReachable,authValid:check.authValid,agentName:check.agentName,status:"ok"})}]}}},{optional:!0}),logger.debug("[manifest] Registered agent tools: manifest_usage, manifest_costs, manifest_health")}function registerCommand(api,config,logger){if(typeof api.registerCommand!="function"){logger.debug("[manifest] registerCommand not available, skipping /manifest command");return}let formatStatus=async()=>{try{let check=await verifyConnection(config),lines=[`Mode: ${config.mode}`,`Dev mode: ${config.devMode?"yes":"no"}`,`Endpoint reachable: ${check.endpointReachable?"yes":"no"}`,`Auth valid: ${check.authValid?"yes":"no"}`];return check.agentName&&lines.push(`Agent: ${check.agentName}`),check.error&&lines.push(`Error: ${check.error}`),lines.join(`
|
|
11
|
+
`)}catch(err){return`Manifest status check failed: ${err instanceof Error?err.message:String(err)}`}};api.registerCommand({name:"manifest",description:"Show Manifest plugin status and connection info",async handler(){return formatStatus()},async execute(){let text=await formatStatus();return{text,content:[{type:"text",text}]}}}),logger.debug("[manifest] Registered /manifest command")}var import_fs2=require("fs"),import_path=require("path"),import_os=require("os");var import_fs=require("fs");function loadJsonFile(path){if(!(0,import_fs.existsSync)(path))return{};try{let parsed=JSON.parse((0,import_fs.readFileSync)(path,"utf-8"));return parsed&&typeof parsed=="object"&&!Array.isArray(parsed)?parsed:(console.warn(`[manifest] Invalid JSON object in ${path}; expected a top-level object`),{})}catch(err){let msg=err instanceof Error?err.message:String(err);return console.warn(`[manifest] Failed to read JSON file ${path}: ${msg}`),{}}}var OPENCLAW_DIR=(0,import_path.join)((0,import_os.homedir)(),".openclaw"),OPENCLAW_CONFIG=(0,import_path.join)(OPENCLAW_DIR,"openclaw.json");function atomicWriteJson(path,data){let dir=(0,import_path.dirname)(path);(0,import_fs2.existsSync)(dir)||(0,import_fs2.mkdirSync)(dir,{recursive:!0,mode:448});let tmp=`${path}.tmp.${process.pid}`;(0,import_fs2.writeFileSync)(tmp,JSON.stringify(data,null,2),{mode:384}),(0,import_fs2.renameSync)(tmp,path)}function injectProviderConfig(api,baseUrl,apiKey,logger){let providerConfig={baseUrl,api:"openai-completions",apiKey,models:[{id:"auto",name:"auto"}]};try{let config=loadJsonFile(OPENCLAW_CONFIG);config.models||(config.models={}),config.models.providers||(config.models.providers={}),config.models.providers.manifest=providerConfig,config.agents||(config.agents={}),config.agents.defaults||(config.agents.defaults={}),config.agents.defaults.models||(config.agents.defaults.models={});let models=config.agents.defaults.models;Array.isArray(models)?models.includes("manifest/auto")||models.push("manifest/auto"):typeof models=="object"&&("manifest/auto"in models||(models["manifest/auto"]={})),atomicWriteJson(OPENCLAW_CONFIG,config),logger.debug("[manifest] Wrote provider config to openclaw.json")}catch(err){let msg=err instanceof Error?err.message:String(err);logger.debug(`[manifest] Could not write openclaw.json: ${msg}`)}try{let agentsDir=(0,import_path.join)(OPENCLAW_DIR,"agents");if((0,import_fs2.existsSync)(agentsDir)){let agentDirs=(0,import_fs2.readdirSync)(agentsDir,{withFileTypes:!0}).filter(d=>d.isDirectory());for(let dir of agentDirs){let modelsPath=(0,import_path.join)(agentsDir,dir.name,"agent","models.json");if(!(0,import_fs2.existsSync)(modelsPath))continue;let data=loadJsonFile(modelsPath);data.providers?.manifest&&(delete data.providers.manifest,atomicWriteJson(modelsPath,data),logger.debug(`[manifest] Removed stale manifest entry from models.json for agent ${dir.name}`))}}}catch(err){let msg=err instanceof Error?err.message:String(err);logger.debug(`[manifest] Could not clean agent models.json: ${msg}`)}try{if(api.config){api.config.models||(api.config.models={}),api.config.models.providers||(api.config.models.providers={}),api.config.models.providers.manifest=providerConfig,api.config.agents||(api.config.agents={}),api.config.agents.defaults||(api.config.agents.defaults={}),api.config.agents.defaults.models||(api.config.agents.defaults.models={});let rtModels=api.config.agents.defaults.models;Array.isArray(rtModels)?rtModels.includes("manifest/auto")||rtModels.push("manifest/auto"):typeof rtModels=="object"&&("manifest/auto"in rtModels||(rtModels["manifest/auto"]={}))}logger.debug("[manifest] Injected provider into runtime config")}catch(err){let msg=err instanceof Error?err.message:String(err);logger.debug(`[manifest] Could not inject runtime config: ${msg}`)}}function injectAuthProfile(apiKey,logger){let agentsDir=(0,import_path.join)(OPENCLAW_DIR,"agents");if(!(0,import_fs2.existsSync)(agentsDir)){logger.debug("[manifest] No agents directory found, skipping auth profile injection");return}let profileEntry={type:"api_key",provider:"manifest",key:apiKey},injected=0;try{let agentDirs=(0,import_fs2.readdirSync)(agentsDir,{withFileTypes:!0}).filter(d=>d.isDirectory());for(let dir of agentDirs){let profilePath=(0,import_path.join)(agentsDir,dir.name,"agent","auth-profiles.json"),profileDir=(0,import_path.dirname)(profilePath);if(!(0,import_fs2.existsSync)(profileDir))continue;let data=loadJsonFile(profilePath);data.version||(data.version=1),data.profiles||(data.profiles={});let existing=data.profiles["manifest:default"];existing&&existing.key===apiKey||(data.profiles["manifest:default"]=profileEntry,atomicWriteJson(profilePath,data),injected++)}}catch(err){let msg=err instanceof Error?err.message:String(err);logger.debug(`[manifest] Auth profile injection error: ${msg}`)}injected>0&&logger.debug(`[manifest] Injected auth profile into ${injected} agent(s)`)}var import_fs3=require("fs"),import_path2=require("path"),import_os2=require("os");var OPENCLAW_DIR2=(0,import_path2.join)((0,import_os2.homedir)(),".openclaw");function getFetch(){return globalThis.fetch??null}function getAbortSignalTimeout(ms){return globalThis.AbortSignal?.timeout?.(ms)}var OPENCLAW_TO_MANIFEST={anthropic:"anthropic","openai-codex":"openai",openai:"openai","google-gemini":"gemini","google-antigravity":"gemini",google:"gemini",gemini:"gemini","github-copilot":"copilot",qwen:"qwen","qwen-portal":"qwen",moonshot:"moonshot",kimi:"moonshot",minimax:"minimax","minimax-portal":"minimax"};function discoverSubscriptionProviders(logger){let agentsDir=(0,import_path2.join)(OPENCLAW_DIR2,"agents");if(!(0,import_fs3.existsSync)(agentsDir))return logger.debug("[manifest] No agents directory, no subscription providers"),[];let seen=new Map;try{let agentDirs=(0,import_fs3.readdirSync)(agentsDir,{withFileTypes:!0}).filter(d=>d.isDirectory());for(let dir of agentDirs){let profilePath=(0,import_path2.join)(agentsDir,dir.name,"agent","auth-profiles.json"),data=loadJsonFile(profilePath);if(!(!data.profiles||typeof data.profiles!="object"))for(let entry of Object.values(data.profiles)){let profile=entry;if(profile.type==="api_key"||profile.provider==="manifest")continue;let manifestId=OPENCLAW_TO_MANIFEST[profile.provider?.toLowerCase()??""];if(!manifestId){logger.debug(`[manifest] Unknown subscription provider: ${profile.provider}`);continue}if(!supportsSubscriptionProvider(manifestId)){logger.debug(`[manifest] Ignoring unsupported subscription provider: ${profile.provider} -> ${manifestId}`);continue}if(!seen.has(manifestId)){let token=manifestId==="copilot"?profile.access_token??profile.key:void 0;seen.set(manifestId,{openclawId:profile.provider,manifestId,authType:profile.type,...token&&{token}})}}}}catch(err){let msg=err instanceof Error?err.message:String(err);logger.debug(`[manifest] Error scanning auth profiles: ${msg}`)}let providers=Array.from(seen.values());return providers.length>0&&logger.info(`[manifest] Detected ${providers.length} subscription provider(s): ${providers.map(p=>p.manifestId).join(", ")}`),providers}async function registerSubscriptionProviders(providers,endpoint,apiKey,logger){if(providers.length===0)return;let url=`${endpoint.replace(/\/otlp(\/v1)?\/?$/,"")}/api/v1/routing/subscription-providers`;try{let fetchImpl=getFetch();if(!fetchImpl){logger.debug("[manifest] Global fetch is not available");return}let headers={"Content-Type":"application/json"};apiKey&&(headers.Authorization=`Bearer ${apiKey}`);let res=await fetchImpl(url,{method:"POST",headers,body:JSON.stringify({providers:providers.map(p=>({provider:p.manifestId,...p.token&&{token:p.token}}))}),signal:getAbortSignalTimeout(5e3)});if(res.ok){let data=await res.json();logger.info(`[manifest] Registered ${data.registered} subscription provider(s)`)}else logger.debug(`[manifest] Failed to register subscription providers: ${res.status}`)}catch(err){let msg=err instanceof Error?err.message:String(err);logger.debug(`[manifest] Error registering subscription providers: ${msg}`)}}function stripOtlpSuffix(endpoint,logger){let cleaned=endpoint.replace(/\/otlp(\/v1)?\/?$/,"");return cleaned!==endpoint&&logger.warn?.(`[manifest] Endpoint "${endpoint}" contains a deprecated /otlp suffix.
|
|
12
|
+
The endpoint should now be the base URL (e.g. "${cleaned}").
|
|
13
|
+
Update your config:
|
|
14
|
+
openclaw config set plugins.entries.manifest.config.endpoint ${cleaned}`),cleaned}var AUTO_MODEL={id:"auto",name:"Auto Router",reasoning:!1,input:["text"],cost:{input:0,output:0,cacheRead:0,cacheWrite:0},contextWindow:2e5,maxTokens:16384};async function runApiKeyAuth(ctx){await ctx.prompter.intro("Manifest \u2014 Smart LLM Router"),await ctx.prompter.note(`Manifest analyzes each request and picks the best
|
|
15
|
+
model from your connected providers \u2014 balancing
|
|
16
|
+
quality, speed, and cost automatically.
|
|
17
|
+
|
|
18
|
+
Get your API key at https://app.manifest.build`,"About Manifest");let trimmedKey=(await ctx.prompter.text({message:"Enter your Manifest API key",placeholder:`${API_KEY_PREFIX}...`,validate:v=>{let trimmed=v?.trim()??"";if(trimmed.length===0)return"API key is required";if(!trimmed.startsWith(API_KEY_PREFIX))return`Key must start with '${API_KEY_PREFIX}'`}})).trim();return await ctx.prompter.outro("Manifest configured! Use model manifest/auto in your agent config.\nRun `openclaw gateway restart` to activate."),{profiles:[{profileId:"manifest:default",credential:{type:"api_key",provider:"manifest",key:trimmedKey}}],configPatch:{models:{providers:{manifest:{baseUrl:`${DEFAULTS.ENDPOINT}/v1`,api:"openai-completions",models:[AUTO_MODEL]}}}},defaultModel:"manifest/auto"}}function buildModelConfig(baseUrl){return{baseUrl:`${baseUrl}/v1`,api:"openai-completions",models:[AUTO_MODEL]}}module.exports={id:"manifest-provider",name:"Manifest Provider \u2014 Smart LLM Router",register(api){let logger=api.logger||{info:(...args)=>console.log(...args),debug:()=>{},error:(...args)=>console.error(...args),warn:(...args)=>console.warn(...args)},{config,_deprecatedDevMode}=parseConfigWithDeprecation(api.pluginConfig);if(_deprecatedDevMode&&logger.warn?.(`[manifest] mode: "dev" is deprecated. Use mode: "cloud" with devMode: true instead.
|
|
19
|
+
openclaw config set plugins.entries.manifest.config.mode cloud
|
|
20
|
+
openclaw config set plugins.entries.manifest.config.devMode true`),registerProvider(api,config.endpoint,logger),config.mode==="local"){logger.info(`[manifest-provider] Local mode requires the manifest plugin.
|
|
21
|
+
Install it with: openclaw plugins install manifest
|
|
22
|
+
Then restart: openclaw gateway restart`);return}let error=validateConfig(config);if(error){!config.devMode&&config.mode==="cloud"&&!config.apiKey?logger.info(`[manifest] Cloud mode requires an API key.
|
|
23
|
+
|
|
24
|
+
Run the setup wizard:
|
|
25
|
+
openclaw providers setup manifest
|
|
26
|
+
|
|
27
|
+
Or set your key manually:
|
|
28
|
+
openclaw config set plugins.entries.manifest.config.apiKey mnfst_YOUR_KEY
|
|
29
|
+
openclaw gateway restart`):logger.error(`[manifest] Configuration error:
|
|
30
|
+
${error}`);return}let baseOrigin=stripOtlpSuffix(config.endpoint,logger),effectiveKey=config.devMode?"dev-no-auth":config.apiKey;logger.info(config.devMode?"[manifest] Dev mode \u2014 connecting to external server...":"[manifest] Initializing routing..."),injectProviderConfig(api,`${baseOrigin}/v1`,effectiveKey,logger),injectAuthProfile(effectiveKey,logger),typeof api.registerTool=="function"?registerTools(api,config,logger):config.devMode||logger.info("[manifest] Agent tools not available in this OpenClaw version"),registerCommand(api,config,logger),config.devMode&&logger.info(`[manifest] Dashboard: ${baseOrigin}`);let subscriptions=discoverSubscriptionProviders(logger);api.registerService({id:"manifest-routing",start:()=>{logger.info(config.devMode?"[manifest] Dev mode routing active":"[manifest] Routing active"),logger.info(`[manifest] Endpoint=${config.endpoint}`),verifyConnection(config).then(check=>{if(check.error){logger.warn?.(`[manifest] Connection check failed: ${check.error}`);return}let agent=check.agentName?` (agent: ${check.agentName})`:"";logger.info(`[manifest] Connection verified${agent}`)}).catch(()=>{}),registerSubscriptionProviders(subscriptions,config.endpoint,effectiveKey,logger).catch(()=>{})}})}};function registerProvider(api,endpoint,logger){if(typeof api.registerProvider!="function"){logger.debug("[manifest] registerProvider not available, skipping provider registration");return}try{api.registerProvider({id:"manifest",label:"Manifest Router",envVars:[ENV.API_KEY],auth:[{id:"api-key",label:"Manifest API Key",hint:"Get your key at https://app.manifest.build",kind:"api_key",run:runApiKeyAuth}],models:buildModelConfig(stripOtlpSuffix(endpoint,{info:()=>{},error:()=>{},debug:()=>{}}))}),logger.info("[manifest] Registered as provider (model: manifest/auto)")}catch(err){let msg=err instanceof Error?err.message:String(err);logger.debug(`[manifest] registerProvider failed (${msg})`)}}
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../shared/src/api-key.ts", "../../../shared/src/subscription/configs.ts", "../../../shared/src/subscription/helpers.ts", "../src/constants.ts", "../src/config.ts", "../src/verify.ts", "../src/tools.ts", "../src/command.ts", "../src/provider-inject.ts", "../src/json-file.ts", "../src/subscription.ts", "../src/compat.ts", "../src/auth.ts", "../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["export const API_KEY_PREFIX = 'mnfst_' as const;\n", "import type { SubscriptionProviderConfig } from './types';\n\nexport const SUBSCRIPTION_PROVIDER_CONFIGS: Readonly<\n Record<string, Readonly<SubscriptionProviderConfig>>\n> = Object.freeze({\n anthropic: Object.freeze({\n supportsSubscription: true as const,\n subscriptionLabel: 'Claude Max / Pro subscription',\n subscriptionAuthMode: 'token' as const,\n subscriptionKeyPlaceholder: 'Paste your setup-token',\n subscriptionCommand: 'claude setup-token',\n subscriptionTokenPrefix: 'sk-ant-oat',\n knownModels: Object.freeze(['claude-opus-4', 'claude-sonnet-4', 'claude-haiku-4']),\n subscriptionCapabilities: Object.freeze({\n maxContextWindow: 200000,\n supportsPromptCaching: false,\n supportsBatching: false,\n }),\n }),\n openai: Object.freeze({\n supportsSubscription: true as const,\n subscriptionLabel: 'ChatGPT Plus/Pro/Team',\n subscriptionAuthMode: 'popup_oauth' as const,\n subscriptionOAuth: true,\n knownModels: Object.freeze([\n 'gpt-5.4',\n 'gpt-5.3-codex',\n 'gpt-5.2-codex',\n 'gpt-5.2',\n 'gpt-5.1-codex-max',\n 'gpt-5.1-codex',\n ]),\n subscriptionCapabilities: Object.freeze({\n maxContextWindow: 200000,\n supportsPromptCaching: false,\n supportsBatching: false,\n }),\n }),\n minimax: Object.freeze({\n supportsSubscription: true as const,\n subscriptionLabel: 'MiniMax Coding Plan',\n subscriptionAuthMode: 'device_code' as const,\n knownModels: Object.freeze([\n 'MiniMax-M2.5',\n 'MiniMax-M2.5-highspeed',\n 'MiniMax-M2.1',\n 'MiniMax-M2.1-highspeed',\n 'MiniMax-M2',\n ]),\n subscriptionCapabilities: Object.freeze({\n maxContextWindow: 200000,\n supportsPromptCaching: false,\n supportsBatching: false,\n }),\n }),\n copilot: Object.freeze({\n supportsSubscription: true as const,\n subscriptionLabel: 'GitHub Copilot subscription',\n subscriptionAuthMode: 'device_code' as const,\n knownModels: Object.freeze([\n 'copilot/claude-opus-4.6',\n 'copilot/claude-opus-4.6-fast',\n 'copilot/claude-sonnet-4.6',\n 'copilot/claude-haiku-4.5',\n 'copilot/gpt-5.4',\n 'copilot/gpt-5.2-codex',\n 'copilot/gpt-5-mini',\n 'copilot/gpt-4.1',\n 'copilot/gpt-4o',\n 'copilot/gpt-4o-mini',\n 'copilot/gemini-3.1-pro-preview',\n 'copilot/grok-code-fast-1',\n ]),\n subscriptionCapabilities: Object.freeze({\n maxContextWindow: 200000,\n supportsPromptCaching: false,\n supportsBatching: false,\n }),\n }),\n});\n\nexport const SUPPORTED_SUBSCRIPTION_PROVIDER_IDS: readonly string[] = Object.freeze(\n Object.keys(SUBSCRIPTION_PROVIDER_CONFIGS),\n);\n", "import type { SubscriptionCapabilities, SubscriptionProviderConfig } from './types';\nimport { SUBSCRIPTION_PROVIDER_CONFIGS } from './configs';\n\nfunction normalizeProviderId(providerId: string): string {\n return String(providerId || '').toLowerCase();\n}\n\nexport function getSubscriptionProviderConfig(\n providerId: string,\n): Readonly<SubscriptionProviderConfig> | null {\n return (\n SUBSCRIPTION_PROVIDER_CONFIGS[\n normalizeProviderId(providerId) as keyof typeof SUBSCRIPTION_PROVIDER_CONFIGS\n ] ?? null\n );\n}\n\nexport function supportsSubscriptionProvider(providerId: string): boolean {\n return getSubscriptionProviderConfig(providerId) !== null;\n}\n\nexport function getSubscriptionKnownModels(providerId: string): readonly string[] | null {\n const config = getSubscriptionProviderConfig(providerId);\n return config?.knownModels ?? null;\n}\n\nexport function getSubscriptionCapabilities(\n providerId: string,\n): Readonly<SubscriptionCapabilities> | null {\n const config = getSubscriptionProviderConfig(providerId);\n return config?.subscriptionCapabilities ?? null;\n}\n", "// Environment variable names (fallback when plugin config is missing)\nexport const ENV = {\n API_KEY: 'MANIFEST_API_KEY',\n ENDPOINT: 'MANIFEST_ENDPOINT',\n} as const;\n\n// Re-export from shared \u2014 single source of truth\nexport { API_KEY_PREFIX } from 'manifest-shared';\n\n// Plugin defaults\nexport const DEFAULTS = {\n ENDPOINT: 'https://app.manifest.build',\n SERVICE_NAME: 'openclaw-gateway',\n} as const;\n", "import { API_KEY_PREFIX, DEFAULTS, ENV } from './constants';\n\nexport interface ManifestConfig {\n mode: 'cloud' | 'local';\n devMode: boolean;\n apiKey: string;\n endpoint: string;\n port: number;\n host: string;\n}\n\nexport interface ParseResult {\n config: ManifestConfig;\n _deprecatedDevMode: boolean;\n}\n\nfunction isLoopback(endpoint: string): boolean {\n try {\n const url = new URL(endpoint);\n const host = url.hostname;\n return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '[::1]';\n } catch {\n return false;\n }\n}\n\nexport function parseConfig(raw: unknown): ManifestConfig {\n return parseConfigWithDeprecation(raw).config;\n}\n\nexport function parseConfigWithDeprecation(raw: unknown): ParseResult {\n // OpenClaw may pass the full plugin entry { enabled, config: {...} }\n // or just the inner config object. Handle both.\n let obj: Record<string, unknown> =\n raw && typeof raw === 'object' && !Array.isArray(raw) ? (raw as Record<string, unknown>) : {};\n\n if (obj.config && typeof obj.config === 'object' && !Array.isArray(obj.config)) {\n obj = obj.config as Record<string, unknown>;\n }\n\n // Backward compat: mode: \"dev\" \u2192 mode: \"cloud\" + devMode: true\n let _deprecatedDevMode = false;\n let mode: 'cloud' | 'local';\n if (obj.mode === 'local') {\n mode = 'local';\n } else if (obj.mode === 'dev') {\n mode = 'cloud';\n _deprecatedDevMode = true;\n } else {\n mode = 'cloud';\n }\n\n const apiKey =\n typeof obj.apiKey === 'string' && obj.apiKey.length > 0\n ? obj.apiKey\n : process.env[ENV.API_KEY] || '';\n\n const envEndpoint = process.env[ENV.ENDPOINT];\n\n const endpoint =\n typeof obj.endpoint === 'string' && obj.endpoint.length > 0\n ? obj.endpoint\n : envEndpoint && envEndpoint.length > 0\n ? envEndpoint\n : DEFAULTS.ENDPOINT;\n\n const port = typeof obj.port === 'number' && obj.port > 0 ? obj.port : 2099;\n\n const host = typeof obj.host === 'string' && obj.host.length > 0 ? obj.host : '127.0.0.1';\n\n // Determine devMode: explicit > deprecated mode: \"dev\" > auto-detect\n let devMode: boolean;\n if (typeof obj.devMode === 'boolean') {\n devMode = obj.devMode;\n } else if (_deprecatedDevMode) {\n devMode = true;\n } else {\n // Auto-detect: loopback endpoint + no mnfst_ API key\n devMode = isLoopback(endpoint) && !apiKey.startsWith(API_KEY_PREFIX);\n }\n\n return {\n config: { mode, devMode, apiKey, endpoint, port, host },\n _deprecatedDevMode,\n };\n}\n\nexport function validateConfig(config: ManifestConfig): string | null {\n // In local mode, API key is auto-generated \u2014 skip validation\n if (config.mode === 'local') return null;\n\n // devMode requires an endpoint but no API key\n if (config.devMode) {\n if (!config.endpoint.startsWith('http')) {\n return (\n `Invalid endpoint URL '${config.endpoint}'. ` +\n 'Must start with http:// or https://. Fix it via:\\n' +\n ' openclaw config set plugins.entries.manifest.config.endpoint http://localhost:<PORT>'\n );\n }\n return null;\n }\n\n if (!config.apiKey) {\n return (\n 'Missing apiKey. Set it via:\\n' +\n ` openclaw config set plugins.entries.manifest.config.apiKey ${API_KEY_PREFIX}YOUR_KEY\\n` +\n ` or export MANIFEST_API_KEY=${API_KEY_PREFIX}YOUR_KEY`\n );\n }\n if (!config.apiKey.startsWith(API_KEY_PREFIX)) {\n return (\n 'Invalid apiKey format. ' +\n `Keys must start with '${API_KEY_PREFIX}'. Fix it via:\\n` +\n ` openclaw config set plugins.entries.manifest.config.apiKey ${API_KEY_PREFIX}YOUR_KEY`\n );\n }\n if (!config.endpoint.startsWith('http')) {\n return (\n `Invalid endpoint URL '${config.endpoint}'. ` +\n 'Must start with http:// or https://. Fix it via:\\n' +\n ' openclaw config set plugins.entries.manifest.config.endpoint https://app.manifest.build\\n\\n' +\n 'Or run the setup wizard:\\n' +\n ' openclaw providers setup manifest'\n );\n }\n return null;\n}\n", "import { ManifestConfig } from './config';\n\nexport interface VerifyResult {\n endpointReachable: boolean;\n authValid: boolean;\n agentName: string | null;\n error: string | null;\n}\n\nexport async function verifyConnection(config: ManifestConfig): Promise<VerifyResult> {\n const baseUrl = config.endpoint.replace(/\\/otlp(\\/v1)?\\/?$/, ''); // backward compat\n const result: VerifyResult = {\n endpointReachable: false,\n authValid: false,\n agentName: null,\n error: null,\n };\n\n // Step 1: health check (no auth)\n try {\n const healthRes = await fetch(`${baseUrl}/api/v1/health`, {\n signal: AbortSignal.timeout(5000),\n });\n if (!healthRes.ok) {\n result.error = `Health endpoint returned ${healthRes.status}`;\n return result;\n }\n result.endpointReachable = true;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n result.error = `Cannot reach endpoint: ${msg}`;\n return result;\n }\n\n // Step 2: auth check (Bearer token, or no auth in dev mode)\n try {\n const authHeaders: Record<string, string> = config.apiKey\n ? { Authorization: `Bearer ${config.apiKey}` }\n : {};\n const usageRes = await fetch(`${baseUrl}/api/v1/agent/usage?range=24h`, {\n headers: authHeaders,\n signal: AbortSignal.timeout(5000),\n });\n if (usageRes.status === 401 || usageRes.status === 403) {\n result.error =\n 'API key rejected. Re-run setup: openclaw providers setup manifest';\n return result;\n }\n if (!usageRes.ok) {\n result.error = `Usage endpoint returned ${usageRes.status}`;\n return result;\n }\n result.authValid = true;\n\n const body = (await usageRes.json()) as Record<string, unknown>;\n if (body && typeof body.agentName === 'string') {\n result.agentName = body.agentName;\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n result.error = `Auth check failed: ${msg}`;\n return result;\n }\n\n return result;\n}\n", "import { ManifestConfig } from './config';\nimport { PluginLogger } from './types';\nimport { verifyConnection } from './verify';\n\nconst RANGE_MAP: Record<string, string> = {\n today: '24h',\n week: '7d',\n month: '30d',\n};\n\ninterface ToolResult {\n content: Array<{ type: 'text'; text: string }>;\n}\n\nasync function callApi(\n baseUrl: string,\n path: string,\n apiKey: string,\n logger: PluginLogger,\n): Promise<ToolResult> {\n const url = `${baseUrl}${path}`;\n try {\n const headers: Record<string, string> = apiKey ? { Authorization: `Bearer ${apiKey}` } : {};\n const res = await fetch(url, { headers });\n if (!res.ok) {\n return {\n content: [{ type: 'text', text: JSON.stringify({ error: `API returned ${res.status}` }) }],\n };\n }\n const data = await res.json();\n return { content: [{ type: 'text', text: JSON.stringify(data) }] };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.error(`[manifest] API call failed: ${msg}`);\n return { content: [{ type: 'text', text: JSON.stringify({ error: msg }) }] };\n }\n}\n\n/** Convert new content format to legacy { result?, error? } for older gateways */\nfunction toLegacy(r: ToolResult): { result?: unknown; error?: string } {\n try {\n const parsed = JSON.parse(r.content[0].text);\n if (parsed.error) return { error: parsed.error };\n return { result: parsed };\n } catch {\n return { result: r.content[0].text };\n }\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nexport function registerTools(api: any, config: ManifestConfig, logger: PluginLogger): void {\n const baseUrl = config.endpoint.replace(/\\/otlp(\\/v1)?\\/?$/, '');\n\n api.registerTool(\n {\n name: 'manifest_usage',\n description:\n 'Get token consumption for this agent: total, input, output, ' +\n 'cache-read tokens, and action count. ' +\n 'Use when the user asks about token usage or consumption.',\n parameters: {\n type: 'object',\n properties: {\n period: {\n type: 'string',\n enum: ['today', 'week', 'month'],\n default: 'today',\n description: 'Time period',\n },\n },\n },\n async handler(params: { period?: string }) {\n const range = RANGE_MAP[params.period || 'today'] || '24h';\n return toLegacy(\n await callApi(baseUrl, `/api/v1/agent/usage?range=${range}`, config.apiKey, logger),\n );\n },\n async execute(_id: unknown, params: { period?: string }): Promise<ToolResult> {\n const range = RANGE_MAP[params.period || 'today'] || '24h';\n return callApi(baseUrl, `/api/v1/agent/usage?range=${range}`, config.apiKey, logger);\n },\n },\n { optional: true },\n );\n\n api.registerTool(\n {\n name: 'manifest_costs',\n description:\n 'Get cost breakdown for this agent in USD, grouped by model. ' +\n 'Use when the user asks about costs, spending, or money burned.',\n parameters: {\n type: 'object',\n properties: {\n period: {\n type: 'string',\n enum: ['today', 'week', 'month'],\n default: 'week',\n description: 'Time period',\n },\n },\n },\n async handler(params: { period?: string }) {\n const range = RANGE_MAP[params.period || 'week'] || '7d';\n return toLegacy(\n await callApi(baseUrl, `/api/v1/agent/costs?range=${range}`, config.apiKey, logger),\n );\n },\n async execute(_id: unknown, params: { period?: string }): Promise<ToolResult> {\n const range = RANGE_MAP[params.period || 'week'] || '7d';\n return callApi(baseUrl, `/api/v1/agent/costs?range=${range}`, config.apiKey, logger);\n },\n },\n { optional: true },\n );\n\n api.registerTool(\n {\n name: 'manifest_health',\n description:\n 'Check whether Manifest routing is connected and working. ' +\n 'Use when the user asks if routing is set up or wants a connectivity test.',\n parameters: { type: 'object', properties: {} },\n async handler() {\n const check = await verifyConnection(config);\n if (check.error) return { error: check.error };\n return {\n result: {\n endpointReachable: check.endpointReachable,\n authValid: check.authValid,\n agentName: check.agentName,\n status: 'ok',\n },\n };\n },\n async execute() {\n const check = await verifyConnection(config);\n if (check.error) {\n return { content: [{ type: 'text', text: JSON.stringify({ error: check.error }) }] };\n }\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify({\n endpointReachable: check.endpointReachable,\n authValid: check.authValid,\n agentName: check.agentName,\n status: 'ok',\n }),\n },\n ],\n };\n },\n },\n { optional: true },\n );\n\n logger.debug(\n '[manifest] Registered agent tools: manifest_usage, manifest_costs, manifest_health',\n );\n}\n", "import { ManifestConfig } from './config';\nimport { PluginLogger } from './types';\nimport { verifyConnection } from './verify';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nexport function registerCommand(api: any, config: ManifestConfig, logger: PluginLogger): void {\n if (typeof api.registerCommand !== 'function') {\n logger.debug('[manifest] registerCommand not available, skipping /manifest command');\n return;\n }\n\n const formatStatus = async (): Promise<string> => {\n try {\n const check = await verifyConnection(config);\n const lines = [\n `Mode: ${config.mode}`,\n `Dev mode: ${config.devMode ? 'yes' : 'no'}`,\n `Endpoint reachable: ${check.endpointReachable ? 'yes' : 'no'}`,\n `Auth valid: ${check.authValid ? 'yes' : 'no'}`,\n ];\n if (check.agentName) {\n lines.push(`Agent: ${check.agentName}`);\n }\n if (check.error) {\n lines.push(`Error: ${check.error}`);\n }\n return lines.join('\\n');\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return `Manifest status check failed: ${msg}`;\n }\n };\n\n api.registerCommand({\n name: 'manifest',\n description: 'Show Manifest plugin status and connection info',\n async handler() {\n return formatStatus();\n },\n async execute() {\n const text = await formatStatus();\n return { text, content: [{ type: 'text', text }] };\n },\n });\n\n logger.debug('[manifest] Registered /manifest command');\n}\n", "import { writeFileSync, existsSync, mkdirSync, readdirSync, renameSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { homedir } from 'os';\nimport { PluginLogger } from './types';\nimport { loadJsonFile } from './json-file';\n\nconst OPENCLAW_DIR = join(homedir(), '.openclaw');\nconst OPENCLAW_CONFIG = join(OPENCLAW_DIR, 'openclaw.json');\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nfunction atomicWriteJson(path: string, data: unknown): void {\n const dir = dirname(path);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n const tmp = `${path}.tmp.${process.pid}`;\n writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });\n renameSync(tmp, path);\n}\n\n/**\n * Injects the Manifest provider configuration into OpenClaw's config file\n * and runtime config so that `manifest/auto` is recognized as a valid model.\n *\n * `baseUrl` must include the `/v1` path (e.g. `http://127.0.0.1:2099/v1`\n * or `https://app.manifest.build/v1`).\n */\nexport function injectProviderConfig(\n api: any,\n baseUrl: string,\n apiKey: string,\n logger: PluginLogger,\n): void {\n const providerConfig = {\n baseUrl,\n api: 'openai-completions',\n apiKey,\n models: [{ id: 'auto', name: 'auto' }],\n };\n\n // 1. Write to ~/.openclaw/openclaw.json (atomic write)\n try {\n const config = loadJsonFile(OPENCLAW_CONFIG);\n\n if (!config.models) config.models = {};\n if (!config.models.providers) config.models.providers = {};\n config.models.providers.manifest = providerConfig;\n\n if (!config.agents) config.agents = {};\n if (!config.agents.defaults) config.agents.defaults = {};\n if (!config.agents.defaults.models) config.agents.defaults.models = {};\n\n const models = config.agents.defaults.models;\n if (Array.isArray(models)) {\n if (!models.includes('manifest/auto')) models.push('manifest/auto');\n } else if (typeof models === 'object') {\n if (!('manifest/auto' in models)) models['manifest/auto'] = {};\n }\n\n atomicWriteJson(OPENCLAW_CONFIG, config);\n logger.debug('[manifest] Wrote provider config to openclaw.json');\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.debug(`[manifest] Could not write openclaw.json: ${msg}`);\n }\n\n // 2. Remove stale manifest entries from per-agent models.json files.\n try {\n const agentsDir = join(OPENCLAW_DIR, 'agents');\n if (existsSync(agentsDir)) {\n const agentDirs = readdirSync(agentsDir, { withFileTypes: true }).filter((d) =>\n d.isDirectory(),\n );\n\n for (const dir of agentDirs) {\n const modelsPath = join(agentsDir, dir.name, 'agent', 'models.json');\n if (!existsSync(modelsPath)) continue;\n\n const data = loadJsonFile(modelsPath);\n if (!data.providers?.manifest) continue;\n\n delete data.providers.manifest;\n atomicWriteJson(modelsPath, data);\n logger.debug(\n `[manifest] Removed stale manifest entry from models.json for agent ${dir.name}`,\n );\n }\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.debug(`[manifest] Could not clean agent models.json: ${msg}`);\n }\n\n // 3. Set runtime config for immediate availability\n try {\n if (api.config) {\n if (!api.config.models) api.config.models = {};\n if (!api.config.models.providers) api.config.models.providers = {};\n api.config.models.providers.manifest = providerConfig;\n\n if (!api.config.agents) api.config.agents = {};\n if (!api.config.agents.defaults) api.config.agents.defaults = {};\n if (!api.config.agents.defaults.models) api.config.agents.defaults.models = {};\n\n const rtModels = api.config.agents.defaults.models;\n if (Array.isArray(rtModels)) {\n if (!rtModels.includes('manifest/auto')) rtModels.push('manifest/auto');\n } else if (typeof rtModels === 'object') {\n if (!('manifest/auto' in rtModels)) rtModels['manifest/auto'] = {};\n }\n }\n logger.debug('[manifest] Injected provider into runtime config');\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.debug(`[manifest] Could not inject runtime config: ${msg}`);\n }\n}\n\n/**\n * Injects a placeholder auth profile for the `manifest` provider in each\n * agent's auth-profiles.json.\n */\nexport function injectAuthProfile(apiKey: string, logger: PluginLogger): void {\n const agentsDir = join(OPENCLAW_DIR, 'agents');\n if (!existsSync(agentsDir)) {\n logger.debug('[manifest] No agents directory found, skipping auth profile injection');\n return;\n }\n\n const profileEntry = {\n type: 'api_key',\n provider: 'manifest',\n key: apiKey,\n };\n\n let injected = 0;\n try {\n const agentDirs = readdirSync(agentsDir, { withFileTypes: true }).filter((d) =>\n d.isDirectory(),\n );\n\n for (const dir of agentDirs) {\n const profilePath = join(agentsDir, dir.name, 'agent', 'auth-profiles.json');\n const profileDir = dirname(profilePath);\n\n if (!existsSync(profileDir)) continue;\n\n const data = loadJsonFile(profilePath);\n if (!data.version) data.version = 1;\n if (!data.profiles) data.profiles = {};\n\n const existing = data.profiles['manifest:default'];\n if (existing && existing.key === apiKey) continue;\n\n data.profiles['manifest:default'] = profileEntry;\n atomicWriteJson(profilePath, data);\n injected++;\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.debug(`[manifest] Auth profile injection error: ${msg}`);\n }\n\n if (injected > 0) {\n logger.debug(`[manifest] Injected auth profile into ${injected} agent(s)`);\n }\n}\n", "import { existsSync, readFileSync } from 'fs';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport function loadJsonFile(path: string): Record<string, any> {\n if (!existsSync(path)) return {};\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf-8'));\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return parsed;\n }\n\n console.warn(`[manifest] Invalid JSON object in ${path}; expected a top-level object`);\n return {};\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[manifest] Failed to read JSON file ${path}: ${msg}`);\n return {};\n }\n}\n", "import { existsSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { PluginLogger } from './types';\nimport { supportsSubscriptionProvider } from 'manifest-shared';\nimport { loadJsonFile } from './json-file';\n\nconst OPENCLAW_DIR = join(homedir(), '.openclaw');\n\ninterface AuthProfileEntry {\n type: string;\n provider: string;\n key?: string;\n access_token?: string;\n}\n\nexport interface SubscriptionProvider {\n /** OpenClaw provider id (e.g. \"anthropic\", \"openai-codex\", \"google-gemini\") */\n openclawId: string;\n /** Manifest-compatible provider id (e.g. \"anthropic\", \"openai\", \"gemini\") */\n manifestId: string;\n /** Auth type in OpenClaw (e.g. \"oauth\", \"setup_token\", \"device_login\") */\n authType: string;\n /** Access token extracted from auth-profile (e.g. Copilot device-login token). */\n token?: string;\n}\n\ninterface FetchResponseLike {\n ok: boolean;\n status: number;\n json(): Promise<unknown>;\n}\n\ntype FetchLike = (\n input: string,\n init?: {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n signal?: unknown;\n },\n) => Promise<FetchResponseLike>;\n\nfunction getFetch(): FetchLike | null {\n return (globalThis as typeof globalThis & { fetch?: FetchLike }).fetch ?? null;\n}\n\nfunction getAbortSignalTimeout(ms: number): unknown {\n return (\n globalThis as typeof globalThis & {\n AbortSignal?: { timeout: (timeoutMs: number) => unknown };\n }\n ).AbortSignal?.timeout?.(ms);\n}\n\n/**\n * Map OpenClaw provider names from auth-profiles to Manifest provider IDs.\n * OpenClaw uses names like \"openai-codex\", \"google-gemini\", \"github-copilot\"\n * while Manifest uses simpler IDs like \"openai\", \"gemini\".\n */\nconst OPENCLAW_TO_MANIFEST: Record<string, string> = {\n anthropic: 'anthropic',\n 'openai-codex': 'openai',\n openai: 'openai',\n 'google-gemini': 'gemini',\n 'google-antigravity': 'gemini',\n google: 'gemini',\n gemini: 'gemini',\n 'github-copilot': 'copilot',\n qwen: 'qwen',\n 'qwen-portal': 'qwen',\n moonshot: 'moonshot',\n kimi: 'moonshot',\n minimax: 'minimax',\n 'minimax-portal': 'minimax',\n};\n\n/**\n * Scans all OpenClaw agent auth-profiles.json files to discover\n * providers authenticated via subscription (OAuth, setup-token, device-login).\n *\n * Returns deduplicated list of subscription providers mapped to Manifest IDs.\n */\nexport function discoverSubscriptionProviders(logger: PluginLogger): SubscriptionProvider[] {\n const agentsDir = join(OPENCLAW_DIR, 'agents');\n if (!existsSync(agentsDir)) {\n logger.debug('[manifest] No agents directory, no subscription providers');\n return [];\n }\n\n const seen = new Map<string, SubscriptionProvider>();\n\n try {\n const agentDirs = readdirSync(agentsDir, { withFileTypes: true }).filter((d) =>\n d.isDirectory(),\n );\n\n for (const dir of agentDirs) {\n const profilePath = join(agentsDir, dir.name, 'agent', 'auth-profiles.json');\n const data = loadJsonFile(profilePath);\n if (!data.profiles || typeof data.profiles !== 'object') continue;\n\n for (const entry of Object.values(data.profiles)) {\n const profile = entry as AuthProfileEntry;\n\n // Skip API key entries and our own manifest profile\n if (profile.type === 'api_key') continue;\n if (profile.provider === 'manifest') continue;\n\n const manifestId = OPENCLAW_TO_MANIFEST[profile.provider?.toLowerCase() ?? ''];\n if (!manifestId) {\n logger.debug(`[manifest] Unknown subscription provider: ${profile.provider}`);\n continue;\n }\n if (!supportsSubscriptionProvider(manifestId)) {\n logger.debug(\n `[manifest] Ignoring unsupported subscription provider: ${profile.provider} -> ${manifestId}`,\n );\n continue;\n }\n\n if (!seen.has(manifestId)) {\n const token =\n manifestId === 'copilot' ? (profile.access_token ?? profile.key) : undefined;\n seen.set(manifestId, {\n openclawId: profile.provider,\n manifestId,\n authType: profile.type,\n ...(token && { token }),\n });\n }\n }\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.debug(`[manifest] Error scanning auth profiles: ${msg}`);\n }\n\n const providers = Array.from(seen.values());\n if (providers.length > 0) {\n logger.info(\n `[manifest] Detected ${providers.length} subscription provider(s): ${providers.map((p) => p.manifestId).join(', ')}`,\n );\n }\n\n return providers;\n}\n\n/**\n * Registers discovered subscription providers with the Manifest backend.\n * This allows the routing/tier logic to consider subscription providers\n * alongside API key providers.\n */\nexport async function registerSubscriptionProviders(\n providers: SubscriptionProvider[],\n endpoint: string,\n apiKey: string,\n logger: PluginLogger,\n): Promise<void> {\n if (providers.length === 0) return;\n\n const baseUrl = endpoint.replace(/\\/otlp(\\/v1)?\\/?$/, '');\n const url = `${baseUrl}/api/v1/routing/subscription-providers`;\n\n try {\n const fetchImpl = getFetch();\n if (!fetchImpl) {\n logger.debug('[manifest] Global fetch is not available');\n return;\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (apiKey) {\n headers['Authorization'] = `Bearer ${apiKey}`;\n }\n\n const res = await fetchImpl(url, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n providers: providers.map((p) => ({\n provider: p.manifestId,\n ...(p.token && { token: p.token }),\n })),\n }),\n signal: getAbortSignalTimeout(5000),\n });\n\n if (res.ok) {\n const data = (await res.json()) as { registered: number };\n logger.info(`[manifest] Registered ${data.registered} subscription provider(s)`);\n } else {\n logger.debug(`[manifest] Failed to register subscription providers: ${res.status}`);\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.debug(`[manifest] Error registering subscription providers: ${msg}`);\n }\n}\n", "import { PluginLogger } from './types';\n\n/**\n * Strip legacy `/otlp` suffix from endpoint for backward compatibility.\n * Logs a deprecation warning when the old format is detected.\n */\nexport function stripOtlpSuffix(endpoint: string, logger: PluginLogger): string {\n const cleaned = endpoint.replace(/\\/otlp(\\/v1)?\\/?$/, '');\n if (cleaned !== endpoint) {\n logger.warn?.(\n `[manifest] Endpoint \"${endpoint}\" contains a deprecated /otlp suffix.\\n` +\n ` The endpoint should now be the base URL (e.g. \"${cleaned}\").\\n` +\n ` Update your config:\\n` +\n ` openclaw config set plugins.entries.manifest.config.endpoint ${cleaned}`,\n );\n }\n return cleaned;\n}\n", "import { API_KEY_PREFIX, DEFAULTS } from './constants';\nimport type { ProviderAuthContext, ProviderAuthResult } from './types';\n\nconst AUTO_MODEL = {\n id: 'auto',\n name: 'Auto Router',\n reasoning: false,\n input: ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 200000,\n maxTokens: 16384,\n};\n\n/**\n * Interactive onboarding flow for `openclaw providers setup manifest`.\n * Prompts the user for their Manifest API key and configures the provider.\n */\nexport async function runApiKeyAuth(ctx: ProviderAuthContext): Promise<ProviderAuthResult> {\n await ctx.prompter.intro('Manifest \u2014 Smart LLM Router');\n\n await ctx.prompter.note(\n 'Manifest analyzes each request and picks the best\\n' +\n 'model from your connected providers \u2014 balancing\\n' +\n 'quality, speed, and cost automatically.\\n\\n' +\n 'Get your API key at https://app.manifest.build',\n 'About Manifest',\n );\n\n const key = await ctx.prompter.text({\n message: 'Enter your Manifest API key',\n placeholder: `${API_KEY_PREFIX}...`,\n validate: (v) => {\n const trimmed = v?.trim() ?? '';\n if (trimmed.length === 0) return 'API key is required';\n if (!trimmed.startsWith(API_KEY_PREFIX))\n return `Key must start with '${API_KEY_PREFIX}'`;\n return undefined;\n },\n });\n\n const trimmedKey = key.trim();\n\n await ctx.prompter.outro(\n 'Manifest configured! Use model manifest/auto in your agent config.\\n' +\n 'Run `openclaw gateway restart` to activate.',\n );\n\n return {\n profiles: [\n {\n profileId: 'manifest:default',\n credential: { type: 'api_key', provider: 'manifest', key: trimmedKey },\n },\n ],\n configPatch: {\n models: {\n providers: {\n manifest: {\n baseUrl: `${DEFAULTS.ENDPOINT}/v1`,\n api: 'openai-completions',\n models: [AUTO_MODEL],\n },\n },\n },\n },\n defaultModel: 'manifest/auto',\n };\n}\n\n/**\n * Static model definition for the \"auto\" router model.\n * Used by registerProvider() to declare available models.\n */\nexport function buildModelConfig(baseUrl: string) {\n return {\n baseUrl: `${baseUrl}/v1`,\n api: 'openai-completions' as const,\n models: [AUTO_MODEL],\n };\n}\n", "import { parseConfigWithDeprecation, validateConfig } from './config';\nimport { PluginLogger } from './types';\nimport { registerTools } from './tools';\nimport { registerCommand } from './command';\nimport { verifyConnection } from './verify';\nimport { injectProviderConfig, injectAuthProfile } from './provider-inject';\nimport { discoverSubscriptionProviders, registerSubscriptionProviders } from './subscription';\nimport { stripOtlpSuffix } from './compat';\nimport { runApiKeyAuth, buildModelConfig } from './auth';\nimport { ENV } from './constants';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nmodule.exports = {\n id: 'manifest-provider',\n name: 'Manifest Provider \u2014 Smart LLM Router',\n\n register(api: any) {\n const logger: PluginLogger = api.logger || {\n info: (...args: unknown[]) => console.log(...args),\n debug: () => {},\n error: (...args: unknown[]) => console.error(...args),\n warn: (...args: unknown[]) => console.warn(...args),\n };\n\n const { config, _deprecatedDevMode } = parseConfigWithDeprecation(api.pluginConfig);\n\n if (_deprecatedDevMode) {\n logger.warn?.(\n '[manifest] mode: \"dev\" is deprecated. Use mode: \"cloud\" with devMode: true instead.\\n' +\n ' openclaw config set plugins.entries.manifest.config.mode cloud\\n' +\n ' openclaw config set plugins.entries.manifest.config.devMode true',\n );\n }\n\n // Register as a provider plugin with auth onboarding\n registerProvider(api, config.endpoint, logger);\n\n if (config.mode === 'local') {\n logger.info(\n '[manifest-provider] Local mode requires the manifest plugin.\\n' +\n ' Install it with: openclaw plugins install manifest\\n' +\n ' Then restart: openclaw gateway restart',\n );\n return;\n }\n\n const error = validateConfig(config);\n if (error) {\n if (!config.devMode && config.mode === 'cloud' && !config.apiKey) {\n logger.info(\n '[manifest] Cloud mode requires an API key.\\n\\n' +\n 'Run the setup wizard:\\n' +\n ' openclaw providers setup manifest\\n\\n' +\n 'Or set your key manually:\\n' +\n ' openclaw config set plugins.entries.manifest.config.apiKey mnfst_YOUR_KEY\\n' +\n ' openclaw gateway restart',\n );\n } else {\n logger.error(`[manifest] Configuration error:\\n${error}`);\n }\n return;\n }\n\n // Derive the base origin (strip legacy /otlp suffix for backward compat).\n const baseOrigin = stripOtlpSuffix(config.endpoint, logger);\n\n // Unified cloud/dev path\n const effectiveKey = config.devMode ? 'dev-no-auth' : config.apiKey;\n\n logger.info(\n config.devMode\n ? '[manifest] Dev mode \u2014 connecting to external server...'\n : '[manifest] Initializing routing...',\n );\n\n injectProviderConfig(api, `${baseOrigin}/v1`, effectiveKey, logger);\n injectAuthProfile(effectiveKey, logger);\n\n if (typeof api.registerTool === 'function') {\n registerTools(api, config, logger);\n } else if (!config.devMode) {\n logger.info('[manifest] Agent tools not available in this OpenClaw version');\n }\n registerCommand(api, config, logger);\n\n if (config.devMode) {\n logger.info(`[manifest] Dashboard: ${baseOrigin}`);\n }\n\n // Discover subscription providers from OpenClaw auth profiles\n const subscriptions = discoverSubscriptionProviders(logger);\n\n api.registerService({\n id: 'manifest-routing',\n start: () => {\n logger.info(\n config.devMode ? '[manifest] Dev mode routing active' : '[manifest] Routing active',\n );\n logger.info(`[manifest] Endpoint=${config.endpoint}`);\n\n verifyConnection(config)\n .then((check) => {\n if (check.error) {\n logger.warn?.(`[manifest] Connection check failed: ${check.error}`);\n return;\n }\n const agent = check.agentName ? ` (agent: ${check.agentName})` : '';\n logger.info(`[manifest] Connection verified${agent}`);\n })\n .catch(() => {});\n\n // Register subscription providers after startup\n registerSubscriptionProviders(subscriptions, config.endpoint, effectiveKey, logger).catch(\n () => {},\n );\n },\n });\n },\n};\n\n/**\n * Registers Manifest as a provider in OpenClaw with interactive auth onboarding.\n * This enables `openclaw providers setup manifest` for easy cloud setup.\n */\nfunction registerProvider(api: any, endpoint: string, logger: PluginLogger): void {\n if (typeof api.registerProvider !== 'function') {\n logger.debug('[manifest] registerProvider not available, skipping provider registration');\n return;\n }\n\n try {\n api.registerProvider({\n id: 'manifest',\n label: 'Manifest Router',\n envVars: [ENV.API_KEY],\n auth: [\n {\n id: 'api-key',\n label: 'Manifest API Key',\n hint: 'Get your key at https://app.manifest.build',\n kind: 'api_key',\n run: runApiKeyAuth,\n },\n ],\n models: buildModelConfig(stripOtlpSuffix(endpoint, { info: () => {}, error: () => {}, debug: () => {} })),\n });\n\n logger.info('[manifest] Registered as provider (model: manifest/auto)');\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n logger.debug(`[manifest] registerProvider failed (${msg})`);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_fs", "import_fs", "import_path", "import_os", "OPENCLAW_DIR"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "manifest-provider",
|
|
3
|
+
"name": "Manifest Provider — Smart LLM Router",
|
|
4
|
+
"version": "5.32.0",
|
|
5
|
+
"description": "Picks the best model for each request — balancing quality, speed, and cost. Supports 10+ providers with automatic tier-based routing and fallbacks.",
|
|
6
|
+
"author": "MNFST Inc.",
|
|
7
|
+
"homepage": "https://manifest.build",
|
|
8
|
+
"providers": ["manifest"],
|
|
9
|
+
"configSchema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"devMode": {
|
|
14
|
+
"type": "boolean",
|
|
15
|
+
"default": false,
|
|
16
|
+
"description": "Connect to a local dev server without API key validation. Auto-detected when endpoint is localhost."
|
|
17
|
+
},
|
|
18
|
+
"endpoint": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"default": "https://app.manifest.build",
|
|
21
|
+
"description": "Manifest server URL (cloud users should not change this)."
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"uiHints": {
|
|
26
|
+
"devMode": {
|
|
27
|
+
"label": "Dev Mode",
|
|
28
|
+
"help": "For plugin developers: connect to a local backend without API key validation.",
|
|
29
|
+
"advanced": true
|
|
30
|
+
},
|
|
31
|
+
"endpoint": {
|
|
32
|
+
"label": "Server URL",
|
|
33
|
+
"placeholder": "https://app.manifest.build",
|
|
34
|
+
"advanced": true
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"skills": [
|
|
38
|
+
"skills/manifest"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "manifest-provider",
|
|
3
|
+
"name": "Manifest Provider — Smart LLM Router",
|
|
4
|
+
"version": "5.32.0",
|
|
5
|
+
"description": "Picks the best model for each request — balancing quality, speed, and cost. Supports 10+ providers with automatic tier-based routing and fallbacks.",
|
|
6
|
+
"author": "MNFST Inc.",
|
|
7
|
+
"homepage": "https://manifest.build",
|
|
8
|
+
"providers": ["manifest"],
|
|
9
|
+
"configSchema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"devMode": {
|
|
14
|
+
"type": "boolean",
|
|
15
|
+
"default": false,
|
|
16
|
+
"description": "Connect to a local dev server without API key validation. Auto-detected when endpoint is localhost."
|
|
17
|
+
},
|
|
18
|
+
"endpoint": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"default": "https://app.manifest.build",
|
|
21
|
+
"description": "Manifest server URL (cloud users should not change this)."
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"uiHints": {
|
|
26
|
+
"devMode": {
|
|
27
|
+
"label": "Dev Mode",
|
|
28
|
+
"help": "For plugin developers: connect to a local backend without API key validation.",
|
|
29
|
+
"advanced": true
|
|
30
|
+
},
|
|
31
|
+
"endpoint": {
|
|
32
|
+
"label": "Server URL",
|
|
33
|
+
"placeholder": "https://app.manifest.build",
|
|
34
|
+
"advanced": true
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"skills": [
|
|
38
|
+
"skills/manifest"
|
|
39
|
+
]
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "manifest-provider",
|
|
3
|
+
"version": "5.32.0",
|
|
4
|
+
"description": "Smart LLM router plugin for OpenClaw — picks the best model for each request, balancing quality, speed, and cost",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "MNFST Inc.",
|
|
8
|
+
"homepage": "https://manifest.build",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/mnfst/manifest",
|
|
12
|
+
"directory": "packages/openclaw-plugins/manifest-provider"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/mnfst/manifest/issues"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/index.js",
|
|
19
|
+
"dist/index.js.map",
|
|
20
|
+
"openclaw.plugin.json",
|
|
21
|
+
"skills",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE.md"
|
|
24
|
+
],
|
|
25
|
+
"openclaw": {
|
|
26
|
+
"extensions": [
|
|
27
|
+
"./dist/index.js"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20.0.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"prebuild": "rm -rf dist",
|
|
35
|
+
"build": "tsx build.ts",
|
|
36
|
+
"dev": "tsx watch build.ts",
|
|
37
|
+
"prepublishOnly": "npm run build",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "jest"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"openclaw",
|
|
43
|
+
"openclaw-plugin",
|
|
44
|
+
"openclaw-provider",
|
|
45
|
+
"manifest",
|
|
46
|
+
"llm-router",
|
|
47
|
+
"llm-routing",
|
|
48
|
+
"model-routing",
|
|
49
|
+
"ai-gateway",
|
|
50
|
+
"llm-proxy",
|
|
51
|
+
"smart-routing",
|
|
52
|
+
"cost-optimization",
|
|
53
|
+
"ai-agent",
|
|
54
|
+
"multi-provider",
|
|
55
|
+
"openai",
|
|
56
|
+
"anthropic",
|
|
57
|
+
"claude",
|
|
58
|
+
"gemini",
|
|
59
|
+
"deepseek"
|
|
60
|
+
],
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"manifest-shared": "*"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"esbuild": "^0.25.0",
|
|
66
|
+
"tsx": "^4.19.0",
|
|
67
|
+
"typescript": "^5.7.0",
|
|
68
|
+
"@types/jest": "^29.5.0",
|
|
69
|
+
"jest": "^29.7.0",
|
|
70
|
+
"ts-jest": "^29.2.0"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: manifest
|
|
3
|
+
description: Model Router for OpenClaw. Save up to 70% by routing requests to the right model. Choose LLM fallback to avoid API rate limits, set thresholds and reduce token consumption.
|
|
4
|
+
metadata: {"openclaw":{"requires":{"bins":["openclaw"]},"homepage":"https://github.com/mnfst/manifest"}}
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Manifest — LLM Router & Observability for OpenClaw
|
|
8
|
+
|
|
9
|
+
Manifest is an OpenClaw plugin that:
|
|
10
|
+
|
|
11
|
+
- **Routes every request** to the most cost-effective model via a 23-dimension scoring algorithm (<2ms latency)
|
|
12
|
+
- **Tracks costs and tokens** in a real-time dashboard
|
|
13
|
+
- **Sets limits** with email alerts and hard spending caps
|
|
14
|
+
|
|
15
|
+
Source: [github.com/mnfst/manifest](https://github.com/mnfst/manifest) — MIT licensed. Homepage: [manifest.build](https://manifest.build)
|
|
16
|
+
|
|
17
|
+
## Security & Privacy
|
|
18
|
+
|
|
19
|
+
> **TL;DR** — OTLP telemetry collects only metadata (model, tokens, latency, tool names) — never prompt or response text. When `manifest/auto` routing is active, the last 10 non-system messages are sent to the routing endpoint for tier scoring; set a fixed model to avoid this. The API key (`mnfst_*`) authenticates your telemetry — it is not exfiltration. In local mode, all data stays on your machine.
|
|
20
|
+
|
|
21
|
+
### External Endpoints
|
|
22
|
+
|
|
23
|
+
| Endpoint | When | Data Sent |
|
|
24
|
+
| ----------------------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
25
|
+
| `{endpoint}/v1/traces` | Every LLM call (batched 10-30s) | OTLP spans (see fields below) |
|
|
26
|
+
| `{endpoint}/v1/metrics` | Every 10-30s | Counters: request count, token totals, tool call counts — grouped by model/provider |
|
|
27
|
+
| `{endpoint}/api/v1/routing/resolve` | Only when model is `manifest/auto` | Last 10 non-system/non-developer messages (`{role, content}` only) |
|
|
28
|
+
|
|
29
|
+
### OTLP Span Fields
|
|
30
|
+
|
|
31
|
+
Exhaustive list of attributes sent per span:
|
|
32
|
+
|
|
33
|
+
- `openclaw.session.key` — opaque session identifier
|
|
34
|
+
- `openclaw.message.channel` — message channel name
|
|
35
|
+
- `gen_ai.request.model` — model name (e.g. "claude-sonnet-4-20250514")
|
|
36
|
+
- `gen_ai.system` — provider name (e.g. "anthropic")
|
|
37
|
+
- `gen_ai.usage.input_tokens` — integer
|
|
38
|
+
- `gen_ai.usage.output_tokens` — integer
|
|
39
|
+
- `gen_ai.usage.cache_read_input_tokens` — integer
|
|
40
|
+
- `gen_ai.usage.cache_creation_input_tokens` — integer
|
|
41
|
+
- `openclaw.agent.name` — agent identifier
|
|
42
|
+
- `tool.name` — tool name string
|
|
43
|
+
- `tool.success` — boolean
|
|
44
|
+
- `manifest.routing.tier` — routing tier (if routed)
|
|
45
|
+
- `manifest.routing.reason` — routing reason (if routed)
|
|
46
|
+
- Error status: agent errors truncated to 500 chars; tool errors include `event.error.message` untruncated
|
|
47
|
+
|
|
48
|
+
**Not collected by OTLP telemetry**: user prompts, assistant responses, tool input/output arguments, file contents, or any message body. Note: when `manifest/auto` routing is active, the last 10 non-system messages (`{role, content}` only) are sent to the routing endpoint for tier scoring — see Routing Data below. To avoid this, set a fixed model instead of `manifest/auto`.
|
|
49
|
+
|
|
50
|
+
### Routing Data
|
|
51
|
+
|
|
52
|
+
- Only active when model is `manifest/auto`
|
|
53
|
+
- Excludes messages with `role: "system"` or `role: "developer"`
|
|
54
|
+
- Sends only `{role, content}` — all other message properties are stripped (`routing.ts:77`)
|
|
55
|
+
- 3-second timeout, non-blocking
|
|
56
|
+
- To avoid sending content: disable routing in the dashboard or set a fixed model
|
|
57
|
+
|
|
58
|
+
### Credential Storage
|
|
59
|
+
|
|
60
|
+
- **Cloud mode** (`manifest-provider` plugin): API key provided via `openclaw providers setup manifest-provider` or `MANIFEST_API_KEY` env var
|
|
61
|
+
- **Local mode** (`manifest` plugin): auto-generated key stored in `~/.openclaw/manifest/config.json` with file mode `0600`
|
|
62
|
+
|
|
63
|
+
### Local Mode
|
|
64
|
+
|
|
65
|
+
All data stays on your machine. No external calls are made in local mode.
|
|
66
|
+
|
|
67
|
+
## Install Provenance
|
|
68
|
+
|
|
69
|
+
`openclaw plugins install manifest` installs the [`manifest`](https://www.npmjs.com/package/manifest) npm package.
|
|
70
|
+
|
|
71
|
+
- **Source**: [github.com/mnfst/manifest](https://github.com/mnfst/manifest) (`packages/openclaw-plugins/manifest`)
|
|
72
|
+
- **License**: MIT
|
|
73
|
+
- **Author**: MNFST Inc.
|
|
74
|
+
|
|
75
|
+
Verify before installing:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm view manifest repository.url
|
|
79
|
+
npm view manifest dist.integrity
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The package is published with [npm provenance attestations](https://docs.npmjs.com/generating-provenance-statements). Verify with:
|
|
83
|
+
```bash
|
|
84
|
+
npm audit signatures
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Setup (Local — recommended for evaluation)
|
|
88
|
+
|
|
89
|
+
No account or API key required. Telemetry and dashboard data stay local; LLM requests still go to your configured providers.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
openclaw plugins install manifest
|
|
93
|
+
openclaw gateway restart
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Dashboard opens at **http://127.0.0.1:2099**. Data stored locally in `~/.openclaw/manifest/manifest.db`. No account or API key needed.
|
|
97
|
+
|
|
98
|
+
To expose over Tailscale (requires Tailscale on both devices, only accessible within your Tailnet): `tailscale serve --bg 2099`
|
|
99
|
+
|
|
100
|
+
## Setup (Cloud)
|
|
101
|
+
|
|
102
|
+
Two commands:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
openclaw plugins install manifest-provider
|
|
106
|
+
openclaw providers setup manifest-provider
|
|
107
|
+
openclaw gateway restart
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The setup wizard prompts for your API key from [app.manifest.build](https://app.manifest.build) → create an account → create an agent → copy the `mnfst_*` key. You can also set `MANIFEST_API_KEY` env var for CI/CD.
|
|
111
|
+
|
|
112
|
+
After restart, the plugin auto-configures:
|
|
113
|
+
|
|
114
|
+
- Adds `manifest/auto` to the model allowlist (does not change your current default)
|
|
115
|
+
- Injects the `manifest` provider into `~/.openclaw/openclaw.json`
|
|
116
|
+
- Starts exporting OTLP telemetry to `app.manifest.build`
|
|
117
|
+
- Exposes three agent tools: `manifest_usage`, `manifest_costs`, `manifest_health`
|
|
118
|
+
|
|
119
|
+
Dashboard at [app.manifest.build](https://app.manifest.build). Telemetry arrives within 10-30 seconds (batched OTLP export).
|
|
120
|
+
|
|
121
|
+
### Verify connection
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
openclaw manifest
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Shows: mode, endpoint reachability, auth validity, agent name.
|
|
128
|
+
|
|
129
|
+
## Configuration Changes
|
|
130
|
+
|
|
131
|
+
On plugin registration, Manifest writes to these files:
|
|
132
|
+
|
|
133
|
+
| File | Change | Reversible |
|
|
134
|
+
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
|
|
135
|
+
| `~/.openclaw/openclaw.json` | Adds `models.providers.manifest` provider entry; adds `manifest/auto` to `agents.defaults.models` allowlist | Yes — `openclaw plugins uninstall manifest` |
|
|
136
|
+
| `~/.openclaw/agents/*/agent/auth-profiles.json` | Adds `manifest:default` auth profile | Yes — uninstall removes it |
|
|
137
|
+
| `~/.openclaw/manifest/config.json` | Stores auto-generated API key (local mode only, file mode 0600) | Yes — delete `~/.openclaw/manifest/` |
|
|
138
|
+
| `~/.openclaw/manifest/manifest.db` | SQLite database (local mode only) | Yes — delete the file |
|
|
139
|
+
|
|
140
|
+
No other files are modified. The plugin does not change your current default model.
|
|
141
|
+
|
|
142
|
+
## What Manifest Answers
|
|
143
|
+
|
|
144
|
+
Manifest answers these questions about your OpenClaw agents — via the dashboard or directly in-conversation via agent tools:
|
|
145
|
+
|
|
146
|
+
**Spending & budget**
|
|
147
|
+
|
|
148
|
+
- How much have I spent today / this week / this month?
|
|
149
|
+
- What's my cost breakdown by model?
|
|
150
|
+
- Which model consumes the biggest share of my budget?
|
|
151
|
+
- Am I approaching my spending limit?
|
|
152
|
+
|
|
153
|
+
**Token consumption**
|
|
154
|
+
|
|
155
|
+
- How many tokens has my agent used (input vs. output)?
|
|
156
|
+
- What's my token trend compared to the previous period?
|
|
157
|
+
- How much cache am I reading vs. writing?
|
|
158
|
+
|
|
159
|
+
**Activity & performance**
|
|
160
|
+
|
|
161
|
+
- How many LLM calls has my agent made?
|
|
162
|
+
- How long do LLM calls take (latency)?
|
|
163
|
+
- Are there errors or rate limits occurring? What are the error messages?
|
|
164
|
+
- Which skills/tools are running and how often?
|
|
165
|
+
|
|
166
|
+
**Routing intelligence**
|
|
167
|
+
|
|
168
|
+
- What routing tier (simple/standard/complex/reasoning) was each request assigned?
|
|
169
|
+
- Why was a specific tier chosen?
|
|
170
|
+
- What model pricing is available across all providers?
|
|
171
|
+
|
|
172
|
+
**Connectivity**
|
|
173
|
+
|
|
174
|
+
- Is Manifest connected and healthy?
|
|
175
|
+
- Is telemetry flowing correctly?
|
|
176
|
+
|
|
177
|
+
## Agent Tools
|
|
178
|
+
|
|
179
|
+
Three tools are available to the agent in-conversation:
|
|
180
|
+
|
|
181
|
+
| Tool | Trigger phrases | What it returns |
|
|
182
|
+
| ----------------- | ----------------------------------------------- | --------------------------------------------------------------------------- |
|
|
183
|
+
| `manifest_usage` | "how many tokens", "token usage", "consumption" | Total, input, output, cache-read tokens + action count for today/week/month |
|
|
184
|
+
| `manifest_costs` | "how much spent", "costs", "money burned" | Cost breakdown by model in USD for today/week/month |
|
|
185
|
+
| `manifest_health` | "is monitoring working", "connectivity test" | Endpoint reachable, auth valid, agent name, status |
|
|
186
|
+
|
|
187
|
+
Each accepts a `period` parameter: `"today"`, `"week"`, or `"month"`.
|
|
188
|
+
|
|
189
|
+
All three tools are read-only — they query the agent's own usage data and never send message content.
|
|
190
|
+
|
|
191
|
+
## LLM Routing
|
|
192
|
+
|
|
193
|
+
When the model is set to `manifest/auto`, the router scores each conversation across 23 dimensions and assigns one of 4 tiers:
|
|
194
|
+
|
|
195
|
+
| Tier | Use case | Examples |
|
|
196
|
+
| ------------- | --------------------------------------- | ------------------------------------------------------- |
|
|
197
|
+
| **Simple** | Greetings, confirmations, short lookups | "hi", "yes", "what time is it" |
|
|
198
|
+
| **Standard** | General tasks, balanced quality/cost | "summarize this", "write a test" |
|
|
199
|
+
| **Complex** | Multi-step reasoning, nuanced analysis | "compare these architectures", "debug this stack trace" |
|
|
200
|
+
| **Reasoning** | Formal logic, proofs, critical planning | "prove this theorem", "design a migration strategy" |
|
|
201
|
+
|
|
202
|
+
Each tier maps to a model. Default models are auto-assigned per provider, but overridable in the dashboard under **Routing**.
|
|
203
|
+
|
|
204
|
+
Short-circuit rules:
|
|
205
|
+
|
|
206
|
+
- Messages <50 chars with no tools → **Simple**
|
|
207
|
+
- Formal logic keywords → **Reasoning**
|
|
208
|
+
- Tools present → floor at **Standard**
|
|
209
|
+
- Context >50k tokens → floor at **Complex**
|
|
210
|
+
|
|
211
|
+
## Dashboard Pages
|
|
212
|
+
|
|
213
|
+
| Page | What it shows |
|
|
214
|
+
| ---------------- | ----------------------------------------------------------------------------- |
|
|
215
|
+
| **Workspace** | All connected agents as cards with sparkline activity charts |
|
|
216
|
+
| **Overview** | Per-agent cost, tokens, messages with trend badges and time-series charts |
|
|
217
|
+
| **Messages** | Full paginated message log with filters (status, model, cost range) |
|
|
218
|
+
| **Routing** | 4-tier model config, provider connections, enable/disable routing |
|
|
219
|
+
| **Limits** | Email alerts and hard spending caps (tokens or cost, per hour/day/week/month) |
|
|
220
|
+
| **Settings** | Agent rename, delete, OTLP key management |
|
|
221
|
+
| **Model Prices** | Sortable table of 300+ model prices across all providers |
|
|
222
|
+
|
|
223
|
+
## Supported Providers
|
|
224
|
+
|
|
225
|
+
Anthropic, OpenAI, Google Gemini, DeepSeek, xAI, Mistral AI, Qwen, MiniMax, Kimi, Amazon Nova, Z.ai, OpenRouter, Ollama. 300+ models total.
|
|
226
|
+
|
|
227
|
+
## Uninstall
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
openclaw plugins uninstall manifest
|
|
231
|
+
openclaw gateway restart
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
This removes the plugin, provider config, and auth profiles. After uninstalling, `manifest/auto` is no longer available. If any agent uses it, switch to another model.
|
|
235
|
+
|
|
236
|
+
## Troubleshooting
|
|
237
|
+
|
|
238
|
+
**Telemetry not appearing**: The gateway batches OTLP data every 10-30 seconds. Wait, then check `openclaw manifest` for connection status.
|
|
239
|
+
|
|
240
|
+
**Auth errors in cloud mode**: Verify the API key starts with `mnfst_` and matches the key in the dashboard under Settings → Agent setup.
|
|
241
|
+
|
|
242
|
+
**Port conflict in local mode**: If port 2099 is busy, the plugin checks if the existing process is Manifest and reuses it. To change the port: `openclaw config set plugins.entries.manifest.config.port <PORT>`.
|
|
243
|
+
|
|
244
|
+
**Plugin conflicts**: Manifest conflicts with the built-in `diagnostics-otel` plugin. Disable it before enabling Manifest.
|
|
245
|
+
|
|
246
|
+
**After backend restart**: Always restart the gateway too (`openclaw gateway restart`) — the OTLP pipeline doesn't auto-reconnect.
|