@konbakuyomu/smart-search-darwin-arm64 0.1.32 → 0.1.34

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.
Files changed (28) hide show
  1. package/package.json +1 -1
  2. package/runtime/smart-search/_internal/base_library.zip +0 -0
  3. package/runtime/smart-search/_internal/smart_search/assets/i18n/messages.json +96 -4
  4. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/SKILL.md +1 -1
  5. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/cli-core.md +1 -1
  6. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/command-patterns.md +2 -0
  7. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/provider-routing.md +2 -1
  8. package/runtime/smart-search/_internal/smart_search/assets/skills/smart-search-cli/references/setup-config.md +1 -0
  9. package/runtime/smart-search/_internal/smart_search/cli.py +27 -14
  10. package/runtime/smart-search/_internal/smart_search/config.py +59 -8
  11. package/runtime/smart-search/_internal/smart_search/jev_search.py +3 -0
  12. package/runtime/smart-search/_internal/smart_search/providers/openai_compatible.py +32 -2
  13. package/runtime/smart-search/_internal/smart_search/providers/xai_responses.py +14 -1
  14. package/runtime/smart-search/_internal/smart_search/service.py +51 -17
  15. package/runtime/smart-search/_internal/smart_search/sources.py +12 -0
  16. package/runtime/smart-search/_internal/smart_search/ui_metadata.py +11 -2
  17. package/runtime/smart-search/_internal/smart_search/utils.py +7 -3
  18. package/runtime/smart-search/_internal/{smart_search-0.1.32.dist-info → smart_search-0.1.34.dist-info}/METADATA +1 -1
  19. package/runtime/smart-search/_internal/{smart_search-0.1.32.dist-info → smart_search-0.1.34.dist-info}/RECORD +24 -24
  20. package/runtime/smart-search/package.json +7 -7
  21. package/runtime/smart-search/smart-search +0 -0
  22. /package/runtime/smart-search/_internal/{smart_search-0.1.32.dist-info → smart_search-0.1.34.dist-info}/INSTALLER +0 -0
  23. /package/runtime/smart-search/_internal/{smart_search-0.1.32.dist-info → smart_search-0.1.34.dist-info}/REQUESTED +0 -0
  24. /package/runtime/smart-search/_internal/{smart_search-0.1.32.dist-info → smart_search-0.1.34.dist-info}/WHEEL +0 -0
  25. /package/runtime/smart-search/_internal/{smart_search-0.1.32.dist-info → smart_search-0.1.34.dist-info}/direct_url.json +0 -0
  26. /package/runtime/smart-search/_internal/{smart_search-0.1.32.dist-info → smart_search-0.1.34.dist-info}/entry_points.txt +0 -0
  27. /package/runtime/smart-search/_internal/{smart_search-0.1.32.dist-info → smart_search-0.1.34.dist-info}/licenses/LICENSE +0 -0
  28. /package/runtime/smart-search/_internal/{smart_search-0.1.32.dist-info → smart_search-0.1.34.dist-info}/top_level.txt +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konbakuyomu/smart-search-darwin-arm64",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "Self-contained Smart Search CLI for darwin/arm64",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1447,6 +1447,54 @@
1447
1447
  "TinyFish 搜索与抓取 API 均可用",
1448
1448
  "Both TinyFish search and fetch APIs are available"
1449
1449
  ],
1450
+ "Invalid OPENAI_COMPATIBLE_TOOLS: {0}. Supported values: {1}": [
1451
+ "无效的 OPENAI_COMPATIBLE_TOOLS:{0}。支持:{1}",
1452
+ "Invalid OPENAI_COMPATIBLE_TOOLS: {0}. Supported values: {1}"
1453
+ ],
1454
+ "OPENAI_COMPATIBLE_TOOLS only works with OPENAI_COMPATIBLE_API_MODE=responses. Set the API mode to responses, or clear OPENAI_COMPATIBLE_TOOLS.": [
1455
+ "OPENAI_COMPATIBLE_TOOLS 只在 OPENAI_COMPATIBLE_API_MODE=responses 时生效。请把接口模式改成 responses,或清空 OPENAI_COMPATIBLE_TOOLS。",
1456
+ "OPENAI_COMPATIBLE_TOOLS only works with OPENAI_COMPATIBLE_API_MODE=responses. Set the API mode to responses, or clear OPENAI_COMPATIBLE_TOOLS."
1457
+ ],
1458
+ "OPENAI_COMPATIBLE_TOOLS 只在 OPENAI_COMPATIBLE_API_MODE=responses 时生效。请把接口模式改成 responses,或清空 OPENAI_COMPATIBLE_TOOLS。": [
1459
+ "OPENAI_COMPATIBLE_TOOLS 只在 OPENAI_COMPATIBLE_API_MODE=responses 时生效。请把接口模式改成 responses,或清空 OPENAI_COMPATIBLE_TOOLS。",
1460
+ "OPENAI_COMPATIBLE_TOOLS only works with OPENAI_COMPATIBLE_API_MODE=responses. Set the API mode to responses, or clear OPENAI_COMPATIBLE_TOOLS."
1461
+ ],
1462
+ "OpenAI 兼容联网工具": [
1463
+ "OpenAI 兼容联网工具",
1464
+ "openai-compatible tools"
1465
+ ],
1466
+ "OpenAI-compatible tools are invalid.": [
1467
+ "OpenAI-compatible 联网工具配置无效。",
1468
+ "OpenAI-compatible tools are invalid."
1469
+ ],
1470
+ "OpenAI-compatible was rate limited upstream (HTTP 429), so this diagnosis cannot tell whether the route works.": [
1471
+ "OpenAI-compatible 被上游限流(HTTP 429),这次诊断判断不了链路是否正常。",
1472
+ "OpenAI-compatible was rate limited upstream (HTTP 429), so this diagnosis cannot tell whether the route works."
1473
+ ],
1474
+ "OpenAI-compatible 联网工具配置无效。": [
1475
+ "OpenAI-compatible 联网工具配置无效。",
1476
+ "OpenAI-compatible tools are invalid."
1477
+ ],
1478
+ "OpenAI-compatible 被上游限流(HTTP 429),这次诊断判断不了链路是否正常。": [
1479
+ "OpenAI-compatible 被上游限流(HTTP 429),这次诊断判断不了链路是否正常。",
1480
+ "OpenAI-compatible was rate limited upstream (HTTP 429), so this diagnosis cannot tell whether the route works."
1481
+ ],
1482
+ "Run this diagnosis again in a minute; if it keeps happening, lower concurrency, switch models, or raise the account's rate limit. The URL, key and model name are not necessarily wrong.": [
1483
+ "等一分钟再运行本诊断;经常出现就降低并发、换模型,或提高账户的速率额度。地址、key 和模型名不一定有问题。",
1484
+ "Run this diagnosis again in a minute; if it keeps happening, lower concurrency, switch models, or raise the account's rate limit. The URL, key and model name are not necessarily wrong."
1485
+ ],
1486
+ "Save OPENAI_COMPATIBLE_TOOLS (web_search or web_search_preview; needs --openai-compatible-api-mode responses).": [
1487
+ "保存 OPENAI_COMPATIBLE_TOOLS(web_search 或 web_search_preview;需要 --openai-compatible-api-mode responses)。",
1488
+ "Save OPENAI_COMPATIBLE_TOOLS (web_search or web_search_preview; needs --openai-compatible-api-mode responses)."
1489
+ ],
1490
+ "Strict validation needs sources that a search service retrieved on its own; this run only has the search model's own citations, which are not independent evidence. Configure a web_search provider (for example Tavily, Zhipu or Yuanbao Search) before using strict, or switch to balanced.": [
1491
+ "strict 校验需要搜索服务独立检索到的来源;这次只有搜索模型自带的引用,不算独立证据。配置一个 web_search 提供方(例如 Tavily、智谱、元宝搜索)后再用 strict,或改用 balanced。",
1492
+ "Strict validation needs sources that a search service retrieved on its own; this run only has the search model's own citations, which are not independent evidence. Configure a web_search provider (for example Tavily, Zhipu or Yuanbao Search) before using strict, or switch to balanced."
1493
+ ],
1494
+ "Use OPENAI_COMPATIBLE_TOOLS=web_search (or web_search_preview) only with OPENAI_COMPATIBLE_API_MODE=responses; otherwise leave it empty.": [
1495
+ "只在 OPENAI_COMPATIBLE_API_MODE=responses 时把 OPENAI_COMPATIBLE_TOOLS 设为 web_search(或 web_search_preview);其他情况留空。",
1496
+ "Use OPENAI_COMPATIBLE_TOOLS=web_search (or web_search_preview) only with OPENAI_COMPATIBLE_API_MODE=responses; otherwise leave it empty."
1497
+ ],
1450
1498
  "Yuanbao Search API is available": [
1451
1499
  "元宝搜索 API 可用",
1452
1500
  "Yuanbao Search API is available"
@@ -4663,6 +4711,14 @@
4663
4711
  "OpenAI 兼容 API 模式",
4664
4712
  "openai-compatible API mode"
4665
4713
  ],
4714
+ "openai-compatible tools": [
4715
+ "OpenAI 兼容联网工具",
4716
+ "openai-compatible tools"
4717
+ ],
4718
+ "strict 校验需要搜索服务独立检索到的来源;这次只有搜索模型自带的引用,不算独立证据。配置一个 web_search 提供方(例如 Tavily、智谱、元宝搜索)后再用 strict,或改用 balanced。": [
4719
+ "strict 校验需要搜索服务独立检索到的来源;这次只有搜索模型自带的引用,不算独立证据。配置一个 web_search 提供方(例如 Tavily、智谱、元宝搜索)后再用 strict,或改用 balanced。",
4720
+ "Strict validation needs sources that a search service retrieved on its own; this run only has the search model's own citations, which are not independent evidence. Configure a web_search provider (for example Tavily, Zhipu or Yuanbao Search) before using strict, or switch to balanced."
4721
+ ],
4666
4722
  "xai-responses 流式": [
4667
4723
  "xai-responses 流式",
4668
4724
  "xai-responses stream"
@@ -7423,6 +7479,34 @@
7423
7479
  "TinyFish 搜索与抓取 API 均可用",
7424
7480
  "Both TinyFish search and fetch APIs are available"
7425
7481
  ],
