dsh-web-search-enhanced 0.0.1
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 +9 -0
- package/README.md +77 -0
- package/cordis.patch.yml +20 -0
- package/docs/design.zh-CN.md +79 -0
- package/docs/harness-contract.md +30 -0
- package/docs/template-comparison.md +35 -0
- package/lib/client.js +872 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +507 -0
- package/lib/types/client/SearchSettingsCard.d.ts +62 -0
- package/lib/types/client/index.d.ts +11 -0
- package/lib/types/client/locales.d.ts +61 -0
- package/lib/types/index.d.ts +54 -0
- package/lib/types/protocols.d.ts +45 -0
- package/lib/types/provider.d.ts +16 -0
- package/package.json +129 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["useSyncExternalStore","useMemo","useState","useId","_jsxs","_jsx","_Fragment"],"sources":["types/client/SearchSettingsCard.js","types/client/locales.js","types/client/index.js"],"sourcesContent":["import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from \"react/jsx-runtime\";\nimport { useEffect, useId, useMemo, useState, useSyncExternalStore } from 'react';\nconst editableFields = ['modelMode', 'protocol', 'baseURL', 'model', 'fallbackModel', 'apiKeyEnv', 'apiVersion', 'toolIdentifier', 'maxTokens', 'maxUses', 'chatSearchMode', 'searchContextSize'];\n/** Convert resolved settings into a stable form draft. */\nexport function draftFrom(value) {\n return {\n modelMode: value?.modelMode ?? 'configured',\n protocol: value?.protocol ?? 'anthropic-messages',\n baseURL: value?.baseURL ?? 'https://api.deepseek.com/anthropic/v1',\n model: value?.model ?? 'deepseek-v4-flash',\n fallbackModel: value?.fallbackModel ?? '',\n apiKeyEnv: value?.apiKeyEnv ?? 'WEB_SEARCH_ENHANCED_API',\n apiVersion: value?.apiVersion ?? '2023-06-01',\n toolIdentifier: value?.toolIdentifier ?? '',\n maxTokens: String(value?.maxTokens ?? 4096),\n maxUses: String(value?.maxUses ?? 5),\n chatSearchMode: value?.chatSearchMode ?? 'search-model',\n searchContextSize: value?.searchContextSize ?? '',\n };\n}\n/** Validate a draft and return field-specific locale keys. */\nexport async function saveCredential(credentials, ref, value) {\n const secret = value.trim();\n if (secret.length === 0)\n return false;\n const result = await credentials.set(ref.trim(), secret);\n if (!result.ok)\n throw new Error(result.error.message);\n return true;\n}\nexport function validateDraft(draft) {\n const errors = {};\n try {\n const url = new URL(draft.baseURL);\n if (url.protocol !== 'http:' && url.protocol !== 'https:')\n errors.baseURL = 'invalidURL';\n }\n catch {\n errors.baseURL = 'invalidURL';\n }\n if (draft.model.trim().length === 0)\n errors.model = 'invalidRequired';\n if (draft.apiKeyEnv.trim().length === 0)\n errors.apiKeyEnv = 'invalidRequired';\n if (draft.apiKeyEnv.trim().length > 0 && !/^[A-Za-z_][A-Za-z0-9_]*$/u.test(draft.apiKeyEnv.trim()))\n errors.apiKeyEnv = 'invalidCredentialRef';\n if (draft.apiVersion.trim().length === 0)\n errors.apiVersion = 'invalidRequired';\n if (draft.toolIdentifier.length > 0 && !/^[A-Za-z][A-Za-z0-9_.:-]{0,127}$/u.test(draft.toolIdentifier))\n errors.toolIdentifier = 'invalidIdentifier';\n if (!positiveInteger(draft.maxTokens))\n errors.maxTokens = 'invalidInteger';\n if (!positiveInteger(draft.maxUses))\n errors.maxUses = 'invalidInteger';\n return errors;\n}\nconst cardCss = `\n.wse-card {\n list-style: none;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n transition: border-color .16s, background .16s;\n}\n.wse-card:hover {\n border-color: var(--dsw-alias-label-dimmed);\n}\n.wse-card[data-open='true'] {\n background: var(--dsw-alias-bg-layer-2);\n border-color: var(--dsw-alias-label-dimmed);\n}\n.wse-header {\n width: 100%;\n appearance: none;\n border: 0;\n background: none;\n font: inherit;\n color: inherit;\n text-align: left;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 14px 16px;\n border-radius: 12px;\n}\n.wse-header:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n.wse-head {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.wse-name {\n color: var(--dsw-alias-label-primary);\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n}\n.wse-desc {\n color: var(--dsw-alias-label-tertiary);\n font-size: 13px;\n line-height: 1.5;\n}\n.wse-pending {\n white-space: nowrap;\n background: var(--dsw-alias-bg-module-platform);\n color: var(--dsw-alias-label-secondary);\n border-radius: 999px;\n flex: none;\n padding: 1px 8px;\n font-size: 11px;\n font-weight: 500;\n line-height: 17px;\n}\n.wse-chevron {\n box-sizing: border-box;\n flex: none;\n width: 8px;\n height: 8px;\n margin-right: 3px;\n border-right: 1.5px solid var(--dsw-alias-label-tertiary);\n border-bottom: 1.5px solid var(--dsw-alias-label-tertiary);\n transform: rotate(45deg);\n transition: transform .16s;\n}\n.wse-card[data-open='true'] .wse-chevron {\n transform: rotate(225deg);\n}\n.wse-body {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin: 0 16px;\n padding-bottom: 8px;\n}\n.wse-fields {\n display: flex;\n flex-direction: column;\n}\n.wse-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 12px 0;\n}\n.wse-field + .wse-field {\n border-top: 1px solid var(--dsw-alias-border-l2);\n}\n.wse-label {\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n font-weight: 500;\n line-height: 1.5;\n}\n.wse-input, .wse-select {\n box-sizing: border-box;\n width: 100%;\n height: 34px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 0 12px;\n background: var(--dsw-alias-bg-layer-3);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 13px;\n line-height: 1.5;\n outline: none;\n transition: border-color .16s, background .16s;\n}\n.wse-input:focus-visible, .wse-select:focus-visible,\n.wse-input:focus, .wse-select:focus {\n border-color: var(--dsw-alias-brand-primary);\n outline: none;\n}\n.wse-input[aria-invalid='true'] {\n border-color: var(--dsw-alias-label-error);\n}\n.wse-input:disabled, .wse-select:disabled {\n cursor: default;\n color: var(--dsw-alias-label-tertiary);\n opacity: .55;\n}\n.wse-hint, .wse-error, .wse-readonly, .wse-failed {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n}\n.wse-hint {\n color: var(--dsw-alias-label-tertiary);\n}\n.wse-error, .wse-failed {\n color: var(--dsw-alias-label-error);\n}\n.wse-readonly {\n margin: 12px 0 0;\n color: var(--dsw-alias-label-tertiary);\n}\n.wse-footer {\n border-top: 1px solid var(--dsw-alias-border-l2);\n margin-top: 4px;\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 8px;\n padding: 12px 0 4px;\n}\n.wse-failed {\n flex: 1;\n min-width: 0;\n margin: 0;\n}\n.wse-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-left: auto;\n}\n.wse-button {\n appearance: none;\n font: inherit;\n cursor: pointer;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 8px;\n padding: 5px 14px;\n font-size: 13px;\n line-height: 1.5;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n transition: color .16s, border-color .16s, background .16s, opacity .16s;\n}\n.wse-button:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n border-color: var(--dsw-alias-label-dimmed);\n background: var(--dsw-alias-bg-layer-4);\n}\n.wse-button:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n.wse-button:disabled {\n opacity: .4;\n cursor: default;\n}\n.wse-save {\n border-color: transparent;\n background: var(--dsw-alias-label-primary);\n color: var(--dsw-alias-label-primary-foreground, var(--dsw-alias-bg-layer-3));\n}\n.wse-save:hover:not(:disabled) {\n background: var(--dsw-alias-label-primary);\n opacity: .9;\n}\n`;\nexport function SearchSettingsCard({ scope, credentials, t }) {\n const snapshot = useSyncExternalStore(scope.subscribe.bind(scope), scope.getSnapshot.bind(scope));\n const resolved = useMemo(() => draftFrom(snapshot.value), [snapshot.value]);\n const [draft, setDraft] = useState(resolved);\n const [open, setOpen] = useState(false);\n const [saving, setSaving] = useState(false);\n const [failed, setFailed] = useState(false);\n const [hasDraft, setHasDraft] = useState(false);\n const [resetToProfile, setResetToProfile] = useState(false);\n const [apiKey, setApiKey] = useState('');\n const [credentialConfigured, setCredentialConfigured] = useState(false);\n const bodyId = useId();\n useEffect(() => { if (!saving && !hasDraft)\n setDraft(resolved); }, [hasDraft, resolved, saving]);\n useEffect(() => {\n const ref = draft.apiKeyEnv.trim();\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(ref)) {\n setCredentialConfigured(false);\n return;\n }\n let active = true;\n void credentials.describe([ref]).then(result => {\n if (active)\n setCredentialConfigured(result.ok && result.value[ref]?.configured === true);\n }).catch(() => { if (active)\n setCredentialConfigured(false); });\n return () => { active = false; };\n }, [credentials, draft.apiKeyEnv]);\n const errors = useMemo(() => validateDraft(draft), [draft]);\n const dirty = resetToProfile || apiKey.length > 0 || JSON.stringify(draft) !== JSON.stringify(resolved);\n const invalid = Object.keys(errors).length > 0;\n const disabled = snapshot.status !== 'ready' || !snapshot.writable || saving;\n const edit = (field, value) => {\n setDraft(current => ({ ...current, [field]: value }));\n setResetToProfile(false);\n setHasDraft(true);\n setFailed(false);\n };\n const save = async () => {\n if (disabled || !dirty || invalid)\n return;\n setSaving(true);\n setFailed(false);\n try {\n if (await saveCredential(credentials, draft.apiKeyEnv, apiKey))\n setCredentialConfigured(true);\n if (resetToProfile) {\n for (const field of editableFields)\n await scope.unset(field);\n }\n else {\n await scope.set('modelMode', draft.modelMode);\n await scope.set('protocol', draft.protocol);\n await scope.set('baseURL', draft.baseURL.trim());\n await scope.set('model', draft.model.trim());\n if (draft.fallbackModel.trim().length === 0)\n await scope.unset('fallbackModel');\n else\n await scope.set('fallbackModel', draft.fallbackModel.trim());\n await scope.set('apiKeyEnv', draft.apiKeyEnv.trim());\n await scope.set('apiVersion', draft.apiVersion.trim());\n if (draft.toolIdentifier.trim().length === 0)\n await scope.unset('toolIdentifier');\n else\n await scope.set('toolIdentifier', draft.toolIdentifier.trim());\n await scope.set('maxTokens', Number(draft.maxTokens));\n await scope.set('maxUses', Number(draft.maxUses));\n await scope.set('chatSearchMode', draft.chatSearchMode);\n if (draft.searchContextSize === '')\n await scope.unset('searchContextSize');\n else\n await scope.set('searchContextSize', draft.searchContextSize);\n }\n setApiKey('');\n setHasDraft(false);\n setResetToProfile(false);\n setOpen(false);\n }\n catch {\n setFailed(true);\n }\n finally {\n setSaving(false);\n }\n };\n const reset = () => { setApiKey(''); setDraft(draftFrom(asSearchSettings(snapshot.base))); setResetToProfile(true); setHasDraft(true); setFailed(false); };\n const discard = () => { setApiKey(''); setDraft(resolved); setResetToProfile(false); setHasDraft(false); setFailed(false); };\n if (snapshot.status === 'unavailable')\n return null;\n const toolHint = draft.protocol === 'anthropic-messages' ? 'toolHintAnthropic' : draft.protocol === 'openai-responses' ? 'toolHintResponses' : draft.chatSearchMode === 'search-model' ? 'toolHintChatOfficial' : 'toolHintChatVendor';\n return _jsxs(\"li\", { className: \"wse-card\", \"data-open\": open, children: [_jsx(\"style\", { children: cardCss }), _jsxs(\"button\", { type: \"button\", className: \"wse-header\", \"aria-expanded\": open, \"aria-controls\": bodyId, \"aria-label\": `${t(open ? 'collapse' : 'expand')}: ${t('title')}`, onClick: () => { setOpen(value => !value); }, children: [_jsxs(\"span\", { className: \"wse-head\", children: [_jsx(\"span\", { className: \"wse-name\", children: t('title') }), _jsx(\"span\", { className: \"wse-desc\", children: t('description') })] }), dirty ? _jsx(\"span\", { className: \"wse-pending\", children: t('unsaved') }) : null, _jsx(\"span\", { className: \"wse-chevron\", \"aria-hidden\": \"true\" })] }), open ? _jsxs(\"div\", { className: \"wse-body\", id: bodyId, children: [!snapshot.writable && snapshot.status === 'ready' ? _jsx(\"p\", { className: \"wse-readonly\", role: \"status\", children: t('readOnly') }) : null, _jsxs(\"div\", { className: \"wse-fields\", children: [_jsx(SelectField, { label: t('modelMode'), hint: t('modelModeHint'), value: draft.modelMode, disabled: disabled, onChange: value => { edit('modelMode', value); }, options: [[\"configured\", t('configuredMode')], [\"current-session\", t('currentSessionMode')]] }), _jsx(SelectField, { label: t('protocol'), hint: t('protocolHint'), value: draft.protocol, disabled: disabled, onChange: value => { edit('protocol', value); }, options: [[\"anthropic-messages\", t('anthropic')], [\"openai-responses\", t('responses')], [\"openai-chat-completions\", t('chat')]] }), _jsx(TextField, { label: t('toolIdentifier'), hint: t(toolHint), value: draft.toolIdentifier, error: errors.toolIdentifier === undefined ? undefined : t(errors.toolIdentifier), disabled: disabled || (draft.protocol === 'openai-chat-completions' && draft.chatSearchMode === 'search-model'), onChange: value => { edit('toolIdentifier', value); } }), _jsx(TextField, { label: t('baseURL'), hint: t('baseURLHint'), value: draft.baseURL, error: errors.baseURL === undefined ? undefined : t(errors.baseURL), disabled: disabled, onChange: value => { edit('baseURL', value); } }), _jsx(TextField, { label: t('model'), hint: t('modelHint'), value: draft.model, error: errors.model === undefined ? undefined : t(errors.model), disabled: disabled, onChange: value => { edit('model', value); } }), _jsx(TextField, { label: t('fallbackModel'), hint: t('fallbackModelHint'), value: draft.fallbackModel, error: errors.fallbackModel === undefined ? undefined : t(errors.fallbackModel), disabled: disabled, onChange: value => { edit('fallbackModel', value); } }), _jsx(TextField, { label: t('apiKeyEnv'), hint: credentialConfigured ? t('credentialConfigured') : t('apiKeyEnvHint'), value: draft.apiKeyEnv, error: errors.apiKeyEnv === undefined ? undefined : t(errors.apiKeyEnv), disabled: disabled, onChange: value => { edit('apiKeyEnv', value); } }), _jsx(TextField, { type: \"password\", label: t('apiKey'), hint: t('apiKeyHint'), value: apiKey, disabled: disabled, onChange: value => { setApiKey(value); setHasDraft(true); setFailed(false); } }), _jsx(TextField, { inputMode: \"numeric\", label: t('maxTokens'), hint: t('maxTokensHint'), value: draft.maxTokens, error: errors.maxTokens === undefined ? undefined : t(errors.maxTokens), disabled: disabled, onChange: value => { edit('maxTokens', value); } }), draft.modelMode === 'current-session' ? (_jsxs(_Fragment, { children: [_jsx(TextField, { inputMode: \"numeric\", label: t('maxUses'), hint: t('maxUsesHint'), value: draft.maxUses, error: errors.maxUses === undefined ? undefined : t(errors.maxUses), disabled: disabled, onChange: value => { edit('maxUses', value); } }), _jsx(TextField, { label: t('apiVersion'), hint: t('apiVersionHint'), value: draft.apiVersion, error: errors.apiVersion === undefined ? undefined : t(errors.apiVersion), disabled: disabled, onChange: value => { edit('apiVersion', value); } }), _jsx(SelectField, { label: t('chatSearchMode'), hint: t('chatSearchModeHint'), value: draft.chatSearchMode, disabled: disabled, onChange: value => { edit('chatSearchMode', value); }, options: [[\"search-model\", t('chatOfficial')], [\"vendor-options\", t('chatVendor')]] }), _jsx(SelectField, { label: t('searchContextSize'), hint: t('searchContextSizeHint'), value: draft.searchContextSize, disabled: disabled, onChange: value => { edit('searchContextSize', value); }, options: [[\"\", t('contextDefault')], [\"low\", t('contextLow')], [\"medium\", t('contextMedium')], [\"high\", t('contextHigh')]] })] })) : draft.protocol === 'anthropic-messages' ? (_jsxs(_Fragment, { children: [_jsx(TextField, { inputMode: \"numeric\", label: t('maxUses'), hint: t('maxUsesHint'), value: draft.maxUses, error: errors.maxUses === undefined ? undefined : t(errors.maxUses), disabled: disabled, onChange: value => { edit('maxUses', value); } }), _jsx(TextField, { label: t('apiVersion'), hint: t('apiVersionHint'), value: draft.apiVersion, error: errors.apiVersion === undefined ? undefined : t(errors.apiVersion), disabled: disabled, onChange: value => { edit('apiVersion', value); } })] })) : (_jsxs(_Fragment, { children: [draft.protocol === 'openai-chat-completions' ? (_jsx(SelectField, { label: t('chatSearchMode'), hint: t('chatSearchModeHint'), value: draft.chatSearchMode, disabled: disabled, onChange: value => { edit('chatSearchMode', value); }, options: [[\"search-model\", t('chatOfficial')], [\"vendor-options\", t('chatVendor')]] })) : null, _jsx(SelectField, { label: t('searchContextSize'), hint: t('searchContextSizeHint'), value: draft.searchContextSize, disabled: disabled, onChange: value => { edit('searchContextSize', value); }, options: [[\"\", t('contextDefault')], [\"low\", t('contextLow')], [\"medium\", t('contextMedium')], [\"high\", t('contextHigh')]] })] }))] }), _jsxs(\"div\", { className: \"wse-footer\", children: [failed ? _jsx(\"p\", { className: \"wse-failed\", role: \"alert\", children: t('failed') }) : null, _jsxs(\"div\", { className: \"wse-actions\", children: [_jsx(\"button\", { type: \"button\", className: \"wse-button\", disabled: disabled || resetToProfile, onClick: reset, children: t('reset') }), _jsx(\"button\", { type: \"button\", className: \"wse-button\", disabled: disabled || !dirty, onClick: discard, children: t('discard') }), _jsx(\"button\", { type: \"button\", className: \"wse-button wse-save\", disabled: disabled || !dirty || invalid, onClick: () => { void save(); }, children: saving ? t('saving') : t('save') })] })] })] }) : null] });\n}\nfunction TextField(props) {\n const id = useId();\n const describedBy = useId();\n return _jsxs(\"div\", { className: \"wse-field\", children: [_jsx(\"label\", { className: \"wse-label\", htmlFor: id, children: props.label }), _jsx(\"input\", { id: id, className: \"wse-input\", type: props.type ?? 'text', value: props.value, disabled: props.disabled, inputMode: props.inputMode, \"aria-invalid\": props.error === undefined ? undefined : true, \"aria-describedby\": describedBy, spellCheck: false, onChange: event => { props.onChange(event.target.value); } }), _jsx(\"p\", { id: describedBy, className: props.error === undefined ? 'wse-hint' : 'wse-error', children: props.error ?? props.hint })] });\n}\nfunction SelectField(props) {\n const id = useId();\n const describedBy = useId();\n return _jsxs(\"div\", { className: \"wse-field\", children: [_jsx(\"label\", { className: \"wse-label\", htmlFor: id, children: props.label }), _jsx(\"select\", { id: id, className: \"wse-select\", value: props.value, disabled: props.disabled, \"aria-describedby\": describedBy, onChange: event => { props.onChange(event.target.value); }, children: props.options.map(([value, label]) => _jsx(\"option\", { value: value, children: label }, value)) }), _jsx(\"p\", { id: describedBy, className: \"wse-hint\", children: props.hint })] });\n}\nfunction asSearchSettings(value) { return typeof value === 'object' && value !== null ? value : undefined; }\nfunction positiveInteger(value) { const parsed = Number(value); return Number.isInteger(parsed) && parsed > 0; }\n//# sourceMappingURL=SearchSettingsCard.js.map","export const en = {\n title: 'Web Search Enhanced',\n description: 'Choose the model route and built-in search settings used by web_search.',\n expand: 'Expand', collapse: 'Collapse', unsaved: 'Unsaved',\n modelMode: 'Model route', modelModeHint: 'Use the fixed search route or follow the model and calling protocol of the current session.',\n configuredMode: 'Fixed configuration', currentSessionMode: 'Current session model',\n protocol: 'API protocol', protocolHint: 'The fixed or fallback route protocol. Follow mode uses the current model protocol when available.',\n anthropic: 'Anthropic Messages v1', responses: 'OpenAI Responses API', chat: 'OpenAI Chat Completions API',\n baseURL: 'Endpoint base URL', baseURLHint: 'The protocol endpoint suffix is appended only when it is not already present.',\n model: 'Model ID', modelHint: 'The model used by the fixed route and when no session model is available.',\n fallbackModel: 'Fallback search model', fallbackModelHint: 'Optional model used when the current session route cannot be resolved; blank uses Model ID.',\n apiKeyEnv: 'API key environment variable', apiKeyEnvHint: 'The fixed and fallback route reads the API key from this credential reference.',\n apiKey: 'API key', apiKeyHint: 'Enter a new key to store it securely. The field is cleared after saving.', credentialConfigured: 'A credential is configured for this reference.',\n toolIdentifier: 'Internal search identifier',\n toolHintAnthropic: 'Anthropic tool type. Leave blank for web_search_20250305.',\n toolHintResponses: 'Responses built-in tool type. Leave blank for web_search.',\n toolHintChatOfficial: 'Official Chat search models use the fixed web_search_options field.',\n toolHintChatVendor: 'Vendor-specific Chat search options field. Leave blank for web_search_options.',\n maxTokens: 'Max output tokens', maxTokensHint: 'Positive integer sent as max_tokens or max_output_tokens for one search request.',\n maxUses: 'Max search uses', maxUsesHint: 'Anthropic-only maximum number of server-tool uses in one request.',\n apiVersion: 'Anthropic API version', apiVersionHint: 'Value sent in the anthropic-version request header.',\n chatSearchMode: 'Chat search capability', chatSearchModeHint: 'Use the official specialized-search-model contract unless the gateway documents a custom options field.',\n chatOfficial: 'Official search model', chatVendor: 'Vendor custom options field',\n searchContextSize: 'Search context size', searchContextSizeHint: 'Optional OpenAI-compatible context budget.',\n contextDefault: 'Provider default', contextLow: 'Low', contextMedium: 'Medium', contextHigh: 'High',\n save: 'Save', saving: 'Saving…', reset: 'Reset to Profile', discard: 'Discard',\n readOnly: 'This connection cannot persist Host Profile settings.',\n failed: 'The Host rejected the change. Review the fields and try again.',\n invalidURL: 'Enter an absolute HTTP(S) endpoint URL.', invalidRequired: 'This field must not be blank.', invalidCredentialRef: 'Use a valid environment-variable name such as WEB_SEARCH_ENHANCED_API.',\n invalidIdentifier: 'Use 1–128 identifier characters and start with a letter.', invalidInteger: 'Enter a positive integer.',\n};\nexport const zh = {\n title: 'Web Search Enhanced',\n description: '配置 web_search 使用的模型路由与内置搜索参数。',\n expand: '展开', collapse: '折叠', unsaved: '未保存',\n modelMode: '模型路由', modelModeHint: '选择固定搜索路由,或跟随当前会话模型及其调用协议。',\n configuredMode: '固定配置', currentSessionMode: '当前会话模型',\n protocol: 'API 协议', protocolHint: '固定或兜底路由使用的协议;跟随模式会优先使用当前模型协议。',\n anthropic: 'Anthropic Messages v1', responses: 'OpenAI Responses API', chat: 'OpenAI Chat Completions API',\n baseURL: '接口基础地址', baseURLHint: '仅当地址尚未包含协议端点时,插件才会自动追加相应路径。',\n model: '模型标识', modelHint: '固定路由使用的模型;当前会话没有可用模型时也会使用它。',\n fallbackModel: '兜底搜索模型', fallbackModelHint: '当前会话路由无法解析时使用的可选模型;留空使用模型标识。',\n apiKeyEnv: 'API Key 环境变量', apiKeyEnvHint: '固定和兜底路由从此凭据引用读取 API Key。',\n apiKey: 'API Key', apiKeyHint: '输入新 Key 后安全保存;保存成功后输入框会清空。', credentialConfigured: '此凭据引用已配置。',\n toolIdentifier: '内部搜索标识符',\n toolHintAnthropic: 'Anthropic 工具类型;留空使用 web_search_20250305。',\n toolHintResponses: 'Responses 内置工具类型;留空使用 web_search。',\n toolHintChatOfficial: '官方 Chat 搜索模型固定使用 web_search_options 字段。',\n toolHintChatVendor: '提供方自定义的 Chat 搜索选项字段;留空使用 web_search_options。',\n maxTokens: '最大输出 Token', maxTokensHint: '单次搜索请求使用的正整数 max_tokens 或 max_output_tokens。',\n maxUses: '最大搜索次数', maxUsesHint: '仅 Anthropic:单次请求最多调用服务端搜索工具的次数。',\n apiVersion: 'Anthropic API 版本', apiVersionHint: '写入 anthropic-version 请求头的版本值。',\n chatSearchMode: 'Chat 搜索能力', chatSearchModeHint: '除非网关明确说明自定义选项字段,否则请选择官方专用搜索模型协议。',\n chatOfficial: '官方专用搜索模型', chatVendor: '提供方自定义选项字段',\n searchContextSize: '搜索上下文大小', searchContextSizeHint: '可选的 OpenAI 兼容搜索上下文预算。',\n contextDefault: '提供方默认值', contextLow: '低', contextMedium: '中', contextHigh: '高',\n save: '保存', saving: '正在保存…', reset: '重置为 Profile 配置', discard: '放弃修改',\n readOnly: '当前连接不能持久化 Host Profile 设置。',\n failed: 'Host 拒绝了修改,请检查字段后重试。',\n invalidURL: '请输入绝对 HTTP(S) 接口地址。', invalidRequired: '此字段不能为空。', invalidCredentialRef: '请输入有效的环境变量名,例如 WEB_SEARCH_ENHANCED_API。',\n invalidIdentifier: '请输入 1–128 位标识符字符,并以字母开头。', invalidInteger: '请输入正整数。',\n};\n//# sourceMappingURL=locales.js.map","import { SearchSettingsCard } from \"./SearchSettingsCard.js\";\nimport { en, zh } from \"./locales.js\";\nconst SETTINGS_NAMESPACE = 'web-search-enhanced';\nconst LOCALE_NAMESPACE = 'settings.webSearchEnhanced';\n/** Browser plugin dependencies. */\nexport const inject = ['slots', 'locale', 'settingsScope', 'remote', 'remote.credentials'];\n/** Register the localized card under Settings -> Plugins -> Plugin configuration. */\nexport function apply(ctx) {\n const t = ctx.locale.bind(LOCALE_NAMESPACE);\n ctx.effect(() => ctx.locale.register(LOCALE_NAMESPACE, { en, zh }), 'web-search-enhanced: settings dictionaries');\n const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE });\n ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({\n name: 'settings.plugin.item',\n key: SETTINGS_NAMESPACE,\n locale: LOCALE_NAMESPACE,\n inject: () => ({ scope, credentials: ctx.remote.credentials, t }),\n }, SearchSettingsCard));\n}\n//# sourceMappingURL=index.js.map"],"mappings":"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"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
2
|
+
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
|
|
3
|
+
import z from "@deepseek-ai/schemastery";
|
|
4
|
+
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
5
|
+
import { WebError } from "@deepseek-ai/dsh-web";
|
|
6
|
+
//#region lib/types/protocols.js
|
|
7
|
+
const ENDPOINT_SUFFIX = {
|
|
8
|
+
"anthropic-messages": "/messages",
|
|
9
|
+
"openai-responses": "/responses",
|
|
10
|
+
"openai-chat-completions": "/chat/completions"
|
|
11
|
+
};
|
|
12
|
+
/** Return the protocol-specific built-in search identifier. */
|
|
13
|
+
function defaultToolIdentifier(protocol) {
|
|
14
|
+
switch (protocol) {
|
|
15
|
+
case "anthropic-messages": return "web_search_20250305";
|
|
16
|
+
case "openai-responses": return "web_search";
|
|
17
|
+
case "openai-chat-completions": return "web_search_options";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Build one protocol request from resolved settings and a model-facing search query. */
|
|
21
|
+
function buildWireRequest(config, query, apiKey) {
|
|
22
|
+
const prompt = `Perform a web search for the query: ${query}`;
|
|
23
|
+
const commonHeaders = {
|
|
24
|
+
"accept": "application/json",
|
|
25
|
+
"content-type": "application/json",
|
|
26
|
+
"user-agent": "dsh-web-search-enhanced/0.0.1"
|
|
27
|
+
};
|
|
28
|
+
switch (config.protocol) {
|
|
29
|
+
case "anthropic-messages": return {
|
|
30
|
+
endpoint: appendEndpoint(config.baseURL, ENDPOINT_SUFFIX[config.protocol]),
|
|
31
|
+
headers: {
|
|
32
|
+
...commonHeaders,
|
|
33
|
+
"anthropic-version": config.apiVersion,
|
|
34
|
+
"authorization": `Bearer ${apiKey}`,
|
|
35
|
+
"x-api-key": apiKey
|
|
36
|
+
},
|
|
37
|
+
body: {
|
|
38
|
+
model: config.model,
|
|
39
|
+
max_tokens: config.maxTokens,
|
|
40
|
+
messages: [{
|
|
41
|
+
role: "user",
|
|
42
|
+
content: [{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: prompt
|
|
45
|
+
}]
|
|
46
|
+
}],
|
|
47
|
+
tools: [{
|
|
48
|
+
type: config.toolIdentifier,
|
|
49
|
+
name: "web_search",
|
|
50
|
+
max_uses: config.maxUses
|
|
51
|
+
}]
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
case "openai-responses": return {
|
|
55
|
+
endpoint: appendEndpoint(config.baseURL, ENDPOINT_SUFFIX[config.protocol]),
|
|
56
|
+
headers: {
|
|
57
|
+
...commonHeaders,
|
|
58
|
+
authorization: `Bearer ${apiKey}`
|
|
59
|
+
},
|
|
60
|
+
body: {
|
|
61
|
+
model: config.model,
|
|
62
|
+
input: [{
|
|
63
|
+
role: "user",
|
|
64
|
+
content: [{
|
|
65
|
+
type: "input_text",
|
|
66
|
+
text: prompt
|
|
67
|
+
}]
|
|
68
|
+
}],
|
|
69
|
+
tools: [{
|
|
70
|
+
type: config.toolIdentifier,
|
|
71
|
+
...config.searchContextSize === void 0 ? {} : { search_context_size: config.searchContextSize }
|
|
72
|
+
}],
|
|
73
|
+
tool_choice: { type: config.toolIdentifier },
|
|
74
|
+
max_output_tokens: config.maxTokens,
|
|
75
|
+
stream: false
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
case "openai-chat-completions": {
|
|
79
|
+
const body = {
|
|
80
|
+
model: config.model,
|
|
81
|
+
messages: [{
|
|
82
|
+
role: "user",
|
|
83
|
+
content: prompt
|
|
84
|
+
}],
|
|
85
|
+
max_tokens: config.maxTokens,
|
|
86
|
+
stream: false
|
|
87
|
+
};
|
|
88
|
+
const optionsField = config.chatSearchMode === "search-model" ? "web_search_options" : config.toolIdentifier;
|
|
89
|
+
body[optionsField] = config.searchContextSize === void 0 ? {} : { search_context_size: config.searchContextSize };
|
|
90
|
+
return {
|
|
91
|
+
endpoint: appendEndpoint(config.baseURL, ENDPOINT_SUFFIX[config.protocol]),
|
|
92
|
+
headers: {
|
|
93
|
+
...commonHeaders,
|
|
94
|
+
authorization: `Bearer ${apiKey}`
|
|
95
|
+
},
|
|
96
|
+
body
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Parse a protocol response into DSH's provider-neutral search result. */
|
|
102
|
+
function parseSearchResponse(protocol, payload) {
|
|
103
|
+
switch (protocol) {
|
|
104
|
+
case "anthropic-messages": return parseAnthropicResponse(payload);
|
|
105
|
+
case "openai-responses": return parseResponsesResponse(payload);
|
|
106
|
+
case "openai-chat-completions": return parseChatCompletionsResponse(payload);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Parse Anthropic Messages server-tool result blocks and citation snippets. */
|
|
110
|
+
function parseAnthropicResponse(payload) {
|
|
111
|
+
const blocks = asArray(asRecord(payload)?.content);
|
|
112
|
+
const snippets = /* @__PURE__ */ new Map();
|
|
113
|
+
for (const blockValue of blocks) {
|
|
114
|
+
const block = asRecord(blockValue);
|
|
115
|
+
if (block?.type !== "text") continue;
|
|
116
|
+
for (const citationValue of asArray(block.citations)) {
|
|
117
|
+
const citation = asRecord(citationValue);
|
|
118
|
+
const url = nonEmptyString(citation?.url);
|
|
119
|
+
const snippet = nonEmptyString(citation?.cited_text);
|
|
120
|
+
if (url !== void 0 && snippet !== void 0 && !snippets.has(url)) snippets.set(url, snippet);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const sources = /* @__PURE__ */ new Map();
|
|
124
|
+
let sawResultBlock = false;
|
|
125
|
+
for (const blockValue of blocks) {
|
|
126
|
+
const block = asRecord(blockValue);
|
|
127
|
+
if (block?.type !== "web_search_tool_result") continue;
|
|
128
|
+
sawResultBlock = true;
|
|
129
|
+
for (const itemValue of asArray(block.content)) {
|
|
130
|
+
const item = asRecord(itemValue);
|
|
131
|
+
if (item?.type !== "web_search_result") continue;
|
|
132
|
+
addSource(sources, item, snippets.get(nonEmptyString(item.url) ?? ""));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!sawResultBlock) throw providerError("Anthropic Messages response contains no web_search_tool_result block");
|
|
136
|
+
return {
|
|
137
|
+
sources: [...sources.values()],
|
|
138
|
+
truncated: false
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** Parse Responses web_search_call items, message text, and URL annotations. */
|
|
142
|
+
function parseResponsesResponse(payload) {
|
|
143
|
+
const output = asArray(asRecord(payload)?.output);
|
|
144
|
+
const sources = /* @__PURE__ */ new Map();
|
|
145
|
+
const contentParts = [];
|
|
146
|
+
for (const result of asArray(asRecord(payload)?.results)) addSource(sources, asRecord(result));
|
|
147
|
+
let sawSearchCall = false;
|
|
148
|
+
for (const itemValue of output) {
|
|
149
|
+
const item = asRecord(itemValue);
|
|
150
|
+
if (item?.type === "web_search_call") {
|
|
151
|
+
sawSearchCall = true;
|
|
152
|
+
if (item.status === "failed") throw providerError("Responses web_search_call failed");
|
|
153
|
+
for (const result of asArray(item.results)) addSource(sources, asRecord(result));
|
|
154
|
+
for (const source of asArray(asRecord(item.action)?.sources)) addSource(sources, asRecord(source));
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (item?.type !== "message") continue;
|
|
158
|
+
for (const blockValue of asArray(item.content)) {
|
|
159
|
+
const block = asRecord(blockValue);
|
|
160
|
+
if (block?.type !== "output_text" && block?.type !== "text") continue;
|
|
161
|
+
const text = nonEmptyString(block.text);
|
|
162
|
+
if (text !== void 0) contentParts.push(text);
|
|
163
|
+
for (const annotation of asArray(block.annotations)) addSource(sources, citationRecord(annotation));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (!sawSearchCall) throw providerError("Responses payload contains no web_search_call item");
|
|
167
|
+
const content = contentParts.length === 0 ? void 0 : contentParts.join("\n");
|
|
168
|
+
return {
|
|
169
|
+
...content === void 0 ? {} : { content },
|
|
170
|
+
sources: [...sources.values()],
|
|
171
|
+
truncated: false
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/** Parse Chat Completions message text and URL citation annotations. */
|
|
175
|
+
function parseChatCompletionsResponse(payload) {
|
|
176
|
+
const root = asRecord(payload);
|
|
177
|
+
const message = asRecord(asRecord(asArray(root?.choices)[0])?.message);
|
|
178
|
+
if (message === void 0) throw providerError("Chat Completions payload contains no assistant message");
|
|
179
|
+
const content = nonEmptyString(message.content);
|
|
180
|
+
const sources = /* @__PURE__ */ new Map();
|
|
181
|
+
for (const annotation of asArray(message.annotations)) addSource(sources, citationRecord(annotation));
|
|
182
|
+
for (const citation of asArray(root?.citations)) if (typeof citation === "string") addSource(sources, { url: citation });
|
|
183
|
+
else addSource(sources, asRecord(citation));
|
|
184
|
+
return {
|
|
185
|
+
...content === void 0 ? {} : { content },
|
|
186
|
+
sources: [...sources.values()],
|
|
187
|
+
truncated: false
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function appendEndpoint(baseURL, suffix) {
|
|
191
|
+
const trimmed = baseURL.replace(/\/+$/u, "");
|
|
192
|
+
return trimmed.toLowerCase().endsWith(suffix) ? trimmed : trimmed + suffix;
|
|
193
|
+
}
|
|
194
|
+
function addSource(target, value, forcedSnippet) {
|
|
195
|
+
if (value === void 0) return;
|
|
196
|
+
const url = nonEmptyString(value.url) ?? nonEmptyString(value.source_website_url);
|
|
197
|
+
if (url === void 0) return;
|
|
198
|
+
const title = nonEmptyString(value.title);
|
|
199
|
+
const snippet = forcedSnippet ?? nonEmptyString(value.snippet) ?? nonEmptyString(value.caption);
|
|
200
|
+
const publishedAt = nonEmptyString(value.page_age) ?? nonEmptyString(value.published_at);
|
|
201
|
+
const existing = target.get(url);
|
|
202
|
+
if (existing !== void 0) {
|
|
203
|
+
if (existing.title === void 0 && title !== void 0) existing.title = title;
|
|
204
|
+
if (existing.snippet === void 0 && snippet !== void 0) existing.snippet = snippet;
|
|
205
|
+
if (existing.publishedAt === void 0 && publishedAt !== void 0) existing.publishedAt = publishedAt;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const source = { url };
|
|
209
|
+
if (title !== void 0) source.title = title;
|
|
210
|
+
if (snippet !== void 0) source.snippet = snippet;
|
|
211
|
+
if (publishedAt !== void 0) source.publishedAt = publishedAt;
|
|
212
|
+
target.set(url, source);
|
|
213
|
+
}
|
|
214
|
+
function citationRecord(value) {
|
|
215
|
+
const annotation = asRecord(value);
|
|
216
|
+
return asRecord(annotation?.url_citation) ?? annotation;
|
|
217
|
+
}
|
|
218
|
+
function asRecord(value) {
|
|
219
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
220
|
+
}
|
|
221
|
+
function asArray(value) {
|
|
222
|
+
return Array.isArray(value) ? value : [];
|
|
223
|
+
}
|
|
224
|
+
function nonEmptyString(value) {
|
|
225
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
226
|
+
}
|
|
227
|
+
function providerError(message) {
|
|
228
|
+
return new WebError(`dsh-web-search-enhanced: ${message}`, "WEB_PROVIDER_ERROR");
|
|
229
|
+
}
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region lib/types/provider.js
|
|
232
|
+
/** Multi-protocol search provider. One resolved settings snapshot serves each operation. */
|
|
233
|
+
var EnhancedSearchProvider = class {
|
|
234
|
+
resolveConfig;
|
|
235
|
+
fetcher;
|
|
236
|
+
resolveApiKey;
|
|
237
|
+
/** Stable provider id selected by ctx.web. */
|
|
238
|
+
id;
|
|
239
|
+
/** Create a provider backed by a dynamic settings resolver. */
|
|
240
|
+
constructor(resolveConfig, fetcher = globalThis.fetch, resolveApiKey = async (config) => config.apiKey ?? process.env[config.apiKeyEnv]) {
|
|
241
|
+
this.resolveConfig = resolveConfig;
|
|
242
|
+
this.fetcher = fetcher;
|
|
243
|
+
this.resolveApiKey = resolveApiKey;
|
|
244
|
+
this.id = resolveConfig().providerId;
|
|
245
|
+
}
|
|
246
|
+
/** Report whether local configuration can attempt a request without network I/O. */
|
|
247
|
+
available() {
|
|
248
|
+
try {
|
|
249
|
+
const config = this.resolveConfig();
|
|
250
|
+
return URL.canParse(config.baseURL) && config.model.length > 0 && config.apiKeyEnv.length > 0 && Number.isInteger(config.maxTokens) && config.maxTokens > 0;
|
|
251
|
+
} catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/** Execute one server-side web search and normalize its citations. */
|
|
256
|
+
async search(request, signal) {
|
|
257
|
+
const config = this.resolveConfig();
|
|
258
|
+
throwIfAborted(signal);
|
|
259
|
+
const apiKey = config.apiKey ?? await resolveCredential(this.resolveApiKey(config), signal);
|
|
260
|
+
if (apiKey === void 0 || apiKey.length === 0) throw new WebError(`dsh-web-search-enhanced: no API key for "${config.apiKeyEnv}"`, "WEB_PROVIDER_CREDENTIAL_MISSING");
|
|
261
|
+
const wire = buildWireRequest(config, request.query, apiKey);
|
|
262
|
+
let response;
|
|
263
|
+
try {
|
|
264
|
+
response = await this.fetcher(wire.endpoint, {
|
|
265
|
+
method: "POST",
|
|
266
|
+
redirect: "error",
|
|
267
|
+
headers: wire.headers,
|
|
268
|
+
body: JSON.stringify(wire.body),
|
|
269
|
+
...signal === void 0 ? {} : { signal }
|
|
270
|
+
});
|
|
271
|
+
} catch (error) {
|
|
272
|
+
if (signal?.aborted === true || isAbortError(error)) throw aborted(signal, error);
|
|
273
|
+
throw new WebError(`dsh-web-search-enhanced: ${config.protocol} request failed: ${String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
|
|
274
|
+
}
|
|
275
|
+
if (!response.ok) {
|
|
276
|
+
let detail = `HTTP ${response.status}`;
|
|
277
|
+
try {
|
|
278
|
+
const payload = await response.json();
|
|
279
|
+
const root = typeof payload === "object" && payload !== null ? payload : void 0;
|
|
280
|
+
const candidate = (typeof root?.error === "object" && root.error !== null ? root.error : void 0)?.message ?? root?.message;
|
|
281
|
+
if (typeof candidate === "string" && candidate.length > 0) detail = candidate;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
if (signal?.aborted === true || isAbortError(error)) throw aborted(signal, error);
|
|
284
|
+
}
|
|
285
|
+
throw new WebError(`dsh-web-search-enhanced: upstream API error (${detail})`, "WEB_PROVIDER_ERROR");
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
const payload = await response.json();
|
|
289
|
+
return parseSearchResponse(config.protocol, payload);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (signal?.aborted === true || isAbortError(error)) throw aborted(signal, error);
|
|
292
|
+
if (error instanceof WebError) throw error;
|
|
293
|
+
throw new WebError(`dsh-web-search-enhanced: unprocessable ${config.protocol} response: ${String(error)}`, "WEB_PROVIDER_ERROR", { cause: error });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
async function resolveCredential(pending, signal) {
|
|
298
|
+
if (signal === void 0) return await pending;
|
|
299
|
+
throwIfAborted(signal);
|
|
300
|
+
return await new Promise((resolve, reject) => {
|
|
301
|
+
const onAbort = () => {
|
|
302
|
+
reject(aborted(signal));
|
|
303
|
+
};
|
|
304
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
305
|
+
pending.then((value) => {
|
|
306
|
+
signal.removeEventListener("abort", onAbort);
|
|
307
|
+
resolve(value);
|
|
308
|
+
}, (error) => {
|
|
309
|
+
signal.removeEventListener("abort", onAbort);
|
|
310
|
+
reject(error);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
function throwIfAborted(signal) {
|
|
315
|
+
if (signal?.aborted === true) throw aborted(signal);
|
|
316
|
+
}
|
|
317
|
+
function aborted(signal, fallback) {
|
|
318
|
+
return new WebError("dsh-web-search-enhanced: search aborted", "WEB_ABORTED", { cause: signal?.aborted === true ? signal.reason : fallback });
|
|
319
|
+
}
|
|
320
|
+
function isAbortError(error) {
|
|
321
|
+
return error instanceof DOMException && error.name === "AbortError";
|
|
322
|
+
}
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region lib/types/index.js
|
|
325
|
+
/** Provider id selected by the bundled Web Profile patch. */
|
|
326
|
+
const DEFAULT_PROVIDER_ID = "enhanced-search";
|
|
327
|
+
/** Default Anthropic-compatible search endpoint. */
|
|
328
|
+
const DEFAULT_BASE_URL = "https://api.deepseek.com/anthropic/v1";
|
|
329
|
+
/** Default model aligned with DSH's built-in search provider. */
|
|
330
|
+
const DEFAULT_MODEL = "deepseek-v4-flash";
|
|
331
|
+
/** Credential reference owned by this plugin's fixed and fallback routes. */
|
|
332
|
+
const DEFAULT_API_KEY_ENV = "WEB_SEARCH_ENHANCED_API";
|
|
333
|
+
/** Settings namespace paired with the plugin configuration card. */
|
|
334
|
+
const SETTINGS_NAMESPACE = settingsNamespace("web-search-enhanced");
|
|
335
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
336
|
+
const name = "web-search-enhanced";
|
|
337
|
+
/** The web seam this provider contributes to. */
|
|
338
|
+
const inject = ["web"];
|
|
339
|
+
/** Schema shared by composition defaults and the live settings section. */
|
|
340
|
+
const Config = z.object({
|
|
341
|
+
providerId: z.string().default(DEFAULT_PROVIDER_ID),
|
|
342
|
+
modelMode: z.union(["configured", "current-session"]).default("configured"),
|
|
343
|
+
protocol: z.union([
|
|
344
|
+
"anthropic-messages",
|
|
345
|
+
"openai-responses",
|
|
346
|
+
"openai-chat-completions"
|
|
347
|
+
]).default("anthropic-messages"),
|
|
348
|
+
baseURL: z.string().default(DEFAULT_BASE_URL),
|
|
349
|
+
model: z.string().default(DEFAULT_MODEL),
|
|
350
|
+
fallbackModel: z.string(),
|
|
351
|
+
apiKey: z.string().role("secret"),
|
|
352
|
+
apiKeyEnv: z.string().role("credential-ref").default(DEFAULT_API_KEY_ENV),
|
|
353
|
+
apiVersion: z.string().default("2023-06-01"),
|
|
354
|
+
toolIdentifier: z.string(),
|
|
355
|
+
maxTokens: z.number().step(1).min(1).default(4096),
|
|
356
|
+
maxUses: z.number().step(1).min(1).default(5),
|
|
357
|
+
chatSearchMode: z.union(["search-model", "vendor-options"]).default("search-model"),
|
|
358
|
+
searchContextSize: z.union([
|
|
359
|
+
"low",
|
|
360
|
+
"medium",
|
|
361
|
+
"high"
|
|
362
|
+
])
|
|
363
|
+
});
|
|
364
|
+
/** Resolve and validate one fixed/fallback route. */
|
|
365
|
+
function resolveConfig(config) {
|
|
366
|
+
const protocol = config.protocol ?? "anthropic-messages";
|
|
367
|
+
const model = config.model ?? "deepseek-v4-flash";
|
|
368
|
+
const resolved = {
|
|
369
|
+
providerId: config.providerId ?? "enhanced-search",
|
|
370
|
+
modelMode: config.modelMode ?? "configured",
|
|
371
|
+
protocol,
|
|
372
|
+
baseURL: config.baseURL ?? "https://api.deepseek.com/anthropic/v1",
|
|
373
|
+
model,
|
|
374
|
+
fallbackModel: config.fallbackModel?.trim() || void 0,
|
|
375
|
+
apiKey: config.apiKey,
|
|
376
|
+
apiKeyEnv: config.apiKeyEnv ?? "WEB_SEARCH_ENHANCED_API",
|
|
377
|
+
apiVersion: config.apiVersion ?? "2023-06-01",
|
|
378
|
+
toolIdentifier: config.toolIdentifier?.trim() || defaultToolIdentifier(protocol),
|
|
379
|
+
maxTokens: config.maxTokens ?? 4096,
|
|
380
|
+
maxUses: config.maxUses ?? 5,
|
|
381
|
+
chatSearchMode: config.chatSearchMode ?? "search-model",
|
|
382
|
+
searchContextSize: config.searchContextSize
|
|
383
|
+
};
|
|
384
|
+
assertResolvedConfig(resolved);
|
|
385
|
+
return resolved;
|
|
386
|
+
}
|
|
387
|
+
function contextService(ctx, name) {
|
|
388
|
+
try {
|
|
389
|
+
const direct = ctx[name];
|
|
390
|
+
if (direct !== void 0) return direct;
|
|
391
|
+
} catch {}
|
|
392
|
+
return typeof ctx.get === "function" ? ctx.get(name) : void 0;
|
|
393
|
+
}
|
|
394
|
+
function currentSelection(ctx) {
|
|
395
|
+
const initiator = contextService(ctx, "agents")?.currentInitiator?.();
|
|
396
|
+
const requestSelection = initiator?.session?.requestHeader?.()?.config;
|
|
397
|
+
if (requestSelection?.provider && requestSelection.model) return requestSelection;
|
|
398
|
+
if (initiator?.options?.provider && initiator.options.model) return initiator.options;
|
|
399
|
+
return contextService(ctx, "agentDefaultModel")?.currentSelection?.();
|
|
400
|
+
}
|
|
401
|
+
function routeProfile(ctx, provider) {
|
|
402
|
+
return (contextService(ctx, "settings")?.get("llm-pi-ai"))?.providers?.[provider];
|
|
403
|
+
}
|
|
404
|
+
function searchProtocolOf(api) {
|
|
405
|
+
switch (api) {
|
|
406
|
+
case "anthropic-messages": return "anthropic-messages";
|
|
407
|
+
case "openai-responses": return "openai-responses";
|
|
408
|
+
case "openai-completions":
|
|
409
|
+
case "openai-chat-completions": return "openai-chat-completions";
|
|
410
|
+
default: return;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Resolve the current Session route at search time. The fixed route is also the
|
|
415
|
+
* fallback route, so a configured fallback remains useful outside an Agent turn.
|
|
416
|
+
*/
|
|
417
|
+
async function resolveRuntimeConfig(ctx, config) {
|
|
418
|
+
const requestedMode = config.modelMode ?? "configured";
|
|
419
|
+
const fixed = resolveConfig({
|
|
420
|
+
...config,
|
|
421
|
+
modelMode: "configured"
|
|
422
|
+
});
|
|
423
|
+
const fallback = fixed.fallbackModel === void 0 ? fixed : resolveConfig({
|
|
424
|
+
...config,
|
|
425
|
+
modelMode: "configured",
|
|
426
|
+
model: fixed.fallbackModel
|
|
427
|
+
});
|
|
428
|
+
if (requestedMode === "configured") return fixed;
|
|
429
|
+
const selection = currentSelection(ctx);
|
|
430
|
+
if (!selection?.provider || !selection.model) return fallback;
|
|
431
|
+
const route = routeProfile(ctx, selection.provider);
|
|
432
|
+
const llm = contextService(ctx, "llm");
|
|
433
|
+
let modelInfo;
|
|
434
|
+
try {
|
|
435
|
+
modelInfo = await llm?.resolveModelInfo?.(selection.provider, selection.model);
|
|
436
|
+
} catch {
|
|
437
|
+
return fallback;
|
|
438
|
+
}
|
|
439
|
+
const protocol = searchProtocolOf(modelInfo?.api ?? route?.api);
|
|
440
|
+
if (protocol === void 0 || route?.baseURL === void 0 || route.baseURL.length === 0) return fallback;
|
|
441
|
+
const { apiKey: _literalApiKey, ...withoutLiteralKey } = config;
|
|
442
|
+
return resolveConfig({
|
|
443
|
+
...withoutLiteralKey,
|
|
444
|
+
modelMode: "configured",
|
|
445
|
+
model: selection.model,
|
|
446
|
+
protocol,
|
|
447
|
+
baseURL: route.baseURL,
|
|
448
|
+
apiKeyEnv: route.apiKeyEnv ?? fixed.apiKeyEnv
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
/** Construct a fixed-config provider for tests or custom compositions. */
|
|
452
|
+
function createProvider(config = {}, fetcher = globalThis.fetch) {
|
|
453
|
+
const resolved = resolveConfig(config);
|
|
454
|
+
return new EnhancedSearchProvider(() => resolved, fetcher);
|
|
455
|
+
}
|
|
456
|
+
/** Register the provider and its live Web Profile settings section. */
|
|
457
|
+
function apply(ctx, config) {
|
|
458
|
+
const providerId = resolveConfig(config).providerId;
|
|
459
|
+
let current = () => config;
|
|
460
|
+
installSettingsSection(ctx, SETTINGS_NAMESPACE, Config, config, {
|
|
461
|
+
setSource: (source) => {
|
|
462
|
+
current = source;
|
|
463
|
+
},
|
|
464
|
+
onChange: () => {},
|
|
465
|
+
validate: (value) => {
|
|
466
|
+
if (resolveConfig(value).providerId !== providerId) throw new Error("dsh-web-search-enhanced: providerId cannot be changed through live settings");
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
const dynamicProvider = {
|
|
470
|
+
id: providerId,
|
|
471
|
+
available: () => {
|
|
472
|
+
try {
|
|
473
|
+
resolveConfig(current());
|
|
474
|
+
return true;
|
|
475
|
+
} catch {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
search: async (request, signal) => {
|
|
480
|
+
const resolved = await resolveRuntimeConfig(ctx, current());
|
|
481
|
+
return await new EnhancedSearchProvider(() => resolved, globalThis.fetch, async (runtime) => {
|
|
482
|
+
if (runtime.apiKey !== void 0 && runtime.apiKey.length > 0) return runtime.apiKey;
|
|
483
|
+
const ref = credentialRef(runtime.apiKeyEnv);
|
|
484
|
+
const credentials = ctx.get("credentials");
|
|
485
|
+
if (credentials !== void 0) return (await credentials.resolve(ref))?.value;
|
|
486
|
+
return launchEnvironmentOf(ctx).get(runtime.apiKeyEnv)?.value;
|
|
487
|
+
}).search(request, signal);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
ctx.web.registerSearchProvider(dynamicProvider);
|
|
491
|
+
}
|
|
492
|
+
function assertResolvedConfig(config) {
|
|
493
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(config.providerId)) throw new Error("dsh-web-search-enhanced: providerId must be 1-128 letters, digits, dots, underscores, or hyphens");
|
|
494
|
+
if (!URL.canParse(config.baseURL)) throw new Error("dsh-web-search-enhanced: baseURL must be an absolute URL");
|
|
495
|
+
const endpointBase = new URL(config.baseURL);
|
|
496
|
+
if (endpointBase.protocol !== "http:" && endpointBase.protocol !== "https:") throw new Error("dsh-web-search-enhanced: baseURL protocol must be HTTP or HTTPS");
|
|
497
|
+
if (endpointBase.username.length > 0 || endpointBase.password.length > 0 || endpointBase.search.length > 0 || endpointBase.hash.length > 0) throw new Error("dsh-web-search-enhanced: baseURL must not contain credentials, query parameters, or a fragment");
|
|
498
|
+
if (config.model.trim().length === 0) throw new Error("dsh-web-search-enhanced: model must not be blank");
|
|
499
|
+
if (config.fallbackModel !== void 0 && config.fallbackModel.trim().length === 0) throw new Error("dsh-web-search-enhanced: fallbackModel must not be blank when provided");
|
|
500
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(config.apiKeyEnv)) throw new Error("dsh-web-search-enhanced: apiKeyEnv must be a valid credential reference");
|
|
501
|
+
if (config.apiVersion.trim().length === 0) throw new Error("dsh-web-search-enhanced: apiVersion must not be blank");
|
|
502
|
+
if (!/^[A-Za-z][A-Za-z0-9_.:-]{0,127}$/u.test(config.toolIdentifier)) throw new Error("dsh-web-search-enhanced: toolIdentifier must be 1-128 identifier characters and start with a letter");
|
|
503
|
+
if (!Number.isInteger(config.maxTokens) || config.maxTokens <= 0) throw new Error("dsh-web-search-enhanced: maxTokens must be a positive integer");
|
|
504
|
+
if (!Number.isInteger(config.maxUses) || config.maxUses <= 0) throw new Error("dsh-web-search-enhanced: maxUses must be a positive integer");
|
|
505
|
+
}
|
|
506
|
+
//#endregion
|
|
507
|
+
export { Config, DEFAULT_API_KEY_ENV, DEFAULT_BASE_URL, DEFAULT_MODEL, DEFAULT_PROVIDER_ID, EnhancedSearchProvider, SETTINGS_NAMESPACE, apply, createProvider, inject, name, resolveConfig, resolveRuntimeConfig };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { SettingsScope } from '@deepseek-ai/dsh-client-runtime/client';
|
|
2
|
+
interface CredentialRemoteFailure {
|
|
3
|
+
ok: false;
|
|
4
|
+
error: {
|
|
5
|
+
message: string;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
interface CredentialRemoteSuccess<T> {
|
|
9
|
+
ok: true;
|
|
10
|
+
value: T;
|
|
11
|
+
}
|
|
12
|
+
type CredentialRemoteResult<T> = CredentialRemoteSuccess<T> | CredentialRemoteFailure;
|
|
13
|
+
export interface CredentialRemote {
|
|
14
|
+
describe: (refs: string[]) => Promise<CredentialRemoteResult<Record<string, {
|
|
15
|
+
configured: boolean;
|
|
16
|
+
writable?: boolean;
|
|
17
|
+
}>>>;
|
|
18
|
+
set: (ref: string, value: string) => Promise<CredentialRemoteResult<unknown>>;
|
|
19
|
+
}
|
|
20
|
+
import type { LocaleKey } from './locales.ts';
|
|
21
|
+
export type SearchProtocol = 'anthropic-messages' | 'openai-responses' | 'openai-chat-completions';
|
|
22
|
+
export type ModelMode = 'configured' | 'current-session';
|
|
23
|
+
export interface SearchSettings {
|
|
24
|
+
modelMode?: ModelMode;
|
|
25
|
+
protocol?: SearchProtocol;
|
|
26
|
+
baseURL?: string;
|
|
27
|
+
model?: string;
|
|
28
|
+
fallbackModel?: string;
|
|
29
|
+
apiKeyEnv?: string;
|
|
30
|
+
apiVersion?: string;
|
|
31
|
+
toolIdentifier?: string;
|
|
32
|
+
maxTokens?: number;
|
|
33
|
+
maxUses?: number;
|
|
34
|
+
chatSearchMode?: 'search-model' | 'vendor-options';
|
|
35
|
+
searchContextSize?: 'low' | 'medium' | 'high';
|
|
36
|
+
}
|
|
37
|
+
export interface SearchSettingsCardProps {
|
|
38
|
+
scope: SettingsScope<SearchSettings>;
|
|
39
|
+
credentials: CredentialRemote;
|
|
40
|
+
t: (key: LocaleKey) => string;
|
|
41
|
+
}
|
|
42
|
+
interface Draft {
|
|
43
|
+
modelMode: ModelMode;
|
|
44
|
+
protocol: SearchProtocol;
|
|
45
|
+
baseURL: string;
|
|
46
|
+
model: string;
|
|
47
|
+
fallbackModel: string;
|
|
48
|
+
apiKeyEnv: string;
|
|
49
|
+
apiVersion: string;
|
|
50
|
+
toolIdentifier: string;
|
|
51
|
+
maxTokens: string;
|
|
52
|
+
maxUses: string;
|
|
53
|
+
chatSearchMode: 'search-model' | 'vendor-options';
|
|
54
|
+
searchContextSize: '' | 'low' | 'medium' | 'high';
|
|
55
|
+
}
|
|
56
|
+
/** Convert resolved settings into a stable form draft. */
|
|
57
|
+
export declare function draftFrom(value: SearchSettings | undefined): Draft;
|
|
58
|
+
/** Validate a draft and return field-specific locale keys. */
|
|
59
|
+
export declare function saveCredential(credentials: CredentialRemote, ref: string, value: string): Promise<boolean>;
|
|
60
|
+
export declare function validateDraft(draft: Draft): Partial<Record<keyof Draft, LocaleKey>>;
|
|
61
|
+
export declare function SearchSettingsCard({ scope, credentials, t }: SearchSettingsCardProps): import("react").JSX.Element | null;
|
|
62
|
+
export {};
|