11// Tavily helper module supports config behavior.
22import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts" ;
3+ import { canResolveEnvSecretRefInReadOnlyPath } from "openclaw/plugin-sdk/extension-shared" ;
34import { resolvePositiveTimeoutSeconds } from "openclaw/plugin-sdk/provider-web-search" ;
4- import {
5- normalizeResolvedSecretInputString ,
6- normalizeSecretInput ,
7- } from "openclaw/plugin-sdk/secret-input" ;
5+ import { resolveSecretInputString , normalizeSecretInput } from "openclaw/plugin-sdk/secret-input" ;
86import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime" ;
97
108export const DEFAULT_TAVILY_BASE_URL = "https://api.tavily.com" ;
119const DEFAULT_TAVILY_SEARCH_TIMEOUT_SECONDS = 30 ;
1210const DEFAULT_TAVILY_EXTRACT_TIMEOUT_SECONDS = 60 ;
11+ const TAVILY_API_KEY_ENV_VAR = "TAVILY_API_KEY" ;
1312
1413type TavilySearchConfig =
1514 | {
@@ -34,22 +33,64 @@ function resolveTavilySearchConfig(cfg?: OpenClawConfig): TavilySearchConfig {
3433 return undefined ;
3534}
3635
37- function normalizeConfiguredSecret ( value : unknown , path : string ) : string | undefined {
38- return normalizeSecretInput (
39- normalizeResolvedSecretInputString ( {
40- value,
41- path,
42- } ) ,
43- ) ;
36+ type ConfiguredSecretResolution =
37+ | { status : "available" ; value : string }
38+ | { status : "missing" }
39+ | { status : "blocked" } ;
40+
41+ function resolveConfiguredSecret (
42+ value : unknown ,
43+ path : string ,
44+ cfg ?: OpenClawConfig ,
45+ ) : ConfiguredSecretResolution {
46+ const resolved = resolveSecretInputString ( {
47+ value,
48+ path,
49+ defaults : cfg ?. secrets ?. defaults ,
50+ mode : "inspect" ,
51+ } ) ;
52+ if ( resolved . status === "available" ) {
53+ const normalized = normalizeSecretInput ( resolved . value ) ;
54+ return normalized ? { status : "available" , value : normalized } : { status : "missing" } ;
55+ }
56+ if ( resolved . status === "missing" ) {
57+ return { status : "missing" } ;
58+ }
59+ // Explicit unavailable refs must not silently borrow an unrelated ambient credential.
60+ if ( resolved . ref . source !== "env" ) {
61+ return { status : "blocked" } ;
62+ }
63+ const envVarName = resolved . ref . id . trim ( ) ;
64+ if ( envVarName !== TAVILY_API_KEY_ENV_VAR ) {
65+ return { status : "blocked" } ;
66+ }
67+ if (
68+ ! canResolveEnvSecretRefInReadOnlyPath ( {
69+ cfg,
70+ provider : resolved . ref . provider ,
71+ id : envVarName ,
72+ } )
73+ ) {
74+ return { status : "blocked" } ;
75+ }
76+ const envValue = normalizeSecretInput ( process . env [ envVarName ] ) ;
77+ return envValue ? { status : "available" , value : envValue } : { status : "missing" } ;
4478}
4579
4680export function resolveTavilyApiKey ( cfg ?: OpenClawConfig ) : string | undefined {
4781 const search = resolveTavilySearchConfig ( cfg ) ;
48- return (
49- normalizeConfiguredSecret ( search ?. apiKey , "plugins.entries.tavily.config.webSearch.apiKey" ) ||
50- normalizeSecretInput ( process . env . TAVILY_API_KEY ) ||
51- undefined
82+ const resolved = resolveConfiguredSecret (
83+ search ?. apiKey ,
84+ "plugins.entries.tavily.config.webSearch.apiKey" ,
85+ cfg ,
5286 ) ;
87+ if ( resolved . status === "available" ) {
88+ return resolved . value ;
89+ }
90+ if ( resolved . status === "blocked" ) {
91+ return undefined ;
92+ }
93+ return normalizeSecretInput ( process . env . TAVILY_API_KEY ) || undefined ;
5394}
5495
5596export function resolveTavilyBaseUrl ( cfg ?: OpenClawConfig ) : string {
0 commit comments