7482
+ "{0} Also, the web search capability check did not pass: {1}. If this model can only search through the Responses web_search tool (for example OpenAI's gpt-6.1-sol), set OPENAI_COMPATIBLE_API_MODE=responses and OPENAI_COMPATIBLE_TOOLS=web_search; use web_search_preview for relays that only know the older name.": [
7483
+ "{0} 另外,联网搜索能力检查未通过:{1}。如果这个模型只能通过 Responses 的 web_search 工具联网(例如 OpenAI 官方的 gpt-6.1-sol),请设置 OPENAI_COMPATIBLE_API_MODE=responses 和 OPENAI_COMPATIBLE_TOOLS=web_search;中转站只认旧名时改用 web_search_preview。",
7484
+ "{0} Also, the web search capability check did not pass: {1}. If this model can only search through the Responses web_search tool (for example OpenAI's gpt-6.1-sol), set OPENAI_COMPATIBLE_API_MODE=responses and OPENAI_COMPATIBLE_TOOLS=web_search; use web_search_preview for relays that only know the older name."
7485
+ ],
7486
+ "{0} Also, the web search capability check did not pass: {1}. The request already carried the {2} tool, so this model or relay may not support Responses web search tools; switch to a model that does, or clear OPENAI_COMPATIBLE_TOOLS and use a model that searches on its own.": [
7487
+ "{0} 另外,联网搜索能力检查未通过:{1}。请求里已经带了 {2} 工具,可能是这个模型或中转站不支持 Responses 的联网工具;换一个支持的模型,或清空 OPENAI_COMPATIBLE_TOOLS 改用自带搜索的模型。",
7488
+ "{0} Also, the web search capability check did not pass: {1}. The request already carried the {2} tool, so this model or relay may not support Responses web search tools; switch to a model that does, or clear OPENAI_COMPATIBLE_TOOLS and use a model that searches on its own."
7489
+ ],
7490
+ "{0} does not support x_search in XAI_TOOLS (HTTP 400). Only the official xAI API serves x_search; set XAI_TOOLS to web_search and try again.": [
7491
+ "{0} 不支持 XAI_TOOLS 里的 x_search(HTTP 400)。x_search 只有 xAI 官方接口能用;把 XAI_TOOLS 改成 web_search 再试。",
7492
+ "{0} does not support x_search in XAI_TOOLS (HTTP 400). Only the official xAI API serves x_search; set XAI_TOOLS to web_search and try again."
7493
+ ],
7494
+ "{0} 不支持 XAI_TOOLS 里的 x_search(HTTP 400)。x_search 只有 xAI 官方接口能用;把 XAI_TOOLS 改成 web_search 再试。": [
7495
+ "{0} 不支持 XAI_TOOLS 里的 x_search(HTTP 400)。x_search 只有 xAI 官方接口能用;把 XAI_TOOLS 改成 web_search 再试。",
7496
+ "{0} does not support x_search in XAI_TOOLS (HTTP 400). Only the official xAI API serves x_search; set XAI_TOOLS to web_search and try again."
7497
+ ],
7498
+ "{0} 另外,联网搜索能力检查未通过:{1}。如果这个模型只能通过 Responses 的 web_search 工具联网(例如 OpenAI 官方的 gpt-6.1-sol),请设置 OPENAI_COMPATIBLE_API_MODE=responses 和 OPENAI_COMPATIBLE_TOOLS=web_search;中转站只认旧名时改用 web_search_preview。": [
7499
+ "{0} 另外,联网搜索能力检查未通过:{1}。如果这个模型只能通过 Responses 的 web_search 工具联网(例如 OpenAI 官方的 gpt-6.1-sol),请设置 OPENAI_COMPATIBLE_API_MODE=responses 和 OPENAI_COMPATIBLE_TOOLS=web_search;中转站只认旧名时改用 web_search_preview。",
7500
+ "{0} Also, the web search capability check did not pass: {1}. If this model can only search through the Responses web_search tool (for example OpenAI's gpt-6.1-sol), set OPENAI_COMPATIBLE_API_MODE=responses and OPENAI_COMPATIBLE_TOOLS=web_search; use web_search_preview for relays that only know the older name."
7501
+ ],
7502
+ "{0} 另外,联网搜索能力检查未通过:{1}。请求里已经带了 {2} 工具,可能是这个模型或中转站不支持 Responses 的联网工具;换一个支持的模型,或清空 OPENAI_COMPATIBLE_TOOLS 改用自带搜索的模型。": [
7503
+ "{0} 另外,联网搜索能力检查未通过:{1}。请求里已经带了 {2} 工具,可能是这个模型或中转站不支持 Responses 的联网工具;换一个支持的模型,或清空 OPENAI_COMPATIBLE_TOOLS 改用自带搜索的模型。",
7504
+ "{0} Also, the web search capability check did not pass: {1}. The request already carried the {2} tool, so this model or relay may not support Responses web search tools; switch to a model that does, or clear OPENAI_COMPATIBLE_TOOLS and use a model that searches on its own."
7505
+ ],
7506
+ "保存 OPENAI_COMPATIBLE_TOOLS(web_search 或 web_search_preview;需要 --openai-compatible-api-mode responses)。": [
7507
+ "保存 OPENAI_COMPATIBLE_TOOLS(web_search 或 web_search_preview;需要 --openai-compatible-api-mode responses)。",
7508
+ "Save OPENAI_COMPATIBLE_TOOLS (web_search or web_search_preview; needs --openai-compatible-api-mode responses)."
7509
+ ],
7426
7510
  "元宝搜索 API 可用": [
7427
7511
  "元宝搜索 API 可用",
7428
7512
  "Yuanbao Search API is available"
@@ -8783,6 +8867,10 @@
8783
8867
  "可选;设置后在 xAI Responses 请求中发送 reasoning.effort,默认不发送。",
8784
8868
  "Optional; when set, sends reasoning.effort in xAI Responses requests; omitted by default."
8785
8869
  ],
8870
+ "只在 OPENAI_COMPATIBLE_API_MODE=responses 时把 OPENAI_COMPATIBLE_TOOLS 设为 web_search(或 web_search_preview);其他情况留空。": [
8871
+ "只在 OPENAI_COMPATIBLE_API_MODE=responses 时把 OPENAI_COMPATIBLE_TOOLS 设为 web_search(或 web_search_preview);其他情况留空。",
8872
+ "Use OPENAI_COMPATIBLE_TOOLS=web_search (or web_search_preview) only with OPENAI_COMPATIBLE_API_MODE=responses; otherwise leave it empty."
8873
+ ],
8786
8874
  "可选;设置后在 xAI Responses 请求中发送 reasoning.effort,默认不发送。": [
8787
8875
  "可选;设置后在 xAI Responses 请求中发送 reasoning.effort,默认不发送。",
8788
8876
  "Optional; when set, sends reasoning.effort in xAI Responses requests; omitted by default."
@@ -11703,6 +11791,10 @@
11703
11791
  "无效的 --timeout:{0}。需要有限的正数。",
11704
11792
  "Invalid --timeout: {0}. Expected a positive finite number."
11705
11793
  ],
11794
+ "无效的 OPENAI_COMPATIBLE_TOOLS:{0}。支持:{1}": [
11795
+ "无效的 OPENAI_COMPATIBLE_TOOLS:{0}。支持:{1}",
11796
+ "Invalid OPENAI_COMPATIBLE_TOOLS: {0}. Supported values: {1}"
11797
+ ],
11706
11798
  "无效的 OpenAI 兼容 API 模式:{0}。支持:{1}": [
11707
11799
  "无效的 OpenAI 兼容 API 模式:{0}。支持:{1}",
11708
11800
  "Invalid OpenAI-compatible API mode: {0}. Supported values: {1}"
@@ -13655,6 +13747,10 @@
13655
13747
  "站点地图",
13656
13748
  "Site map"
13657
13749
  ],
13750
+ "等一分钟再运行本诊断;经常出现就降低并发、换模型,或提高账户的速率额度。地址、key 和模型名不一定有问题。": [
13751
+ "等一分钟再运行本诊断;经常出现就降低并发、换模型,或提高账户的速率额度。地址、key 和模型名不一定有问题。",
13752
+ "Run this diagnosis again in a minute; if it keeps happening, lower concurrency, switch models, or raise the account's rate limit. The URL, key and model name are not necessarily wrong."
13753
+ ],
13658
13754
  "等多久、失败之后怎么办、冷却多长时间。默认值适合大多数人。": [
13659
13755
  "等多久、失败之后怎么办、冷却多长时间。默认值适合大多数人。",
13660
13756
  "How long to wait, what happens after a failure, how long a dead provider stays out. The defaults suit most people."
@@ -17943,10 +18039,6 @@
17943
18039
  "尚未配置 Jev 独立汇总模型",
17944
18040
  "Jev synthesis model is not configured"
17945
18041
  ],
