diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..000b13b1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,150 @@ +# AGENTS.md - Notemd Obsidian Plugin + +This is the project-level execution guide for Codex and other repository agents. Treat this file as the authority for build, verification, release, and repository workflow rules. [`GEMINI.md`](./GEMINI.md) remains as a background project overview and should not be rewritten to mirror this file. + +## Project Overview + +Notemd is an Obsidian plugin written in TypeScript. It enhances note workflows with LLM-powered processing, concept extraction, Mermaid generation/fixing, translation, web research, and custom one-click workflows. + +## High-Risk Areas + +These files and modules have the highest regression risk and should be treated as coupled surfaces during implementation: + +- `src/main.ts`: plugin entrypoint, commands, settings loading, and task routing. +- `src/llmProviders.ts`: single source of truth for provider presets, metadata, ordering, and provider-side validation. +- `src/llmUtils.ts`: provider transport dispatch, request shaping, retry logic, and connection testing. +- `src/fileUtils.ts` and `src/searchUtils.ts`: task flows that depend on `callLLM()` behavior. +- `src/ui/NotemdSettingTab.ts`: provider settings, workflow builder, and test-connection UI. +- `src/workflowButtons.ts` and `src/ui/NotemdSidebarView.ts`: sidebar actions and custom workflow composition. +- `src/mermaidProcessor.ts`: Mermaid fix and error-detection logic. + +When changing one of these areas, check whether a sibling module must also be updated. + +## Build And Verification + +Use these commands from repository root: + +```bash +npm install +npm run build +npm test -- --runInBand +``` + +Verification requirements: + +- Do not claim work is complete without fresh build and test output. +- Prefer `npm test -- --runInBand` for reliable local verification in this repository. +- Run targeted Jest tests first when doing TDD, then rerun the full suite before commit or release. +- Run `git diff --check` before committing to catch whitespace and patch-format issues. + +### Obsidian CLI Checks + +When release or integration verification is requested: + +```bash +obsidian help +obsidian-cli help +``` + +Rules: + +- Try both commands when the user asks for Obsidian CLI validation. +- If `obsidian-cli` is not installed or `obsidian` is blocked by desktop/X11 constraints, report that explicitly. +- Do not claim successful CLI validation unless the command actually executed and produced usable output. + +## TDD Rules + +For behavior changes, bug fixes, or new providers: + +1. Write the failing test first. +2. Run the targeted test and confirm it fails for the expected reason. +3. Implement the minimal change. +4. Re-run the targeted test. +5. Re-run the full test suite. + +New providers or provider-behavior changes must include tests in the relevant files: + +- `src/tests/llmProviders.test.ts` +- `src/tests/llmUtilsProviderSupport.test.ts` +- Additional focused tests when adding local validation or UI-facing provider rules + +## Provider Extension Rules + +All new LLM providers must follow these rules: + +- Add the preset to `src/llmProviders.ts`. +- Prefer transport-driven support in `src/llmUtils.ts`; do not add provider-name branching unless the transport genuinely differs. +- Set provider metadata for: + - transport + - category + - API key mode + - API test mode + - default base URL and model + - setup hint and description +- Update documentation in both `README.md` and `README_zh.md`. +- Add or update tests for provider registry coverage, runtime routing, and connection testing. + +For OpenAI-compatible endpoints, default to the shared runtime unless the provider requires a dedicated protocol. + +## UI, Workflow, And Mermaid Rules + +If you touch workflow or sidebar behavior: + +- Verify button wiring and workflow chaining tests still pass. +- Preserve the default `One-Click Extract` workflow unless intentionally changing product behavior. + +If you touch Mermaid-related behavior: + +- Re-check batch Mermaid fix coverage. +- Re-check workflow paths that can generate Mermaid output. +- Keep Mermaid repair logic scoped to Mermaid code blocks. + +## Documentation Rules + +Documentation is part of the feature. If behavior, provider support, or release workflow changes, update the docs in the same branch. + +At minimum, evaluate whether these files need updates: + +- `README.md` +- `README_zh.md` +- `change.md` +- `AGENTS.md` + +## Release Rules + +Before creating a release: + +1. Sync version references in repository metadata. + - `package.json` + - `manifest.json` + - `versions.json` + - any version text in `README.md` and `README_zh.md` +2. Run `npm run build`. +3. Run `npm test -- --runInBand`. +4. Confirm `git status` is clean after the release commit. +5. Create/push the Git tag. +6. Create the GitHub Release with release notes. + +### Required GitHub Release Assets + +Every GitHub release for this plugin must upload all of the following assets: + +- `main.js` +- `manifest.json` +- `styles.css` +- `README.md` + +This is mandatory. Do not publish a release that omits `README.md`. + +## Git Safety Rules + +- Use non-interactive git commands. +- Never use destructive resets or reverts unless explicitly requested. +- Do not overwrite or discard unrelated user changes. +- If the working tree contains unexpected edits you did not make and cannot explain, stop and ask. +- Prefer feature branches or worktrees for substantial work. + +## Repository Notes + +- `main.js` is gitignored and is expected to be generated during builds and uploaded as a release asset. +- This repository already has broad Jest coverage; treat test regressions as meaningful unless proven otherwise. diff --git a/README.md b/README.md index 6900a253..ae8a3bf5 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ A Easy way to create your own Knowledge-base! Notemd enhances your Obsidian workflow by integrating with various Large Language Models (LLMs) to process your multi-languages notes, automatically generate wiki-links for key concepts, create corresponding concept notes, perform web research, helping you build powerful knowledge graphs and more. -**Version:** 1.7.0 +**Version:** 1.7.1 show multi-langu @@ -58,7 +58,7 @@ That's it! Explore the settings to unlock more features like web research, trans - **Cancellable Operations**: Cancel any processing task (single or batch) initiated from the sidebar via its dedicated cancel button. Command palette operations use a modal which can also be cancelled. - **Multi-Model Configuration**: Use different LLM providers *and* specific models for different tasks (Add Links, Research, Generate Title, Translate) or use a single provider for all. - **Stable API Calls (Retry Logic)**: Optionally enable automatic retries for failed LLM API calls with configurable interval and attempt limits. -- **China-Ready Provider Presets**: Built-in presets now cover `Qwen`, `Doubao`, `Moonshot`, `GLM`, and `MiniMax` in addition to the existing global and local providers. +- **China-Ready Provider Presets**: Built-in presets now cover `Qwen`, `Doubao`, `Moonshot`, `GLM`, `MiniMax`, `Baidu Qianfan`, and `SiliconFlow` in addition to the existing global and local providers. - **Reliable Batch Processing**: Improved concurrent processing logic with **staggered API calls** to prevent rate-limiting errors and ensure stable performance during large batch jobs. The new implementation ensures that tasks are initiated at different intervals rather than all at once. - **Accurate Progress Reporting**: Fixed a bug where the progress bar could get stuck, ensuring that the UI always reflects the true status of the operation. - **Robust Parallel Batch Processing**: Resolved an issue where parallel batch operations would stall prematurely, ensuring all files are processed reliably and efficiently. @@ -185,14 +185,14 @@ Access plugin settings via: ### LLM Provider Configuration 1. **Active Provider**: Select the LLM provider you want to use from the dropdown menu. 2. **Provider Settings**: Configure the specific settings for the selected provider: - * **API Key**: Required for most cloud providers (e.g., OpenAI, Anthropic, DeepSeek, Qwen, Doubao, Moonshot, GLM, MiniMax, Google, Mistral, Azure OpenAI, OpenRouter, xAI, Groq, Together, Fireworks, Requesty). Not needed for Ollama. Optional for LM Studio and the generic `OpenAI Compatible` preset when your endpoint accepts anonymous or placeholder access. + * **API Key**: Required for most cloud providers (e.g., OpenAI, Anthropic, DeepSeek, Qwen, Doubao, Moonshot, GLM, MiniMax, Baidu Qianfan, SiliconFlow, Google, Mistral, Azure OpenAI, OpenRouter, xAI, Groq, Together, Fireworks, Requesty). Not needed for Ollama. Optional for LM Studio and the generic `OpenAI Compatible` preset when your endpoint accepts anonymous or placeholder access. * **Base URL / Endpoint**: The API endpoint for the service. Defaults are provided, but you may need to change this for local models (LMStudio, Ollama), gateways (OpenRouter, Requesty, OpenAI Compatible), or specific Azure deployments. **Required for Azure OpenAI.** * **Model**: The specific model name/ID to use (e.g., `gpt-4o`, `claude-3-5-sonnet-20240620`, `google/gemini-flash-1.5`, `grok-4`, `moonshotai/kimi-k2-instruct-0905`, `accounts/fireworks/models/kimi-k2p5`, `anthropic/claude-3-7-sonnet-latest`). Ensure the model is available at your endpoint/provider. * **Temperature**: Controls the randomness of the LLM's output (0=deterministic, 1=max creativity). Lower values (e.g., 0.2-0.5) are generally better for structured tasks. * **API Version (Azure Only)**: Required for Azure OpenAI deployments (e.g., `2024-02-15-preview`). -3. **Test Connection**: Use the "Test Connection" button for the active provider to verify your settings. OpenAI-compatible providers now use provider-aware checks: endpoints such as `Qwen`, `Doubao`, `Moonshot`, `GLM`, `MiniMax`, `Groq`, `Together`, `Fireworks`, `LMStudio`, and `OpenAI Compatible` probe `chat/completions` directly, while providers with a reliable `/models` endpoint can still use model listing first. +3. **Test Connection**: Use the "Test Connection" button for the active provider to verify your settings. OpenAI-compatible providers now use provider-aware checks: endpoints such as `Qwen`, `Doubao`, `Moonshot`, `GLM`, `MiniMax`, `Baidu Qianfan`, `SiliconFlow`, `Groq`, `Together`, `Fireworks`, `LMStudio`, and `OpenAI Compatible` probe `chat/completions` directly, while providers with a reliable `/models` endpoint can still use model listing first. 4. **Manage Provider Configurations**: Use the "Export Providers" and "Import Providers" buttons to save/load your LLM provider settings to/from a `notemd-providers.json` file within the plugin's configuration directory. This allows for easy backup and sharing. -5. **Preset Coverage**: In addition to the original providers, Notemd now includes preset entries for `Qwen`, `Doubao`, `Moonshot`, `GLM`, `MiniMax`, `xAI`, `Groq`, `Together`, `Fireworks`, `Requesty`, and a generic `OpenAI Compatible` target for LiteLLM, vLLM, Perplexity, Vercel AI Gateway, or custom proxies. +5. **Preset Coverage**: In addition to the original providers, Notemd now includes preset entries for `Qwen`, `Doubao`, `Moonshot`, `GLM`, `MiniMax`, `Baidu Qianfan`, `SiliconFlow`, `xAI`, `Groq`, `Together`, `Fireworks`, `Requesty`, and a generic `OpenAI Compatible` target for LiteLLM, vLLM, Perplexity, Vercel AI Gateway, or custom proxies. LLM ### Multi-Model Configuration @@ -526,6 +526,8 @@ This is the core functionality focused on identifying concepts and adding `[[wik | Moonshot | Cloud | Yes | Official Kimi / Moonshot endpoint | | GLM | Cloud | Yes | Official Zhipu BigModel OpenAI-compatible endpoint | | MiniMax | Cloud | Yes | Official MiniMax chat-completions endpoint | +| Baidu Qianfan | Cloud | Yes | Official Qianfan OpenAI-compatible endpoint for ERNIE models | +| SiliconFlow | Cloud | Yes | Official SiliconFlow OpenAI-compatible endpoint for hosted OSS models | | OpenAI | Cloud | Yes | Supports GPT and o-series models | | Anthropic | Cloud | Yes | Supports Claude models | | Google | Cloud | Yes | Supports Gemini models | @@ -543,7 +545,7 @@ This is the core functionality focused on identifying concepts and adding `[[wik *Note: For local providers (LMStudio, Ollama), ensure the respective server application is running and accessible at the configured Base URL.* *Note: For OpenRouter and Requesty, use the provider-prefixed/full model identifier shown by the gateway (for example `google/gemini-flash-1.5` or `anthropic/claude-3-7-sonnet-latest`).* -*Note: `Doubao` usually expects an Ark endpoint/deployment ID in the model field rather than a raw model family name. Replace the preset placeholder before testing or running tasks.* +*Note: `Doubao` usually expects an Ark endpoint/deployment ID in the model field rather than a raw model family name. The settings screen now warns when the placeholder value is still present and blocks connection tests until you replace it with a real endpoint ID.* *Note: China-focused presets use chat-first connection checks so the test validates the actual configured model/deployment, not only API-key reachability.* *Note: `OpenAI Compatible` is intended for custom gateways and proxies. Set the Base URL, API key policy, and model ID according to your provider's documentation.* diff --git a/README_zh.md b/README_zh.md index db162622..dfd572cb 100644 --- a/README_zh.md +++ b/README_zh.md @@ -18,7 +18,7 @@ Notemd 通过与各种大型语言模型 (LLM) 集成来增强您的 Obsidian 工作流程,支持多语言笔记处理,自动为关键概念生成维基链接、创建相应的概念笔记、执行网页搜索与摘要、翻译内容、总结为Mermaid脑图等,助力构建强大的知识图谱。 -**版本:** 1.7.0 +**版本:** 1.7.1 ![image](https://img.shields.io/github/v/release/Jacobinwwey/obsidian-NotEMD?label=Version&link=https%3A%2F%2Fgithub.com%2Jacobinwwey%2Fobsidian-NotEMD%2Freleases%2Flatest) ![image](https://img.shields.io/github/downloads/Jacobinwwey/obsidian-NotEMD/total?logo=Obsidian&label=Downloads&labelColor=%237C3AED&color=%235b5b5b&link=https%3A%2F%2Fgithub.com%2Jacobinwwey%2Fobsidian-NotEMD%2Freleases%2Flatest) @@ -58,7 +58,7 @@ Notemd 通过与各种大型语言模型 (LLM) 集成来增强您的 Obsidian - **可取消操作**: 可以通过侧边栏的专用取消按钮取消任何处理任务(单个或批量)。命令面板操作使用模式窗口,也可以取消。 - **多模型配置**: 为不同任务(添加链接、研究、生成标题)使用不同的 LLM 提供商*和*特定模型,或为所有任务使用单一提供商。 - **稳定的 API 调用(重试逻辑)**: 可选择为失败的 LLM API 调用启用自动重试,并可配置重试间隔和尝试次数限制。 -- **中国区 Provider 预设增强**: 内置补充了 `Qwen`、`Doubao`、`Moonshot`、`GLM`、`MiniMax` 等中国区常用云端模型服务商预设。 +- **中国区 Provider 预设增强**: 内置补充了 `Qwen`、`Doubao`、`Moonshot`、`GLM`、`MiniMax`、`Baidu Qianfan`、`SiliconFlow` 等中国区常用云端模型服务商预设。 - **可靠的批处理**: 改进了并发处理逻辑,通过**交错的API调用**来防止速率限制错误,确保在大型批处理作业中性能稳定。新的实现确保任务在不同时间间隔启动,而不是同时启动。 - **准确的进度报告**: 修复了进度条可能卡住的错误,确保用户界面始终反映操作的真实状态。 - **健壮的并行批处理**: 解决了并行批处理操作过早停止的问题,确保所有文件都能可靠高效地处理。 @@ -187,17 +187,17 @@ Notemd 通过与各种大型语言模型 (LLM) 集成来增强您的 Obsidian : 配置所选提供商的具体设置: - - **API 密钥 (API Key)**: 大多数云提供商(例如 OpenAI、Anthropic、DeepSeek、Qwen、Doubao、Moonshot、GLM、MiniMax、Google、Mistral、Azure OpenAI、OpenRouter、xAI、Groq、Together、Fireworks、Requesty)需要。Ollama 不需要。LMStudio 和通用的 `OpenAI Compatible` 预设在某些允许匿名或占位密钥的端点上可留空。 + - **API 密钥 (API Key)**: 大多数云提供商(例如 OpenAI、Anthropic、DeepSeek、Qwen、Doubao、Moonshot、GLM、MiniMax、Baidu Qianfan、SiliconFlow、Google、Mistral、Azure OpenAI、OpenRouter、xAI、Groq、Together、Fireworks、Requesty)需要。Ollama 不需要。LMStudio 和通用的 `OpenAI Compatible` 预设在某些允许匿名或占位密钥的端点上可留空。 - **基础 URL / 端点 (Base URL / Endpoint)**: 服务的 API 端点。提供了默认值,但您可能需要为本地模型(LMStudio、Ollama)、网关(OpenRouter、Requesty、OpenAI Compatible)或特定 Azure 部署修改此项。**Azure OpenAI 必填。** - **模型 (Model)**: 要使用的具体模型名称/ID(例如 `gpt-4o`, `claude-3-5-sonnet-20240620`, `google/gemini-flash-1.5`, `grok-4`, `moonshotai/kimi-k2-instruct-0905`, `accounts/fireworks/models/kimi-k2p5`, `anthropic/claude-3-7-sonnet-latest`)。请确保该模型在您的端点/提供商处可用。 - **温度 (Temperature)**: 控制 LLM 输出的随机性(0=确定性,1=最大创造力)。较低的值(例如 0.2-0.5)通常更适合结构化任务。 - **API 版本 (仅限 Azure) (API Version (Azure Only))**: Azure OpenAI 部署需要(例如 `2024-02-15-preview`)。 -3. **测试连接 (Test Connection)**: 使用活动提供商的“测试连接”按钮来验证您的设置。OpenAI-compatible 提供商现在会按 provider 特性自动选择测试策略:`Qwen`、`Doubao`、`Moonshot`、`GLM`、`MiniMax`、`Groq`、`Together`、`Fireworks`、`LMStudio` 与 `OpenAI Compatible` 会直接探测 `chat/completions`,而具备稳定 `/models` 端点的服务仍会优先走模型列表探测。 +3. **测试连接 (Test Connection)**: 使用活动提供商的“测试连接”按钮来验证您的设置。OpenAI-compatible 提供商现在会按 provider 特性自动选择测试策略:`Qwen`、`Doubao`、`Moonshot`、`GLM`、`MiniMax`、`Baidu Qianfan`、`SiliconFlow`、`Groq`、`Together`、`Fireworks`、`LMStudio` 与 `OpenAI Compatible` 会直接探测 `chat/completions`,而具备稳定 `/models` 端点的服务仍会优先走模型列表探测。 4. **管理提供商配置 (Manage Provider Configurations)**: 使用“导出提供商”和“导入提供商”按钮将您的 LLM 提供商设置保存到插件配置目录中的 `notemd-providers.json` 文件或从中加载。这便于备份和共享。 -5. **预设覆盖范围 (Preset Coverage)**: 除了原有提供商外,Notemd 现在还内置了 `Qwen`、`Doubao`、`Moonshot`、`GLM`、`MiniMax`、`xAI`、`Groq`、`Together`、`Fireworks`、`Requesty` 以及通用的 `OpenAI Compatible` 预设,可用于 LiteLLM、vLLM、Perplexity、Vercel AI Gateway 或自定义代理。 +5. **预设覆盖范围 (Preset Coverage)**: 除了原有提供商外,Notemd 现在还内置了 `Qwen`、`Doubao`、`Moonshot`、`GLM`、`MiniMax`、`Baidu Qianfan`、`SiliconFlow`、`xAI`、`Groq`、`Together`、`Fireworks`、`Requesty` 以及通用的 `OpenAI Compatible` 预设,可用于 LiteLLM、vLLM、Perplexity、Vercel AI Gateway 或自定义代理。 LLM @@ -599,6 +599,8 @@ Notemd 通过与各种大型语言模型 (LLM) 集成来增强您的 Obsidian | Moonshot | 云 | 是 | Moonshot / Kimi 官方端点 | | GLM | 云 | 是 | 智谱 BigModel 官方 OpenAI-compatible 端点 | | MiniMax | 云 | 是 | MiniMax 官方 chat-completions 端点 | +| Baidu Qianfan | 云 | 是 | 百度千帆官方 OpenAI-compatible 端点,适合 ERNIE 等模型 | +| SiliconFlow | 云 | 是 | SiliconFlow 官方 OpenAI-compatible 端点,适合托管开源模型 | | OpenAI | 云 | 是 | 支持 GPT 与 o 系列模型 | | Anthropic | 云 | 是 | 支持 Claude 系列 | | Google | 云 | 是 | 支持 Gemini 系列 | @@ -616,7 +618,7 @@ Notemd 通过与各种大型语言模型 (LLM) 集成来增强您的 Obsidian *本地服务商请确保服务器已启动且 Base URL 配置正确。* *OpenRouter 与 Requesty 请使用网关要求的完整/带前缀模型 ID(例如 `google/gemini-flash-1.5` 或 `anthropic/claude-3-7-sonnet-latest`)。* -*`Doubao` 预设通常要求在模型字段中填写 Ark Endpoint/Deployment ID,而不是直接填写模型家族名。请先替换默认占位值再测试连接或执行任务。* +*`Doubao` 预设通常要求在模型字段中填写 Ark Endpoint/Deployment ID,而不是直接填写模型家族名。设置页现在会在仍使用默认占位值时发出警告,并阻止连接测试,直到你替换为真实 Endpoint ID。* *中国区兼容协议预设默认使用 chat-first 测试,这样可以同时验证 API Key、模型名或部署 ID 是否真正可用。* *`OpenAI Compatible` 预设用于自定义网关与代理,请按对应服务文档填写 Base URL、鉴权方式和模型 ID。* diff --git a/docs/superpowers/plans/2026-03-26-agents-and-provider-expansion.md b/docs/superpowers/plans/2026-03-26-agents-and-provider-expansion.md new file mode 100644 index 00000000..c474716a --- /dev/null +++ b/docs/superpowers/plans/2026-03-26-agents-and-provider-expansion.md @@ -0,0 +1,135 @@ +# AGENTS And Provider Expansion Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add a project-level `AGENTS.md`, improve Doubao settings validation, and extend Notemd with Baidu Qianfan and SiliconFlow provider support. + +**Architecture:** Keep provider metadata centralized in `src/llmProviders.ts`, keep runtime dispatch transport-driven in `src/llmUtils.ts`, and add a small pure validation helper for settings UI warnings. Repository workflow rules live in a new root `AGENTS.md` without modifying `GEMINI.md`. + +**Tech Stack:** TypeScript, Jest, Obsidian plugin settings UI, GitHub Releases + +--- + +### Task 1: Add Project-Level AGENTS Guide + +**Files:** +- Create: `AGENTS.md` +- Test/Verify: `README.md`, `README_zh.md`, release workflow in local git history + +- [ ] **Step 1: Draft the guide content** +Write a concise but authoritative `AGENTS.md` covering project overview, key files, build/test commands, release steps, documentation sync, provider-extension rules, and Git safety. + +- [ ] **Step 2: Ensure release asset rule is explicit** +Document that GitHub release assets must include `main.js`, `manifest.json`, `styles.css`, and `README.md`. + +- [ ] **Step 3: Self-review for overlap** +Confirm the guide is complete enough for agent execution while avoiding unnecessary duplication of `GEMINI.md`. + +### Task 2: Add Doubao Validation Test First + +**Files:** +- Create: `src/tests/providerValidation.test.ts` +- Modify: `src/llmProviders.ts` + +- [ ] **Step 1: Write failing tests** +Add tests that describe the expected warning behavior for Doubao placeholder/invalid endpoint-like models and a no-warning path for valid-looking values. + +- [ ] **Step 2: Run targeted test to verify failure** +Run: `npm test -- --runInBand src/tests/providerValidation.test.ts` +Expected: FAIL because validation helper does not exist yet. + +- [ ] **Step 3: Implement minimal validation helper** +Add a small pure helper in `src/llmProviders.ts` that returns warnings based on provider config. + +- [ ] **Step 4: Re-run targeted test** +Run: `npm test -- --runInBand src/tests/providerValidation.test.ts` +Expected: PASS. + +### Task 3: Surface Doubao Validation In Settings UI + +**Files:** +- Modify: `src/ui/NotemdSettingTab.ts` +- Modify: `styles.css` + +- [ ] **Step 1: Add failing UI-oriented test or helper-consumer test if needed** +If pure UI testing is too heavy, add a helper-level assertion plus a focused behavior test around connection-test gating. + +- [ ] **Step 2: Implement warning rendering** +Render a visible warning callout when the active provider has validation warnings. + +- [ ] **Step 3: Gate connection test for obviously invalid Doubao config** +If warnings exist for the active provider, show a Notice and skip the remote test request. + +- [ ] **Step 4: Re-run affected tests** +Run relevant Jest tests covering provider validation and settings behavior. + +### Task 4: Add Qianfan And SiliconFlow Tests First + +**Files:** +- Modify: `src/tests/llmProviders.test.ts` +- Modify: `src/tests/llmUtilsProviderSupport.test.ts` + +- [ ] **Step 1: Extend registry tests** +Add failing assertions for `Baidu Qianfan` and `SiliconFlow` provider presence and metadata. + +- [ ] **Step 2: Extend runtime/API tests** +Add failing tests asserting both providers route through the openai-compatible runtime and use chat-first API probing. + +- [ ] **Step 3: Run targeted tests to verify failure** +Run: `npm test -- --runInBand src/tests/llmProviders.test.ts src/tests/llmUtilsProviderSupport.test.ts` +Expected: FAIL until provider definitions are added. + +### Task 5: Implement Provider Registry And Runtime Support + +**Files:** +- Modify: `src/llmProviders.ts` +- Modify: `src/llmUtils.ts` +- Modify: `src/ui/NotemdSettingTab.ts` + +- [ ] **Step 1: Add provider definitions** +Add `Baidu Qianfan` and `SiliconFlow` with official base URLs, representative model IDs, metadata, and chat-first API-test mode. + +- [ ] **Step 2: Reuse transport-driven runtime** +Ensure both providers work through the existing openai-compatible runtime and connection-test path. + +- [ ] **Step 3: Update settings copy if needed** +Mention the expanded provider surface in the settings callout or related provider descriptions. + +- [ ] **Step 4: Re-run targeted provider tests** +Run: `npm test -- --runInBand src/tests/llmProviders.test.ts src/tests/llmUtilsProviderSupport.test.ts` +Expected: PASS. + +### Task 6: Update Documentation + +**Files:** +- Modify: `README.md` +- Modify: `README_zh.md` + +- [ ] **Step 1: Update provider lists and configuration notes** +Document `Baidu Qianfan` and `SiliconFlow`, and clarify Doubao endpoint-ID guidance. + +- [ ] **Step 2: Update release-process guidance if appropriate** +Make sure repository documentation is consistent with the new `AGENTS.md` release asset rule. + +### Task 7: Final Verification + +**Files:** +- Verify: repository root and touched source/test/docs files + +- [ ] **Step 1: Run build** +Run: `npm run build` +Expected: PASS. + +- [ ] **Step 2: Run full test suite** +Run: `npm test -- --runInBand` +Expected: PASS with the known Jest open-handle warning only if it remains unchanged. + +- [ ] **Step 3: Check git diff quality** +Run: `git diff --check` +Expected: PASS. + +- [ ] **Step 4: Commit implementation** +```bash +git add AGENTS.md docs/superpowers/specs/2026-03-26-agents-and-provider-expansion-design.md docs/superpowers/plans/2026-03-26-agents-and-provider-expansion.md README.md README_zh.md src/llmProviders.ts src/llmUtils.ts src/ui/NotemdSettingTab.ts src/tests/llmProviders.test.ts src/tests/llmUtilsProviderSupport.test.ts src/tests/providerValidation.test.ts styles.css +git commit -m "feat: add agent guide and expand provider presets" +``` diff --git a/docs/superpowers/specs/2026-03-26-agents-and-provider-expansion-design.md b/docs/superpowers/specs/2026-03-26-agents-and-provider-expansion-design.md new file mode 100644 index 00000000..b3aa657c --- /dev/null +++ b/docs/superpowers/specs/2026-03-26-agents-and-provider-expansion-design.md @@ -0,0 +1,56 @@ +# AGENTS And Provider Expansion Design + +**Date:** 2026-03-26 + +## Scope + +This design covers three related changes for Notemd: + +1. Add a project-level `AGENTS.md` as the execution authority for Codex/agent workflows without rewriting `GEMINI.md`. +2. Add front-end validation guidance for the `Doubao` preset so users get explicit feedback when the model field still looks like an unset Ark endpoint placeholder. +3. Extend the centralized LLM provider registry with `Baidu Qianfan` and `SiliconFlow`, including runtime support, API connection testing, documentation, and regression tests. + +These changes are grouped because they all improve agent/operator reliability: one at repository workflow level, one at provider UX level, and one at provider surface level. + +## Architecture + +The provider architecture should continue to use `src/llmProviders.ts` as the single source of truth for provider metadata. New providers must be represented by registry definitions and flow through transport-driven dispatch in `src/llmUtils.ts` instead of adding more provider-name branching. + +Provider validation should be implemented as a small pure helper that returns warnings for UI consumers. The settings tab can render those warnings and gate connection tests when the provider configuration is obviously incomplete. This keeps the validation logic testable and reusable without embedding brittle UI-only conditionals. + +The new `AGENTS.md` should be repository-local and opinionated about Notemd's build, verification, documentation, release, and asset-upload workflow. It should explicitly document that GitHub releases for this plugin must include `README.md` in addition to `main.js`, `manifest.json`, and `styles.css`. + +## Components + +### 1. Repository Guidance +- Create `AGENTS.md` at repository root. +- Keep `GEMINI.md` unchanged. +- Include project overview, key files, build/test commands, release workflow, documentation sync rules, provider-extension rules, and Git safety rules. + +### 2. Provider Validation +- Add a small validation helper near the provider registry. +- Detect when `Doubao` still uses the placeholder endpoint model, or when the configured model does not resemble an Ark endpoint ID. +- Surface warnings in the settings UI before users run tasks or connection tests. + +### 3. Provider Expansion +- Add `Baidu Qianfan` and `SiliconFlow` presets to the registry. +- Keep them on the existing `openai-compatible` transport. +- Assign API-test metadata so the connection test validates the actual configured model. +- Update docs in both `README.md` and `README_zh.md`. + +## Error Handling + +The connection-test path should keep current behavior for runtime/API errors. The new provider-validation layer should only handle obviously incomplete local configuration and produce user-facing warnings. It should not attempt to guess or auto-rewrite provider models. + +For `Doubao`, the UX should nudge users toward a valid Ark endpoint configuration while leaving room for provider-side model naming changes. A warning is safer than destructive normalization. + +## Testing Strategy + +- Add unit tests for provider validation warnings. +- Extend provider registry tests to assert presence of `Baidu Qianfan` and `SiliconFlow`. +- Extend provider runtime/API tests to assert both providers use the openai-compatible runtime and chat-first API probing. +- Re-run full build and Jest suite after implementation. + +## Review Notes + +A dedicated spec-review subagent was not used here because this session does not have explicit user authorization to delegate sub-agent work. This design was self-reviewed against the repository's current provider architecture and release workflow. diff --git a/manifest.json b/manifest.json index 70b2ef61..b9e253d2 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "notemd", "name": "Notemd", - "version": "1.7.0", + "version": "1.7.1", "minAppVersion": "0.15.0", "description": "Enhances notes using LLMs: 1) Processes text to add [[wiki-links]] and create concept notes. 2) Performs web research (Tavily/DuckDuckGo) and summarizes topics. 3) Generates content from note titles. 4) Translates notes/selections. 5) build powerful knowledge graphs Supports multiple LLM providers and customizable output.", "author": "jacob", diff --git a/package-lock.json b/package-lock.json index 6955e7d3..266eec13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "notemd", - "version": "1.6.6", + "version": "1.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "notemd", - "version": "1.6.6", + "version": "1.7.0", "license": "MIT", "dependencies": { "mermaid": "^11.12.2" diff --git a/package.json b/package.json index 8457ce76..31d6b249 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "notemd", - "version": "1.7.0", + "version": "1.7.1", "description": "Enhances notes using LLMs: 1) Processes text to add [[wiki-links]] and create concept notes. 2) Performs web research (Tavily/DuckDuckGo) and summarizes topics. 3) Generates content from note titles. 4) Translates notes/selections. Supports multiple LLM providers and customizable output.", "main": "main.js", "scripts": { diff --git a/src/llmProviders.ts b/src/llmProviders.ts index 980aa96b..817545b4 100644 --- a/src/llmProviders.ts +++ b/src/llmProviders.ts @@ -4,6 +4,7 @@ export type LLMProviderCategory = 'cloud' | 'gateway' | 'local'; export type LLMProviderTransport = 'openai-compatible' | 'anthropic' | 'google' | 'azure-openai' | 'ollama'; export type LLMProviderApiKeyMode = 'required' | 'optional' | 'none'; export type LLMProviderApiTestMode = 'models-then-chat' | 'chat-only'; +export type LLMProviderValidationLevel = 'warning' | 'error'; export interface LLMProviderDefinition { name: string; @@ -16,6 +17,11 @@ export interface LLMProviderDefinition { defaultConfig: LLMProviderConfig; } +export interface LLMProviderValidationIssue { + level: LLMProviderValidationLevel; + message: string; +} + export const LLM_PROVIDER_DEFINITIONS: LLMProviderDefinition[] = [ { name: 'DeepSeek', @@ -113,6 +119,38 @@ export const LLM_PROVIDER_DEFINITIONS: LLMProviderDefinition[] = [ temperature: 0.3 } }, + { + name: 'Baidu Qianfan', + category: 'cloud', + transport: 'openai-compatible', + apiKeyMode: 'required', + apiTestMode: 'chat-only', + description: 'Baidu Qianfan OpenAI-compatible endpoint for ERNIE and other hosted models.', + setupHint: 'Use Qianfan model IDs such as ernie-4.5-turbo-32k or other Qianfan-hosted models available in your account.', + defaultConfig: { + name: 'Baidu Qianfan', + apiKey: '', + baseUrl: 'https://qianfan.baidubce.com/v2', + model: 'ernie-4.5-turbo-32k', + temperature: 0.3 + } + }, + { + name: 'SiliconFlow', + category: 'cloud', + transport: 'openai-compatible', + apiKeyMode: 'required', + apiTestMode: 'chat-only', + description: 'SiliconFlow OpenAI-compatible endpoint for hosted OSS models and reasoning models.', + setupHint: 'Use SiliconFlow model IDs such as Qwen/QwQ-32B, DeepSeek-R1, or other SiliconFlow-hosted model names.', + defaultConfig: { + name: 'SiliconFlow', + apiKey: '', + baseUrl: 'https://api.siliconflow.cn/v1', + model: 'Qwen/QwQ-32B', + temperature: 0.3 + } + }, { name: 'OpenAI', category: 'cloud', @@ -355,6 +393,37 @@ export function isOpenAICompatibleProvider(name: string): boolean { return getLLMProviderDefinition(name)?.transport === 'openai-compatible'; } +function looksLikeDoubaoEndpointId(model: string): boolean { + return /^ep-[a-z0-9-]{8,}$/i.test(model.trim()); +} + +export function getProviderValidationIssues(provider: Pick): LLMProviderValidationIssue[] { + if (provider.name !== 'Doubao') { + return []; + } + + const normalizedModel = (provider.model || '').trim(); + if (!normalizedModel || normalizedModel === 'ep-xxxxxxxxxxxxxxxx') { + return [{ + level: 'error', + message: 'Doubao needs a real Ark endpoint ID in the model field before testing or running tasks.' + }]; + } + + if (!looksLikeDoubaoEndpointId(normalizedModel)) { + return [{ + level: 'warning', + message: 'Doubao usually expects an Ark endpoint ID such as ep-xxxxxxxx. If you are intentionally using a raw model ID, verify that your Ark deployment accepts it.' + }]; + } + + return []; +} + +export function hasBlockingProviderValidationIssues(provider: Pick): boolean { + return getProviderValidationIssues(provider).some(issue => issue.level === 'error'); +} + export function getOrderedProviderNames(providers: LLMProviderConfig[]): string[] { return providers .map(provider => provider.name) diff --git a/src/tests/llmProviders.test.ts b/src/tests/llmProviders.test.ts index 08cc062e..3be35874 100644 --- a/src/tests/llmProviders.test.ts +++ b/src/tests/llmProviders.test.ts @@ -17,6 +17,8 @@ describe('llmProviders registry', () => { 'Moonshot', 'GLM', 'MiniMax', + 'Baidu Qianfan', + 'SiliconFlow', 'OpenAI', 'Anthropic', 'Google', @@ -53,6 +55,8 @@ describe('llmProviders registry', () => { expect(isOpenAICompatibleProvider('Moonshot')).toBe(true); expect(isOpenAICompatibleProvider('GLM')).toBe(true); expect(isOpenAICompatibleProvider('MiniMax')).toBe(true); + expect(isOpenAICompatibleProvider('Baidu Qianfan')).toBe(true); + expect(isOpenAICompatibleProvider('SiliconFlow')).toBe(true); expect(isOpenAICompatibleProvider('Anthropic')).toBe(false); expect(getLLMProviderDefinition('Ollama')?.transport).toBe('ollama'); }); @@ -78,5 +82,13 @@ describe('llmProviders registry', () => { transport: 'openai-compatible', apiTestMode: 'chat-only' })); + expect(getLLMProviderDefinition('Baidu Qianfan')).toEqual(expect.objectContaining({ + transport: 'openai-compatible', + apiTestMode: 'chat-only' + })); + expect(getLLMProviderDefinition('SiliconFlow')).toEqual(expect.objectContaining({ + transport: 'openai-compatible', + apiTestMode: 'chat-only' + })); }); }); diff --git a/src/tests/llmUtilsProviderSupport.test.ts b/src/tests/llmUtilsProviderSupport.test.ts index 87db8d89..6cb6c577 100644 --- a/src/tests/llmUtilsProviderSupport.test.ts +++ b/src/tests/llmUtilsProviderSupport.test.ts @@ -92,6 +92,33 @@ describe('llmUtils expanded provider support', () => { expect(requestBody.model).toBe('qwen-plus'); }); + test('callLLM routes SiliconFlow through the OpenAI-compatible runtime', async () => { + const provider: LLMProviderConfig = { + name: 'SiliconFlow', + apiKey: 'sf-key', + baseUrl: 'https://api.siliconflow.cn/v1', + model: 'Qwen/QwQ-32B', + temperature: 0.2 + }; + + (requestUrl as jest.Mock).mockResolvedValue({ + status: 200, + json: { choices: [{ message: { content: 'siliconflow-ok' } }] }, + text: '{"choices":[{"message":{"content":"siliconflow-ok"}}]}' + }); + + const result = await callLLM(provider, 'System prompt', 'Provider content', settings, reporter); + + expect(result).toBe('siliconflow-ok'); + expect(requestUrl).toHaveBeenCalledWith(expect.objectContaining({ + url: 'https://api.siliconflow.cn/v1/chat/completions', + method: 'POST', + headers: expect.objectContaining({ + Authorization: 'Bearer sf-key' + }) + })); + }); + test('OpenAI Compatible preset can call unauthenticated local gateways', async () => { const provider: LLMProviderConfig = { name: 'OpenAI Compatible', @@ -169,4 +196,34 @@ describe('llmUtils expanded provider support', () => { const requestBody = JSON.parse((requestUrl as jest.Mock).mock.calls[0][0].body); expect(requestBody.model).toBe('qwen-plus'); }); + + test('testAPI uses direct chat probing for Baidu Qianfan', async () => { + const provider: LLMProviderConfig = { + name: 'Baidu Qianfan', + apiKey: 'qianfan-key', + baseUrl: 'https://qianfan.baidubce.com/v2', + model: 'ernie-4.5-turbo-32k', + temperature: 0.2 + }; + + (requestUrl as jest.Mock).mockResolvedValue({ + status: 200, + json: { choices: [{ message: { content: 'ok' } }] }, + text: '{"choices":[{"message":{"content":"ok"}}]}' + }); + + const result = await testAPI(provider); + + expect(result.success).toBe(true); + expect(requestUrl).toHaveBeenCalledTimes(1); + expect(requestUrl).toHaveBeenCalledWith(expect.objectContaining({ + url: 'https://qianfan.baidubce.com/v2/chat/completions', + method: 'POST', + headers: expect.objectContaining({ + Authorization: 'Bearer qianfan-key' + }) + })); + const requestBody = JSON.parse((requestUrl as jest.Mock).mock.calls[0][0].body); + expect(requestBody.model).toBe('ernie-4.5-turbo-32k'); + }); }); diff --git a/src/tests/providerValidation.test.ts b/src/tests/providerValidation.test.ts new file mode 100644 index 00000000..f37ee1aa --- /dev/null +++ b/src/tests/providerValidation.test.ts @@ -0,0 +1,72 @@ +import { + getProviderValidationIssues, + hasBlockingProviderValidationIssues +} from '../llmProviders'; + +describe('provider validation', () => { + test('Doubao placeholder endpoint ID is treated as blocking', () => { + const provider = { + name: 'Doubao', + apiKey: 'test-key', + baseUrl: 'https://ark.cn-beijing.volces.com/api/v3', + model: 'ep-xxxxxxxxxxxxxxxx', + temperature: 0.3 + }; + + const issues = getProviderValidationIssues(provider); + + expect(issues).toEqual([ + expect.objectContaining({ + level: 'error' + }) + ]); + expect(issues[0].message).toContain('real Ark endpoint ID'); + expect(hasBlockingProviderValidationIssues(provider)).toBe(true); + }); + + test('Doubao model that does not look like an endpoint ID returns a non-blocking warning', () => { + const provider = { + name: 'Doubao', + apiKey: 'test-key', + baseUrl: 'https://ark.cn-beijing.volces.com/api/v3', + model: 'doubao-pro-32k', + temperature: 0.3 + }; + + const issues = getProviderValidationIssues(provider); + + expect(issues).toEqual([ + expect.objectContaining({ + level: 'warning' + }) + ]); + expect(issues[0].message).toContain('usually expects an Ark endpoint ID'); + expect(hasBlockingProviderValidationIssues(provider)).toBe(false); + }); + + test('Doubao endpoint-like IDs pass without issues', () => { + const provider = { + name: 'Doubao', + apiKey: 'test-key', + baseUrl: 'https://ark.cn-beijing.volces.com/api/v3', + model: 'ep-20260326-abc123xyz789', + temperature: 0.3 + }; + + expect(getProviderValidationIssues(provider)).toEqual([]); + expect(hasBlockingProviderValidationIssues(provider)).toBe(false); + }); + + test('other providers are not affected by Doubao-specific validation', () => { + const provider = { + name: 'Qwen', + apiKey: 'test-key', + baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', + model: 'qwen-plus', + temperature: 0.3 + }; + + expect(getProviderValidationIssues(provider)).toEqual([]); + expect(hasBlockingProviderValidationIssues(provider)).toBe(false); + }); +}); diff --git a/src/ui/NotemdSettingTab.ts b/src/ui/NotemdSettingTab.ts index 31f4e4b0..bef2e345 100644 --- a/src/ui/NotemdSettingTab.ts +++ b/src/ui/NotemdSettingTab.ts @@ -2,7 +2,13 @@ import { App, PluginSettingTab, Setting, Notice, TextAreaComponent } from 'obsid import NotemdPlugin from '../main'; // Import the plugin class itself import { LLMProviderConfig, NotemdSettings, TaskKey } from '../types'; import { DEFAULT_SETTINGS } from '../constants'; -import { getLLMProviderDefinition, getOrderedProviderNames, getProviderCategoryLabel } from '../llmProviders'; +import { + getLLMProviderDefinition, + getOrderedProviderNames, + getProviderCategoryLabel, + getProviderValidationIssues, + hasBlockingProviderValidationIssues +} from '../llmProviders'; import { testAPI } from '../llmUtils'; // Import testAPI import { getDefaultPrompt } from '../promptUtils'; // Import for default prompts import { @@ -55,6 +61,23 @@ export class NotemdSettingTab extends PluginSettingTab { callout.createEl('p', { text: definition.setupHint }); } + private renderProviderValidation(containerEl: HTMLElement, provider: LLMProviderConfig): void { + const issues = getProviderValidationIssues(provider); + if (issues.length === 0) { + return; + } + + issues.forEach(issue => { + const callout = containerEl.createDiv({ + cls: `notemd-provider-validation notemd-provider-validation-${issue.level}` + }); + callout.createEl('strong', { + text: issue.level === 'error' ? 'Configuration required' : 'Configuration warning' + }); + callout.createEl('p', { text: issue.message }); + }); + } + private getWorkflowBuilderStateFromSettings(): CustomWorkflowButton[] { const resolved = resolveCustomWorkflowButtons(this.plugin.settings.customWorkflowButtonsDsl); if (resolved.buttons.length > 0) { @@ -305,7 +328,7 @@ export class NotemdSettingTab extends PluginSettingTab { text: `Provider presets expanded to ${this.plugin.settings.providers.length} entries.` }); providerSupportCallout.createEl('p', { - text: 'OpenAI-compatible providers now share one runtime path. Built-in presets cover China-focused services such as Qwen, Doubao, Moonshot, GLM, and MiniMax, and the generic "OpenAI Compatible" preset can target LiteLLM, vLLM, Perplexity, Vercel AI Gateway, or your own proxy.' + text: 'OpenAI-compatible providers now share one runtime path. Built-in presets cover China-focused services such as Qwen, Doubao, Moonshot, GLM, MiniMax, Baidu Qianfan, and SiliconFlow, and the generic "OpenAI Compatible" preset can target LiteLLM, vLLM, Perplexity, Vercel AI Gateway, or your own proxy.' }); const providerMgmtSetting = new Setting(containerEl) @@ -336,6 +359,7 @@ export class NotemdSettingTab extends PluginSettingTab { if (activeProvider) { new Setting(containerEl).setName(`${activeProvider.name} details`).setHeading(); this.renderProviderSummary(containerEl, activeProvider); + this.renderProviderValidation(containerEl, activeProvider); const providerDefinition = getLLMProviderDefinition(activeProvider.name); const apiKeyMode = providerDefinition?.apiKeyMode ?? 'required'; @@ -394,6 +418,13 @@ export class NotemdSettingTab extends PluginSettingTab { .addButton(button => button .setButtonText('Test connection').setCta() .onClick(async () => { + const blockingIssues = getProviderValidationIssues(activeProvider) + .filter(issue => issue.level === 'error') + .map(issue => issue.message); + if (hasBlockingProviderValidationIssues(activeProvider)) { + new Notice(`Cannot test ${activeProvider.name}: ${blockingIssues.join(' ')}`, 8000); + return; + } button.setDisabled(true).setButtonText('Testing...'); const testingNotice = new Notice(`Testing connection to ${activeProvider.name}...`, 0); try { diff --git a/styles.css b/styles.css index b6ecf93d..816e5c0e 100644 --- a/styles.css +++ b/styles.css @@ -1,5 +1,5 @@ /* - * Notemd v1.7.0 + * Notemd v1.7.1 */ :root { @@ -277,6 +277,33 @@ line-height: 1.5; } +.notemd-provider-validation { + border-radius: 10px; + padding: 0.75em 0.85em; + margin: 0 0 0.85em; + border: 1px solid transparent; +} + +.notemd-provider-validation strong { + display: block; + line-height: 1.4; +} + +.notemd-provider-validation p { + margin: 0.4em 0 0; + line-height: 1.45; +} + +.notemd-provider-validation-warning { + background: color-mix(in srgb, #f59e0b 12%, var(--background-secondary) 88%); + border-color: color-mix(in srgb, #f59e0b 42%, var(--background-modifier-border) 58%); +} + +.notemd-provider-validation-error { + background: color-mix(in srgb, #ef4444 10%, var(--background-secondary) 90%); + border-color: color-mix(in srgb, #ef4444 38%, var(--background-modifier-border) 62%); +} + .notemd-provider-badge-row { display: flex; flex-wrap: wrap; diff --git a/versions.json b/versions.json index 55e9f7ca..f94023d4 100644 --- a/versions.json +++ b/versions.json @@ -10,5 +10,6 @@ "1.6.4": "0.15.0", "1.6.5": "0.15.0", "1.6.6": "0.15.0", - "1.7.0": "0.15.0" + "1.7.0": "0.15.0", + "1.7.1": "0.15.0" }