17946
- "{0} 另外,联网搜索能力检查未通过:{1}。OpenAI-compatible 不会发送搜索工具;如果该模型只能通过 Responses 的 web_search 工具联网,请把 XAI_API_URL、XAI_API_KEY、XAI_MODEL 指向支持 /v1/responses 的中转站。": [
17947
- "{0} 另外,联网搜索能力检查未通过:{1}。OpenAI-compatible 不会发送搜索工具;如果该模型只能通过 Responses 的 web_search 工具联网,请把 XAI_API_URL、XAI_API_KEY、XAI_MODEL 指向支持 /v1/responses 的中转站。",
17948
- "{0} Also, the web search capability check did not pass: {1}. OpenAI-compatible sends no search tool; if this model can only search through the Responses web_search tool, point XAI_API_URL, XAI_API_KEY and XAI_MODEL at a relay that supports /v1/responses."
17949
- ],
17950
18042
  "DeepSeek search response is not a JSON object": [
17951
18043
  "DeepSeek 搜索响应不是 JSON 对象",
17952
18044
  "DeepSeek search response is not a JSON object"
@@ -23,7 +23,7 @@ Use the local `smart-search` command as the default execution layer for web rese
23
23
  1. Run `smart-search doctor --format json` when configuration or availability is uncertain.
24
24
  2. If `doctor` reports missing configuration, use `smart-search setup` or `smart-search config set KEY VALUE` when the user provides keys. Do not ask users to edit global environment variables by default.
25
25
  3. If OpenAI-compatible `search` hangs or times out after `doctor` succeeds, run `smart-search diagnose openai-compatible --format markdown` and use its summary.
26
- 4. Keep `OPENAI_COMPATIBLE_API_MODE=chat-completions` unless a named relay has passed the official Responses subset probes; set `responses` only for that relay and keep the diagnostic evidence.
26
+ 4. Keep `OPENAI_COMPATIBLE_API_MODE=chat-completions` unless a named relay has passed the official Responses subset probes; set `responses` only for that relay and keep the diagnostic evidence. For a model that searches only through the Responses `web_search` tool (for example `gpt-6.1-sol` with an official OpenAI key), also set `OPENAI_COMPATIBLE_TOOLS=web_search`.
27
27
  5. If `doctor` returns `ok: true`, use only `smart-search` CLI subcommands for web research. Do not call Codex native web search in the same task.
28
28
  6. Use `smart-search skills status --targets codex --format json` when the installed global skill may be stale; use `smart-search skills update --targets codex --format json` to refresh it without rerunning setup.
29
29
  7. Use `smart-search smoke --mock --format json` after CLI/provider architecture changes. Use `--live` only when real keys are available and the user expects live checks.
@@ -48,7 +48,7 @@
48
48
  - `smart-search map URL [--instructions TEXT] [--max-depth N] [--max-breadth N] [--limit N] [--timeout SECONDS] [--format json|markdown|content] [--output PATH]`
49
49
  - `smart-search doctor [--format json|markdown|content] [--output PATH]`
50
50
  - `smart-search diagnose openai-compatible [--timeout SECONDS] [--format json|markdown] [--output PATH]`
51
- - `smart-search setup [--lang zh|en] [--advanced] [--non-interactive] [--skip-skills] [--install-skills CSV] [--skills-root PATH] [--xai-api-url URL] [--xai-api-key KEY] [--xai-model ID] [--xai-tools-explicit CSV] [--xai-stream true|false] [--openai-compatible-api-url URL] [--openai-compatible-api-key KEY] [--openai-compatible-model ID] [--openai-compatible-fallback-models CSV] [--openai-compatible-api-mode chat-completions|responses] [--openai-compatible-stream true|false] [--validation-level fast|balanced|strict] [--fallback-mode auto|off] [--minimum-profile standard|off] [--intent-router jev|local] [--search-timeout SECONDS] [--provider-cooldown SECONDS] [--provider-failure-threshold N] [--exa-key KEY] [--context7-key KEY] [--zhipu-key KEY] [--zhipu-api-url URL] [--zhipu-search-engine ENGINE] [--zhipu-mcp-key KEY] [--zhipu-mcp-search-api-url URL] [--zhipu-mcp-reader-api-url URL] [--zhipu-mcp-zread-api-url URL] [--zhipu-mcp-timeout SECONDS] [--jina-key KEY] [--jina-reader-api-url URL] [--jina-respond-with MODE] [--jina-timeout SECONDS] [--tavily-api-url URL] [--tavily-key KEY] [--firecrawl-api-url URL] [--firecrawl-key KEY] [--tinyfish-key KEY] [--tinyfish-search-api-url URL] [--tinyfish-fetch-api-url URL] [--tinyfish-timeout SECONDS] [--yuanbao-secret-id ID] [--yuanbao-secret-key KEY] [--yuanbao-api-url URL] [--yuanbao-timeout SECONDS] [--deepseek-key KEY] [--deepseek-api-url URL] [--deepseek-timeout SECONDS] [--anysearch-api-url URL] [--anysearch-key KEY] [--anysearch-timeout SECONDS] [--sciverse-api-url URL] [--sciverse-token TOKEN] [--sciverse-timeout SECONDS] [--format json|markdown|content] [--output PATH]`
51
+ - `smart-search setup [--lang zh|en] [--advanced] [--non-interactive] [--skip-skills] [--install-skills CSV] [--skills-root PATH] [--xai-api-url URL] [--xai-api-key KEY] [--xai-model ID] [--xai-tools-explicit CSV] [--xai-stream true|false] [--openai-compatible-api-url URL] [--openai-compatible-api-key KEY] [--openai-compatible-model ID] [--openai-compatible-fallback-models CSV] [--openai-compatible-api-mode chat-completions|responses] [--openai-compatible-tools web_search|web_search_preview] [--openai-compatible-stream true|false] [--validation-level fast|balanced|strict] [--fallback-mode auto|off] [--minimum-profile standard|off] [--intent-router jev|local] [--search-timeout SECONDS] [--provider-cooldown SECONDS] [--provider-failure-threshold N] [--exa-key KEY] [--context7-key KEY] [--zhipu-key KEY] [--zhipu-api-url URL] [--zhipu-search-engine ENGINE] [--zhipu-mcp-key KEY] [--zhipu-mcp-search-api-url URL] [--zhipu-mcp-reader-api-url URL] [--zhipu-mcp-zread-api-url URL] [--zhipu-mcp-timeout SECONDS] [--jina-key KEY] [--jina-reader-api-url URL] [--jina-respond-with MODE] [--jina-timeout SECONDS] [--tavily-api-url URL] [--tavily-key KEY] [--firecrawl-api-url URL] [--firecrawl-key KEY] [--tinyfish-key KEY] [--tinyfish-search-api-url URL] [--tinyfish-fetch-api-url URL] [--tinyfish-timeout SECONDS] [--yuanbao-secret-id ID] [--yuanbao-secret-key KEY] [--yuanbao-api-url URL] [--yuanbao-timeout SECONDS] [--deepseek-key KEY] [--deepseek-api-url URL] [--deepseek-timeout SECONDS] [--anysearch-api-url URL] [--anysearch-key KEY] [--anysearch-timeout SECONDS] [--sciverse-api-url URL] [--sciverse-token TOKEN] [--sciverse-timeout SECONDS] [--format json|markdown|content] [--output PATH]`
52
52
  - `smart-search config path|list|set|unset ... [--format json|markdown|content] [--output PATH]`
53
53
  - `smart-search model set MODEL [--format json|markdown|content] [--output PATH]`
54
54
  - `smart-search model current [--format json|markdown|content] [--output PATH]`
@@ -58,6 +58,7 @@ smart-search skills update --all --format json
58
58
  smart-search route "React useEffect API docs" --format markdown
59
59
  smart-search setup --non-interactive --zhipu-api-url "https://open.bigmodel.cn/api" --zhipu-search-engine "search_std"
60
60
  smart-search setup --non-interactive --openai-compatible-api-mode responses
61
+ smart-search setup --non-interactive --openai-compatible-api-mode responses --openai-compatible-tools web_search
61
62
  smart-search setup --non-interactive --openai-compatible-stream true
62
63
  smart-search setup --non-interactive --openai-compatible-fallback-models "model-a,model-b"
63
64
  smart-search setup --non-interactive --anysearch-api-url "https://api.anysearch.com/mcp" --anysearch-key "key"
@@ -77,6 +78,7 @@ smart-search config set OPENAI_COMPATIBLE_API_URL "https://api.openai.com/v1" --
77
78
  smart-search config set OPENAI_COMPATIBLE_API_KEY "key" --format json
78
79
  smart-search config set OPENAI_COMPATIBLE_MODEL "model-id" --format json
79
80
  smart-search config set OPENAI_COMPATIBLE_API_MODE "responses" --format json
81
+ smart-search config set OPENAI_COMPATIBLE_TOOLS "web_search" --format json
80
82
  smart-search config set OPENAI_COMPATIBLE_FALLBACK_MODELS "model-a,model-b" --format json
81
83
  smart-search config set OPENAI_COMPATIBLE_STREAM "true" --format json
82
84
  smart-search config set SMART_SEARCH_TIMEOUT_SECONDS "180" --format json
@@ -27,7 +27,7 @@ For research, `SMART_SEARCH_JEV_SYNTHESIZE` accepts `true`, `false`, and `auto`.
27
27
  - Legacy `SMART_SEARCH_API_URL`, `SMART_SEARCH_API_KEY`, `SMART_SEARCH_API_MODE`, `SMART_SEARCH_MODEL`, and `SMART_SEARCH_XAI_TOOLS` are unsupported config keys.
28
28
  - xAI Responses mode may use only `XAI_TOOLS=web_search,x_search` and a subset of those tools.
29
29
  - `XAI_REASONING_EFFORT` accepts `low`, `medium`, or `high` (case-insensitive, stored lowercase). Optional; when set, sends reasoning.effort in xAI Responses requests; omitted by default. This also applies to Grok search selected by Jev. Unsupported reasoning values reported by the upstream API remain errors; the field is not silently removed and retried. The xAI connection test in `doctor` and the App sends the same `reasoning.effort` and `stream` as a search, without tools, and names them in its message and under `sent`; a stream that fails after HTTP 200 fails the test.
30
- - Neither OpenAI-compatible API mode sends xAI `web_search` / `x_search` tools or legacy `search_parameters`; xAI Chat Completions Live Search is deprecated.
30
+ - OpenAI-compatible sends no search tools by default. `OPENAI_COMPATIBLE_TOOLS` (`web_search` or `web_search_preview`, comma-separated) attaches Responses tools to `search()` only; it requires `OPENAI_COMPATIBLE_API_MODE=responses`, and any other mode is a `parameter_error` on save and before a request. It never applies to `fetch()`, `describe_url()`, `rank_sources()`, synthesis, or the `doctor` connection test. `x_search` and legacy `search_parameters` are never sent; xAI Chat Completions Live Search is deprecated.
31
31
  - AnySearch is reported only as optional experimental `vertical_search`; it is not part of the `web_search` fallback and is not required by the `standard` minimum profile.
32
32
  - Sciverse is reported only as optional experimental explicit-only `vertical_search`; it is not `docs_search`, not part of default `search` / `research` fallback, and not required by the `standard` minimum profile.
33
33
  - Jina Reader is `web_fetch` only, not a general search provider. `JINA_API_KEY` is required before Jina satisfies the standard minimum profile; anonymous `r.jina.ai` is explicit/experimental fetch behavior.
@@ -138,6 +138,7 @@ OpenAI-compatible streaming:
138
138
 
139
139
  - `OPENAI_COMPATIBLE_API_MODE` defaults to `chat-completions` and accepts only `chat-completions` or `responses`. The selected protocol applies consistently to `search()`, provider-side `fetch()`, `describe_url()`, `rank_sources()`, `doctor`, and `diagnose openai-compatible`.
140
140
  - Responses mode handles the official `model` + `instructions`/`input` subset, heterogeneous `output` text parts, URL citations, typed stream deltas, and terminal failures. It does not promise `/responses` support from every relay marketed as OpenAI-compatible; accept each named relay with both diagnose stream probes.
141
+ - Models that search only through the Responses `web_search` tool, such as `gpt-6.1-sol` on the official OpenAI API, need `OPENAI_COMPATIBLE_API_MODE=responses` plus `OPENAI_COMPATIBLE_TOOLS=web_search`. Chat Completions search models such as `gpt-5-search-api` search on their own; their `url_citation` annotations become structured sources. `diagnose openai-compatible` sends the configured tools with its search-shape and web-search probes.
141
142
  - `OPENAI_COMPATIBLE_STREAM` defaults to `false` and accepts `true`, `1`, or `yes` as true.
142
143
  - For OpenAI-compatible search, `search --stream` means "prefer stream first"; stream empty/timeout/retryable protocol failures fall back to the same provider/model with `stream=false`.
143
144
  - `search --no-stream` forces `stream=false` for the current invocation.
@@ -84,6 +84,7 @@
84
84
  - Use `smart-search setup --non-interactive --jina-key "key"` to let Jina satisfy `web_fetch`; `JINA_RESPOND_WITH=readerlm-v2` also requires `JINA_API_KEY`.
85
85
  - Use `smart-search setup --non-interactive --zhipu-mcp-key "key"` only when the user explicitly wants Coding Plan Remote MCP quota.
86
86
  - Use `smart-search setup --non-interactive --openai-compatible-api-mode responses` only when a named relay requires `/responses`. The default is `chat-completions` and uses `/chat/completions`; invalid modes fail before setup saves any field.
87
+ - Use `smart-search setup --non-interactive --openai-compatible-api-mode responses --openai-compatible-tools web_search` when the model searches only through the Responses `web_search` tool, such as `gpt-6.1-sol` with an official OpenAI key. Use `web_search_preview` for a relay that only knows the older name. Tools without `responses` mode fail before setup saves any field.
87
88
  - Use `smart-search setup --non-interactive --openai-compatible-stream true` only when an OpenAI-compatible relay benefits from SSE streaming for long requests. Default remains false.
88
89
  - Use `smart-search setup --non-interactive --openai-compatible-fallback-models "model-a,model-b"` to save ordered OpenAI-compatible backup models for primary model hard failure. These models do not receive a reserved time slice; the primary model keeps the remaining shared main-search budget. `--fallback off` and `search --model MODEL` disable this model fallback for one invocation.
89
90
  - Use `smart-search setup --non-interactive --search-timeout 300` or `smart-search config set SMART_SEARCH_TIMEOUT_SECONDS 300` to persist the normal search budget. Invalid non-positive values fail before provider work.
@@ -869,6 +869,8 @@ def _format_model_markdown(data: dict[str, Any]) -> str:
869
869
  rows.append(["openai-compatible", data.get("openai_compatible_model")])
870
870
  if data.get("openai_compatible_api_mode"):
871
871
  rows.append([tr('openai-compatible API mode'), data.get("openai_compatible_api_mode")])
872
+ if data.get("openai_compatible_tools"):
873
+ rows.append([tr('openai-compatible tools'), ", ".join(data["openai_compatible_tools"])])
872
874
  fallback_models = data.get("openai_compatible_fallback_models") or []
873
875
  if fallback_models:
874
876
  rows.append([tr('openai-compatible fallback'), ", ".join(fallback_models)])
@@ -2337,6 +2339,7 @@ def _run_advanced_setup_prompts(values: dict[str, str], current: dict[str, str],
2337
2339
  ("OPENAI_COMPATIBLE_MODEL", "OpenAI-compatible model", True),
2338
2340
  ("OPENAI_COMPATIBLE_FALLBACK_MODELS", "OpenAI-compatible fallback models (comma-separated)", True),
2339
2341
  ("OPENAI_COMPATIBLE_API_MODE", "OpenAI-compatible API mode (chat-completions/responses)", True),
2342
+ ("OPENAI_COMPATIBLE_TOOLS", "OpenAI-compatible Responses tools (web_search; needs API mode responses)", True),
2340
2343
  ("OPENAI_COMPATIBLE_STREAM", "OpenAI-compatible stream mode (true/false)", True),
2341
2344
  ("SMART_SEARCH_JEV_SYNTHESIS_API_URL", "JEV synthesis API URL", True),
2342
2345
  ("SMART_SEARCH_JEV_SYNTHESIS_API_KEY", "JEV synthesis API key", True),
@@ -2770,6 +2773,7 @@ def _run_setup(args: argparse.Namespace) -> int:
2770
2773
  "OPENAI_COMPATIBLE_MODEL": args.openai_compatible_model,
2771
2774
  "OPENAI_COMPATIBLE_FALLBACK_MODELS": args.openai_compatible_fallback_models,
2772
2775
  "OPENAI_COMPATIBLE_API_MODE": args.openai_compatible_api_mode,
2776
+ "OPENAI_COMPATIBLE_TOOLS": args.openai_compatible_tools,
2773
2777
  "OPENAI_COMPATIBLE_STREAM": args.openai_compatible_stream,
2774
2778
  "SMART_SEARCH_JEV_SYNTHESIS_API_URL": args.jev_synthesis_api_url,
2775
2779
  "SMART_SEARCH_JEV_SYNTHESIS_API_KEY": args.jev_synthesis_api_key,
@@ -2844,20 +2848,28 @@ def _run_setup(args: argparse.Namespace) -> int:
2844
2848
 
2845
2849
  # Validate all local enum/numeric constraints before persisting any setup
2846
2850
  # field, so an invalid API mode cannot leave a partial provider profile.
2847
- for key, value in values.items():
2848
- if not value:
2849
- continue
2850
- try:
2851
- service.config._validate_config_value(key, value)
2852
- except ValueError as e:
2853
- data = {
2854
- "ok": False,
2855
- "error_type": "parameter_error",
2856
- "error": str(e),
2857
- "config_file": service.config_path()["config_file"],
2858
- "saved": {},
2859
- }
2860
- return _print_result("setup", data, args.format, args.output)
2851
+ def validate_before_saving() -> None:
2852
+ for key, value in values.items():
2853
+ if value:
2854
+ service.config._validate_config_value(key, value)
2855
+ # Tools and API mode are saved one key at a time below, so check the pair up front.
2856
+ if values.get("OPENAI_COMPATIBLE_TOOLS") or values.get("OPENAI_COMPATIBLE_API_MODE"):
2857
+ service.config.check_openai_compatible_tools_mode(
2858
+ values.get("OPENAI_COMPATIBLE_TOOLS") or service.config.openai_compatible_tools_raw,
2859
+ values.get("OPENAI_COMPATIBLE_API_MODE") or service.config._get_config_value("OPENAI_COMPATIBLE_API_MODE", "") or "",
2860
+ )
2861
+
2862
+ try:
2863
+ validate_before_saving()
2864
+ except ValueError as e:
2865
+ data = {
2866
+ "ok": False,
2867
+ "error_type": "parameter_error",
2868
+ "error": str(e),
2869
+ "config_file": service.config_path()["config_file"],
2870
+ "saved": {},
2871
+ }
2872
+ return _print_result("setup", data, args.format, args.output)
2861
2873
 
2862
2874
  saved: dict[str, str] = {}
2863
2875
  for key, value in values.items():
@@ -3469,6 +3481,7 @@ def build_parser() -> argparse.ArgumentParser:
3469
3481
  setup_parser.add_argument("--openai-compatible-model", default="", help="Save OPENAI_COMPATIBLE_MODEL.")
3470
3482
  setup_parser.add_argument("--openai-compatible-fallback-models", default="", help="Save OPENAI_COMPATIBLE_FALLBACK_MODELS.")
3471
3483
  setup_parser.add_argument("--openai-compatible-api-mode", default="", help="Save OPENAI_COMPATIBLE_API_MODE (chat-completions or responses).")
3484
+ setup_parser.add_argument("--openai-compatible-tools", default="", help="Save OPENAI_COMPATIBLE_TOOLS (web_search or web_search_preview; needs --openai-compatible-api-mode responses).")
3472
3485
  setup_parser.add_argument("--openai-compatible-stream", default="", help="Save OPENAI_COMPATIBLE_STREAM.")
3473
3486
  setup_parser.add_argument("--jev-synthesis-api-url", default="", help="Save SMART_SEARCH_JEV_SYNTHESIS_API_URL.")
3474
3487
  setup_parser.add_argument("--jev-synthesis-api-key", default="", help="Save SMART_SEARCH_JEV_SYNTHESIS_API_KEY.")
@@ -37,6 +37,8 @@ class Config:
37
37
  _ALLOWED_XAI_TOOLS = {"web_search", "x_search"}
38
38
  _ALLOWED_XAI_REASONING_EFFORTS = {"low", "medium", "high"}
39
39
  _ALLOWED_OPENAI_COMPATIBLE_API_MODES = {"chat-completions", "responses"}
40
+ # Responses tools the OpenAI-compatible channel may attach to search(); empty sends none.
41
+ _ALLOWED_OPENAI_COMPATIBLE_TOOLS = {"web_search", "web_search_preview"}
40
42
  _ALLOWED_VALIDATION_LEVELS = {"fast", "balanced", "strict"}
41
43
  _ALLOWED_FALLBACK_MODES = {"auto", "off"}
42
44
  _ALLOWED_MINIMUM_PROFILES = {"standard", "off"}
@@ -77,6 +79,7 @@ class Config:
77
79
  "OPENAI_COMPATIBLE_MODEL",
78
80
  "OPENAI_COMPATIBLE_FALLBACK_MODELS",
79
81
  "OPENAI_COMPATIBLE_API_MODE",
82
+ "OPENAI_COMPATIBLE_TOOLS",
80
83
  "OPENAI_COMPATIBLE_STREAM",
81
84
  "SMART_SEARCH_VALIDATION_LEVEL",
82
85
  "SMART_SEARCH_FALLBACK_MODE",
@@ -166,6 +169,7 @@ class Config:
166
169
  "OPENAI_COMPATIBLE_MODEL",
167
170
  "OPENAI_COMPATIBLE_FALLBACK_MODELS",
168
171
  "OPENAI_COMPATIBLE_API_MODE",
172
+ "OPENAI_COMPATIBLE_TOOLS",
169
173
  "OPENAI_COMPATIBLE_STREAM",
170
174
  "SMART_SEARCH_VALIDATION_LEVEL",
171
175
  "SMART_SEARCH_FALLBACK_MODE",
@@ -521,6 +525,15 @@ class Config:
521
525
  for old_key, new_key in self._LEGACY_CONFIG_KEYS.items():
522
526
  if new_key == key:
523
527
  config_data.pop(old_key, None)
528
+ if {"OPENAI_COMPATIBLE_TOOLS", "OPENAI_COMPATIBLE_API_MODE"} & (set(normalized_set) | set(normalized_unset)):
529
+ try:
530
+ self.check_openai_compatible_tools_mode(
531
+ str(config_data.get("OPENAI_COMPATIBLE_TOOLS") or ""),
532
+ str(config_data.get("OPENAI_COMPATIBLE_API_MODE") or ""),
533
+ )
534
+ except ValueError as e:
535
+ return {"ok": False, "errors": [{"key": "OPENAI_COMPATIBLE_TOOLS", "error": str(e)}],
536
+ "saved": [], "unset": []}
524
537
  self._save_config_file(config_data)
525
538
  except OSError:
526
539
  raise ValueError(tr('无法锁定或保存配置文件,请检查权限或稍后重试。')) from None
@@ -642,27 +655,55 @@ class Config:
642
655
  def openai_compatible_stream(self) -> bool:
643
656
  return (self._get_config_value("OPENAI_COMPATIBLE_STREAM", "false") or "false").lower() in ("true", "1", "yes")
644
657
 
645
- def parse_xai_tools(self, raw: str | None = None) -> list[str]:
646
- raw = raw or self.xai_tools_raw
658
+ @property
659
+ def openai_compatible_tools_raw(self) -> str:
660
+ return self._get_config_value("OPENAI_COMPATIBLE_TOOLS", "") or ""
661
+
662
+ @property
663
+ def openai_compatible_tools(self) -> list[str]:
664
+ """Tools attached to Responses search(); raises when they could never be sent."""
665
+ tools = self.parse_openai_compatible_tools(self.openai_compatible_tools_raw)
666
+ self.check_openai_compatible_tools_mode(",".join(tools), self.openai_compatible_api_mode)
667
+ return tools
668
+
669
+ def check_openai_compatible_tools_mode(self, tools_raw: str, api_mode: str) -> None:
670
+ """Tools ride on Responses requests only, so any other mode would silently drop them."""
671
+ tools, _ = self._split_tool_list(tools_raw, self._ALLOWED_OPENAI_COMPATIBLE_TOOLS)
672
+ if tools and (api_mode or self._DEFAULT_OPENAI_COMPATIBLE_API_MODE).strip().lower() != "responses":
673
+ raise ValueError(tr(
674
+ 'OPENAI_COMPATIBLE_TOOLS only works with OPENAI_COMPATIBLE_API_MODE=responses. '
675
+ 'Set the API mode to responses, or clear OPENAI_COMPATIBLE_TOOLS.'
676
+ ))
677
+
678
+ @staticmethod
679
+ def _split_tool_list(raw: str, allowed: set[str]) -> tuple[list[str], list[str]]:
647
680
  tools: list[str] = []
648
681
  invalid: list[str] = []
649
- seen: set[str] = set()
650
- for item in raw.split(","):
682
+ for item in (raw or "").split(","):
651
683
  tool = item.strip().lower()
652
684
  if not tool:
653
685
  continue
654
- if tool not in self._ALLOWED_XAI_TOOLS:
686
+ if tool not in allowed:
655
687
  invalid.append(tool)
656
- continue
657
- if tool not in seen:
658
- seen.add(tool)
688
+ elif tool not in tools:
659
689
  tools.append(tool)
690
+ return tools, invalid
691
+
692
+ def parse_xai_tools(self, raw: str | None = None) -> list[str]:
693
+ tools, invalid = self._split_tool_list(raw or self.xai_tools_raw, self._ALLOWED_XAI_TOOLS)
660
694
  if invalid:
661
695
  allowed = ", ".join(sorted(self._ALLOWED_XAI_TOOLS))
662
696
  invalid_text = ", ".join(invalid)
663
697
  raise ValueError(tr('Invalid XAI_TOOLS: {0}. Supported values: {1}', invalid_text, allowed))
664
698
  return tools
665
699
 
700
+ def parse_openai_compatible_tools(self, raw: str | None) -> list[str]:
701
+ tools, invalid = self._split_tool_list(raw or "", self._ALLOWED_OPENAI_COMPATIBLE_TOOLS)
702
+ if invalid:
703
+ allowed = ", ".join(sorted(self._ALLOWED_OPENAI_COMPATIBLE_TOOLS))
704
+ raise ValueError(tr('Invalid OPENAI_COMPATIBLE_TOOLS: {0}. Supported values: {1}', ", ".join(invalid), allowed))
705
+ return tools
706
+
666
707
  def _validated_enum(self, key: str, default: str, allowed: set[str]) -> str:
667
708
  return self._validate_enum_value(key, self._get_config_value(key, default) or default, allowed)
668
709
 
@@ -742,6 +783,9 @@ class Config:
742
783
  if key == "XAI_TOOLS":
743
784
  self.parse_xai_tools(value or self._DEFAULT_XAI_TOOLS)
744
785
  return
786
+ if key == "OPENAI_COMPATIBLE_TOOLS":
787
+ self.parse_openai_compatible_tools(value)
788
+ return
745
789
  # Every getter below falls back to its default on an empty value, so an
746
790
  # empty string is how a key is cleared rather than a value to check.
747
791
  if not str(value).strip():
@@ -1225,6 +1269,12 @@ class Config:
1225
1269
  self._DEFAULT_OPENAI_COMPATIBLE_API_MODE,
1226
1270
  self._ALLOWED_OPENAI_COMPATIBLE_API_MODES,
1227
1271
  )
1272
+ openai_compatible_tools = self.openai_compatible_tools_raw
1273
+ try:
1274
+ openai_compatible_tools = ",".join(self.openai_compatible_tools)
1275
+ except ValueError as exc:
1276
+ if not openai_compatible_api_mode_error:
1277
+ config_parameter_errors.append(str(exc))
1228
1278
  primary_api_mode = tr('未配置')
1229
1279
  if xai_configured:
1230
1280
  primary_api_mode = "xai-responses"
@@ -1273,6 +1323,7 @@ class Config:
1273
1323
  "OPENAI_COMPATIBLE_MODEL": self.openai_compatible_model,
1274
1324
  "OPENAI_COMPATIBLE_FALLBACK_MODELS": ",".join(self.openai_compatible_fallback_models),
1275
1325
  "OPENAI_COMPATIBLE_API_MODE": openai_compatible_api_mode,
1326
+ "OPENAI_COMPATIBLE_TOOLS": openai_compatible_tools,
1276
1327
  "OPENAI_COMPATIBLE_STREAM": self.openai_compatible_stream,
1277
1328
  "SMART_SEARCH_VALIDATION_LEVEL": validation_level,
1278
1329
  "SMART_SEARCH_FALLBACK_MODE": fallback_mode,
@@ -678,6 +678,9 @@ async def search(
678
678
  error_type = "" if ok else (failure.error_type if failure else "timeout" if stopped == "deadline" else "evidence_error")
679
679
  error = "" if ok else (failure.error if failure else "Search deadline exhausted" if stopped == "deadline"
680
680
  else "Search did not obtain enough verified useful evidence")
681
+ if not ok and not failure and stopped != "deadline" and validation == "strict" and evidence and not has_source_evidence(evidence):
682
+ # Only a search model's own citations came back; strict needs pages a search provider retrieved.
683
+ error = source_message('strict 校验需要搜索服务独立检索到的来源;这次只有搜索模型自带的引用,不算独立证据。配置一个 web_search 提供方(例如 Tavily、智谱、元宝搜索)后再用 strict,或改用 balanced。')
681
684
  if not ok and not evidence and terminal_error:
682
685
  error_type, error = terminal_error["error_type"], terminal_error.get("error", error)
683
686
  if stopped == "cancelled":
@@ -173,6 +173,7 @@ class OpenAICompatibleSearchProvider(BaseSearchProvider):
173
173
  model: str = "grok-4-fast",
174
174
  stream: bool = False,
175
175
  api_mode: str = "chat-completions",
176
+ tools: list[str] | None = None,
176
177
  ):
177
178
  super().__init__(normalize_openai_compatible_api_url(api_url), api_key)
178
179
  self.model = model
@@ -181,6 +182,8 @@ class OpenAICompatibleSearchProvider(BaseSearchProvider):
181
182
  if self.api_mode not in OPENAI_COMPATIBLE_API_MODES:
182
183
  allowed = ", ".join(sorted(OPENAI_COMPATIBLE_API_MODES))
183
184
  raise ValueError(source_message('Invalid OpenAI-compatible API mode: {0}. Supported values: {1}', self.api_mode, allowed))
185
+ # Responses tools such as web_search, sent with search() only (OPENAI_COMPATIBLE_TOOLS).
186
+ self.tools = list(tools or [])
184
187
  self.last_transport_attempts: list[dict[str, Any]] = []
185
188
  self._search_deadline_monotonic: float | None = None
186
189
 
@@ -263,6 +266,13 @@ class OpenAICompatibleSearchProvider(BaseSearchProvider):
263
266
  ]
264
267
  return payload
265
268
 
269
+ def _build_search_payload(self, instructions: str, user_content: str, *, stream: bool) -> dict[str, Any]:
270
+ """The search request; the only one that may carry Responses tools."""
271
+ payload = self._build_request_payload(instructions, user_content, stream=stream)
272
+ if self.api_mode == "responses" and self.tools:
273
+ payload["tools"] = [{"type": tool} for tool in self.tools]
274
+ return payload
275
+
266
276
  async def search(self, query: str, platform: str = "", ctx=None) -> List[SearchResult]:
267
277
  headers = self._build_api_headers()
268
278
  platform_prompt = ""
@@ -272,7 +282,7 @@ class OpenAICompatibleSearchProvider(BaseSearchProvider):
272
282
 
273
283
  time_context = get_local_time_info() + "\n"
274
284
 
275
- payload = self._build_request_payload(
285
+ payload = self._build_search_payload(
276
286
  search_prompt,
277
287
  time_context + query + platform_prompt,
278
288
  stream=self.stream,
@@ -487,6 +497,7 @@ class OpenAICompatibleSearchProvider(BaseSearchProvider):
487
497
  return await self._parse_responses_streaming_response(response, ctx)
488
498
 
489
499
  content = ""
500
+ sources: list[dict] = []
490
501
  full_body_buffer = []
491
502
 
492
503
  async for line in response.aiter_lines():
@@ -507,6 +518,8 @@ class OpenAICompatibleSearchProvider(BaseSearchProvider):
507
518
  delta = choices[0].get("delta", {})
508
519
  if "content" in delta:
509
520
  content += delta["content"]
521
+ if isinstance(delta, dict) and delta.get("annotations"):
522
+ sources = self._merge_citations(sources, self._normalize_chat_annotations(delta["annotations"]))
510
523
  except (json.JSONDecodeError, IndexError):
511
524
  continue
512
525
 
@@ -517,9 +530,11 @@ class OpenAICompatibleSearchProvider(BaseSearchProvider):
517
530
  if "choices" in data and len(data["choices"]) > 0:
518
531
  message = data["choices"][0].get("message", {})
519
532
  content = message.get("content", "")
533
+ sources = self._merge_citations(sources, self._extract_citations(data))
520
534
  except json.JSONDecodeError:
521
535
  pass
522
536
 
537
+ content = self._append_sources(content, sources)
523
538
  await log_info(ctx, f"content: {content}", config.debug_enabled)
524
539
 
525
540
  return content
@@ -857,9 +872,24 @@ class OpenAICompatibleSearchProvider(BaseSearchProvider):
857
872
  continue
858
873
  message = choice.get("message")
859
874
  if isinstance(message, dict):
860
- sources = self._merge_citations(sources, self._normalize_citations(message.get("citations")))
875
+ sources = self._merge_citations(
876
+ sources,
877
+ self._normalize_citations(message.get("citations")),
878
+ self._normalize_chat_annotations(message.get("annotations")),
879
+ )
861
880
  return sources
862
881
 
882
+ def _normalize_chat_annotations(self, annotations: Any) -> list[dict]:
883
+ """Chat Completions search models nest each citation: {"type": "url_citation", "url_citation": {...}}."""
884
+ if not isinstance(annotations, list):
885
+ return []
886
+ citations = [
887
+ item.get("url_citation") if isinstance(item.get("url_citation"), dict) else item
888
+ for item in annotations
889
+ if isinstance(item, dict) and item.get("type") == "url_citation"
890
+ ]
891
+ return self._normalize_citations(citations)
892
+
863
893
  def _normalize_responses_citations(self, citations: Any) -> list[dict]:
864
894
  if not citations:
865
895
  return []
@@ -14,6 +14,7 @@ from .base import BaseSearchProvider
14
14
  from .openai_compatible import OpenAICompatibleSearchProvider, _StopAfterSlowFailure, _StopAtDeadline, _WaitWithRetryAfter, _is_retryable_exception, get_local_time_info
15
15
  from ..config import config
16
16
  from ..logger import log_info
17
+ from ..provider_errors import ProviderCallError
17
18
  from ..utils import search_prompt
18
19
 
19
20
 
@@ -115,7 +116,19 @@ class XAIResponsesSearchProvider(BaseSearchProvider):
115
116
  async def search(self, query: str, platform: str = "", ctx=None) -> str:
116
117
  payload = self._build_search_payload(query, platform)
117
118
  await log_info(ctx, f"platform_prompt: {query}", config.debug_enabled)
118
- return await self._execute_response_with_retry(self._build_api_headers(), payload, ctx)
119
+ try:
120
+ return await self._execute_response_with_retry(self._build_api_headers(), payload, ctx)
121
+ except httpx.HTTPStatusError as exc:
122
+ # The default XAI_TOOLS includes x_search, which only xAI serves; other Responses
123
+ # services (OpenAI, most relays) answer 400. Say which setting to change.
124
+ if exc.response.status_code == 400 and "x_search" in self.tools and "x_search" in (exc.response.text or ""):
125
+ host = urlsplit(self.api_url).netloc or self.api_url
126
+ raise ProviderCallError(
127
+ "parameter_error",
128
+ source_message('{0} 不支持 XAI_TOOLS 里的 x_search(HTTP 400)。x_search 只有 xAI 官方接口能用;把 XAI_TOOLS 改成 web_search 再试。', host),
129
+ additional_secrets=(self.api_key,),
130
+ ) from exc
131
+ raise
119
132
 
120
133
  async def _execute_response_with_retry(self, headers: dict, payload: dict, ctx=None) -> str:
121
134
  timeout = self._request_timeout()
@@ -1656,7 +1656,7 @@ def _main_search_provider_configs(model_override: str = "", providers: str = "au
1656
1656
  "model": model_override or config.openai_compatible_model,
1657
1657
  "fallback_models": [] if model_override else config.openai_compatible_fallback_models,
1658
1658
  "stream": config.openai_compatible_stream,
1659
- "tools": [],
1659
+ "tools": config.openai_compatible_tools,
1660
1660
  "source": "OPENAI_COMPATIBLE_*",
1661
1661
  }
1662
1662
 
@@ -1690,6 +1690,7 @@ def _main_search_providers(provider_configs: list[dict[str, Any]], fallback: str
1690
1690
  provider_config["model"],
1691
1691
  provider_config.get("stream", False),
1692
1692
  provider_config.get("api_mode", provider_config.get("mode", "chat-completions")),
1693
+ tools=provider_config.get("tools") or [],
1693
1694
  )
1694
1695
  )
1695
1696
  return providers
@@ -2841,6 +2842,10 @@ def _diagnose_check_result(
2841
2842
  return result
2842
2843
 
2843
2844
 
2845
+ def _rate_limited(*checks: dict[str, Any]) -> bool:
2846
+ return any(check.get("http_status") == 429 for check in checks)
2847
+
2848
+
2844
2849
  def _openai_compatible_diagnosis(quick: dict[str, Any], no_stream: dict[str, Any], stream: dict[str, Any]) -> tuple[bool, str, str]:
2845
2850
  quick_ok = quick.get("status") == "ok"
2846
2851
  no_stream_ok = no_stream.get("status") == "ok"
@@ -2853,6 +2858,12 @@ def _openai_compatible_diagnosis(quick: dict[str, Any], no_stream: dict[str, Any
2853
2858
  source_message('OpenAI-compatible 主链路正常。'),
2854
2859
  source_message('真实 search 形态的 stream=false 和 stream=true 都能返回。若用户仍卡住,更可能是调用方、PATH、超时设置或上游偶发波动。'),
2855
2860
  )
2861
+ if _rate_limited(quick, no_stream, stream):
2862
+ return (
2863
+ False,
2864
+ source_message('OpenAI-compatible 被上游限流(HTTP 429),这次诊断判断不了链路是否正常。'),
2865
+ source_message('等一分钟再运行本诊断;经常出现就降低并发、换模型,或提高账户的速率额度。地址、key 和模型名不一定有问题。'),
2866
+ )
2856
2867
  if stream_ok and not no_stream_ok:
2857
2868
  return (
2858
2869
  False,
@@ -2892,11 +2903,12 @@ async def _probe_openai_compatible_search_shape(
2892
2903
  stream: bool,
2893
2904
  timeout_seconds: float,
2894
2905
  api_mode: str = "chat-completions",
2906
+ tools: list[str] | None = None,
2895
2907
  ) -> dict[str, Any]:
2896
2908
  name = f"真实 {api_mode} search 请求 (stream={'true' if stream else 'false'})"
2897
2909
  start = time.time()
2898
- provider = OpenAICompatibleSearchProvider(api_url, api_key, model, stream=stream, api_mode=api_mode)
2899
- payload = provider._build_request_payload(search_prompt, get_local_time_info() + "\nping", stream=stream)
2910
+ provider = OpenAICompatibleSearchProvider(api_url, api_key, model, stream=stream, api_mode=api_mode, tools=tools)
2911
+ payload = provider._build_search_payload(search_prompt, get_local_time_info() + "\nping", stream=stream)
2900
2912
  headers = provider._build_api_headers()
2901
2913
  headers["User-Agent"] = "smart-search/diagnose"
2902
2914
  timeout = httpx.Timeout(connect=6.0, read=timeout_seconds, write=10.0, pool=None)
@@ -2996,10 +3008,11 @@ async def _probe_openai_compatible_web_search(
2996
3008
  api_mode: str,
2997
3009
  stream: bool,
2998
3010
  timeout_seconds: float,
3011
+ tools: list[str] | None = None,
2999
3012
  ) -> dict:
3000
3013
  name = "联网搜索能力"
3001
3014
  start = time.time()
3002
- provider = OpenAICompatibleSearchProvider(api_url, api_key, model, stream=stream, api_mode=api_mode)
3015
+ provider = OpenAICompatibleSearchProvider(api_url, api_key, model, stream=stream, api_mode=api_mode, tools=tools)
3003
3016
  answer = ""
3004
3017
  try:
3005
3018
  content = await asyncio.wait_for(provider.search("What is the latest stable Python release? Cite the source URL."), timeout_seconds)
@@ -3026,16 +3039,15 @@ async def diagnose_openai_compatible(timeout_seconds: float = 30.0) -> dict[str,
3026
3039
  api_key = config.openai_compatible_api_key
3027
3040
  model = config.openai_compatible_model
3028
3041
  info = config.config_path_info()
3029
- try:
3030
- api_mode = config.openai_compatible_api_mode
3031
- except ValueError as e:
3042
+
3043
+ def invalid_config(error: Exception, configured_api_mode: str, summary: str, recommendation: str) -> dict[str, Any]:
3032
3044
  return {
3033
3045
  "ok": False,
3034
3046
  "provider": "openai-compatible",
3035
3047
  "api_url": api_url or "未配置",
3036
3048
  "api_key": config._mask_api_key(api_key) if api_key else "未配置",
3037
3049
  "model": model,
3038
- "configured_api_mode": "invalid",
3050
+ "configured_api_mode": configured_api_mode,
3039
3051
  "configured_stream": config.openai_compatible_stream,
3040
3052
  "timeout_seconds": timeout_seconds,
3041
3053
  "config_file": info.get("config_file", ""),
@@ -3043,11 +3055,23 @@ async def diagnose_openai_compatible(timeout_seconds: float = 30.0) -> dict[str,
3043
3055
  "checks": [],
3044
3056
  "next_command": OPENAI_COMPATIBLE_DIAGNOSE_COMMAND,
3045
3057
  "error_type": "parameter_error",
3046
- "error": str(e),
3047
- "summary": source_message('OpenAI-compatible API mode is invalid.'),
3048
- "recommendation": source_message('Set OPENAI_COMPATIBLE_API_MODE to chat-completions or responses.'),
3058
+ "error": str(error),
3059
+ "summary": summary,
3060
+ "recommendation": recommendation,
3049
3061
  "elapsed_ms": _elapsed_ms(start),
3050
3062
  }
3063
+
3064
+ try:
3065
+ api_mode = config.openai_compatible_api_mode
3066
+ except ValueError as e:
3067
+ return invalid_config(e, "invalid", source_message('OpenAI-compatible API mode is invalid.'),
3068
+ source_message('Set OPENAI_COMPATIBLE_API_MODE to chat-completions or responses.'))
3069
+ try:
3070
+ tools = config.openai_compatible_tools
3071
+ except ValueError as e:
3072
+ return invalid_config(e, api_mode, source_message('OpenAI-compatible tools are invalid.'),
3073
+ source_message('Use OPENAI_COMPATIBLE_TOOLS=web_search (or web_search_preview) only with '
3074
+ 'OPENAI_COMPATIBLE_API_MODE=responses; otherwise leave it empty.'))
3051
3075
  endpoint = openai_compatible_endpoint(api_url or "", api_mode)
3052
3076
  result: dict[str, Any] = {
3053
3077
  "ok": False,
@@ -3056,6 +3080,7 @@ async def diagnose_openai_compatible(timeout_seconds: float = 30.0) -> dict[str,
3056
3080
  "api_key": config._mask_api_key(api_key) if api_key else "未配置",
3057
3081
  "model": model,
3058
3082
  "configured_api_mode": api_mode,
3083
+ "configured_tools": tools,
3059
3084
  "endpoint": endpoint if api_url else "",
3060
3085
  "configured_stream": config.openai_compatible_stream,
3061
3086
  "timeout_seconds": timeout_seconds,
@@ -3104,18 +3129,21 @@ async def diagnose_openai_compatible(timeout_seconds: float = 30.0) -> dict[str,
3104
3129
  "has_content": bool(quick.get("has_content", quick.get("status") == "ok")),
3105
3130
  }
3106
3131
  result["checks"].append(quick_check)
3107
- probe_kwargs = {"stream": False, "timeout_seconds": timeout_seconds}
3132
+ # Probes send what a real search sends, tools included; the quick check above never does.
3133
+ tool_kwargs = {"tools": tools} if tools else {}
3134
+ probe_kwargs = {"stream": False, "timeout_seconds": timeout_seconds, **tool_kwargs}
3108
3135
  if api_mode != "chat-completions":
3109
3136
  probe_kwargs["api_mode"] = api_mode
3110
3137
  no_stream = await _probe_openai_compatible_search_shape(api_url, api_key, model, **probe_kwargs)
3111
3138
  result["checks"].append(no_stream)
3112
- probe_kwargs = {"stream": True, "timeout_seconds": timeout_seconds}
3139
+ probe_kwargs = {"stream": True, "timeout_seconds": timeout_seconds, **tool_kwargs}
3113
3140
  if api_mode != "chat-completions":
3114
3141
  probe_kwargs["api_mode"] = api_mode
3115
3142
  stream = await _probe_openai_compatible_search_shape(api_url, api_key, model, **probe_kwargs)
3116
3143
  result["checks"].append(stream)
3117
3144
  web = await _probe_openai_compatible_web_search(api_url, api_key, model, api_mode=api_mode,
3118
- stream=config.openai_compatible_stream, timeout_seconds=timeout_seconds)
3145
+ stream=config.openai_compatible_stream, timeout_seconds=timeout_seconds,
3146
+ **tool_kwargs)
3119
3147
  result["checks"].append(web)
3120
3148
  result["web_search_capable"] = web["status"] == "ok"
3121
3149
 
@@ -3144,14 +3172,18 @@ async def diagnose_openai_compatible(timeout_seconds: float = 30.0) -> dict[str,
3144
3172
  result["timeout_policy"] = OPENAI_COMPATIBLE_TIMEOUT_POLICY
3145
3173
 
3146
3174
  ok, summary, recommendation = _openai_compatible_diagnosis(quick_check, no_stream, stream)
3147
- if web["status"] != "ok":
3148
- recommendation = source_message('{0} 另外,联网搜索能力检查未通过:{1}。OpenAI-compatible 不会发送搜索工具;如果该模型只能通过 Responses 的 web_search 工具联网,请把 XAI_API_URL、XAI_API_KEY、XAI_MODEL 指向支持 /v1/responses 的中转站。', recommendation, web['message'])
3175
+ # Under a rate limit the web probe fails for the same reason; its advice would mislead.
3176
+ rate_limited = not ok and _rate_limited(quick_check, no_stream, stream)
3177
+ if web["status"] != "ok" and not rate_limited and tools:
3178
+ recommendation = source_message('{0} 另外,联网搜索能力检查未通过:{1}。请求里已经带了 {2} 工具,可能是这个模型或中转站不支持 Responses 的联网工具;换一个支持的模型,或清空 OPENAI_COMPATIBLE_TOOLS 改用自带搜索的模型。', recommendation, web['message'], ", ".join(tools))
3179
+ elif web["status"] != "ok" and not rate_limited:
3180
+ recommendation = source_message('{0} 另外,联网搜索能力检查未通过:{1}。如果这个模型只能通过 Responses 的 web_search 工具联网(例如 OpenAI 官方的 gpt-6.1-sol),请设置 OPENAI_COMPATIBLE_API_MODE=responses 和 OPENAI_COMPATIBLE_TOOLS=web_search;中转站只认旧名时改用 web_search_preview。', recommendation, web['message'])
3149
3181
  if inventory.get("status") == "warning":
3150
3182
  recommendation = source_message('{0} 另外,{1}。兜底只在主模型硬失败后接力,不会再把主模型砍成 30 秒。', recommendation, inventory['message'])
3151
3183
  result.update(
3152
3184
  {
3153
3185
  "ok": ok,
3154
- "error_type": "" if ok else "network_error",
3186
+ "error_type": "" if ok else "rate_limited" if rate_limited else "network_error",
3155
3187
  "error": "" if ok else summary,
3156
3188
  "summary": summary,
3157
3189
  "recommendation": recommendation,
@@ -3906,6 +3938,7 @@ async def doctor() -> dict[str, Any]:
3906
3938
  def current_model() -> dict[str, Any]:
3907
3939
  try:
3908
3940
  api_mode = config.openai_compatible_api_mode
3941
+ tools = config.openai_compatible_tools
3909
3942
  except ValueError as e:
3910
3943
  return {
3911
3944
  "ok": False,
@@ -3920,6 +3953,7 @@ def current_model() -> dict[str, Any]:
3920
3953
  "openai_compatible_model": config.openai_compatible_model,
3921
3954
  "openai_compatible_fallback_models": config.openai_compatible_fallback_models,
3922
3955
  "openai_compatible_api_mode": api_mode,
3956
+ "openai_compatible_tools": tools,
3923
3957
  "config_file": str(config.config_file),
3924
3958
  }
3925
3959
 
@@ -13,6 +13,9 @@ from .utils import NO_WEB_SEARCH_MARKER, extract_unique_urls
13
13
 
14
14
  _MD_LINK_PATTERN = re.compile(r"\[([^\]]+)\]\((https?://[^)]+)\)")
15
15
  _INLINE_CITATION_LINK_PATTERN = re.compile(r"\[\[(\d+)\]\]\((https?://[^)]+)\)")
16
+ # How OpenAI search models cite at the end of a sentence or bullet: "... in 2026. ([python.org](https://...))"
17
+ _PARENTHESIZED_CITATION_PATTERN = re.compile(r"[((]\s*\[[^\]]+\]\(https?://[^)\s]+\)\s*[))]")
18
+ _BARE_OR_ANGLE_URL_PATTERN = re.compile(r"<?https?://[^\s>]+>?")
16
19
  _SOURCES_HEADING_PATTERN = re.compile(
17
20
  r"(?im)^"
18
21
  r"(?:#{1,6}\s*)?"
@@ -338,11 +341,20 @@ def _is_link_only_line(line: str) -> bool:
338
341
  return False
339
342
  if stripped.startswith(("http://", "https://")):
340
343
  return True
344
+ citation = _PARENTHESIZED_CITATION_PATTERN.search(stripped)
345
+ if citation and _has_prose(stripped[: citation.start()]):
346
+ # A sentence that ends with its citation belongs to the answer, not to a trailing source list.
347
+ return False
341
348
  if _MD_LINK_PATTERN.search(stripped):
342
349
  return True
343
350
  return False
344
351
 
345
352
 
353
+ def _has_prose(text: str) -> bool:
354
+ text = _BARE_OR_ANGLE_URL_PATTERN.sub("", _MD_LINK_PATTERN.sub("", text))
355
+ return re.search(r"[^\W\d_]", text) is not None
356
+
357
+
346
358
  def _parse_sources_payload(payload: str) -> list[dict]:
347
359
  payload = (payload or "").strip().rstrip(";")
348
360
  if not payload:
@@ -198,11 +198,20 @@ CONFIG_FIELDS: tuple[ConfigField, ...] = (
198
198
  provider="openai-compatible", capabilities=("main_search",)),
199
199
  _f(key="OPENAI_COMPATIBLE_API_MODE", section="getting_started", tier="advanced", kind="enum",
200
200
  label_zh="接口模式", label_en="API mode",
201
- help_zh="大多数服务用 chat-completions。",
202
- help_en="Most services want chat-completions.",
201
+ help_zh="大多数服务用 chat-completions。要让 gpt-6.1-sol 这类只在 Responses 里联网的模型搜索,选 responses,再在下面的联网工具填 web_search。",
202
+ help_en="Most services want chat-completions. For models such as gpt-6.1-sol that only search through Responses, choose responses and set the tools below to web_search.",
203
203
  default=Config._DEFAULT_OPENAI_COMPATIBLE_API_MODE,
204
204
  choices=tuple(sorted(Config._ALLOWED_OPENAI_COMPATIBLE_API_MODES)),
205
205
  provider="openai-compatible", capabilities=("main_search",)),
206
+ # A plain text field on purpose, with no `choices`: the Windows App draws any field with choices as a closed
207
+ # dropdown, preselects the first choice when the saved value is not one of them (an empty value never is),
208
+ # and counts that preselection as "unchanged". Empty could not be shown and web_search could not be saved.
209
+ _f(key="OPENAI_COMPATIBLE_TOOLS", section="getting_started", tier="advanced", kind="csv",
210
+ label_zh="联网工具", label_en="Responses tools",
211
+ help_zh="留空表示不发送工具。只在接口模式为 responses 时可用,只随搜索请求发送。用 OpenAI 官方 key 跑 gpt-6.1-sol 这类模型要联网,填 web_search;中转站只认旧名时填 web_search_preview;模型自带搜索(如 gpt-5-search-api)保持留空。",
212
+ help_en="Leave empty to send no tool. Only with API mode responses, and sent with search requests only. Enter web_search to let models such as gpt-6.1-sol search with an official OpenAI key; enter web_search_preview for relays that only know the older name; keep it empty for models that search on their own, such as gpt-5-search-api.",
213
+ placeholder="web_search",
214
+ provider="openai-compatible", capabilities=("main_search",)),
206
215
  _f(key="OPENAI_COMPATIBLE_STREAM", section="getting_started", tier="advanced", kind="bool",
207
216
  label_zh="流式返回", label_en="Stream responses",
208
217
  help_zh="部分服务只在流式下才肯长时间思考。", help_en="Some services only think for long under streaming.",
@@ -1,6 +1,10 @@
1
- from typing import List
1
+ from typing import TYPE_CHECKING, List
2
2
  import re
3
- from .providers.base import SearchResult
3
+
4
+ if TYPE_CHECKING:
5
+ # A runtime import would load providers/__init__.py, whose providers import this
6
+ # module back: importing smart_search.sources first then fails halfway.
7
+ from .providers.base import SearchResult
4
8
 
5
9
  _URL_PATTERN = re.compile(r'https?://[^\s<>"\'`,。、;:!?》)】\)]+')
6
10
 
@@ -52,7 +56,7 @@ def format_extra_sources(tavily_results: list[dict] | None, firecrawl_results: l
52
56
  return "\n\n".join(sections)
53
57
 
54
58
 
55
- def format_search_results(results: List[SearchResult]) -> str:
59
+ def format_search_results(results: List["SearchResult"]) -> str:
56
60
  if not results:
57
61
  return "No results found."
58
62
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smart-search
3
- Version: 0.1.32
3
+ Version: 0.1.34
4
4
  Summary: CLI-first multi-source web research with xAI/OpenAI-compatible search, Exa, Context7, Zhipu, Tavily, Firecrawl, and Deep Research planning.
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,13 +1,13 @@
1
1
  ../../../bin/smart-search,sha256=q9upy4clf9uCDMAJIt-pBkuMwhZ7KupZeJLPO-SMjG4,206
2
- smart_search-0.1.32.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
3
- smart_search-0.1.32.dist-info/METADATA,sha256=qmcpDJFjrG5h-22--FHugd1BJdcmiCC7YSeg-kBif_k,19724
4
- smart_search-0.1.32.dist-info/RECORD,,
5
- smart_search-0.1.32.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- smart_search-0.1.32.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
7
- smart_search-0.1.32.dist-info/direct_url.json,sha256=uxzJhtkKbMt-CGh9XSoaSD7MmHAe_3DVycaTwRP2IhQ,76
8
- smart_search-0.1.32.dist-info/entry_points.txt,sha256=fiHguhz7hQ6ybeepO0OPpZm19XCi2J-Bkigf8DGBuKM,55
9
- smart_search-0.1.32.dist-info/licenses/LICENSE,sha256=o_zbH827gkTiZfsNLJGQn1z8Z4eLAvb8heFaSqSGVv8,1067
10
- smart_search-0.1.32.dist-info/top_level.txt,sha256=mBPprcb_b3tBApeRa2nlLaGj3UVNxXCdyz6QQvDzJpc,13
2
+ smart_search-0.1.34.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
3
+ smart_search-0.1.34.dist-info/METADATA,sha256=uJ3uNzgvo0uQucn9VGLkewJIwG_CMrfEumNZPM5l6Q8,19724
4
+ smart_search-0.1.34.dist-info/RECORD,,
5
+ smart_search-0.1.34.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ smart_search-0.1.34.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
7
+ smart_search-0.1.34.dist-info/direct_url.json,sha256=uxzJhtkKbMt-CGh9XSoaSD7MmHAe_3DVycaTwRP2IhQ,76
8
+ smart_search-0.1.34.dist-info/entry_points.txt,sha256=fiHguhz7hQ6ybeepO0OPpZm19XCi2J-Bkigf8DGBuKM,55
9
+ smart_search-0.1.34.dist-info/licenses/LICENSE,sha256=o_zbH827gkTiZfsNLJGQn1z8Z4eLAvb8heFaSqSGVv8,1067
10
+ smart_search-0.1.34.dist-info/top_level.txt,sha256=mBPprcb_b3tBApeRa2nlLaGj3UVNxXCdyz6QQvDzJpc,13
11
11
  smart_search/__init__.py,sha256=da1PTClDMl-IBkrSvq6JC1lnS-K_BASzCvxVhNxN5Ls,13
12
12
  smart_search/__pycache__/__init__.cpython-313.pyc,,
13
13
  smart_search/__pycache__/activity.cpython-313.pyc,,
@@ -40,19 +40,19 @@ smart_search/__pycache__/ui_metadata.cpython-313.pyc,,
40
40
  smart_search/__pycache__/ui_server.cpython-313.pyc,,
41
41
  smart_search/__pycache__/utils.cpython-313.pyc,,
42
42
  smart_search/activity.py,sha256=66yPPAfuaDSF4UTEuRRDefp2m1-l8eVnr8K5_ShoQCU,13368
43
- smart_search/assets/i18n/messages.json,sha256=Z0M41dCaUUYZfQY8hMNIBTjJjtNMAVgFootXQEJ76aY,643411
44
- smart_search/assets/skills/smart-search-cli/SKILL.md,sha256=465CCVKGCHfiJLDD9LlYbIhlbxWucwLiNVVAcRRlEf8,8561
43
+ smart_search/assets/i18n/messages.json,sha256=4ovku0BT4KouTk0g58b_jX3RqXvBDFlWFxDo-DSGI38,653887
44
+ smart_search/assets/skills/smart-search-cli/SKILL.md,sha256=gQ5HpNRnZ7VVYCu_MnyfAla62vQA2m6wl6mlvjfNLo8,8736
45
45
  smart_search/assets/skills/smart-search-cli/agents/openai.yaml,sha256=4S1VuEER7Puw9JvxDsyI__Tdrr1j2h6aF26yHho_x8E,162
46
46
  smart_search/assets/skills/smart-search-cli/references/cli-contract.md,sha256=mZyQRioJ4_ySkxcl9WQBMGXk5fcWOEMBMnXXUGnRoEA,1843
47
- smart_search/assets/skills/smart-search-cli/references/cli-core.md,sha256=aqUFtSDCfzB0StrxqnmeFUfE12roXGCMHtVHf3nIxLQ,15182
48
- smart_search/assets/skills/smart-search-cli/references/command-patterns.md,sha256=Io6DExbu4pSDz7-r51hyYtjd80CndCR8N7Q0XoEV9L4,12714
47
+ smart_search/assets/skills/smart-search-cli/references/cli-core.md,sha256=QusO29X5_psX-K2I67rUlz3cMqyuOyFZU6MnWIMMJ0Y,15240
48
+ smart_search/assets/skills/smart-search-cli/references/command-patterns.md,sha256=TC3HCEgTwrQf1w5AbmhhPqJRpCJHyyf9jTpCmfdy3WE,12902
49
49
  smart_search/assets/skills/smart-search-cli/references/deep-research-mode.md,sha256=Yt1qHQnbpKgA6P5PTXwv4qFDZTJn7MqarZvCqlmCg-0,14485
50
- smart_search/assets/skills/smart-search-cli/references/provider-routing.md,sha256=JVWHguq7OgguoNiV0Xr7Ay5g893saQPjNKp4QKGB7Ys,29423
50
+ smart_search/assets/skills/smart-search-cli/references/provider-routing.md,sha256=zSEOTKBpiIC_vTXCRTU5wiwcGmnGFzZbd_LgBjqdbko,30231
51
51
  smart_search/assets/skills/smart-search-cli/references/regression-release.md,sha256=SkOUwxpPm_HeBGlpUil5ybTBiwa-wAzt-765zw9kYqk,5673
52
- smart_search/assets/skills/smart-search-cli/references/setup-config.md,sha256=KpEOBLkoV0GZZ6PHRV0G3RUgbFilZlSguhcwy03GHVc,18587
52
+ smart_search/assets/skills/smart-search-cli/references/setup-config.md,sha256=lqXOSoIs0dGV7uamLeEvaLHX0MNQoMNmuKIyvFFEcuw,18964
53
53
  smart_search/assets/ui/index.html,sha256=i3se6JX5iHnGXIEXhHvLKZgPzNh8JMJAeRqfIlqac3U,63800
54
- smart_search/cli.py,sha256=QQk86ZqKqhK0xFuENCtUC3zAramfXzNUdOk5kBVRg80,168553
55
- smart_search/config.py,sha256=61Cp0UJiz1AD34ef80pQf1QnSz5w5zDbi458aUt8SxE,58275
54
+ smart_search/cli.py,sha256=kA3-roJfQsIjPHj81aA-7qXcJLEDkdxPDrYBrTZP9-M,169589
55
+ smart_search/config.py,sha256=hcK15frDWVyHFCrTMaejfM3cSPNO1ntkP0AOCfNm-tI,61256
56
56
  smart_search/desktop_backend.py,sha256=VYFc83YBllSljgbgx9oPPNbQLwGADZGVtN_CF08ShD8,42417
57
57
  smart_search/desktop_catalog.py,sha256=VMIsLFAD98Czetwy-ka606QnyUSk0gDBhsfz7k4QDl8,11485
58
58
  smart_search/desktop_cli.py,sha256=dpXLwf6b9Dgw9lKPTDZpKH1Z85OpIgrYyNJM2hXIu68,11069
@@ -64,7 +64,7 @@ smart_search/desktop_worker.py,sha256=Nkw_FMNNejrt8oJ6aNUmziSd7vVu_Xy9ijvfgstdaw
64
64
  smart_search/i18n.py,sha256=-mDfpi-P4AQPCUpnuI--W9c1AFurN5QPhAGYMXB02kU,4869
65
65
  smart_search/intent_router.py,sha256=UkFnv4R2xC6bF6QEn_lqSYqYOoWgoT_mXeG4CNcSJ3M,7931
66
66
  smart_search/jev.py,sha256=C80OBG2RMOJCMtogltVDHUg0xoCHO_8uIT69736myq8,27169
67
- smart_search/jev_search.py,sha256=k8V4xSXeIKraHTvDp2HDmbX2pPq5fTd-00n_FQwdPF0,49369
67
+ smart_search/jev_search.py,sha256=k2e5nkmB3tx9wSU0784yAR3QiwsbUubqWh8y-DJiW3A,49881
68
68
  smart_search/logger.py,sha256=pwfjP50ywoTRirH4H7qYTNgt4TgoMhZlX2pb8dNvO9k,1203
69
69
  smart_search/provider_descriptions.py,sha256=PISS5lhXG4nLtr8xW8Y2_Okcc21SwSWPPTmdLOhsOfY,11027
70
70
  smart_search/provider_errors.py,sha256=IXyLo6Kc4WSP7faH5mMTQyAIVKR0BR0680qd0ZApKUo,5384
@@ -90,20 +90,20 @@ smart_search/providers/context7.py,sha256=zOImhEiKUVg60h9SbYdJRBSUfoVvS-8N6O62su
90
90
  smart_search/providers/deepseek.py,sha256=br_XHY0lc5xvgppp_4dPd_g5UU9jzEVCronn_UvyoH4,4678
91
91
  smart_search/providers/exa.py,sha256=6BLnSxDLM0PKXlkcqRKw49G728yBkRicDzQiJt2vIzo,6875
92
92
  smart_search/providers/jina.py,sha256=hz8KAcFSstWT4DIBWKdEhymYjBjUvo_SFeZcwCdPkTs,4048
93
- smart_search/providers/openai_compatible.py,sha256=CL-zuya3gx8-8_hGn7rTT5vXtubYKfnHe2aC2jzcSI0,41439
93
+ smart_search/providers/openai_compatible.py,sha256=NmrEjO8vj6R_SDdQFAvv320DX2MLQg6NoEAFfhOwRVc,43120
94
94
  smart_search/providers/sciverse.py,sha256=kxhhkx4K08H7VGf4RWFdnB-dlmeEelDM2OcEwERTr8c,13567
95
95
  smart_search/providers/tinyfish.py,sha256=rdEjmPK_dDe-aD5M5sfJM6n2MA_uBuE2NVR6i7lKBqI,8446
96
- smart_search/providers/xai_responses.py,sha256=el6zfbZhGNMBKyhA2YtXWT1jmjsPEFStrTU41gi8sYU,10640
96
+ smart_search/providers/xai_responses.py,sha256=PL_1SOMWKm6BWwWgdjMntdN_VoFckXly1ye8fr4gBQ8,11485
97
97
  smart_search/providers/yuanbao.py,sha256=CsbL1iEson5lAbIRyuv2Rai0Nm6LiHPZTq8WhL3wUbE,6357
98
98
  smart_search/providers/zhipu.py,sha256=PN0_Vc51rDYlxEmz2B1vgSxEt9jXf_RIsRobGp-LBPs,7088
99
99
  smart_search/providers/zhipu_mcp.py,sha256=4XgckrHzwSdhUtMlTSvhVxrAA7Fr7QX_4BYWfSHmXUg,10976
100
100
  smart_search/sciverse_schema.py,sha256=ml5iDXEpY7cnEMz74BGTo4yxX7UvPOe_i7w4Pi5t1wY,18178
101
- smart_search/service.py,sha256=ZTa3zFz1mkPVN33S-lfsOOq2FKlyUUqOyfeHAOaMGRc,195325
101
+ smart_search/service.py,sha256=Viq6Of5jyB0XwQPAa_EOk8-J8gIpw7nyTGzaIgER4nk,197746
102
102
  smart_search/skill_installer.py,sha256=CvM9GQShaTR8ttNhilx8Up0vVsHZfsnYD5uM7wZrzBs,21782
103
103
  smart_search/skill_maintenance.py,sha256=2s2-1ZEEjRg2MfRKlaw6Y51OO86kOf9axl7fW6KMv0g,8818
104
- smart_search/sources.py,sha256=WvyRXqX_IdbDPpsqcSbulipxNtZvQbWv1MsZ2Ud5bTY,14376
104
+ smart_search/sources.py,sha256=uGRPMZ9wnNrkkby4edQkPSwBxJfiyfgtaDdKsQ9rdTA,15067
105
105
  smart_search/state_files.py,sha256=K02dlR3Xuj2vIkRiEdrFr3OSiEsPxN5_HSo4NTmz0sc,2063
106
106
  smart_search/ui_api.py,sha256=p73zozirtzu7u8AQS4uWTCbsbomkbkxCExs_mE1_HW4,10043
107
- smart_search/ui_metadata.py,sha256=t_w-g2kP6HWCDbaE_LcoyRfHhwE5IT4bWsX1s1yYoCc,53939
107
+ smart_search/ui_metadata.py,sha256=o1K1ZbPhHYyLF9mSDn5V0C6aM7WhC5Vf77Nofi1Seww,55433
108
108
  smart_search/ui_server.py,sha256=pcAPiYWmJXTmX1Xpy12v97JhkbRx-nJ3zRYefGW1Tc0,20508
109
- smart_search/utils.py,sha256=HVpxtyveEHcpGSkIObf97GbCJVRd68f1X6ecQeH14Ks,8992
109
+ smart_search/utils.py,sha256=2b1Wy1Jk0loJ1MainS1N8WYjtMXwTQ9JlVOra3gYhEY,9193
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konbakuyomu/smart-search",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
4
4
  "description": "CLI-first multi-source web research with xAI/OpenAI-compatible search, Exa, Context7, Zhipu, Tavily, Firecrawl, and Deep Research planning.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/konbakuyomu/smartsearch#readme",
@@ -39,11 +39,11 @@
39
39
  },
40
40
  "smartSearchBinary": true,
41
41
  "optionalDependencies": {
42
- "@konbakuyomu/smart-search-darwin-x64": "0.1.32",
43
- "@konbakuyomu/smart-search-darwin-arm64": "0.1.32",
44
- "@konbakuyomu/smart-search-win32-x64": "0.1.32",
45
- "@konbakuyomu/smart-search-win32-arm64": "0.1.32",
46
- "@konbakuyomu/smart-search-linux-x64": "0.1.32",
47
- "@konbakuyomu/smart-search-linux-arm64": "0.1.32"
42
+ "@konbakuyomu/smart-search-darwin-x64": "0.1.34",
43
+ "@konbakuyomu/smart-search-darwin-arm64": "0.1.34",
44
+ "@konbakuyomu/smart-search-win32-x64": "0.1.34",
45
+ "@konbakuyomu/smart-search-win32-arm64": "0.1.34",
46
+ "@konbakuyomu/smart-search-linux-x64": "0.1.34",
47
+ "@konbakuyomu/smart-search-linux-arm64": "0.1.34"
48
48
  }
49
49
  }
Binary file