From ffc6318dee6d73a567173ccecff9706e4fbffb0f Mon Sep 17 00:00:00 2001 From: musi Date: Thu, 2 Jul 2026 09:22:28 +0800 Subject: [PATCH 01/62] Revise overview dashboard docs and nav links --- .../content/docs/en/configuration/overview.md | 148 +++++++++++++----- docs/src/content/docs/en/index.md | 4 +- .../content/docs/zh/configuration/overview.md | 148 +++++++++++++----- docs/src/content/docs/zh/index.md | 4 +- docs/src/i18n/content.ts | 8 +- docs/src/pages/configuration.astro | 6 +- docs/src/pages/en/configuration.astro | 6 +- 7 files changed, 240 insertions(+), 84 deletions(-) diff --git a/docs/src/content/docs/en/configuration/overview.md b/docs/src/content/docs/en/configuration/overview.md index 0399669..9a7bf49 100644 --- a/docs/src/content/docs/en/configuration/overview.md +++ b/docs/src/content/docs/en/configuration/overview.md @@ -2,52 +2,130 @@ title: Overview Dashboard pageTitle: Overview Dashboard eyebrow: Detailed Configuration -lead: Customize the CCR home dashboard for system status, account balance, requests, tokens, cost, model distribution, and share cards. +lead: Customize the CCR home dashboard to inspect system status, account balance, requests, tokens, cost, and model distribution. --- -## Top Controls +## When To Use It -| Field | Capability | +| Scenario | What to inspect | | --- | --- | -| Usage over time | Changes the statistics range used by the dashboard. | -| Today / 24h / 7d / 30d | Available time windows. Widgets recalculate requests, tokens, cost, and trends for the selected range. | -| Edit widgets | Enters layout editing mode. | -| Reset layout | Restores the default overview layout. | -| Done | Leaves editing mode and keeps the current widget configuration. | +| Check gateway health | System status, success rate, errors, average latency | +| Estimate recent spend | Requests, input / output / cache tokens, estimated cost | +| Compare upstream usage | Provider analysis, model distribution, client analysis | +| Watch account quota | Balance, subscription quota, remaining quota, account status | +| Report or share usage | AI Usage Wrapped, CCR Route Map, Model Leaderboard, Spend Receipt, and other share cards | -## Widget Editing +## Time Range -In editing mode, the left `Components` panel adds widgets, the middle `Preview` panel shows the current layout, and the right `Component properties` panel edits the selected widget. +The `Usage over time` control at the top drives every widget that depends on usage stats. After you switch ranges, requests, tokens, cost, trends, distribution, and share cards are recomputed for the selected window. -| Field | Capability | +| Option | Window | | --- | --- | -| Components | List of widgets that can be added. | -| Preview | Current dashboard layout. Widgets can be dragged to reorder. | -| Component properties | Configuration for the selected widget. | -| Component category | Changes the widget category, such as status, account, metric, trend, activity, breakdown, analysis, or share card. | -| Data | Selects the data shown by the widget, such as requests, tokens, cost, account, client analysis, or provider analysis. | -| Widget size | Controls the widget's grid width and height. | -| Style | Changes visual style, such as cards, compact, bar, line, ring, and more. | -| Remove widget | Removes the selected widget from the overview. | +| `Today` | Current local date from 00:00 to now, bucketed hourly. | +| `24h` | Last 24 hours, bucketed hourly. | +| `7d` | Last 7 days, bucketed daily. | +| `30d` | Last 30 days, bucketed daily. | -## Widget Types +The account balance widget does not use this time range. It shows the latest snapshot returned by provider account connectors. -| Widget | Capability | +## Edit Layout + +Click the pencil button in the upper-right corner to enter editing mode. Editing mode has three columns: + +| Area | Purpose | | --- | --- | -| Status component | Shows a system status timeline for recent gateway health. | -| Account component | Shows provider account balance, quota, or usage. Requires provider `Fetch usage`. | -| Metric component | Shows requests, total tokens, input tokens, output tokens, cache tokens, cache ratio, estimated cost, success rate, errors, or average latency. | -| Trend component | Shows usage trend over time. | -| Activity component | Shows token activity as a heatmap. | -| Breakdown component | Shows Token mix or Model distribution. | -| Analysis component | Shows Client Analysis or Provider Analysis. | -| Share card | Generates shareable PNG cards such as AI Usage Wrapped, CCR Route Map, Model Leaderboard, AI Fuel Cockpit, Token Calendar Poster, and Spend Receipt. | +| Components | Left palette. Click a template to add it to the dashboard. | +| Preview | Middle layout preview. Drag widgets to reorder them or click a widget to select it. | +| Component properties | Right property panel for changing type, data, size, style, or removing the selected widget. | -## Data Sources +Common operations: -| Data | Source | +1. Add a widget: click a template in `Components`. +2. Reorder widgets: drag them in `Preview`. +3. Resize a widget: select it, then drag the right, bottom, or bottom-right resize handle. +4. Change data: use `Component category` and `Data` in `Component properties`. +5. Change presentation: choose `Widget size` and `Style`. +6. Save the result: click `Done`; the layout is persisted in app configuration. +7. Restore defaults: click `Reset layout` while editing. + +Removing a widget only removes that card from the overview layout. It does not delete request logs, providers, account connectors, or upstream configuration. If all widgets are removed, the page shows `No widgets configured`. + +## Widget Catalog + +Sizes are written as `width:height`, with both dimensions from `1` to `4`. The overview grid has up to 4 columns on desktop and collapses automatically on narrow screens. + +| Widget | Data | Default size | Default style | Styles | +| --- | --- | --- | --- | --- | +| Status component | System status | `4:1` | Timeline | Timeline, Compact | +| Account component | All accounts or one account | `4:2` | Cards | Cards, Compact, Bars, Ring, Semicircle, Arc, Nested rings | +| Metric component | Requests, tokens, cost | `1:1` | Cards | Cards, Compact, Bar, Ring | +| Trend component | Usage over time | `3:2` | Composed | Composed, Area, Line, Bar | +| Activity component | Token activity | `4:2` | Heatmap | Heatmap | +| Breakdown component | Token distribution / Model distribution | Token distribution: `1:2`; Model distribution: `2:2` | Token distribution: Bars; Model distribution: Pie | Bars, Stacked, Donut, Pie | +| Analysis component | Client Analysis / Provider Analysis | `2:2` | Table | Table, Compact | +| Share card | AI Usage Wrapped, CCR Route Map, Model Leaderboard, AI Fuel Cockpit, Token Calendar Poster, Spend Receipt | `1:4` | Card | Card | + +Size constraints: + +| Rule | Reason | | --- | --- | -| Requests, tokens, cost, success rate, latency | Request logs and usage stats. | -| Account balance, quota, status messages | Provider `Fetch usage` configuration. | -| Client analysis, provider analysis, model distribution | Client, provider, model, and token data from request logs. | -| Agent analysis data | Agent observability settings and agent execution traces. | +| Share cards have a minimum size of `1:4`. | PNG export uses a vertical poster ratio and needs enough height. | +| The account widget has a minimum size of `2:2` when showing All accounts with the Compact style. | Multi-account lists need readable space. | +| Legacy aliases are still accepted: `small` -> `1:1`, `medium` / `large` -> `2:2`, `wide` -> `3:2`, `full` -> `4:1` or `4:2`. | Backward compatibility for older config. | + +## Metric Data + +`metric` widgets use the `metric` field to choose the displayed value. + +| `metric` | Meaning | +| --- | --- | +| `requests` | Request count | +| `total-tokens` | Total tokens | +| `input-tokens` | Input tokens | +| `output-tokens` | Output tokens | +| `cache-tokens` | Cache tokens | +| `cache-ratio` | Cache ratio | +| `estimated-cost` | Estimated cost, calculated from model pricing data | +| `success-rate` | Success rate | +| `errors` | Error count | +| `avg-latency` | Average latency | + +## Account Widget + +The account widget reads provider account / usage connectors. To show balance or remaining quota, first enable and test `Fetch usage` in provider configuration. + +| Data selection | Behavior | +| --- | --- | +| `All accounts` | Shows every available account snapshot. | +| One account | Shows only one provider or credential snapshot. The internal config value is usually `provider` or `provider::credentialId`. | + +If the account widget is empty, check: + +1. Whether the provider has an account / usage connector configured. +2. Whether the `Fetch usage` test succeeds. +3. Whether the API key or account endpoint is still valid. +4. Whether the selected account was deleted or renamed. + +## Share Cards + +Share card widgets can export PNGs through the download button in the card header. The desktop app uses native export when available; browser environments fall back to frontend canvas export. The exported image size is `1080 x 1350`. + +| Card | `type` | Content | +| --- | --- | --- | +| AI Usage Wrapped | `share-usage-wrapped` | Total tokens, requests, estimated cost, cache ratio, longest activity streak, top model, top provider, peak day. | +| CCR Route Map | `share-route-map` | Main client-to-provider/model route relationships, plus client, provider, and model counts. | +| Model Leaderboard | `share-model-leaderboard` | Models ranked by tokens. | +| AI Fuel Cockpit | `share-fuel-cockpit` | Up to 3 account quota gauges. Requires account / usage connectors. | +| Token Calendar Poster | `share-token-calendar` | Contribution-calendar style token activity poster. | +| Spend Receipt | `share-spend-receipt` | Estimated cost, requests, tokens, latency, and success rate for the selected range. | + +## Data Sources And Troubleshooting + +| Symptom | Likely cause | What to do | +| --- | --- | --- | +| Requests, tokens, or cost are 0 | No requests went through CCR in the selected range, or usage capture has not recorded data yet. | Try `24h` / `7d`, and confirm the client is actually using CCR. | +| Cost shows `$0.00` | The model has no pricing data, or usage is very small. | Check model catalog matching and provider model names; values under 0.01 USD are shown with extra decimals. | +| Success rate or errors look unexpected | The overview only aggregates request results captured by CCR. | Compare with records on the Logs page. | +| Account balance is empty | No account connector exists, or `Fetch usage` failed. | Test account / usage field mapping in provider configuration. | +| Distribution charts have no data | Request logs lack model, provider, or token information. | Confirm requests go through CCR and upstream responses include token usage. | +| PNG export fails | Canvas export is unavailable, the element has no size, or the save dialog was canceled. | Retry in the desktop app, and make sure the card is visible and not resized too small. | diff --git a/docs/src/content/docs/en/index.md b/docs/src/content/docs/en/index.md index d60d2c6..a9a08ba 100644 --- a/docs/src/content/docs/en/index.md +++ b/docs/src/content/docs/en/index.md @@ -13,7 +13,7 @@ The top navigation is split into four standalone pages: | --- | --- | | [Documentation](./) | Product positioning, architecture overview, and reading path | | [Quick Start](guides/) | From installation and provider setup to connecting an agent | -| [Detailed Configuration](configuration/providers/) | Overview dashboard, API keys, server, providers, routing, Agent Config, Fusion, Bots, tray, and config database location | +| [Detailed Configuration](configuration/overview/) | Overview dashboard, API keys, server, providers, routing, Agent Config, Fusion, Bots, tray, and config database location | | [Q&A](troubleshooting/) | Request logs, observability panel, and common questions | Bot platform guides are child pages under Detailed Configuration. Each platform has its own page so platform dashboard fields, callback URLs, signatures, and FAQs can be expanded independently. @@ -24,5 +24,5 @@ If this is your first time using CCR: 1. Start with [Quick Start](guides/) to connect a provider and Agent Config. 2. Use the app's request logs to confirm whether requests are passing through CCR. -3. Open [Detailed Configuration](configuration/providers/) for the overview dashboard, API keys, server, providers, vision, web search, MCP tools, tray, and IM relay. +3. Open [Detailed Configuration](configuration/overview/) for the overview dashboard, API keys, server, providers, vision, web search, MCP tools, tray, and IM relay. 4. Use [Q&A](troubleshooting/) for 401, 404, timeout, wrong-routing, or Bot delivery questions. diff --git a/docs/src/content/docs/zh/configuration/overview.md b/docs/src/content/docs/zh/configuration/overview.md index 27a64e9..d7b4ff9 100644 --- a/docs/src/content/docs/zh/configuration/overview.md +++ b/docs/src/content/docs/zh/configuration/overview.md @@ -2,52 +2,130 @@ title: 概览仪表盘 pageTitle: 概览仪表盘 eyebrow: 详细配置 -lead: 自定义 CCR 首页组件,查看系统状态、账户余额、请求量、tokens、成本、模型分布和可分享卡片。 +lead: 自定义 CCR 首页仪表盘,查看系统状态、账户余额、请求量、令牌、成本和模型分布。 --- -## 顶部控件 +## 适用场景 -| 字段 | 代表的能力 | +| 场景 | 看什么 | | --- | --- | -| 按时间查看用量 | 切换仪表盘使用的统计时间范围。 | -| 今天 / 24 小时 / 7 天 / 30 天 | 可选统计窗口。不同组件会根据这个窗口重新计算请求、tokens、成本和趋势。 | -| 编辑组件 | 进入布局编辑模式。 | -| 重置布局 | 恢复默认概览布局。 | -| 完成 | 退出布局编辑模式并保留当前组件配置。 | +| 检查网关是否正常 | 系统状态、成功率、错误数、平均延迟 | +| 估算今日或近期消耗 | 请求数、输入 / 输出 / 缓存令牌、估算成本 | +| 比较上游使用情况 | 供应商分析、模型分布、客户端分析 | +| 关注账户额度 | 账户余额、套餐额度、剩余额度、账户状态 | +| 汇报或分享用量 | AI 用量年报、CCR 路由图、模型排行榜、消费小票等分享卡片 | -## 组件区 +## 时间范围 -编辑模式下,左侧“组件”用于添加组件,中间“预览”展示当前布局,右侧“组件属性”编辑选中组件。 +顶部的 `按时间查看用量` 控制所有依赖用量统计的组件。切换后,请求、令牌、成本、趋势、分布和分享卡片会按新的窗口重新聚合。 -| 字段 | 代表的能力 | +| 选项 | 统计窗口 | | --- | --- | -| 组件 | 可添加组件列表。 | -| 预览 | 当前仪表盘布局。组件可以拖拽排序。 | -| 组件属性 | 当前选中组件的配置区域。 | -| 组件类型 | 切换组件大类,例如状态、账户、指标、趋势、活跃度、构成、分析或分享卡片。 | -| 数据 | 选择组件展示的数据,例如请求数、tokens、成本、账户、客户端分析或供应商分析。 | -| 组件大小 | 控制组件占用的网格宽高。 | -| 样式 | 切换组件展示样式,例如卡片、紧凑、柱状图、折线图、圆环等。 | -| 移除组件 | 从概览中移除当前组件。 | +| `今天` | 当前本地日期从 00:00 到现在,按小时分桶。 | +| `24 小时` | 最近 24 小时,按小时分桶。 | +| `7 天` | 最近 7 天,按天分桶。 | +| `30 天` | 最近 30 天,按天分桶。 | -## 组件类型 +账户余额组件不按这个时间窗口重算,它展示供应商账户连接器最近一次获取到的快照。 -| 组件 | 代表的能力 | +## 编辑布局 + +点击右上角铅笔按钮进入编辑模式。编辑模式分为三栏: + +| 区域 | 作用 | | --- | --- | -| 状态组件 | 展示系统状态时间线,帮助判断网关近期是否正常。 | -| 账户组件 | 展示供应商账户余额、套餐额度或用量。需要供应商开启“获取用量”。 | -| 指标组件 | 展示请求数、总 tokens、输入 tokens、输出 tokens、缓存 tokens、缓存比例、估算成本、成功率、错误数或平均延迟。 | -| 趋势组件 | 展示按时间聚合的用量趋势。 | -| 活跃度组件 | 展示 token 活跃度热力图。 | -| 构成组件 | 展示 Token 构成或模型分布。 | -| 分析组件 | 展示客户端分析或供应商分析。 | -| 分享卡片 | 生成适合分享的 PNG 卡片,例如 AI Usage Wrapped、CCR Route Map、Model Leaderboard、AI Fuel Cockpit、Token Calendar Poster 和 Spend Receipt。 | +| 组件 | 左侧组件库,点击任一组件即可添加到当前仪表盘。 | +| 预览 | 中间布局预览。可以拖拽组件排序,也可以点击组件选中它。 | +| 组件属性 | 右侧属性面板,用于修改类型、数据、尺寸、样式或移除组件。 | -## 数据来源 +常用操作: -| 数据 | 来源 | +1. 添加组件:在左侧 `组件` 中点击组件模板。 +2. 调整顺序:在 `预览` 中拖拽组件。 +3. 调整尺寸:选中组件后拖动右侧、底部或右下角的缩放手柄。 +4. 修改数据:在 `组件属性` 里切换 `组件类型` 和 `数据`。 +5. 修改展示:在 `组件大小` 和 `样式` 中选择合适的布局。 +6. 保存结果:点击 `完成` 退出编辑模式;布局会保存在应用配置中。 +7. 恢复默认:编辑模式下点击 `重置布局`。 + +移除组件只会从概览布局中删除该卡片,不会删除请求日志、供应商、账户连接器或任何上游配置。如果所有组件都被移除,页面会显示 `未配置组件`。 + +## 组件目录 + +尺寸使用 `宽:高` 表示,宽高范围都是 `1` 到 `4`。概览网格在桌面端最多 4 列,在窄屏上会自动折叠。 + +| 组件 | 可选数据 | 默认尺寸 | 默认样式 | 可选样式 | +| --- | --- | --- | --- | --- | +| 状态组件 | 系统状态 | `4:1` | 时间线 | 时间线、紧凑 | +| 账户组件 | 所有账户或指定账户 | `4:2` | 卡片 | 卡片、紧凑、横条、圆环、半圆、弧形、内外圆 | +| 指标组件 | 请求、Token、成本 | `1:1` | 卡片 | 卡片、紧凑、柱状图、圆环 | +| 趋势组件 | 按时间查看用量 | `3:2` | 组合图 | 组合图、面积图、折线图、柱状图 | +| 活跃度组件 | Token 活跃度 | `4:2` | 热力图 | 热力图 | +| 构成组件 | Token 分布 / 模型分布 | Token 分布:`1:2`;模型分布:`2:2` | Token 分布:横条;模型分布:饼图 | 横条、堆叠、环形图、饼图 | +| 分析组件 | 客户端分析 / 供应商分析 | `2:2` | 表格 | 表格、紧凑 | +| 分享卡片 | AI 用量年报、CCR 路由图、模型排行榜、AI 燃料仪表、Token 日历海报、消费小票 | `1:4` | 卡片 | 卡片 | + +尺寸约束: + +| 规则 | 原因 | | --- | --- | -| 请求数、tokens、成本、成功率、延迟 | 请求日志和用量统计。 | -| 账户余额、套餐额度、状态消息 | 供应商“获取用量”配置。 | -| 客户端分析、供应商分析、模型分布 | 请求日志中的客户端、供应商、模型和 token 信息。 | -| Agent 分析相关数据 | 设置中的“Agent 观测”和 Agent 执行链路数据。 | +| 分享卡片最小高度是 `1:4`。 | 导出图片使用竖版海报比例,需要足够高度。 | +| 账户组件在展示所有账户且使用“紧凑”样式时,最小尺寸是 `2:2`。 | 多账户列表需要保留可读空间。 | +| 旧版尺寸别名仍可被解析:`small` -> `1:1`,`medium` / `large` -> `2:2`,`wide` -> `3:2`,`full` -> `4:1` 或 `4:2`。 | 用于兼容旧配置。 | + +## 指标数据 + +`metric` 组件通过 `metric` 字段选择要展示的指标。 + +| `metric` | 含义 | +| --- | --- | +| `requests` | 请求数 | +| `total-tokens` | 总令牌 | +| `input-tokens` | 输入令牌 | +| `output-tokens` | 输出令牌 | +| `cache-tokens` | 缓存令牌 | +| `cache-ratio` | 缓存率 | +| `estimated-cost` | 估算成本,按模型价格信息计算 | +| `success-rate` | 成功率 | +| `errors` | 错误数 | +| `avg-latency` | 平均延迟 | + +## 账户组件 + +账户组件读取供应商配置里的账户 / 用量连接器。要让它显示余额或剩余额度,需要先在供应商配置中启用并测试 `获取用量`。 + +| 数据选择 | 行为 | +| --- | --- | +| `所有账户` | 展示所有可用账户快照。 | +| 指定账户 | 只展示某个供应商或某个凭据的账户快照。内部配置值格式通常是 `provider` 或 `provider::credentialId`。 | + +如果账户组件为空,优先检查: + +1. 供应商是否配置了账户 / 用量连接器。 +2. `获取用量` 测试是否成功。 +3. API Key 或账户接口是否仍有效。 +4. 当前选择的指定账户是否已经被删除或重命名。 + +## 分享卡片 + +分享卡片组件可以通过右上角下载按钮导出 PNG。桌面 App 会优先使用原生导出能力,浏览器环境会退回到前端 canvas 导出。导出图片尺寸为 `1080 x 1350`。 + +| 卡片 | `type` | 内容 | +| --- | --- | --- | +| AI 用量年报 | `share-usage-wrapped` | 总令牌、请求数、估算成本、缓存率、最长连续、最高频模型、最高频供应商、峰值日期。 | +| CCR 路由图 | `share-route-map` | 客户端到供应商 / 模型的主要路由关系,以及客户端、供应商、模型数量。 | +| 模型排行榜 | `share-model-leaderboard` | 按令牌排序的模型排行榜。 | +| AI 燃料仪表 | `share-fuel-cockpit` | 最多 3 个账户额度仪表,依赖账户 / 用量连接器。 | +| Token 日历海报 | `share-token-calendar` | 类似贡献日历的 Token 活跃度海报。 | +| 消费小票 | `share-spend-receipt` | 当前时间范围内的估算成本、请求、令牌、延迟和成功率小票。 | + +## 数据来源与排障 + +| 现象 | 可能原因 | 处理方式 | +| --- | --- | --- | +| 请求、令牌或成本为 0 | 当前时间范围内没有经过 CCR 的请求,或用量捕获尚未记录。 | 切换到 `24h` / `7d`,确认客户端请求确实走 CCR。 | +| 成本显示为 `$0.00` | 模型没有价格信息,或用量过小。 | 检查模型目录和供应商模型名是否能匹配价格;低于 0.01 美元会显示更多小数。 | +| 成功率、错误数不符合预期 | 只统计 CCR 捕获到的请求结果。 | 对照日志页面中的请求记录。 | +| 账户余额为空 | 没有账户连接器,或 `获取用量` 失败。 | 到供应商配置中测试账户 / 用量字段映射。 | +| 分布图没有数据 | 请求日志里缺少模型、供应商或 token 信息。 | 确认请求经过 CCR,并检查上游响应是否返回 token usage。 | +| PNG 导出失败 | 浏览器不支持 canvas 导出、元素尺寸为空,或用户取消了保存。 | 在桌面 App 中重试,确保卡片可见且没有被缩到过小。 | diff --git a/docs/src/content/docs/zh/index.md b/docs/src/content/docs/zh/index.md index 136c57e..45caad3 100644 --- a/docs/src/content/docs/zh/index.md +++ b/docs/src/content/docs/zh/index.md @@ -13,7 +13,7 @@ lead: 了解 CCR 的定位、能力边界和文档结构。需要动手配置时 | --- | --- | | [文档](./) | 产品定位、架构概览、阅读路径 | | [快速开始](guides/) | 从安装、接供应商,到接入 Agent 的上手流程 | -| [详细配置](configuration/provider/) | 概览仪表盘、API 密钥、服务、供应商、路由、Agent配置、Fusion、Bot、托盘和配置数据库位置 | +| [详细配置](configuration/overview/) | 概览仪表盘、API 密钥、服务、供应商、路由、Agent配置、Fusion、Bot、托盘和配置数据库位置 | | [Q&A](troubleshooting/) | 请求日志、观测面板和常见问题 | Bot 平台教程是「详细配置」分类下的子页面,每个平台有独立页面,方便逐步补齐平台后台字段、回调 URL、签名和 FAQ。 @@ -24,7 +24,7 @@ Bot 平台教程是「详细配置」分类下的子页面,每个平台有独 1. [快速开始](guides/) 覆盖供应商接入和 Agent配置。 2. App 的请求日志页面展示请求是否经过 CCR。 -3. [详细配置](configuration/provider/) 覆盖概览仪表盘、API 密钥、服务、供应商、图像、联网搜索、MCP 工具、托盘和 IM 接力。 +3. [详细配置](configuration/overview/) 覆盖概览仪表盘、API 密钥、服务、供应商、图像、联网搜索、MCP 工具、托盘和 IM 接力。 4. [Q&A](troubleshooting/) 覆盖 401、404、超时、路由不对或 Bot 收不到消息等常见问题。 这样文档不会挤在一个长页面里,后续也能按顶部分类逐步扩展。 diff --git a/docs/src/i18n/content.ts b/docs/src/i18n/content.ts index 731f868..8569685 100644 --- a/docs/src/i18n/content.ts +++ b/docs/src/i18n/content.ts @@ -15,7 +15,7 @@ export const docsContent = { navItems: [ { label: "文档", href: "/", pageKey: "documentation" }, { label: "快速开始", href: "/guides/", pageKey: "guides" }, - { label: "详细配置", href: "/configuration/provider/", pageKey: "configuration" }, + { label: "详细配置", href: "/configuration/overview/", pageKey: "configuration" }, { label: "Q&A", href: "/troubleshooting/", pageKey: "troubleshooting" }, ], pages: { @@ -77,7 +77,7 @@ export const docsContent = { "Bot 与 IM 接力 Agent", "配置数据库位置", ], - active: "供应商配置", + active: "概览仪表盘", }, ], expandableSidebarItems: ["Fusion 组合模型", "Bot 与 IM 接力 Agent"], @@ -163,7 +163,7 @@ export const docsContent = { navItems: [ { label: "Documentation", href: "/en/", pageKey: "documentation" }, { label: "Quick Start", href: "/en/guides/", pageKey: "guides" }, - { label: "Detailed Configuration", href: "/en/configuration/providers/", pageKey: "configuration" }, + { label: "Detailed Configuration", href: "/en/configuration/overview/", pageKey: "configuration" }, { label: "Q&A", href: "/en/troubleshooting/", pageKey: "troubleshooting" }, ], pages: { @@ -225,7 +225,7 @@ export const docsContent = { "Bots And IM Agent Relay", "Config Database Location", ], - active: "Provider Config", + active: "Overview Dashboard", }, ], expandableSidebarItems: ["Fusion Models", "Bots And IM Agent Relay"], diff --git a/docs/src/pages/configuration.astro b/docs/src/pages/configuration.astro index 1ad9fba..5484cef 100644 --- a/docs/src/pages/configuration.astro +++ b/docs/src/pages/configuration.astro @@ -1,5 +1,5 @@ --- -const target = "/configuration/provider/"; +const target = "/configuration/overview/"; --- @@ -8,10 +8,10 @@ const target = "/configuration/provider/"; - 供应商配置 + 概览仪表盘 diff --git a/docs/src/pages/en/configuration.astro b/docs/src/pages/en/configuration.astro index 01088a9..e77c3b1 100644 --- a/docs/src/pages/en/configuration.astro +++ b/docs/src/pages/en/configuration.astro @@ -1,5 +1,5 @@ --- -const target = "/en/configuration/providers/"; +const target = "/en/configuration/overview/"; --- @@ -8,10 +8,10 @@ const target = "/en/configuration/providers/"; - Provider Config + Overview Dashboard From 1adfbbf006054886e210e7999d236adbbeedb94f Mon Sep 17 00:00:00 2001 From: musistudio Date: Thu, 2 Jul 2026 11:44:13 +0800 Subject: [PATCH 02/62] Sort subagent model descriptions deterministically --- .../gateway/claude-code-router-plugin.ts | 37 +++++++-- tests/main/router-builtins.test.mjs | 78 ++++++++++++++++++- 2 files changed, 107 insertions(+), 8 deletions(-) diff --git a/src/server/gateway/claude-code-router-plugin.ts b/src/server/gateway/claude-code-router-plugin.ts index d352db5..9f276d2 100644 --- a/src/server/gateway/claude-code-router-plugin.ts +++ b/src/server/gateway/claude-code-router-plugin.ts @@ -421,8 +421,7 @@ function claudeCodeAgentToolInstructions(config: AppConfig): { prompt: string; t } function configuredSubagentModelDescriptionRows(config: AppConfig): string[] { - const rows: string[] = []; - const seen = new Set(); + const candidates: Array<{ key: string; row: string; selector: string }> = []; for (const provider of config.Providers) { const providerName = provider.name?.trim(); if (!providerName || !Array.isArray(provider.models)) { @@ -436,18 +435,42 @@ function configuredSubagentModelDescriptionRows(config: AppConfig): string[] { } const selector = `${providerName}/${model}`; const key = selector.toLowerCase(); - if (seen.has(key)) { - continue; - } - seen.add(key); const displayName = provider.modelDisplayNames?.[model]?.trim(); const label = displayName && displayName !== model ? `${selector} (${displayName})` : selector; - rows.push(`- ${label}: ${singleLineText(description, 320)}`); + candidates.push({ + key, + row: `- ${label}: ${singleLineText(description, 320)}`, + selector + }); } } + candidates.sort(compareSubagentModelDescriptionRows); + + const rows: string[] = []; + const seen = new Set(); + for (const candidate of candidates) { + if (seen.has(candidate.key)) { + continue; + } + seen.add(candidate.key); + rows.push(candidate.row); + } return rows; } +function compareSubagentModelDescriptionRows( + left: { key: string; row: string; selector: string }, + right: { key: string; row: string; selector: string } +): number { + return compareCodeUnitStrings(left.key, right.key) || + compareCodeUnitStrings(left.selector, right.selector) || + compareCodeUnitStrings(left.row, right.row); +} + +function compareCodeUnitStrings(left: string, right: string): number { + return left < right ? -1 : left > right ? 1 : 0; +} + function removeClaudeCodeBillingSystemHeader(body: Record): void { const system = body.system; if (!Array.isArray(system) || system.length === 0) { diff --git a/tests/main/router-builtins.test.mjs b/tests/main/router-builtins.test.mjs index 747fe76..4532313 100644 --- a/tests/main/router-builtins.test.mjs +++ b/tests/main/router-builtins.test.mjs @@ -6,7 +6,7 @@ function createRouterPlugin(options = {}) { const agent = options.agent ?? "claude-code"; return new ClaudeCodeRouterPlugin({ CUSTOM_ROUTER_PATH: "", - Providers: [ + Providers: options.providers ?? [ { modelDescriptions: options.modelDescriptions, modelDisplayNames: options.modelDisplayNames, @@ -158,6 +158,82 @@ test("built-in Claude Code route injects subagent model instructions into Agent } }); +test("built-in Claude Code route injects subagent model descriptions in stable order", async () => { + const providersA = [ + { + modelDescriptions: { + "z-model": "Zeta model.", + "a-model": "Alpha model." + }, + models: ["z-model", "a-model"], + name: "Zeta", + type: "anthropic_messages" + }, + { + modelDescriptions: { + "z-model": "Z model.", + "a-model": "A model." + }, + models: ["z-model", "a-model"], + name: "Alpha", + type: "anthropic_messages" + } + ]; + const providersB = [ + { + modelDescriptions: { + "a-model": "A model.", + "z-model": "Z model." + }, + models: ["a-model", "z-model"], + name: "Alpha", + type: "anthropic_messages" + }, + { + modelDescriptions: { + "a-model": "Alpha model.", + "z-model": "Zeta model." + }, + models: ["a-model", "z-model"], + name: "Zeta", + type: "anthropic_messages" + } + ]; + const routeRequest = (plugin) => plugin.routeRequest({ + body: { + messages: [], + model: "claude-default", + tools: [ + { + description: "Start a subagent.", + input_schema: { + properties: { + prompt: { description: "Task prompt.", type: "string" } + }, + type: "object" + }, + name: "Agent" + } + ] + }, + headers: { + "user-agent": "Claude Code" + }, + method: "POST", + url: "/v1/messages" + }); + + const pluginA = createRouterPlugin({ profileModel: "Alpha/a-model", providers: providersA }); + const pluginB = createRouterPlugin({ profileModel: "Alpha/a-model", providers: providersB }); + const [resultA, resultB] = await Promise.all([routeRequest(pluginA), routeRequest(pluginB)]); + const description = resultA.body.tools[0].description; + + assert.equal(description, resultB.body.tools[0].description); + assert.ok(description.indexOf("- Alpha/a-model: A model.") < description.indexOf("- Alpha/z-model: Z model.")); + assert.ok(description.indexOf("- Alpha/z-model: Z model.") < description.indexOf("- Zeta/a-model: Alpha model.")); + assert.ok(description.indexOf("- Zeta/a-model: Alpha model.") < description.indexOf("- Zeta/z-model: Zeta model.")); +}); + test("built-in Claude Code route injects workflow subagent model instructions into the Workflow tool", async () => { const plugin = createRouterPlugin({ modelDescriptions: { From 109b83bd80405cfecf440276ec723701c5bbe5dc Mon Sep 17 00:00:00 2001 From: musistudio Date: Thu, 2 Jul 2026 18:23:58 +0800 Subject: [PATCH 03/62] Refactor router and add model fallback handling --- build/esbuild.config.mjs | 3 +- src/main/codex-model-catalog.ts | 11 +- src/main/electron-web-search-mcp.ts | 1265 ++++++++++ src/main/main-app.ts | 2 + .../pages/home/components/virtual-models.tsx | 125 +- src/renderer/pages/home/shared/i18n.tsx | 25 +- src/renderer/pages/home/shared/options.ts | 4 +- .../pages/home/shared/virtual-models.ts | 7 +- src/server/gateway/service.ts | 2229 ++++++++++++++++- .../mcp/browser-web-search-proxy-mcp.ts | 146 ++ src/server/mcp/fusion-vision-mcp.ts | 23 +- src/shared/app.ts | 1 + tests/main/codex-model-catalog.test.mjs | 2 +- tests/main/gateway-virtual-models.test.mjs | 576 ++++- tests/renderer/virtual-models.test.ts | 4 +- 15 files changed, 4368 insertions(+), 55 deletions(-) create mode 100644 src/main/electron-web-search-mcp.ts create mode 100644 src/server/mcp/browser-web-search-proxy-mcp.ts diff --git a/build/esbuild.config.mjs b/build/esbuild.config.mjs index 9bdaba7..bcf3a1a 100644 --- a/build/esbuild.config.mjs +++ b/build/esbuild.config.mjs @@ -27,7 +27,7 @@ export const trayRendererHtmlOutput = path.join(rendererOutDir, "pages", "tray", export const cssInput = path.join(rendererRoot, "styles", "globals.css"); export const cssOutput = path.join(rendererAssetsDir, "main.css"); export const webClientBridgeOutput = path.join(rendererAssetsDir, "web-client-bridge.js"); -const lightweightMcpBundleNames = ["fusion-vision-mcp.js", "fusion-tool-fallback-mcp.js"]; +const lightweightMcpBundleNames = ["browser-web-search-proxy-mcp.js", "fusion-vision-mcp.js", "fusion-tool-fallback-mcp.js"]; const lightweightMcpBundleMaxBytes = 128 * 1024; const forbiddenLightweightMcpInputs = [ { prefix: "src/main/", reason: "main-process modules can pull in config, Electron, or native storage side effects" }, @@ -120,6 +120,7 @@ export function createMainBuildOptions({ mode = "production", plugins = [] } = { entryPoints: [ path.join(projectRoot, "src", "main", "main.ts"), path.join(projectRoot, "src", "main", "browser-preload.ts"), + path.join(projectRoot, "src", "server", "mcp", "browser-web-search-proxy-mcp.ts"), path.join(projectRoot, "src", "server", "mcp", "fusion-vision-mcp.ts"), path.join(projectRoot, "src", "server", "mcp", "fusion-tool-fallback-mcp.ts"), path.join(projectRoot, "src", "main", "preload.ts") diff --git a/src/main/codex-model-catalog.ts b/src/main/codex-model-catalog.ts index 1c87b84..841f553 100644 --- a/src/main/codex-model-catalog.ts +++ b/src/main/codex-model-catalog.ts @@ -415,11 +415,18 @@ function virtualModelProfileSupportsFusionWebSearch(profile: VirtualModelProfile return (profile.tools ?? []).some((tool) => { const name = tool.name.trim(); - return name === BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME || - name.startsWith(`${BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME}_`); + return fusionWebSearchToolNameMatches(name); }); } +function fusionWebSearchToolNameMatches(name: string): boolean { + const normalized = name.toLowerCase().replace(/[-.]/g, "_"); + return normalized === BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME || + normalized.startsWith(`${BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME}_`) || + normalized.endsWith(`_${BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME}`) || + normalized.includes("search_web"); +} + function recordValue(value: unknown): Record | undefined { return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record diff --git a/src/main/electron-web-search-mcp.ts b/src/main/electron-web-search-mcp.ts new file mode 100644 index 0000000..8fa90cd --- /dev/null +++ b/src/main/electron-web-search-mcp.ts @@ -0,0 +1,1265 @@ +import { BrowserWindow, WebContentsView, app, session, type WebContents } from "electron"; +import type { IncomingMessage, ServerResponse } from "node:http"; +import { join as pathJoin } from "node:path"; +import { backendService } from "../server/backend-service"; +import type { GatewayMcpServerConfig } from "../shared/app"; +import type { + BrowserWebSearchMcpIntegration, + BrowserWebSearchMcpRegistration, + BrowserWebSearchProtocolRecord +} from "../server/gateway/service"; + +type JsonPrimitive = boolean | null | number | string; +type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }; + +type JsonRpcRequest = { + id?: null | number | string; + jsonrpc?: string; + method?: string; + params?: unknown; +}; + +type JsonRpcResponse = + | { + id: null | number | string; + jsonrpc: "2.0"; + result: JsonValue; + } + | { + error: { + code: number; + data?: JsonValue; + message: string; + }; + id: null | number | string; + jsonrpc: "2.0"; + }; + +type ToolCallResult = { + content: Array<{ text: string; type: "text" }>; + isError?: boolean; +}; + +type BrowserSearchEngine = "bing" | "duckduckgo" | "google"; +type BrowserSearchFreshness = "day" | "month" | "week" | "year"; + +type BrowserSearchInput = { + after?: string; + anyTerms: string[]; + before?: string; + count: number; + country?: string; + engine: BrowserSearchEngine; + exactPhrase?: string; + excludeDomains: string[]; + excludeTerms: string[]; + freshness?: BrowserSearchFreshness; + includeDomains: string[]; + includeRaw: boolean; + keywords: string[]; + language?: string; + prompt: string; + safeSearch?: "moderate" | "off" | "strict"; + timeoutMs: number; +}; + +type BrowserSearchRequest = BrowserSearchInput & { + query: string; + searchUrl: string; +}; + +type BrowserSearchResult = { + content?: string; + snippet?: string; + title: string; + url: string; +}; + +type BrowserSearchPageResult = { + blocked?: string; + results?: BrowserSearchResult[]; + title?: string; +}; + +type BrowserSearchResponse = { + engine: BrowserSearchEngine; + query: string; + results: BrowserSearchResult[]; + searchUrl: string; +}; + +type BrowserSearchWorker = { + busy: boolean; + view: WebContentsView; +}; + +type BrowserSearchQueueEntry = { + reject: (error: Error) => void; + resolve: () => void; +}; + +const ownerId = "ccr-browser-web-search-mcp"; +const protocolVersion = "2024-11-05"; +const maxMcpRequestBytes = 2 * 1024 * 1024; +const defaultResultCount = 5; +const defaultTimeoutMs = 30_000; +const maxSearchResultCount = 20; +const searchPartition = "persist:ccr-browser-web-search-mcp"; +const defaultEngine: BrowserSearchEngine = "bing"; +const desktopUserAgent = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + + "(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"; + +function bundledBrowserWebSearchProxyMcpEntryPath(): string { + return pathJoin(__dirname, "browser-web-search-proxy-mcp.js"); +} + +class BrowserWebSearchMcpService implements BrowserWebSearchMcpIntegration { + private readonly recentResults: BrowserWebSearchProtocolRecord[] = []; + private readonly servers = new Map(); + private readonly searchPool = new HiddenBrowserSearchPool(); + + async registerBrowserWebSearchMcpServer(options: BrowserWebSearchMcpRegistration): Promise { + await app.whenReady(); + const existing = this.servers.get(options.toolName); + if (existing) { + return existing.mcpConfig(); + } + + const server = new BrowserWebSearchToolServer(options, this.searchPool, (record) => this.recordSearchResult(record)); + const backend = await backendService.registerHttpBackend(ownerId, { + handler: (request, response) => server.handleRequest(request, response), + id: `${ownerId}:${options.name}` + }); + server.setUrl(`${backend.url}/mcp`); + this.servers.set(options.toolName, server); + return server.mcpConfig(); + } + + recentBrowserWebSearchResults(options: { sinceMs: number; toolName?: string }): BrowserWebSearchProtocolRecord[] { + this.pruneRecentResults(); + return this.recentResults.filter((record) => { + if (record.completedAtMs < options.sinceMs) { + return false; + } + return !options.toolName || record.toolName === options.toolName; + }); + } + + async runBrowserWebSearch(options: { count?: number; prompt: string; timeoutMs?: number; toolName?: string }): Promise { + const server = options.toolName ? this.servers.get(options.toolName) : this.servers.values().next().value; + return await server?.searchForProtocolResult(options); + } + + async stopBrowserWebSearchMcpServers(): Promise { + this.servers.clear(); + this.recentResults.splice(0); + await backendService.stopOwner(ownerId); + this.searchPool.destroy(); + } + + private recordSearchResult(record: BrowserWebSearchProtocolRecord): void { + this.recentResults.push(record); + this.pruneRecentResults(); + } + + private pruneRecentResults(): void { + const cutoff = Date.now() - 5 * 60_000; + while (this.recentResults.length > 0 && (this.recentResults[0].completedAtMs < cutoff || this.recentResults.length > 50)) { + this.recentResults.shift(); + } + } +} + +class BrowserWebSearchToolServer { + private readonly defaultCount: number; + private readonly defaultEngine: BrowserSearchEngine; + private readonly defaultLanguage?: string; + private readonly defaultCountry?: string; + private readonly defaultSafeSearch?: "moderate" | "off" | "strict"; + private readonly defaultTimeoutMs: number; + private readonly toolName: string; + private url = ""; + + constructor( + options: BrowserWebSearchMcpRegistration, + private readonly searchPool: HiddenBrowserSearchPool, + private readonly recordSearchResult: (record: BrowserWebSearchProtocolRecord) => void + ) { + const env = options.env ?? {}; + this.toolName = options.toolName; + this.defaultCount = clampInteger( + options.resultCount ?? readNumber(env.BROWSER_SEARCH_RESULT_COUNT) ?? readNumber(env.SEARCH_RESULT_COUNT) ?? defaultResultCount, + 1, + maxSearchResultCount + ); + this.defaultEngine = parseSearchEngine(env.BROWSER_SEARCH_ENGINE || env.SEARCH_ENGINE) ?? defaultEngine; + this.defaultLanguage = readString(env.BROWSER_SEARCH_LANGUAGE || env.SEARCH_LANGUAGE); + this.defaultCountry = readString(env.BROWSER_SEARCH_COUNTRY || env.SEARCH_COUNTRY); + this.defaultSafeSearch = parseSafeSearch(env.BROWSER_SEARCH_SAFE_SEARCH || env.SEARCH_SAFE_SEARCH); + this.defaultTimeoutMs = clampInteger( + options.timeoutMs ?? readNumber(env.BROWSER_SEARCH_TIMEOUT_MS) ?? readNumber(env.SEARCH_TIMEOUT_MS) ?? defaultTimeoutMs, + 100, + 600_000 + ); + } + + setUrl(url: string): void { + this.url = url; + } + + mcpConfig(): GatewayMcpServerConfig { + const requestTimeoutMs = clampInteger(this.defaultTimeoutMs + 5_000, 1_000, 600_000); + return { + args: [bundledBrowserWebSearchProxyMcpEntryPath()], + command: process.execPath, + env: { + BROWSER_WEB_SEARCH_MCP_URL: this.url, + BROWSER_WEB_SEARCH_PROXY_TIMEOUT_MS: String(requestTimeoutMs), + ELECTRON_RUN_AS_NODE: "1" + }, + name: this.toolName, + protocolVersion, + requestTimeoutMs, + startupTimeoutMs: 60_000, + stdioMessageMode: "content-length", + transport: "stdio" + }; + } + + async handleRequest(request: IncomingMessage, response: ServerResponse): Promise { + response.setHeader("MCP-Protocol-Version", protocolVersion); + const path = request.url ? new URL(request.url, this.url || "http://127.0.0.1").pathname : "/"; + + if (request.method === "GET" && (path === "/mcp" || path === "/mcp/")) { + sendJson(response, 200, { + endpoint: "/mcp", + name: this.toolName, + protocol: "mcp", + transport: "streamable-http" + }); + return; + } + + if (request.method !== "POST" || (path !== "/mcp" && path !== "/mcp/")) { + sendJson(response, 404, { error: { message: "In-app browser web search MCP endpoint not found." } }); + return; + } + + let payload: unknown; + try { + payload = JSON.parse((await readRequestBody(request, maxMcpRequestBytes)).toString("utf8")) as unknown; + } catch (error) { + sendJson(response, 400, jsonRpcError(null, -32700, `Invalid JSON-RPC request: ${formatError(error)}`)); + return; + } + + const requests = Array.isArray(payload) ? payload : [payload]; + const responses = await Promise.all(requests.map((item) => this.handleJsonRpcRequest(item))); + const filtered = responses.filter((item): item is JsonRpcResponse => Boolean(item)); + if (filtered.length === 0) { + response.writeHead(204); + response.end(); + return; + } + + sendJson(response, 200, Array.isArray(payload) ? filtered : filtered[0]); + } + + private async handleJsonRpcRequest(payload: unknown): Promise { + if (!isRecord(payload)) { + return jsonRpcError(null, -32600, "JSON-RPC request must be an object."); + } + + const request = payload as JsonRpcRequest; + const id = request.id ?? null; + if (request.id === undefined && request.method?.startsWith("notifications/")) { + return undefined; + } + if (request.jsonrpc !== "2.0" || !request.method) { + return jsonRpcError(id, -32600, "Invalid JSON-RPC 2.0 request."); + } + + try { + switch (request.method) { + case "initialize": + return jsonRpcResult(id, { + capabilities: { + tools: {} + }, + protocolVersion, + serverInfo: { + name: "ccr-browser-web-search", + title: "CCR In-app Browser Web Search", + version: "1.0.0" + } + }); + case "ping": + return jsonRpcResult(id, {}); + case "tools/list": + return jsonRpcResult(id, { tools: [this.tool()] as unknown as JsonValue }); + case "tools/call": + return jsonRpcResult(id, await this.callTool(request.params) as unknown as JsonValue); + default: + return jsonRpcError(id, -32601, `Unsupported MCP method: ${request.method}`); + } + } catch (error) { + return jsonRpcError(id, -32603, formatError(error)); + } + } + + private tool(): JsonValue { + return { + description: + "Search the web through a hidden in-app browser window. " + + "Supports Google, Bing, and DuckDuckGo plus advanced query controls such as domains, keywords, phrases, excluded terms, and date filters.", + inputSchema: objectSchema({ + after: { description: "Earliest result date as YYYY-MM-DD. Also added to the engine query as after:YYYY-MM-DD.", type: "string" }, + allowedDomains: { description: "Alias for includeDomains; compatible with Anthropic web search declarations.", items: { type: "string" }, type: "array" }, + allowed_domains: { description: "Alias for includeDomains; compatible with Anthropic web_search_20250305.", items: { type: "string" }, type: "array" }, + anyTerms: { description: "Terms where any one may match; encoded as OR terms.", items: { type: "string" }, type: "array" }, + before: { description: "Latest result date as YYYY-MM-DD. Also added to the engine query as before:YYYY-MM-DD.", type: "string" }, + blockedDomains: { description: "Alias for excludeDomains; compatible with Anthropic web search declarations.", items: { type: "string" }, type: "array" }, + blocked_domains: { description: "Alias for excludeDomains; compatible with Anthropic web_search_20250305.", items: { type: "string" }, type: "array" }, + count: { maximum: maxSearchResultCount, minimum: 1, type: "number" }, + country: { description: "Country/region hint such as US, CN, GB.", type: "string" }, + domains: { description: "Alias for includeDomains.", items: { type: "string" }, type: "array" }, + engine: { enum: ["auto", "bing", "google", "duckduckgo"], type: "string" }, + exactPhrase: { description: "Phrase to search exactly.", type: "string" }, + excludeDomain: { description: "Single domain alias for excludeDomains.", type: "string" }, + excludeDomains: { description: "Domains to exclude with -site:domain.", items: { type: "string" }, type: "array" }, + excludeTerms: { description: "Terms to exclude with a leading minus.", items: { type: "string" }, type: "array" }, + freshness: { description: "Relative time filter.", enum: ["day", "week", "month", "year"], type: "string" }, + includeDomains: { description: "Domains to restrict with site:domain.", items: { type: "string" }, type: "array" }, + includeRaw: { description: "Include the engine search URL in the text response.", type: "boolean" }, + keywords: { description: "Additional required keywords. A comma-separated string is also accepted.", items: { type: "string" }, type: "array" }, + language: { description: "Language hint such as en, zh-CN, ja.", type: "string" }, + prompt: { description: "Natural-language query. Search engine operators already present here are preserved.", type: "string" }, + query: { description: "Alias for prompt.", type: "string" }, + safeSearch: { enum: ["off", "moderate", "strict"], type: "string" }, + site: { description: "Single domain alias for includeDomains.", type: "string" }, + timeRange: { description: "Alias for freshness.", enum: ["day", "week", "month", "year"], type: "string" }, + timeoutMs: { minimum: 100, type: "number" } + }, ["prompt"]), + name: this.toolName, + title: "In-app Browser Web Search" + }; + } + + private async callTool(params: unknown): Promise { + if (!isRecord(params) || typeof params.name !== "string") { + throw new Error("tools/call params must include a tool name."); + } + if (params.name !== this.toolName) { + throw new Error(`Unknown in-app browser web search tool: ${params.name}`); + } + + const args = isRecord(params.arguments) ? params.arguments : {}; + try { + const input = this.readSearchInput(args); + const record = await this.search(input); + return textResult(formatSearchResponse(record, input.includeRaw)); + } catch (error) { + return { + ...textResult(formatError(error)), + isError: true + }; + } + } + + async searchForProtocolResult(options: { count?: number; prompt: string; timeoutMs?: number }): Promise { + return await this.search(this.readSearchInput({ + count: options.count, + prompt: options.prompt, + timeoutMs: options.timeoutMs + })); + } + + private async search(input: BrowserSearchInput): Promise { + const response = await this.searchPool.search(input); + const record = { + completedAtMs: Date.now(), + engine: response.engine, + query: response.query, + results: response.results, + searchUrl: response.searchUrl, + toolName: this.toolName + }; + this.recordSearchResult(record); + return record; + } + + private readSearchInput(args: Record): BrowserSearchInput { + const prompt = readString(args.prompt) || readString(args.query); + if (!prompt) { + throw new Error(`${this.toolName} requires prompt.`); + } + + const engine = parseSearchEngine(readString(args.engine)) ?? this.defaultEngine; + const includeDomains = uniqueStrings([ + ...readStringArray(args.includeDomains), + ...readStringArray(args.domains), + ...readStringArray(args.domain), + ...readStringArray(args.site), + ...readStringArray(args.allowedDomains), + ...readStringArray(args.allowed_domains) + ].map(normalizeDomain).filter((item): item is string => Boolean(item))); + const excludeDomains = uniqueStrings([ + ...readStringArray(args.excludeDomains), + ...readStringArray(args.excludeDomain), + ...readStringArray(args.blockedDomains), + ...readStringArray(args.blocked_domains) + ].map(normalizeDomain).filter((item): item is string => Boolean(item))); + + return { + after: normalizeSearchDate(readString(args.after) || readString(args.since) || readString(args.fromDate)), + anyTerms: readStringArray(args.anyTerms), + before: normalizeSearchDate(readString(args.before) || readString(args.until) || readString(args.toDate)), + count: clampInteger(readNumber(args.count) ?? this.defaultCount, 1, maxSearchResultCount), + country: readString(args.country) || this.defaultCountry, + engine, + exactPhrase: readString(args.exactPhrase), + excludeDomains, + excludeTerms: readStringArray(args.excludeTerms), + freshness: parseFreshness(readString(args.freshness) || readString(args.timeRange)), + includeDomains, + includeRaw: args.includeRaw === true, + keywords: readStringArray(args.keywords), + language: readString(args.language) || this.defaultLanguage, + prompt, + safeSearch: parseSafeSearch(readString(args.safeSearch)) ?? this.defaultSafeSearch, + timeoutMs: clampInteger(readNumber(args.timeoutMs) ?? this.defaultTimeoutMs, 100, 600_000) + }; + } +} + +class HiddenBrowserSearchPool { + private configuredSession = false; + private queue: BrowserSearchQueueEntry[] = []; + private window?: BrowserWindow; + private workers: BrowserSearchWorker[] = []; + + async search(input: BrowserSearchInput): Promise { + const request = buildBrowserSearchRequest(input); + const worker = await this.acquire(request.timeoutMs); + let page: BrowserSearchPageResult; + try { + page = await runSearchInWorker(worker, request); + } finally { + this.release(worker); + } + if (page.blocked) { + throw new Error(page.blocked); + } + const results = uniqueSearchResults(page.results ?? []).slice(0, request.count); + return { + engine: request.engine, + query: request.query, + results: await this.enrichResults(results, request), + searchUrl: request.searchUrl + }; + } + + destroy(): void { + this.queue.splice(0).forEach((entry) => entry.reject(new Error("Browser search service stopped."))); + this.queue = []; + for (const worker of this.workers) { + if (!worker.view.webContents.isDestroyed()) { + worker.view.webContents.close({ waitForBeforeUnload: false }); + } + } + this.workers = []; + if (this.window && !this.window.isDestroyed()) { + this.window.destroy(); + } + this.window = undefined; + this.configuredSession = false; + } + + private async acquire(timeoutMs: number): Promise { + await app.whenReady(); + this.ensureWindow(); + const maxWorkers = browserSearchConcurrency(); + for (;;) { + const idle = this.workers.find((worker) => !worker.busy); + if (idle) { + idle.busy = true; + return idle; + } + if (this.workers.length < maxWorkers) { + const worker = this.createWorker(); + worker.busy = true; + return worker; + } + await waitForQueueTurn(this.queue, timeoutMs); + } + } + + private release(worker: BrowserSearchWorker): void { + worker.busy = false; + const next = this.queue.shift(); + next?.resolve(); + } + + private ensureWindow(): BrowserWindow { + if (this.window && !this.window.isDestroyed()) { + return this.window; + } + this.configureSearchSession(); + this.window = new BrowserWindow({ + height: 900, + paintWhenInitiallyHidden: true, + show: false, + skipTaskbar: true, + title: "CCR In-app Browser Web Search", + webPreferences: { + backgroundThrottling: false, + contextIsolation: true, + images: false, + nodeIntegration: false, + sandbox: true, + webSecurity: true + }, + width: 1280 + }); + this.window.on("closed", () => { + this.workers = []; + this.window = undefined; + }); + return this.window; + } + + private createWorker(): BrowserSearchWorker { + const window = this.ensureWindow(); + const view = new WebContentsView({ + webPreferences: { + backgroundThrottling: false, + contextIsolation: true, + images: false, + nodeIntegration: false, + partition: searchPartition, + sandbox: true, + webSecurity: true + } + }); + view.webContents.setAudioMuted(true); + view.webContents.on("console-message", (event) => { + event.preventDefault(); + }); + view.setBounds({ height: 900, width: 1280, x: 0, y: 0 }); + window.contentView.addChildView(view); + const worker = { busy: false, view }; + this.workers.push(worker); + return worker; + } + + private configureSearchSession(): void { + if (this.configuredSession) { + return; + } + const browserSession = session.fromPartition(searchPartition); + browserSession.setUserAgent(desktopUserAgent); + browserSession.webRequest.onBeforeRequest((details, callback) => { + if (details.resourceType === "image" || details.resourceType === "media" || details.resourceType === "font") { + callback({ cancel: true }); + return; + } + callback({}); + }); + this.configuredSession = true; + } + + private async enrichResults(results: BrowserSearchResult[], request: BrowserSearchRequest): Promise { + const openCount = Math.min(results.length, browserSearchOpenResultCount()); + if (openCount <= 0) { + return results; + } + const enriched = await Promise.all(results.slice(0, openCount).map((result) => this.enrichResult(result, request))); + return results.map((result, index) => enriched[index] ?? result); + } + + private async enrichResult(result: BrowserSearchResult, request: BrowserSearchRequest): Promise { + if (!shouldOpenSearchResult(result.url)) { + return result; + } + const timeoutMs = browserSearchPageTimeoutMs(request.timeoutMs); + let worker: BrowserSearchWorker | undefined; + try { + worker = await this.acquire(timeoutMs); + await loadSearchUrl(worker.view.webContents, result.url, timeoutMs); + const page = await withTimeout( + worker.view.webContents.executeJavaScript(pageContentExtractionScript(), true) as Promise<{ description?: string; text?: string; title?: string }>, + timeoutMs, + "Browser result page extraction timed out." + ); + const content = compactExtractedContent(page.text); + const snippet = result.snippet || compactExtractedContent(page.description); + return { + ...result, + ...(content ? { content } : {}), + ...(snippet ? { snippet } : {}), + ...(page.title && page.title.length > result.title.length ? { title: page.title.slice(0, 240) } : {}) + }; + } catch { + return result; + } finally { + if (worker) { + this.release(worker); + } + } + } +} + +async function runSearchInWorker(worker: BrowserSearchWorker, request: BrowserSearchRequest): Promise { + const webContents = worker.view.webContents; + if (webContents.isDestroyed()) { + throw new Error("Browser search view is unavailable."); + } + + await loadSearchUrl(webContents, request.searchUrl, request.timeoutMs); + return await pollSearchResults(webContents, request); +} + +async function loadSearchUrl(webContents: WebContents, url: string, timeoutMs: number): Promise { + webContents.stop(); + await withTimeout( + webContents.loadURL(url, { + userAgent: desktopUserAgent + }), + timeoutMs, + "Browser search navigation timed out." + ); +} + +async function pollSearchResults(webContents: WebContents, request: BrowserSearchRequest): Promise { + const deadline = Date.now() + request.timeoutMs; + let last: BrowserSearchPageResult = { results: [] }; + while (Date.now() < deadline) { + last = await webContents.executeJavaScript(searchResultExtractionScript(request.engine, request.count), true) as BrowserSearchPageResult; + if (last.blocked || (last.results?.length ?? 0) >= Math.min(request.count, 3)) { + return last; + } + await sleep(150); + } + return last; +} + +function buildBrowserSearchRequest(input: BrowserSearchInput): BrowserSearchRequest { + const query = buildAdvancedSearchQuery(input); + const searchUrl = searchUrlForEngine(input, query); + return { + ...input, + query, + searchUrl + }; +} + +function searchUrlForEngine(input: BrowserSearchInput, query: string): string { + if (input.engine === "google") { + const url = new URL("https://www.google.com/search"); + url.searchParams.set("q", query); + url.searchParams.set("num", String(Math.min(input.count, 10))); + url.searchParams.set("filter", "0"); + url.searchParams.set("pws", "0"); + url.searchParams.set("udm", "14"); + if (input.language) url.searchParams.set("hl", input.language); + if (input.country) url.searchParams.set("gl", input.country); + if (input.safeSearch) url.searchParams.set("safe", input.safeSearch === "off" ? "off" : "active"); + const tbs = googleTimeFilter(input); + if (tbs) url.searchParams.set("tbs", tbs); + return url.toString(); + } + + if (input.engine === "duckduckgo") { + const url = new URL("https://duckduckgo.com/"); + url.searchParams.set("q", query); + url.searchParams.set("ia", "web"); + if (input.language) url.searchParams.set("kl", input.language); + if (input.safeSearch) url.searchParams.set("kp", input.safeSearch === "off" ? "-2" : "1"); + const df = duckDuckGoTimeFilter(input.freshness); + if (df) url.searchParams.set("df", df); + return url.toString(); + } + + const url = new URL("https://www.bing.com/search"); + url.searchParams.set("q", query); + url.searchParams.set("count", String(input.count)); + url.searchParams.set("qs", "n"); + url.searchParams.set("form", "QBRE"); + if (input.language) url.searchParams.set("setlang", input.language); + if (input.country) url.searchParams.set("cc", input.country); + if (input.safeSearch) url.searchParams.set("safeSearch", bingSafeSearch(input.safeSearch)); + const qft = bingTimeFilter(input.freshness); + if (qft) url.searchParams.set("qft", qft); + return url.toString(); +} + +function buildAdvancedSearchQuery(input: BrowserSearchInput): string { + const parts = [input.prompt.trim()]; + parts.push(...input.keywords.map(searchTerm)); + if (input.exactPhrase) { + parts.push(quoteSearchPhrase(input.exactPhrase)); + } + if (input.anyTerms.length > 0) { + parts.push(`(${input.anyTerms.map(searchTerm).join(" OR ")})`); + } + parts.push(...input.excludeTerms.map((term) => `-${searchTerm(term)}`)); + if (input.includeDomains.length === 1) { + parts.push(`site:${input.includeDomains[0]}`); + } else if (input.includeDomains.length > 1) { + parts.push(`(${input.includeDomains.map((domain) => `site:${domain}`).join(" OR ")})`); + } + parts.push(...input.excludeDomains.map((domain) => `-site:${domain}`)); + if (input.after) { + parts.push(`after:${input.after}`); + } + if (input.before) { + parts.push(`before:${input.before}`); + } + return parts.filter(Boolean).join(" ").replace(/\s+/g, " ").trim(); +} + +function searchTerm(value: string): string { + const trimmed = value.trim(); + if (!trimmed) { + return ""; + } + return /\s/.test(trimmed) && !/^".*"$/.test(trimmed) ? quoteSearchPhrase(trimmed) : trimmed; +} + +function quoteSearchPhrase(value: string): string { + return `"${value.trim().replace(/"/g, " ")}"`; +} + +function googleTimeFilter(input: BrowserSearchInput): string | undefined { + if (input.after || input.before) { + const parts = ["cdr:1"]; + if (input.after) parts.push(`cd_min:${dateToGoogleDate(input.after)}`); + if (input.before) parts.push(`cd_max:${dateToGoogleDate(input.before)}`); + return parts.join(","); + } + if (input.freshness === "day") return "qdr:d"; + if (input.freshness === "week") return "qdr:w"; + if (input.freshness === "month") return "qdr:m"; + if (input.freshness === "year") return "qdr:y"; + return undefined; +} + +function bingTimeFilter(value: BrowserSearchFreshness | undefined): string | undefined { + if (value === "day") return "+filterui:age-lt1440"; + if (value === "week") return "+filterui:age-lt10080"; + if (value === "month") return "+filterui:age-lt43200"; + if (value === "year") return "+filterui:age-lt525600"; + return undefined; +} + +function duckDuckGoTimeFilter(value: BrowserSearchFreshness | undefined): string | undefined { + if (value === "day") return "d"; + if (value === "week") return "w"; + if (value === "month") return "m"; + if (value === "year") return "y"; + return undefined; +} + +function bingSafeSearch(value: "moderate" | "off" | "strict"): string { + if (value === "strict") return "Strict"; + if (value === "off") return "Off"; + return "Moderate"; +} + +function dateToGoogleDate(value: string): string { + const [year, month, day] = value.split("-"); + return `${month}/${day}/${year}`; +} + +function searchResultExtractionScript(engine: BrowserSearchEngine, count: number): string { + return `(() => { + const engine = ${JSON.stringify(engine)}; + const maxResults = ${JSON.stringify(count)}; + const results = []; + const seen = new Set(); + const blockedText = detectBlocked(); + if (blockedText) return { blocked: blockedText, results, title: document.title || "" }; + + function compact(value) { + return String(value || "").replace(/\\s+/g, " ").trim(); + } + function absoluteUrl(href) { + try { return new URL(href, location.href); } catch (_) { return undefined; } + } + function decodeMaybeBase64(value) { + if (!value) return ""; + let raw = value.replace(/-/g, "+").replace(/_/g, "/"); + if (raw.startsWith("a1")) raw = raw.slice(2); + try { + const decoded = atob(raw); + return /^https?:\\/\\//i.test(decoded) ? decoded : ""; + } catch (_) { + return ""; + } + } + function normalizeResultUrl(href) { + const url = absoluteUrl(href); + if (!url || !/^https?:$/i.test(url.protocol)) return ""; + const host = url.hostname.toLowerCase(); + if (host.includes("google.") && url.pathname === "/url") { + const q = url.searchParams.get("q") || url.searchParams.get("url"); + if (q) return normalizeResultUrl(q); + } + if (host.endsWith("bing.com") && url.pathname.startsWith("/ck/")) { + const raw = url.searchParams.get("u"); + const decoded = decodeMaybeBase64(raw || ""); + if (decoded) return normalizeResultUrl(decoded); + } + if (host.endsWith("duckduckgo.com") && url.pathname.startsWith("/l/")) { + const target = url.searchParams.get("uddg"); + if (target) return normalizeResultUrl(target); + } + if (isSearchChromeUrl(url)) return ""; + url.hash = ""; + return url.toString(); + } + function isSearchChromeUrl(url) { + const host = url.hostname.toLowerCase(); + const path = url.pathname.toLowerCase(); + if (host === location.hostname.toLowerCase() && (path === "/" || path === "/search" || path.startsWith("/search/"))) return true; + if (host.includes("google.") && /\\/(preferences|advanced_search|intl|policies|support|sorry)/.test(path)) return true; + if (host.endsWith("bing.com") && /\\/(search|account|profile|images|videos|maps|news)/.test(path)) return true; + if (host.endsWith("duckduckgo.com") && path === "/") return true; + return false; + } + function resultContainer(anchor, title) { + let node = anchor; + for (let depth = 0; node && depth < 8; depth += 1, node = node.parentElement) { + const text = compact(node.textContent || ""); + if (text.length > title.length + 40) return node; + } + return anchor.closest("li.b_algo, article, div.g, div[data-sokoban-container], div[data-testid='result'], .result, li, div"); + } + function cleanSnippetCandidate(value, title, url) { + const host = (() => { try { return new URL(url).hostname.replace(/^www\\./, ""); } catch (_) { return ""; } })(); + return compact(String(value || "") + .replace(title, " ") + .replace(url, " ") + .replace(host, " ") + .replace(/https?:\\/\\/\\S+/g, " ") + .replace(/\\b(?:Cached|Translate this page|Similar)\\b/gi, " ")); + } + function breadcrumbLike(value) { + const text = compact(value); + if (!text) return true; + if (/https?:\\/\\//i.test(text)) return true; + if (text.length < 35 && /[›/]|^https?:|^www\\./i.test(text)) return true; + if (/[›]/.test(text) && !/[.!?,。!?]/.test(text)) return true; + if (!/[.!?:;,。!?:;]/.test(text) && /^[\\w\\s.:-]+(?:[›/]\\s*[\\w\\s.:-]+)+$/i.test(text)) return true; + return false; + } + function snippetFor(anchor, container, title, url) { + const preferred = container && container.querySelector(".b_caption p, .b_snippet, [data-sncf], .VwiC3b, .IsZvec, [data-result='snippet'], .result__snippet, p"); + const preferredText = cleanSnippetCandidate(preferred && preferred.textContent, title, url); + if (preferredText && !breadcrumbLike(preferredText)) return preferredText.slice(0, 700); + const candidates = container + ? Array.from(container.querySelectorAll("p, span, div")) + .map((node) => cleanSnippetCandidate(node.textContent, title, url)) + .filter((text) => text.length >= 35 && !breadcrumbLike(text)) + : []; + const text = candidates.find((candidate) => candidate.length >= 50) || candidates[0] || ""; + if (!text) return ""; + return text.slice(0, 700); + } + function titleFor(anchor, titleOverride) { + const explicit = compact(titleOverride); + if (explicit && explicit.length >= 2) return explicit; + const heading = anchor && anchor.querySelector("h1, h2, h3, [role='heading']"); + return compact((heading && heading.textContent) || (anchor && (anchor.textContent || anchor.innerText))); + } + function add(anchor, titleOverride) { + if (!anchor || results.length >= maxResults) return; + const href = anchor.getAttribute("href") || ""; + const url = normalizeResultUrl(href); + if (!url || seen.has(url)) return; + const title = titleFor(anchor, titleOverride); + if (!title || title.length < 2) return; + const container = resultContainer(anchor, title); + seen.add(url); + results.push({ + snippet: snippetFor(anchor, container, title, url), + title: title.slice(0, 240), + url + }); + } + function collect(selector) { + document.querySelectorAll(selector).forEach((node) => { + const anchor = node.matches && node.matches("a[href]") ? node : node.closest && node.closest("a[href]"); + add(anchor, node.matches && node.matches("h1, h2, h3, [role='heading']") ? node.textContent : ""); + }); + } + function collectGoogle() { + collect("#search h3"); + collect("#rso h3"); + collect("a[href] h3"); + } + function collectBing() { + collect("li.b_algo h2 a[href]"); + collect("main ol li h2 a[href]"); + } + function collectDuckDuckGo() { + collect("a[data-testid='result-title-a']"); + collect(".result__a"); + collect("article h2 a[href]"); + } + if (engine === "google") collectGoogle(); + if (engine === "bing") collectBing(); + if (engine === "duckduckgo") collectDuckDuckGo(); + collect("main a[href], #search a[href], #b_results a[href], article a[href], a[href]"); + return { results, title: document.title || "" }; + + function detectBlocked() { + const text = compact(document.body && document.body.innerText).toLowerCase(); + if (!text) return ""; + if (text.includes("unusual traffic") || text.includes("detected unusual") || text.includes("not a robot") || text.includes("captcha")) { + return "Search engine returned an anti-bot or CAPTCHA page."; + } + if (text.includes("before you continue to google") || text.includes("consent.google")) { + return "Google returned a consent page instead of search results."; + } + return ""; + } + })();`; +} + +function pageContentExtractionScript(): string { + return `(() => { + function compact(value) { + return String(value || "").replace(/[\\t\\f\\v ]+/g, " ").replace(/\\n\\s*\\n+/g, "\\n").trim(); + } + function textFrom(node) { + if (!node) return ""; + const clone = node.cloneNode(true); + clone.querySelectorAll("script, style, noscript, svg, nav, header, footer, aside, form, button, input, select, textarea, iframe, [aria-hidden='true']").forEach((item) => item.remove()); + const lines = String(clone.innerText || clone.textContent || "") + .split(/\\n+/) + .map((line) => compact(line)) + .filter((line) => line.length >= 20) + .filter((line, index, array) => array.indexOf(line) === index); + return lines.join("\\n"); + } + function scoreText(text) { + const length = text.length; + const sentenceMarks = (text.match(/[.!?。!?]/g) || []).length; + return length + sentenceMarks * 80; + } + const title = compact( + document.querySelector("meta[property='og:title']")?.getAttribute("content") || + document.querySelector("h1")?.textContent || + document.title || + "" + ); + const description = compact( + document.querySelector("meta[name='description']")?.getAttribute("content") || + document.querySelector("meta[property='og:description']")?.getAttribute("content") || + "" + ); + const candidates = [ + ...document.querySelectorAll("article, main, [role='main'], .article, .article-content, .post, .post-content, .entry-content, .content, #content, .main-content") + ].map(textFrom).filter(Boolean); + const fallback = textFrom(document.body); + const text = [...candidates, fallback] + .filter(Boolean) + .sort((left, right) => scoreText(right) - scoreText(left))[0] || ""; + return { + description, + text: text.slice(0, 5000), + title + }; + })();`; +} + +function formatSearchResponse( + response: Pick & { engine: string }, + includeRaw: boolean +): string { + if (response.results.length === 0) { + return [ + `Search engine: ${response.engine}`, + `Query: ${response.query}`, + includeRaw ? `Search URL: ${response.searchUrl}` : "", + "No results." + ].filter(Boolean).join("\n"); + } + + return [ + `Search engine: ${response.engine}`, + `Query: ${response.query}`, + includeRaw ? `Search URL: ${response.searchUrl}` : "", + ...response.results.map((result, index) => [ + `${index + 1}. ${result.title}`, + `URL: ${result.url}`, + result.snippet ? `Snippet: ${result.snippet}` : "", + result.content ? `Extracted content: ${result.content}` : "" + ].filter(Boolean).join("\n")) + ].filter(Boolean).join("\n\n"); +} + +function uniqueSearchResults(results: BrowserSearchResult[]): BrowserSearchResult[] { + const seen = new Set(); + const unique: BrowserSearchResult[] = []; + for (const result of results) { + if (!result.url || seen.has(result.url)) { + continue; + } + seen.add(result.url); + unique.push(result); + } + return unique; +} + +function normalizeDomain(value: string): string | undefined { + const trimmed = value.trim().replace(/^site:/i, ""); + if (!trimmed) { + return undefined; + } + try { + const url = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`); + return url.hostname.replace(/^www\./i, "").toLowerCase(); + } catch { + return trimmed.replace(/^www\./i, "").replace(/\/.*$/, "").toLowerCase(); + } +} + +function normalizeSearchDate(value: string | undefined): string | undefined { + if (!value) { + return undefined; + } + const trimmed = value.trim(); + if (/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) { + return trimmed; + } + const time = Date.parse(trimmed); + return Number.isFinite(time) ? new Date(time).toISOString().slice(0, 10) : undefined; +} + +function parseSearchEngine(value: string | undefined): BrowserSearchEngine | undefined { + const normalized = value?.trim().toLowerCase().replace(/[_\s]+/g, "-"); + if (!normalized || normalized === "auto") { + return undefined; + } + if (normalized === "google") return "google"; + if (normalized === "bing") return "bing"; + if (normalized === "duckduckgo" || normalized === "duck-duck-go" || normalized === "ddg") return "duckduckgo"; + return undefined; +} + +function parseFreshness(value: string | undefined): BrowserSearchFreshness | undefined { + const normalized = value?.trim().toLowerCase(); + if (normalized === "day" || normalized === "24h" || normalized === "d") return "day"; + if (normalized === "week" || normalized === "7d" || normalized === "w") return "week"; + if (normalized === "month" || normalized === "30d" || normalized === "m") return "month"; + if (normalized === "year" || normalized === "365d" || normalized === "y") return "year"; + return undefined; +} + +function parseSafeSearch(value: string | undefined): "moderate" | "off" | "strict" | undefined { + const normalized = value?.trim().toLowerCase(); + if (normalized === "off" || normalized === "false" || normalized === "0") return "off"; + if (normalized === "strict") return "strict"; + if (normalized === "moderate" || normalized === "medium" || normalized === "on" || normalized === "true" || normalized === "1") return "moderate"; + return undefined; +} + +function browserSearchConcurrency(): number { + return clampInteger(readNumber(process.env.CCR_BROWSER_SEARCH_CONCURRENCY) ?? 2, 1, 4); +} + +function browserSearchOpenResultCount(): number { + return clampInteger(readNumber(process.env.CCR_BROWSER_SEARCH_OPEN_RESULT_COUNT) ?? 3, 0, 8); +} + +function browserSearchPageTimeoutMs(requestTimeoutMs: number): number { + return clampInteger( + readNumber(process.env.CCR_BROWSER_SEARCH_PAGE_TIMEOUT_MS) ?? Math.min(requestTimeoutMs, 8_000), + 500, + 30_000 + ); +} + +function shouldOpenSearchResult(url: string): boolean { + try { + const parsed = new URL(url); + if (!/^https?:$/i.test(parsed.protocol)) { + return false; + } + return !/\.(?:avi|dmg|docx?|exe|gif|jpe?g|mov|mp3|mp4|pdf|png|pptx?|rar|webp|xlsx?|zip)$/i.test(parsed.pathname); + } catch { + return false; + } +} + +function compactExtractedContent(value: string | undefined): string | undefined { + const compacted = value?.replace(/\s+/g, " ").trim(); + if (!compacted || compacted.length < 40) { + return undefined; + } + return compacted.slice(0, 2_400); +} + +function waitForQueueTurn(queue: BrowserSearchQueueEntry[], timeoutMs: number): Promise { + return new Promise((resolve, reject) => { + let settled = false; + const entry = { + reject: (error: Error) => { + if (settled) { + return; + } + settled = true; + clearTimeout(timer); + reject(error); + }, + resolve: () => { + if (settled) { + return; + } + settled = true; + clearTimeout(timer); + resolve(); + } + }; + const timer = setTimeout(() => { + if (settled) { + return; + } + settled = true; + const index = queue.indexOf(entry); + if (index >= 0) { + queue.splice(index, 1); + } + reject(new Error("Browser search worker queue timed out.")); + }, timeoutMs); + queue.push(entry); + }); +} + +function withTimeout(promise: Promise, timeoutMs: number, message: string): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(message)), timeoutMs); + promise.then( + (value) => { + clearTimeout(timer); + resolve(value); + }, + (error: unknown) => { + clearTimeout(timer); + reject(error); + } + ); + }); +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function textResult(text: string): ToolCallResult { + return { + content: [{ text, type: "text" }] + }; +} + +function objectSchema(properties: Record, required: string[] = []): JsonValue { + return { + additionalProperties: true, + properties, + ...(required.length ? { required } : {}), + type: "object" + }; +} + +function jsonRpcResult(id: null | number | string, result: JsonValue): JsonRpcResponse { + return { + id, + jsonrpc: "2.0", + result + }; +} + +function jsonRpcError(id: null | number | string, code: number, message: string): JsonRpcResponse { + return { + error: { + code, + message + }, + id, + jsonrpc: "2.0" + }; +} + +function readRequestBody(request: IncomingMessage, maxBytes: number): Promise { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + let total = 0; + request.on("data", (chunk: Buffer) => { + total += chunk.byteLength; + if (total > maxBytes) { + reject(new Error(`Request body exceeds ${maxBytes} bytes.`)); + request.destroy(); + return; + } + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + }); + request.on("end", () => resolve(Buffer.concat(chunks))); + request.on("error", reject); + }); +} + +function sendJson(response: ServerResponse, statusCode: number, payload: unknown): void { + const body = `${JSON.stringify(payload)}\n`; + response.writeHead(statusCode, { + "cache-control": "no-store", + "content-length": Buffer.byteLength(body), + "content-type": "application/json; charset=utf-8" + }); + response.end(body); +} + +function readString(value: unknown): string | undefined { + return typeof value === "string" && value.trim() ? value.trim() : undefined; +} + +function readNumber(value: unknown): number | undefined { + if (typeof value === "number" && Number.isFinite(value)) { + return value; + } + if (typeof value === "string" && value.trim()) { + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : undefined; + } + return undefined; +} + +function readStringArray(value: unknown): string[] { + if (typeof value === "string") { + return value.split(",").map((item) => item.trim()).filter(Boolean); + } + if (!Array.isArray(value)) { + return []; + } + return value.map(readString).filter((item): item is string => Boolean(item)); +} + +function uniqueStrings(values: string[]): string[] { + return [...new Set(values.filter(Boolean))]; +} + +function clampInteger(value: number, minimum: number, maximum: number): number { + return Math.max(minimum, Math.min(maximum, Math.round(value))); +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function formatError(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + +export const browserWebSearchMcpService = new BrowserWebSearchMcpService(); diff --git a/src/main/main-app.ts b/src/main/main-app.ts index 9c1d9ca..a065668 100644 --- a/src/main/main-app.ts +++ b/src/main/main-app.ts @@ -10,6 +10,7 @@ import { ensureCcrCliLauncher } from "./profile-launch-service"; import { proxyService } from "../server/proxy/service"; import trayController from "./tray-controller"; import { appUpdateService } from "./update-service"; +import { browserWebSearchMcpService } from "./electron-web-search-mcp"; import windowsManager from "./windows"; const gotTheLock = app.requestSingleInstanceLock(); @@ -27,6 +28,7 @@ if (!gotTheLock) { } function startPrimaryInstance(): void { + gatewayService.setBrowserWebSearchMcpIntegration(browserWebSearchMcpService); deepLinkService.register(); deepLinkService.handleArgv(process.argv); diff --git a/src/renderer/pages/home/components/virtual-models.tsx b/src/renderer/pages/home/components/virtual-models.tsx index 453a957..6debc9d 100644 --- a/src/renderer/pages/home/components/virtual-models.tsx +++ b/src/renderer/pages/home/components/virtual-models.tsx @@ -3,18 +3,40 @@ import { Card, CardContent, CardHeader, CardTitle, Check, ChevronDown, ChevronRight, cn, createMcpServerDraftFromConfig, createRouteModelOptions, defaultFusionWebSearchProvider, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, ExtensionInstallDraft, Field, FolderOpen, formatPluginDependencies, - createFusionWebSearchEnvRows, customFusionToolName, fusionToolExecutionFlagsFromTools, fusionToolOptions, + createFusionWebSearchEnvRows, createKeyValueDraftRow, customFusionToolName, fusionToolExecutionFlagsFromTools, fusionToolOptions, fusionWebSearchProviderOptions, GatewayMcpServerConfig, GatewayMcpToolInfo, GatewayProviderConfig, Input, isBuiltInFusionToolName, isFusionVisionToolName, isFusionWebSearchToolName, KeyValueRowsControl, LoaderCircle, mcpServerConfigFromDraft, mcpServerEndpointSummary, mcpServerTransportOptions, mcpStdioMessageModeOptions, motion, normalizeFusionToolName, Pencil, PluginMarketplaceEntry, Plus, PopoverContent, RouteTargetControl, Search, selectedFusionToolNames, SelectControl, Toggle, Trash2, translateOptions, useAppErrorText, useAppText, useEffect, useLayoutEffect, useMemo, useRef, useState, validateMcpServerDraft, virtualModelBaseModelSummary, VirtualModelDraft, virtualModelMatchesQuery, virtualModelMatchSummary, + type KeyValueDraftRow, VirtualModelProfileConfig, virtualModelToolSummary, X } from "../shared"; const virtualModelTableGridClass = "grid-cols-[minmax(180px,0.9fr)_minmax(220px,1.1fr)_minmax(220px,1.1fr)_minmax(170px,0.85fr)_112px_96px]"; const virtualModelTableMinWidthClass = "min-w-[1100px]"; +const browserWebSearchEngineEnvKey = "BROWSER_SEARCH_ENGINE"; +const browserWebSearchLanguageEnvKey = "BROWSER_SEARCH_LANGUAGE"; +const browserWebSearchCountryEnvKey = "BROWSER_SEARCH_COUNTRY"; +const browserWebSearchSafeSearchEnvKey = "BROWSER_SEARCH_SAFE_SEARCH"; +const browserWebSearchKnownEnvKeys = new Set([ + browserWebSearchEngineEnvKey, + browserWebSearchLanguageEnvKey, + browserWebSearchCountryEnvKey, + browserWebSearchSafeSearchEnvKey +]); +const browserWebSearchEngineOptions = [ + { label: "Bing", value: "bing" }, + { label: "Google", value: "google" }, + { label: "DuckDuckGo", value: "duckduckgo" } +]; +const browserWebSearchSafeSearchOptions = [ + { label: "Default", value: "default" }, + { label: "Moderate", value: "moderate" }, + { label: "Strict", value: "strict" }, + { label: "Off", value: "off" } +]; function uniqueFusionTools(tools: string[]): string[] { const seen = new Set(); @@ -452,17 +474,112 @@ function WebSearchToolConfigurationPanel({ - - onChange({ webSearchEnvRows })} rows={draft.webSearchEnvRows} /> + ) : ( + + onChange({ webSearchEnvRows })} + rows={draft.webSearchEnvRows} + /> + + )} + + ); +} + +function BrowserWebSearchConfigurationPanel({ + onChange, + rows +}: { + onChange: (rows: KeyValueDraftRow[]) => void; + rows: KeyValueDraftRow[]; +}) { + const t = useAppText(); + const extraRows = browserWebSearchExtraRows(rows); + const engine = browserWebSearchEnvValue(rows, browserWebSearchEngineEnvKey) || "bing"; + const language = browserWebSearchEnvValue(rows, browserWebSearchLanguageEnvKey); + const country = browserWebSearchEnvValue(rows, browserWebSearchCountryEnvKey); + const safeSearch = browserWebSearchEnvValue(rows, browserWebSearchSafeSearchEnvKey) || "default"; + + function updateKnownValue(key: string, value: string) { + onChange(upsertBrowserWebSearchEnvRows(rows, key, value === "default" ? "" : value)); + } + + function updateExtraRows(nextExtraRows: KeyValueDraftRow[]) { + onChange(mergeBrowserWebSearchEnvRows(rows, nextExtraRows)); + } + + return ( +
+
+ + updateKnownValue(browserWebSearchEngineEnvKey, value)} + options={browserWebSearchEngineOptions} + value={browserWebSearchEngineOptions.some((option) => option.value === engine) ? engine : "bing"} + /> + + + updateKnownValue(browserWebSearchSafeSearchEnvKey, value)} + options={translateOptions(browserWebSearchSafeSearchOptions, t)} + value={browserWebSearchSafeSearchOptions.some((option) => option.value === safeSearch) ? safeSearch : "default"} + /> + + + updateKnownValue(browserWebSearchLanguageEnvKey, event.target.value)} + placeholder="en, zh-CN" + value={language} + /> + + + updateKnownValue(browserWebSearchCountryEnvKey, event.target.value)} + placeholder="US, CN" + value={country} + /> + +
+ +
); } +function browserWebSearchEnvValue(rows: KeyValueDraftRow[], key: string): string { + return rows.find((row) => row.key.trim() === key)?.value.trim() ?? ""; +} + +function browserWebSearchExtraRows(rows: KeyValueDraftRow[]): KeyValueDraftRow[] { + return rows.filter((row) => { + const key = row.key.trim(); + return key && !browserWebSearchKnownEnvKeys.has(key); + }); +} + +function upsertBrowserWebSearchEnvRows(rows: KeyValueDraftRow[], key: string, value: string): KeyValueDraftRow[] { + const normalizedValue = value.trim(); + const nextRows = rows.filter((row) => row.key.trim() !== key); + return normalizedValue ? [createKeyValueDraftRow(key, normalizedValue), ...nextRows] : nextRows; +} + +function mergeBrowserWebSearchEnvRows(currentRows: KeyValueDraftRow[], extraRows: KeyValueDraftRow[]): KeyValueDraftRow[] { + const knownRows = currentRows.filter((row) => browserWebSearchKnownEnvKeys.has(row.key.trim()) && row.value.trim()); + const cleanedExtraRows = extraRows.filter((row) => row.key.trim() || row.value.trim()); + return [...knownRows, ...cleanedExtraRows]; +} + function CustomMcpToolDialog({ draft, error, diff --git a/src/renderer/pages/home/shared/i18n.tsx b/src/renderer/pages/home/shared/i18n.tsx index cf70595..cb33420 100644 --- a/src/renderer/pages/home/shared/i18n.tsx +++ b/src/renderer/pages/home/shared/i18n.tsx @@ -380,7 +380,17 @@ export const appCopy: Record = { "Vision model is required.": "Vision model is required.", "Web search configuration": "Web search configuration", "Search provider": "Search provider", - "Provider configuration": "Provider configuration", + "In-app Browser": "In-app Browser", + "Provider configuration": "Provider configuration", + "Search engine": "Search engine", + "Safe search": "Safe search", + "Language": "Language", + "Country": "Country", + "Advanced variables": "Advanced variables", + "Default": "Default", + "Moderate": "Moderate", + "Strict": "Strict", + "Off": "Off", "Add tool": "Add tool", "Add variable": "Add variable", "Add custom MCP": "Add custom MCP", @@ -407,6 +417,7 @@ export const appCopy: Record = { "Image recognition and Web Search": "Image recognition and Web Search", "Generic image understanding tool for OCR, screenshot analysis, chart reading, UI comparison, error diagnosis, and other multi-image tasks.": "Generic image understanding tool for OCR, screenshot analysis, chart reading, UI comparison, error diagnosis, and other multi-image tasks.", "Generic web search tool supporting Brave, Bing, Google CSE, Serper, SerpAPI, Tavily, and Exa.": "Generic web search tool supporting Brave, Bing, Google CSE, Serper, SerpAPI, Tavily, and Exa.", + "Generic web search tool supporting hidden in-app browser search plus Brave, Bing, Google CSE, Serper, SerpAPI, Tavily, and Exa.": "Generic web search tool supporting hidden in-app browser search plus Brave, Bing, Google CSE, Serper, SerpAPI, Tavily, and Exa.", "Web Search": "Web Search", "New model": "New model", "New model is required.": "New model is required.", @@ -1232,7 +1243,16 @@ export const appCopy: Record = { "Vision model is required.": "必须选择视觉模型。", "Web search configuration": "Web Search 配置", "Search provider": "搜索类型", - "Provider configuration": "类型配置", + "In-app Browser": "内置浏览器", + "Provider configuration": "类型配置", + "Search engine": "搜索引擎", + "Safe search": "安全搜索", + "Language": "语言", + "Country": "国家/地区", + "Advanced variables": "高级变量", + "Default": "默认", + "Moderate": "中等", + "Strict": "严格", "Add tool": "添加工具", "Add variable": "添加变量", "Add custom MCP": "添加自定义 MCP", @@ -1250,6 +1270,7 @@ export const appCopy: Record = { "Image recognition and Web Search": "图片识别和 Web Search", "Generic image understanding tool for OCR, screenshot analysis, chart reading, UI comparison, error diagnosis, and other multi-image tasks.": "通用图片理解工具,支持 OCR、截图分析、图表解读、UI 对比、错误诊断等多图任务。", "Generic web search tool supporting Brave, Bing, Google CSE, Serper, SerpAPI, Tavily, and Exa.": "通用网络搜索工具,支持 Brave、Bing、Google CSE、Serper、SerpAPI、Tavily、Exa。", + "Generic web search tool supporting hidden in-app browser search plus Brave, Bing, Google CSE, Serper, SerpAPI, Tavily, and Exa.": "通用网络搜索工具,支持隐藏内置浏览器搜索,以及 Brave、Bing、Google CSE、Serper、SerpAPI、Tavily、Exa。", "Web Search": "网页搜索", "New model": "新模型", "New model is required.": "新模型不能为空。", diff --git a/src/renderer/pages/home/shared/options.ts b/src/renderer/pages/home/shared/options.ts index 25cf305..a47466f 100644 --- a/src/renderer/pages/home/shared/options.ts +++ b/src/renderer/pages/home/shared/options.ts @@ -265,7 +265,7 @@ export const fusionToolOptions: Array<{ description: string; label: string; valu value: BUILTIN_FUSION_VISION_TOOL_NAME }, { - description: "Generic web search tool supporting Brave, Bing, Google CSE, Serper, SerpAPI, Tavily, and Exa.", + description: "Generic web search tool supporting hidden in-app browser search plus Brave, Bing, Google CSE, Serper, SerpAPI, Tavily, and Exa.", label: `${BUILTIN_FUSION_TOOL_SERVER_NAME} / ${BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME}`, value: BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME } @@ -277,6 +277,7 @@ export const customFusionToolName = "custom_mcp_tool"; export const defaultFusionWebSearchProvider: VirtualModelFusionWebSearchProvider = "brave"; export const fusionWebSearchProviderOptions: Array<{ label: string; value: VirtualModelFusionWebSearchProvider }> = [ + { label: "In-app Browser", value: "browser" }, { label: "Brave", value: "brave" }, { label: "Bing", value: "bing" }, { label: "Google CSE", value: "google_cse" }, @@ -288,6 +289,7 @@ export const fusionWebSearchProviderOptions: Array<{ label: string; value: Virtu export const fusionWebSearchEnvKeysByProvider: Record = { bing: ["BING_SEARCH_API_KEY", "BING_SEARCH_ENDPOINT"], + browser: ["BROWSER_SEARCH_ENGINE", "BROWSER_SEARCH_LANGUAGE", "BROWSER_SEARCH_COUNTRY", "BROWSER_SEARCH_SAFE_SEARCH"], brave: ["BRAVE_SEARCH_API_KEY", "BRAVE_SEARCH_ENDPOINT"], exa: ["EXA_API_KEY", "EXA_SEARCH_ENDPOINT"], google_cse: ["GOOGLE_SEARCH_API_KEY", "GOOGLE_SEARCH_CX", "GOOGLE_SEARCH_ENDPOINT"], diff --git a/src/renderer/pages/home/shared/virtual-models.ts b/src/renderer/pages/home/shared/virtual-models.ts index 18ca112..c71cdd0 100644 --- a/src/renderer/pages/home/shared/virtual-models.ts +++ b/src/renderer/pages/home/shared/virtual-models.ts @@ -625,7 +625,7 @@ export function fusionWebSearchConfigFromDraft(draft: VirtualModelDraft, key: st export function fusionWebSearchToolName(key: string): string { const normalized = sanitizeConfigId(key).replace(/[^a-z0-9_]+/g, "_").replace(/^_+|_+$/g, ""); - return `web_search_${normalized || "fusion"}`; + return `${normalized || "fusion"}_web_search`; } export function fusionCustomToolConfigFromProfile(profile: VirtualModelProfileConfig): VirtualModelFusionCustomToolConfig | undefined { @@ -996,7 +996,10 @@ export function isFusionVisionToolName(name: string): boolean { export function isFusionWebSearchToolName(name: string): boolean { const normalized = normalizeFusionToolName(name); - return normalized === BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME || normalized.startsWith(`${BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME}_`); + return normalized === BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME || + normalized.startsWith(`${BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME}_`) || + normalized.endsWith(`_${BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME}`) || + normalized.includes("search_web"); } export function selectedFusionToolName(toolsText: string): string { diff --git a/src/server/gateway/service.ts b/src/server/gateway/service.ts index 8496526..f4ab704 100644 --- a/src/server/gateway/service.ts +++ b/src/server/gateway/service.ts @@ -101,6 +101,49 @@ type GatewayStopOptions = { proxyRestoreTimeoutMs?: number; }; +type HostedWebSearchProtocolContext = { + maxUses?: number; + protocol: GatewayProviderProtocol; + queryHint?: string; + records?: BrowserWebSearchProtocolRecord[]; + requestId: string; + sinceMs: number; + toolName: string; +}; + +type AnthropicWebSearchProtocolContext = HostedWebSearchProtocolContext; + +export type BrowserWebSearchMcpRegistration = { + env?: Record; + name: string; + resultCount?: number; + timeoutMs?: number; + toolName: string; +}; + +export type BrowserWebSearchProtocolResult = { + content?: string; + snippet?: string; + title: string; + url: string; +}; + +export type BrowserWebSearchProtocolRecord = { + completedAtMs: number; + engine: string; + query: string; + results: BrowserWebSearchProtocolResult[]; + searchUrl: string; + toolName: string; +}; + +export type BrowserWebSearchMcpIntegration = { + registerBrowserWebSearchMcpServer: (options: BrowserWebSearchMcpRegistration) => Promise; + recentBrowserWebSearchResults?: (options: { sinceMs: number; toolName?: string }) => BrowserWebSearchProtocolRecord[]; + runBrowserWebSearch?: (options: { count?: number; prompt: string; timeoutMs?: number; toolName?: string }) => Promise; + stopBrowserWebSearchMcpServers: () => Promise; +}; + type CoreGatewayHealth = { runtimeId?: string; status?: string; @@ -261,6 +304,7 @@ const persistedApiKeyCacheTtlMs = 1000; let persistedApiKeyCache: { loadedAt: number; values: ApiKeyConfig[] } | undefined; class GatewayService { + private browserWebSearchMcpIntegration?: BrowserWebSearchMcpIntegration; private child?: ChildProcess; private config?: AppConfig; private coreAuthToken = ""; @@ -276,6 +320,10 @@ class GatewayService { state: "stopped" }; + setBrowserWebSearchMcpIntegration(integration: BrowserWebSearchMcpIntegration): void { + this.browserWebSearchMcpIntegration = integration; + } + async start(config: AppConfig): Promise { const coreHostError = loopbackCoreHostError(config.gateway.coreHost); if (coreHostError) { @@ -324,7 +372,7 @@ class GatewayService { } if (shouldRunGateway) { - writeCoreGatewayConfig(config, this.rawTraceSyncToken); + await writeCoreGatewayConfig(config, this.rawTraceSyncToken, this.browserWebSearchMcpIntegration); await stopPreviousManagedCoreGateway(config, this.status.coreEndpoint); if (await isCoreGatewayHealthy(this.status.coreEndpoint)) { throw new Error(`Core gateway endpoint is already in use: ${this.status.coreEndpoint}`); @@ -379,6 +427,9 @@ class GatewayService { await proxyService.stop(options.proxyRestoreTimeoutMs); await pluginService.stop(); await backendService.stopAll(); + await this.browserWebSearchMcpIntegration?.stopBrowserWebSearchMcpServers().catch((error) => { + console.warn(`[gateway] Failed to stop browser web search MCP: ${formatError(error)}`); + }); this.status = { ...this.status, @@ -739,6 +790,39 @@ class GatewayService { routeFallback = providerCapabilityRouting.fallback; routedModel = providerCapabilityRouting.routedModel; + const hostedWebSearchProtocolContext = createHostedWebSearchProtocolContext({ + body: bodyToForward, + config: this.config, + method, + path, + requestId, + routedModel, + sinceMs: startedAt - 1_000 + }); + + if (hostedWebSearchProtocolContext && this.browserWebSearchMcpIntegration) { + const records = await selectHostedWebSearchProtocolRecords( + hostedWebSearchProtocolContext, + this.browserWebSearchMcpIntegration + ).catch((error) => { + console.warn(`[gateway] Failed to prefetch hosted web search results: ${formatError(error)}`); + return [] as BrowserWebSearchProtocolRecord[]; + }); + if (records.length > 0) { + hostedWebSearchProtocolContext.records = records; + const webSearchContextBody = prepareHostedWebSearchProtocolRequestBody( + bodyToForward, + records, + hostedWebSearchProtocolContext + ); + if (webSearchContextBody) { + bodyToForward = webSearchContextBody; + headers["content-type"] = "application/json"; + headers["x-ccr-hosted-web-search-context"] = hostedWebSearchProtocolContext.protocol; + } + } + } + delete headers["content-length"]; const upstreamUrl = new URL(request.url || "/", this.status.coreEndpoint).toString(); let upstreamResult: UpstreamFetchResult; @@ -795,6 +879,14 @@ class GatewayService { if (codexApplyPatchBridgeActive) { responseHeaders.delete("content-length"); } + const hostedWebSearchResponseContentType = responseHeaders.get("content-type")?.toLowerCase() ?? ""; + if ( + hostedWebSearchProtocolContext && + hostedWebSearchResponseContentType.includes("application/json") && + (this.browserWebSearchMcpIntegration?.recentBrowserWebSearchResults || this.browserWebSearchMcpIntegration?.runBrowserWebSearch) + ) { + responseHeaders.delete("content-length"); + } recordProviderCredentialOutcome(this.config, method, upstreamResult.attempt, upstreamResponse.status, responseHeaders); response.writeHead(upstreamResponse.status, Object.fromEntries(filteredResponseHeaders(responseHeaders))); if (!upstreamResponse.body) { @@ -819,9 +911,17 @@ class GatewayService { } const upstreamBody = Readable.fromWeb(upstreamResponse.body as unknown as import("node:stream/web").ReadableStream); - const responseBody = codexApplyPatchBridgeActive + const patchedResponseBody = codexApplyPatchBridgeActive ? codexApplyPatchBridgeResponseStream(upstreamBody, responseHeaders) : upstreamBody; + const responseBody = hostedWebSearchProtocolContext + ? hostedWebSearchProtocolResponseStream( + patchedResponseBody, + responseHeaders, + hostedWebSearchProtocolContext, + this.browserWebSearchMcpIntegration + ) + : patchedResponseBody; const sampler = createBodySampler(); const sseErrorDetector = createSseErrorDetector(responseHeaders.get("content-type") ?? undefined); let streamDetectedError: string | undefined; @@ -946,7 +1046,11 @@ class GatewayService { export const gatewayService = new GatewayService(); -function writeCoreGatewayConfig(config: AppConfig, rawTraceSyncToken: string): void { +async function writeCoreGatewayConfig( + config: AppConfig, + rawTraceSyncToken: string, + browserWebSearchMcpIntegration?: BrowserWebSearchMcpIntegration +): Promise { assertLoopbackCoreHost(config.gateway.coreHost); mkdirSync(dirname(config.gateway.generatedConfigFile), { mode: privateDirMode, recursive: true }); const pluginCoreGatewayConfig = pluginService.getCoreGatewayConfig(); @@ -960,7 +1064,7 @@ function writeCoreGatewayConfig(config: AppConfig, rawTraceSyncToken: string): v ...pluginService.getVirtualModelProfiles() ])), config); const coreEndpoint = endpoint(config.gateway.coreHost, config.gateway.corePort); - const builtinToolArtifacts = fusionBuiltinToolArtifacts(virtualModelProfiles, coreEndpoint); + const builtinToolArtifacts = await fusionBuiltinToolArtifacts(virtualModelProfiles, coreEndpoint, browserWebSearchMcpIntegration); const providers = [ ...config.Providers .flatMap((provider) => toCoreGatewayProviders(withCodexOauthProviderBaseUrl(provider, codexOauthProviderNames))) @@ -1078,7 +1182,9 @@ function normalizeCoreGatewayVirtualModelProfile(profile: unknown, config: AppCo }; } - return nextProfile ?? profile; + const profileAfterVision = nextProfile ?? profile; + const profileAfterWebSearchToolName = normalizeFusionWebSearchProfileToolName(profileAfterVision) ?? profileAfterVision; + return withFusionWebSearchToolInstructions(profileAfterWebSearchToolName) ?? profileAfterWebSearchToolName; } function rewriteModelSelectorForCoreGatewayProfile( @@ -1241,18 +1347,19 @@ function hasOwn(value: Record, key: string): boolean { return Object.prototype.hasOwnProperty.call(value, key); } -function fusionBuiltinToolArtifacts( +async function fusionBuiltinToolArtifacts( profiles: unknown[], - coreEndpoint: string -): { mcpServers: GatewayMcpServerConfig[]; providers: CoreGatewayProvider[] } { + coreEndpoint: string, + browserWebSearchMcpIntegration?: BrowserWebSearchMcpIntegration +): Promise<{ mcpServers: GatewayMcpServerConfig[]; providers: CoreGatewayProvider[] }> { const providers: CoreGatewayProvider[] = []; const mcpServers: GatewayMcpServerConfig[] = []; const toolServerKeys = new Set(); const entry = bundledFusionBuiltinMcpEntryPath(); - profiles.forEach((profile, index) => { + for (const [index, profile] of profiles.entries()) { if (!isRecord(profile) || profile.enabled === false) { - return; + continue; } const metadata = isRecord(profile.metadata) ? profile.metadata : undefined; const profileId = stringValue(profile.id) || stringValue(profile.key) || `fusion-${index + 1}`; @@ -1285,21 +1392,35 @@ function fusionBuiltinToolArtifacts( const toolServerKey = `web_search:${webSearchConfig.toolName}`; if (!toolServerKeys.has(toolServerKey)) { toolServerKeys.add(toolServerKey); - mcpServers.push(fusionBuiltinMcpServer({ - entry, - env: { - FUSION_BUILTIN_TOOL_KIND: "web_search", - FUSION_TOOL_NAME: webSearchConfig.toolName, - SEARCH_PROVIDER: webSearchConfig.provider ?? defaultFusionWebSearchProvider, - ...(webSearchConfig.resultCount ? { SEARCH_RESULT_COUNT: String(webSearchConfig.resultCount) } : {}), - ...(webSearchConfig.timeoutMs ? { SEARCH_TIMEOUT_MS: String(webSearchConfig.timeoutMs) } : {}), - ...(webSearchConfig.env ?? {}) - }, - name: `fusion-web-search-${sanitizedProfileId}` - })); + const provider = webSearchConfig.provider ?? defaultFusionWebSearchProvider; + if (provider === "browser") { + const browserMcpServer = await browserWebSearchMcpIntegration?.registerBrowserWebSearchMcpServer({ + env: webSearchConfig.env ?? {}, + name: `fusion-browser-web-search-${sanitizedProfileId}`, + resultCount: webSearchConfig.resultCount, + timeoutMs: webSearchConfig.timeoutMs, + toolName: webSearchConfig.toolName + }); + if (browserMcpServer) { + mcpServers.push(browserMcpServer); + } + } else { + mcpServers.push(fusionBuiltinMcpServer({ + entry, + env: { + FUSION_BUILTIN_TOOL_KIND: "web_search", + FUSION_TOOL_NAME: webSearchConfig.toolName, + SEARCH_PROVIDER: provider, + ...(webSearchConfig.resultCount ? { SEARCH_RESULT_COUNT: String(webSearchConfig.resultCount) } : {}), + ...(webSearchConfig.timeoutMs ? { SEARCH_TIMEOUT_MS: String(webSearchConfig.timeoutMs) } : {}), + ...(webSearchConfig.env ?? {}) + }, + name: `fusion-web-search-${sanitizedProfileId}` + })); + } } } - }); + } return { mcpServers, providers }; } @@ -1337,7 +1458,7 @@ function fusionToolFallbackMcpServer( profiles: unknown[], existingServers: unknown[] ): GatewayMcpServerConfig | undefined { - const tools = fusionFallbackToolDefinitions(profiles); + const tools = fusionFallbackToolDefinitions(profiles, fusionToolNamesBackedByMcpServers(existingServers)); if (tools.length === 0) { return undefined; } @@ -1362,8 +1483,9 @@ function bundledFusionToolFallbackMcpEntryPath(): string { return pathJoin(__dirname, "fusion-tool-fallback-mcp.js"); } -function fusionFallbackToolDefinitions( - profiles: unknown[] +export function fusionFallbackToolDefinitions( + profiles: unknown[], + backedToolNames: Set = new Set() ): Array<{ description?: string; inputSchema?: Record; name: string }> { const byName = new Map; name: string }>(); @@ -1379,6 +1501,9 @@ function fusionFallbackToolDefinitions( if (!name) { continue; } + if (backedToolNames.has(name)) { + continue; + } const existing = byName.get(name); const description = stringValue(tool.description); @@ -1408,6 +1533,26 @@ function fusionFallbackToolDefinitions( return [...byName.values()]; } +export function fusionToolNamesBackedByMcpServers(servers: unknown[]): Set { + const names = new Set(); + for (const server of servers) { + if (!isRecord(server)) { + continue; + } + const serverName = stringValue(server.name); + if (serverName) { + names.add(serverName); + } + + const env = isRecord(server.env) ? server.env : undefined; + const fusionToolName = stringValue(env?.FUSION_TOOL_NAME); + if (fusionToolName) { + names.add(fusionToolName); + } + } + return names; +} + function uniqueMcpServerName(baseName: string, servers: unknown[]): string { const used = new Set( servers @@ -1517,7 +1662,7 @@ function legacyFusionBuiltinToolName( const tools = Array.isArray(profile.tools) ? profile.tools : []; const toolName = tools .map((tool) => isRecord(tool) ? stringValue(tool.name) ?? "" : "") - .find((name) => name === baseToolName || name.startsWith(`${baseToolName}_`)); + .find((name) => fusionBuiltinToolNameMatches(name, baseToolName)); if (toolName) { return toolName; } @@ -1525,6 +1670,133 @@ function legacyFusionBuiltinToolName( return execution[executionFlag] === true ? baseToolName : undefined; } +function fusionBuiltinToolNameMatches(name: string, baseToolName: string): boolean { + if (name === baseToolName || name.startsWith(`${baseToolName}_`)) { + return true; + } + if (baseToolName !== BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME) { + return false; + } + return coreGatewayWebSearchToolNameMatches(name); +} + +function normalizeFusionWebSearchProfileToolName(profile: Record): Record | undefined { + const metadata = isRecord(profile.metadata) ? profile.metadata : undefined; + const fusionWebSearch = isRecord(metadata?.fusionWebSearch) ? metadata.fusionWebSearch : undefined; + const configuredToolName = stringValue(fusionWebSearch?.toolName); + const legacyToolName = configuredToolName ? undefined : legacyFusionWebSearchConfig(profile)?.toolName; + const toolName = configuredToolName || legacyToolName; + if (!toolName) { + return undefined; + } + + const nextToolName = coreGatewayCompatibleWebSearchToolName(toolName, stringValue(profile.key) || stringValue(profile.id)); + if (nextToolName === toolName) { + return undefined; + } + + const tools = Array.isArray(profile.tools) + ? profile.tools.map((tool) => { + if (!isRecord(tool) || stringValue(tool.name) !== toolName) { + return tool; + } + return { + ...tool, + name: nextToolName + }; + }) + : profile.tools; + + return { + ...profile, + ...(metadata && fusionWebSearch + ? { + metadata: { + ...metadata, + fusionWebSearch: { + ...fusionWebSearch, + toolName: nextToolName + } + } + } + : {}), + ...(tools ? { tools } : {}) + }; +} + +function withFusionWebSearchToolInstructions(profile: Record): Record | undefined { + const metadata = isRecord(profile.metadata) ? profile.metadata : undefined; + const fusionWebSearch = isRecord(metadata?.fusionWebSearch) ? metadata.fusionWebSearch : undefined; + const toolName = stringValue(fusionWebSearch?.toolName) || legacyFusionWebSearchConfig(profile)?.toolName; + if (!toolName) { + return undefined; + } + const execution = isRecord(profile.execution) ? profile.execution : {}; + if (execution.matchWebSearch !== true) { + return undefined; + } + + const instruction = [ + `When the client request includes a hosted web_search tool declaration, call the ${toolName} function tool before answering.`, + "Pass the user's search query in the prompt field.", + "Do not use provider-native web search or claim that web search is unavailable unless this function tool returns an error." + ].join(" "); + const instructions = isRecord(profile.instructions) ? profile.instructions : {}; + if ([instructions.prepend, instructions.append, instructions.replace].some((value) => stringValue(value)?.includes(instruction))) { + return undefined; + } + const replace = stringValue(instructions.replace); + const append = stringValue(instructions.append); + return { + ...profile, + instructions: { + ...instructions, + ...(replace + ? { replace: `${replace.trim()}\n\n${instruction}` } + : { append: [append, instruction].filter(Boolean).join("\n\n") }) + } + }; +} + +function coreGatewayCompatibleWebSearchToolName(toolName: string, fallbackName?: string): string { + if (coreGatewayWebSearchToolNameMatches(toolName)) { + return toolName; + } + + const normalized = sanitizeFusionToolName(toolName); + const prefix = `${BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME}_`; + if (normalized.startsWith(prefix) && normalized.length > prefix.length) { + return truncateFusionToolName(`${normalized.slice(prefix.length)}_${BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME}`); + } + + const fallback = sanitizeFusionToolName(fallbackName || normalized || "fusion"); + return truncateFusionToolName(`${fallback}_${BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME}`); +} + +function coreGatewayWebSearchToolNameMatches(name: string): boolean { + const normalized = name.toLowerCase().replace(/[-.]/g, "_"); + return normalized === BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME || + normalized.endsWith(`_${BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME}`) || + normalized.includes("search_web"); +} + +function sanitizeFusionToolName(value: string): string { + return value + .toLowerCase() + .replace(/[^a-z0-9_]+/g, "_") + .replace(/^_+|_+$/g, "") || "fusion"; +} + +function truncateFusionToolName(value: string): string { + const maxToolNameLength = 64; + if (value.length <= maxToolNameLength) { + return value; + } + const suffix = `_${BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME}`; + const available = Math.max(1, maxToolNameLength - suffix.length); + return `${value.slice(0, available).replace(/_+$/g, "")}${suffix}`; +} + function readFusionVisionConfig(value: unknown): VirtualModelFusionVisionConfig | undefined { if (!isRecord(value)) { return undefined; @@ -1630,7 +1902,8 @@ function parseFusionWebSearchProvider(value: unknown): VirtualModelFusionWebSear normalized === "serper" || normalized === "serpapi" || normalized === "tavily" || - normalized === "exa" + normalized === "exa" || + normalized === "browser" ) { return normalized; } @@ -2312,6 +2585,1811 @@ export function transformCodexApplyPatchBridgeSseEvent(block: string): string { } } +function createHostedWebSearchProtocolContext(input: { + body: Buffer | undefined; + config: AppConfig; + method: string; + path: string; + requestId: string; + routedModel?: string; + sinceMs: number; +}): HostedWebSearchProtocolContext | undefined { + const protocol = requestProtocolForPath(input.path); + if (input.method !== "POST" || !protocol) { + return undefined; + } + const body = parseJsonObjectSafe(input.body); + if (!body || !hasHostedWebSearchDeclaration(body, protocol)) { + return undefined; + } + const toolName = fusionWebSearchToolNameForRequest(input.config, stringValue(body.model) || input.routedModel); + if (!toolName) { + return undefined; + } + return { + maxUses: readHostedWebSearchMaxUses(body, protocol), + protocol, + queryHint: extractHostedWebSearchQueryHint(body, protocol), + requestId: input.requestId, + sinceMs: input.sinceMs, + toolName + }; +} + +function createAnthropicWebSearchProtocolContext(input: { + body: Buffer | undefined; + config: AppConfig; + method: string; + path: string; + requestId: string; + sinceMs: number; +}): AnthropicWebSearchProtocolContext | undefined { + const context = createHostedWebSearchProtocolContext(input); + return context?.protocol === "anthropic_messages" ? context : undefined; +} + +export function prepareHostedWebSearchProtocolRequestBody( + body: Buffer | undefined, + records: BrowserWebSearchProtocolRecord[], + context: Pick +): Buffer | undefined { + if (context.protocol === "anthropic_messages") { + return prepareAnthropicWebSearchProtocolRequestBody(body, records, context); + } + const parsed = parseJsonObjectSafe(body); + if (!parsed || records.length === 0) { + return undefined; + } + const evidence = hostedWebSearchEvidenceText(records, context.queryHint); + if (!evidence) { + return undefined; + } + let next: Record | undefined; + if (context.protocol === "openai_chat_completions") { + next = prepareOpenAiChatHostedWebSearchRequestBody(parsed, evidence); + } else if (context.protocol === "openai_responses") { + next = prepareOpenAiResponsesHostedWebSearchRequestBody(parsed, evidence); + } else if (context.protocol === "gemini_generate_content") { + next = prepareGeminiHostedWebSearchRequestBody(parsed, evidence); + } + return next ? Buffer.from(`${JSON.stringify(next)}\n`, "utf8") : undefined; +} + +export function prepareAnthropicWebSearchProtocolRequestBody( + body: Buffer | undefined, + records: BrowserWebSearchProtocolRecord[], + context: Pick +): Buffer | undefined { + const parsed = parseJsonObjectSafe(body); + if (!parsed || records.length === 0) { + return undefined; + } + const evidence = hostedWebSearchEvidenceText(records, context.queryHint); + if (!evidence) { + return undefined; + } + const next = applyAnthropicWebSearchSynthesisControls(stripAnthropicHostedWebSearchTools({ + ...parsed, + system: appendAnthropicSystemText(parsed.system, evidence) + })); + return Buffer.from(`${JSON.stringify(next)}\n`, "utf8"); +} + +function applyAnthropicWebSearchSynthesisControls(body: Record): Record { + const next = { ...body }; + const outputConfig = isRecord(next.output_config) ? { ...next.output_config } : {}; + outputConfig.effort = "low"; + next.output_config = outputConfig; + delete next.thinking; + delete next.reasoning; + return next; +} + +function prepareOpenAiChatHostedWebSearchRequestBody(body: Record, evidence: string): Record { + const next = stripOpenAiHostedWebSearchTools({ + ...body, + messages: appendOpenAiChatSystemText(body.messages, evidence) + }); + return applyOpenAiHostedWebSearchSynthesisControls(next); +} + +function prepareOpenAiResponsesHostedWebSearchRequestBody(body: Record, evidence: string): Record { + const next = stripOpenAiHostedWebSearchTools({ + ...body, + instructions: appendStringInstruction(body.instructions, evidence) + }); + return applyOpenAiHostedWebSearchSynthesisControls(next); +} + +function prepareGeminiHostedWebSearchRequestBody(body: Record, evidence: string): Record { + return stripGeminiHostedWebSearchTools({ + ...body, + systemInstruction: appendGeminiSystemInstruction(body.systemInstruction, evidence) + }); +} + +function applyOpenAiHostedWebSearchSynthesisControls(body: Record): Record { + const next = { ...body }; + if (typeof next.reasoning_effort === "string") { + next.reasoning_effort = "low"; + } + if (isRecord(next.reasoning)) { + next.reasoning = { ...next.reasoning, effort: "low" }; + } + return next; +} + +function stripAnthropicHostedWebSearchTools(body: Record): Record { + if (!Array.isArray(body.tools)) { + return body; + } + const tools = body.tools.filter((tool) => !isAnthropicHostedWebSearchTool(tool)); + if (tools.length === body.tools.length) { + return body; + } + const next = { ...body }; + if (tools.length > 0) { + next.tools = tools; + } else { + delete next.tools; + } + const toolChoice = isRecord(next.tool_choice) ? next.tool_choice : undefined; + const toolChoiceName = stringValue(toolChoice?.name); + if (tools.length === 0 || toolChoiceName === "web_search") { + delete next.tool_choice; + } + return next; +} + +function stripOpenAiHostedWebSearchTools(body: Record): Record { + const next = { ...body }; + let removedTools = false; + if (Array.isArray(body.tools)) { + const tools = body.tools.filter((tool) => !isOpenAiHostedWebSearchTool(tool)); + removedTools = tools.length !== body.tools.length; + if (tools.length > 0) { + next.tools = tools; + } else { + delete next.tools; + } + } + if (next.web_search_options !== undefined || next.webSearchOptions !== undefined) { + delete next.web_search_options; + delete next.webSearchOptions; + removedTools = true; + } + if (removedTools && (!Array.isArray(next.tools) || next.tools.length === 0 || openAiToolChoiceNamesWebSearch(next.tool_choice))) { + delete next.tool_choice; + delete next.parallel_tool_calls; + } + return next; +} + +function stripGeminiHostedWebSearchTools(body: Record): Record { + if (!Array.isArray(body.tools)) { + return body; + } + let changed = false; + const tools = body.tools.flatMap((tool) => { + const transformed = stripGeminiHostedWebSearchTool(tool); + changed ||= transformed.changed; + return transformed.value ? [transformed.value] : []; + }); + if (!changed) { + return body; + } + const next = { ...body }; + if (tools.length > 0) { + next.tools = tools; + } else { + delete next.tools; + } + return next; +} + +function stripGeminiHostedWebSearchTool(tool: unknown): { changed: boolean; value?: unknown } { + if (!isRecord(tool)) { + return { changed: false, value: tool }; + } + let changed = false; + const next: Record = { ...tool }; + for (const key of ["google_search", "googleSearch", "google_search_retrieval", "googleSearchRetrieval"]) { + if (key in next) { + delete next[key]; + changed = true; + } + } + return Object.keys(next).length === 0 ? { changed, value: undefined } : { changed, value: next }; +} + +function appendAnthropicSystemText(system: unknown, text: string): unknown { + if (typeof system === "string") { + return `${system.trimEnd()}\n\n${text}`; + } + const block = { text, type: "text" }; + if (Array.isArray(system)) { + return [...system, block]; + } + return [block]; +} + +function appendOpenAiChatSystemText(messages: unknown, text: string): unknown[] { + const message = { content: text, role: "system" }; + return Array.isArray(messages) ? [message, ...messages] : [message]; +} + +function appendStringInstruction(value: unknown, text: string): string { + const existing = rawStringValue(value); + return existing ? `${existing.trimEnd()}\n\n${text}` : text; +} + +function appendGeminiSystemInstruction(value: unknown, text: string): Record { + const part = { text }; + if (typeof value === "string") { + return { parts: [{ text: value }, part] }; + } + if (isRecord(value)) { + const parts = Array.isArray(value.parts) ? value.parts : []; + return { + ...value, + parts: [...parts, part] + }; + } + return { parts: [part] }; +} + +function hostedWebSearchEvidenceText(records: BrowserWebSearchProtocolRecord[], queryHint: string | undefined): string { + const sections = records.flatMap((record, recordIndex) => { + const resultLines = record.results.slice(0, 8).map((result, resultIndex) => { + const content = focusedWebSearchContent(result.content, queryHint); + const details = [ + result.snippet ? `Search snippet: ${result.snippet}` : "", + content ? `Extracted page content: ${content}` : "" + ].filter(Boolean).join("\n"); + return [ + `${resultIndex + 1}. ${result.title}`, + `URL: ${result.url}`, + details + ].filter(Boolean).join("\n"); + }); + if (resultLines.length === 0) { + return []; + } + return [ + [ + `Search ${recordIndex + 1}`, + `Query: ${record.query}`, + `Engine: ${record.engine}`, + `Search URL: ${record.searchUrl}`, + ...resultLines + ].join("\n\n") + ]; + }); + if (sections.length === 0) { + return ""; + } + return [ + "A hidden in-app browser web search has already been performed for this request.", + "Use the evidence below to answer the user's question directly in the visible final response, within 5 concise sentences. Do not call another web search tool, do not merely list links, do not expose hidden reasoning, and do not ask the user to open links. If the evidence is insufficient for an exact value, say that clearly and summarize the most relevant findings with source names.", + queryHint ? `Original search intent: ${queryHint}` : "", + "Web search evidence:", + ...sections + ].filter(Boolean).join("\n\n").slice(0, 10_000); +} + +function focusedWebSearchContent(content: string | undefined, queryHint: string | undefined): string | undefined { + const text = content?.replace(/\s+/g, " ").trim(); + if (!text) { + return undefined; + } + const queryTerms = normalizeSearchComparisonText(queryHint ?? "") + .split(" ") + .filter((term) => term.length >= 2); + const weatherTerms = /天气|weather/i.test(queryHint ?? "") + ? ["天气", "气温", "温度", "体感", "空气质量", "湿度", "风", "降水", "℃", "晴", "多云", "阴", "雨"] + : []; + const terms = uniqueStrings([...queryTerms, ...weatherTerms]); + const indexes = terms.flatMap((term) => { + const index = text.toLowerCase().indexOf(term.toLowerCase()); + return index >= 0 ? [index] : []; + }); + if (indexes.length === 0) { + return text.slice(0, 1_000); + } + const center = Math.min(...indexes); + const start = Math.max(0, center - 300); + return `${start > 0 ? "..." : ""}${text.slice(start, start + 1_200)}${start + 1_200 < text.length ? "..." : ""}`; +} + +function hostedWebSearchProtocolResponseStream( + input: Readable, + headers: Headers, + context: HostedWebSearchProtocolContext, + integration: BrowserWebSearchMcpIntegration | undefined +): Readable { + if (!integration?.recentBrowserWebSearchResults && !integration?.runBrowserWebSearch) { + return input; + } + const contentType = headers.get("content-type")?.toLowerCase() ?? ""; + if (contentType.includes("text/event-stream")) { + return input; + } + if (!contentType.includes("application/json")) { + return input; + } + + const chunks: Buffer[] = []; + return input.pipe(new Transform({ + transform(chunk, _encoding, callback) { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + callback(); + }, + flush(callback) { + const body = Buffer.concat(chunks).toString("utf8"); + void (async () => { + const records = await selectHostedWebSearchProtocolRecords(context, integration); + if (records.length === 0) { + this.push(body); + return; + } + + const parsed = JSON.parse(body) as unknown; + const transformed = transformHostedWebSearchProtocolResponseValue(parsed, records, context); + this.push(transformed.changed ? JSON.stringify(transformed.value) : body); + })().catch((error) => { + console.warn(`[gateway] Hosted web search protocol bridge failed: ${formatError(error)}`); + this.push(body); + }).finally(() => callback()); + } + })); +} + +function transformHostedWebSearchProtocolResponseValue( + value: unknown, + records: BrowserWebSearchProtocolRecord[], + context: Pick +): { changed: boolean; value: unknown } { + if (context.protocol === "anthropic_messages") { + return transformAnthropicWebSearchProtocolResponseValue(value, records, context.requestId, context.queryHint); + } + if (context.protocol === "openai_chat_completions") { + return transformOpenAiChatHostedWebSearchResponseValue(value, records, context.queryHint); + } + if (context.protocol === "openai_responses") { + return transformOpenAiResponsesHostedWebSearchResponseValue(value, records, context.requestId, context.queryHint); + } + if (context.protocol === "gemini_generate_content") { + return transformGeminiHostedWebSearchResponseValue(value, records, context.queryHint); + } + return { changed: false, value }; +} + +function transformHostedWebSearchProtocolSseText( + body: string, + records: BrowserWebSearchProtocolRecord[], + context: Pick +): string { + if (context.protocol === "anthropic_messages") { + return transformAnthropicWebSearchProtocolSseText(body, records, context.requestId, context.queryHint); + } + if (context.protocol === "openai_chat_completions") { + return transformOpenAiChatHostedWebSearchSseText(body, records, context.queryHint); + } + if (context.protocol === "openai_responses") { + return transformOpenAiResponsesHostedWebSearchSseText(body, records, context.requestId, context.queryHint); + } + if (context.protocol === "gemini_generate_content") { + return transformGeminiHostedWebSearchSseText(body, records, context.queryHint); + } + return body; +} + +export function transformAnthropicWebSearchProtocolResponseValue( + value: unknown, + records: BrowserWebSearchProtocolRecord[], + requestId: string, + queryHint?: string +): { changed: boolean; value: unknown } { + if (!isRecord(value) || !Array.isArray(value.content)) { + return { changed: false, value }; + } + const hasWebSearchBlocks = responseValueContainsAnthropicWebSearchBlocks(value); + const blocks = hasWebSearchBlocks ? [] : anthropicWebSearchProtocolBlocks(records, requestId); + const answer = responseValueContainsVisibleText(value) + ? undefined + : synthesizeWebSearchAnswer(records, queryHint); + const injectedBlocks = [ + ...blocks, + ...(answer ? [{ text: answer, type: "text" }] : []) + ]; + const shouldUpdateUsage = hasWebSearchBlocks || blocks.length > 0; + if (injectedBlocks.length === 0 && !shouldUpdateUsage) { + return { changed: false, value }; + } + const insertAt = webSearchProtocolInsertIndex(value.content, hasWebSearchBlocks); + const nextValue = { + ...value, + ...(shouldUpdateUsage ? { usage: mergeAnthropicWebSearchUsage(value.usage, records.length) } : {}), + ...(answer && stringValue(value.stop_reason) === "max_tokens" ? { stop_reason: "end_turn" } : {}), + content: injectedBlocks.length > 0 + ? [ + ...value.content.slice(0, insertAt), + ...injectedBlocks, + ...value.content.slice(insertAt) + ] + : value.content + }; + return { + changed: true, + value: nextValue + }; +} + +export function transformAnthropicWebSearchProtocolSseText( + body: string, + records: BrowserWebSearchProtocolRecord[], + requestId: string, + queryHint?: string +): string { + const events = parseSseEvents(body); + if (events.length === 0) { + return body; + } + const hasWebSearchBlocks = sseEventsContainAnthropicWebSearchBlocks(events); + const blocks = hasWebSearchBlocks ? [] : anthropicWebSearchProtocolBlocks(records, requestId); + const answer = sseEventsContainVisibleText(events) + ? undefined + : synthesizeWebSearchAnswer(records, queryHint); + const injectedBlocks = [ + ...blocks, + ...(answer ? [{ text: answer, type: "text" }] : []) + ]; + const shouldUpdateUsage = hasWebSearchBlocks || blocks.length > 0; + if (injectedBlocks.length === 0 && !shouldUpdateUsage) { + return body; + } + + let maxIndex = -1; + for (const event of events) { + if (isRecord(event.data) && Number.isFinite(event.data.index)) { + maxIndex = Math.max(maxIndex, Number(event.data.index)); + } + } + + const firstTextIndex = events.findIndex((event) => { + const data = isRecord(event.data) ? event.data : undefined; + const contentBlock = isRecord(data?.content_block) ? data.content_block : undefined; + return data?.type === "content_block_start" && contentBlock?.type === "text"; + }); + const messageEndIndex = events.findIndex((event) => { + const type = isRecord(event.data) ? stringValue(event.data.type) : undefined; + return type === "message_delta" || type === "message_stop"; + }); + const insertPosition = firstTextIndex >= 0 + ? firstTextIndex + : messageEndIndex >= 0 + ? messageEndIndex + : events.length; + const insertIndex = firstTextIndex >= 0 && isRecord(events[firstTextIndex].data) && Number.isFinite(events[firstTextIndex].data.index) + ? Number(events[firstTextIndex].data.index) + : maxIndex + 1; + + const shiftedEvents = events + .map((event) => shiftSseContentBlockIndex(event, insertIndex, injectedBlocks.length)) + .map((event) => updateAnthropicWebSearchSseUsage(event, records.length, Boolean(answer))); + const injectedEvents = injectedBlocks.flatMap((block, offset) => anthropicWebSearchSseEventsForBlock(block, insertIndex + offset)); + shiftedEvents.splice(insertPosition, 0, ...injectedEvents); + return `${shiftedEvents.map(serializeSseEvent).join("\n\n")}\n\n`; +} + +export function transformOpenAiChatHostedWebSearchResponseValue( + value: unknown, + records: BrowserWebSearchProtocolRecord[], + queryHint?: string +): { changed: boolean; value: unknown } { + if (!isRecord(value) || openAiChatResponseContainsVisibleText(value)) { + return { changed: false, value }; + } + const answer = synthesizeWebSearchAnswer(records, queryHint); + if (!answer || !Array.isArray(value.choices) || value.choices.length === 0) { + return { changed: false, value }; + } + const choices = value.choices.map((choice, index) => { + if (!isRecord(choice) || index !== 0) { + return choice; + } + const message = isRecord(choice.message) ? choice.message : {}; + return { + ...choice, + finish_reason: stringValue(choice.finish_reason) === "length" ? "stop" : choice.finish_reason, + message: { + ...message, + content: answer, + role: stringValue(message.role) || "assistant" + } + }; + }); + return { + changed: true, + value: { + ...value, + choices + } + }; +} + +export function transformOpenAiChatHostedWebSearchSseText( + body: string, + records: BrowserWebSearchProtocolRecord[], + queryHint?: string +): string { + const events = parseSseEvents(body); + if (events.length === 0 || openAiChatSseContainsVisibleText(events)) { + return body; + } + const answer = synthesizeWebSearchAnswer(records, queryHint); + if (!answer) { + return body; + } + const template = firstSseDataRecord(events); + const injected = sseEventFromValue({ + ...(template?.id ? { id: template.id } : {}), + ...(template?.model ? { model: template.model } : {}), + object: stringValue(template?.object) || "chat.completion.chunk", + choices: [ + { + delta: { content: answer }, + finish_reason: null, + index: 0 + } + ] + }); + const shifted = events.map((event) => updateOpenAiChatSseFinishReason(event)); + const insertAt = doneSseEventIndex(shifted); + shifted.splice(insertAt >= 0 ? insertAt : shifted.length, 0, injected); + return `${shifted.map(serializeSseEvent).join("\n\n")}\n\n`; +} + +export function transformOpenAiResponsesHostedWebSearchResponseValue( + value: unknown, + records: BrowserWebSearchProtocolRecord[], + requestId: string, + queryHint?: string +): { changed: boolean; value: unknown } { + if (!isRecord(value)) { + return { changed: false, value }; + } + const output = Array.isArray(value.output) ? value.output : []; + const hasSearchCall = output.some((item) => isRecord(item) && stringValue(item.type) === "web_search_call"); + const answer = openAiResponsesValueContainsVisibleText(value) ? undefined : synthesizeWebSearchAnswer(records, queryHint); + const injected = [ + ...(hasSearchCall ? [] : openAiResponsesWebSearchCallItems(records, requestId)), + ...(answer ? [openAiResponsesMessageItem(answer, requestId)] : []) + ]; + if (injected.length === 0) { + return { changed: false, value }; + } + const next: Record = { + ...value, + output: [...injected, ...output] + }; + if (answer && stringValue(next.status) === "incomplete") { + next.status = "completed"; + delete next.incomplete_details; + } + return { changed: true, value: next }; +} + +export function transformOpenAiResponsesHostedWebSearchSseText( + body: string, + records: BrowserWebSearchProtocolRecord[], + requestId: string, + queryHint?: string +): string { + const events = parseSseEvents(body); + if (events.length === 0) { + return body; + } + const visibleText = openAiResponsesSseVisibleText(events); + if (visibleText) { + return serializeOpenAiResponsesSseEvents(normalizeOpenAiResponsesSseEvents(events, visibleText)); + } + const answer = synthesizeWebSearchAnswer(records, queryHint); + if (!answer) { + return serializeOpenAiResponsesSseEvents(normalizeOpenAiResponsesSseEvents(events)); + } + const outputIndex = nextOpenAiResponsesOutputIndex(events); + const itemId = `msg_${sanitizeAnthropicToolUseId(requestId)}_web_search_answer`; + const contentIndex = 0; + const injected = [ + sseEventFromValue({ + item: { + id: itemId, + role: "assistant", + status: "in_progress", + type: "message" + }, + output_index: outputIndex, + type: "response.output_item.added" + }), + sseEventFromValue({ + content_index: contentIndex, + item_id: itemId, + output_index: outputIndex, + part: { annotations: [], text: "", type: "output_text" }, + type: "response.content_part.added" + }), + sseEventFromValue({ + content_index: contentIndex, + delta: answer, + item_id: itemId, + output_index: outputIndex, + type: "response.output_text.delta" + }), + sseEventFromValue({ + content_index: contentIndex, + item_id: itemId, + output_index: outputIndex, + text: answer, + type: "response.output_text.done" + }), + sseEventFromValue({ + content_index: contentIndex, + item_id: itemId, + output_index: outputIndex, + part: { annotations: [], text: answer, type: "output_text" }, + type: "response.content_part.done" + }), + sseEventFromValue({ + item: { + content: [{ annotations: [], text: answer, type: "output_text" }], + id: itemId, + role: "assistant", + status: "completed", + type: "message" + }, + output_index: outputIndex, + type: "response.output_item.done" + }) + ]; + const shifted = events.map(updateOpenAiResponsesCompletedStatus); + const insertAt = shifted.findIndex((event) => isRecord(event.data) && stringValue(event.data.type) === "response.completed"); + shifted.splice(insertAt >= 0 ? insertAt : doneSseEventIndex(shifted) >= 0 ? doneSseEventIndex(shifted) : shifted.length, 0, ...injected); + return serializeOpenAiResponsesSseEvents(normalizeOpenAiResponsesSseEvents(shifted, answer)); +} + +export function transformGeminiHostedWebSearchResponseValue( + value: unknown, + records: BrowserWebSearchProtocolRecord[], + queryHint?: string +): { changed: boolean; value: unknown } { + if (Array.isArray(value)) { + if (geminiResponseArrayContainsVisibleText(value)) { + return { changed: false, value }; + } + const answer = synthesizeWebSearchAnswer(records, queryHint); + return answer + ? { changed: true, value: [...value, geminiAnswerCandidateChunk(answer)] } + : { changed: false, value }; + } + if (!isRecord(value) || geminiResponseValueContainsVisibleText(value)) { + return { changed: false, value }; + } + const answer = synthesizeWebSearchAnswer(records, queryHint); + if (!answer) { + return { changed: false, value }; + } + const candidates = Array.isArray(value.candidates) ? value.candidates : []; + const nextCandidate = candidates.length > 0 && isRecord(candidates[0]) + ? { + ...candidates[0], + content: { + ...(isRecord(candidates[0].content) ? candidates[0].content : {}), + parts: [{ text: answer }], + role: "model" + }, + finishReason: stringValue(candidates[0].finishReason) === "MAX_TOKENS" ? "STOP" : candidates[0].finishReason + } + : geminiAnswerCandidate(answer); + return { + changed: true, + value: { + ...value, + candidates: [nextCandidate, ...candidates.slice(1)] + } + }; +} + +export function transformGeminiHostedWebSearchSseText( + body: string, + records: BrowserWebSearchProtocolRecord[], + queryHint?: string +): string { + const events = parseSseEvents(body); + if (events.length === 0 || geminiSseContainsVisibleText(events)) { + return body; + } + const answer = synthesizeWebSearchAnswer(records, queryHint); + if (!answer) { + return body; + } + const injected = sseEventFromValue(geminiAnswerCandidateChunk(answer)); + const insertAt = doneSseEventIndex(events); + events.splice(insertAt >= 0 ? insertAt : events.length, 0, injected); + return `${events.map(serializeSseEvent).join("\n\n")}\n\n`; +} + +function openAiChatResponseContainsVisibleText(value: Record): boolean { + if (!Array.isArray(value.choices)) { + return false; + } + return value.choices.some((choice) => { + const message = isRecord(choice) && isRecord(choice.message) ? choice.message : undefined; + return Boolean(stringValue(message?.content)?.trim()); + }); +} + +function openAiChatSseContainsVisibleText(events: ParsedSseEvent[]): boolean { + return events.some((event) => { + const choices = isRecord(event.data) && Array.isArray(event.data.choices) ? event.data.choices : []; + return choices.some((choice) => { + const delta = isRecord(choice) && isRecord(choice.delta) ? choice.delta : undefined; + return Boolean(stringValue(delta?.content)?.trim()); + }); + }); +} + +function updateOpenAiChatSseFinishReason(event: ParsedSseEvent): ParsedSseEvent { + if (!isRecord(event.data) || !Array.isArray(event.data.choices)) { + return event; + } + let changed = false; + const choices = event.data.choices.map((choice) => { + if (!isRecord(choice) || stringValue(choice.finish_reason) !== "length") { + return choice; + } + changed = true; + return { ...choice, finish_reason: "stop" }; + }); + return changed ? { ...event, data: { ...event.data, choices } } : event; +} + +function openAiResponsesValueContainsVisibleText(value: Record): boolean { + return Array.isArray(value.output) && value.output.some(openAiResponsesItemContainsVisibleText); +} + +function openAiResponsesItemContainsVisibleText(item: unknown): boolean { + if (!isRecord(item)) { + return false; + } + if (stringValue(item.type) === "message" && Array.isArray(item.content)) { + return item.content.some((part) => isRecord(part) && Boolean(stringValue(part.text)?.trim())); + } + return Boolean(stringValue(item.text)?.trim()); +} + +function openAiResponsesSseContainsVisibleText(events: ParsedSseEvent[]): boolean { + return Boolean(openAiResponsesSseVisibleText(events)); +} + +function openAiResponsesSseVisibleText(events: ParsedSseEvent[]): string | undefined { + let deltaText = ""; + let doneText = ""; + let itemText = ""; + for (const event of events) { + const data = isRecord(event.data) ? event.data : undefined; + if (!data) { + continue; + } + const type = stringValue(data.type); + if (type === "response.output_text.delta") { + deltaText += stringValue(data.delta) ?? ""; + continue; + } + if (type === "response.output_text.done") { + doneText = stringValue(data.text) ?? doneText; + continue; + } + const item = isRecord(data.item) ? data.item : undefined; + if (item && openAiResponsesItemContainsVisibleText(item)) { + itemText = openAiResponsesItemText(item) ?? itemText; + } + } + return nonEmptyText(deltaText) ?? nonEmptyText(doneText) ?? nonEmptyText(itemText); +} + +function openAiResponsesItemText(item: unknown): string | undefined { + if (!isRecord(item)) { + return undefined; + } + if (stringValue(item.type) === "message" && Array.isArray(item.content)) { + const text = item.content + .flatMap((part) => isRecord(part) ? [stringValue(part.text) ?? ""] : []) + .join(""); + return text.trim() ? text : undefined; + } + return stringValue(item.text); +} + +function nonEmptyText(value: string | undefined): string | undefined { + return value && value.trim() ? value : undefined; +} + +function openAiResponsesWebSearchCallItems(records: BrowserWebSearchProtocolRecord[], requestId: string): Record[] { + return records.map((record, index) => ({ + action: { + query: record.query, + type: "search" + }, + id: `ws_${sanitizeAnthropicToolUseId(requestId)}_${index + 1}`, + status: "completed", + type: "web_search_call" + })); +} + +function openAiResponsesMessageItem(answer: string, requestId: string): Record { + return { + content: [{ annotations: [], text: answer, type: "output_text" }], + id: `msg_${sanitizeAnthropicToolUseId(requestId)}_web_search_answer`, + role: "assistant", + status: "completed", + type: "message" + }; +} + +function nextOpenAiResponsesOutputIndex(events: ParsedSseEvent[]): number { + const indexes = events.flatMap((event) => { + const data = isRecord(event.data) ? event.data : undefined; + const index = numberValue(data?.output_index); + return index === undefined ? [] : [index]; + }); + return indexes.length === 0 ? 0 : Math.max(...indexes) + 1; +} + +function updateOpenAiResponsesCompletedStatus(event: ParsedSseEvent): ParsedSseEvent { + if (!isRecord(event.data) || stringValue(event.data.type) !== "response.completed") { + return event; + } + const response = isRecord(event.data.response) ? event.data.response : undefined; + if (!response || stringValue(response.status) !== "incomplete") { + return event; + } + const nextResponse: Record = { ...response, status: "completed" }; + delete nextResponse.incomplete_details; + return { + ...event, + data: { + ...event.data, + response: nextResponse + } + }; +} + +function normalizeOpenAiResponsesSseEvents(events: ParsedSseEvent[], visibleText?: string): ParsedSseEvent[] { + const outputIndexMap = openAiResponsesOutputIndexMap(events); + return events.flatMap((event) => { + if (isOpenAiResponsesReasoningSseEvent(event)) { + return []; + } + return [updateOpenAiResponsesCompletedOutput( + remapOpenAiResponsesOutputIndex(event, outputIndexMap), + visibleText + )]; + }); +} + +function serializeOpenAiResponsesSseEvents(events: ParsedSseEvent[]): string { + return `${events.map(serializeSseEvent).join("\n\n")}\n\n`; +} + +function openAiResponsesOutputIndexMap(events: ParsedSseEvent[]): Map { + const indexes: number[] = []; + for (const event of events) { + if (isOpenAiResponsesReasoningSseEvent(event)) { + continue; + } + const data = isRecord(event.data) ? event.data : undefined; + const index = numberValue(data?.output_index); + if (index !== undefined && !indexes.includes(index)) { + indexes.push(index); + } + } + return new Map(indexes.sort((left, right) => left - right).map((index, nextIndex) => [index, nextIndex])); +} + +function remapOpenAiResponsesOutputIndex(event: ParsedSseEvent, outputIndexMap: Map): ParsedSseEvent { + if (!isRecord(event.data)) { + return event; + } + const index = numberValue(event.data.output_index); + if (index === undefined || !outputIndexMap.has(index)) { + return event; + } + return { + ...event, + data: { + ...event.data, + output_index: outputIndexMap.get(index) + } + }; +} + +function updateOpenAiResponsesCompletedOutput(event: ParsedSseEvent, visibleText?: string): ParsedSseEvent { + if (!isRecord(event.data) || stringValue(event.data.type) !== "response.completed") { + return event; + } + const response = isRecord(event.data.response) ? event.data.response : undefined; + if (!response) { + return event; + } + const nextResponse: Record = { ...response }; + if (Array.isArray(response.output)) { + nextResponse.output = response.output.filter((item) => !(isRecord(item) && stringValue(item.type) === "reasoning")); + } + if (visibleText) { + nextResponse.output_text = visibleText; + } + if (visibleText && stringValue(nextResponse.status) === "incomplete") { + nextResponse.status = "completed"; + delete nextResponse.incomplete_details; + } + return { + ...event, + data: { + ...event.data, + response: nextResponse + } + }; +} + +function isOpenAiResponsesReasoningSseEvent(event: ParsedSseEvent): boolean { + const data = isRecord(event.data) ? event.data : undefined; + if (!data) { + return false; + } + const type = stringValue(data.type); + if (type?.startsWith("response.reasoning")) { + return true; + } + const item = isRecord(data.item) ? data.item : undefined; + if (stringValue(item?.type) === "reasoning") { + return true; + } + const part = isRecord(data.part) ? data.part : undefined; + return stringValue(part?.type) === "reasoning_text"; +} + +function geminiResponseValueContainsVisibleText(value: Record): boolean { + return Array.isArray(value.candidates) && value.candidates.some(geminiCandidateContainsVisibleText); +} + +function geminiResponseArrayContainsVisibleText(values: unknown[]): boolean { + return values.some((value) => isRecord(value) && geminiResponseValueContainsVisibleText(value)); +} + +function geminiCandidateContainsVisibleText(candidate: unknown): boolean { + const content = isRecord(candidate) && isRecord(candidate.content) ? candidate.content : undefined; + const parts = Array.isArray(content?.parts) ? content.parts : []; + return parts.some((part) => isRecord(part) && Boolean(stringValue(part.text)?.trim())); +} + +function geminiSseContainsVisibleText(events: ParsedSseEvent[]): boolean { + return events.some((event) => isRecord(event.data) && geminiResponseValueContainsVisibleText(event.data)); +} + +function geminiAnswerCandidate(answer: string): Record { + return { + content: { + parts: [{ text: answer }], + role: "model" + }, + finishReason: "STOP", + index: 0 + }; +} + +function geminiAnswerCandidateChunk(answer: string): Record { + return { + candidates: [geminiAnswerCandidate(answer)] + }; +} + +function firstSseDataRecord(events: ParsedSseEvent[]): Record | undefined { + return events.map((event) => isRecord(event.data) ? event.data : undefined).find(Boolean); +} + +function doneSseEventIndex(events: ParsedSseEvent[]): number { + return events.findIndex((event) => event.raw?.includes("[DONE]")); +} + +function hasAnthropicHostedWebSearchTool(tools: unknown): boolean { + if (!Array.isArray(tools)) { + return false; + } + return tools.some(isAnthropicHostedWebSearchTool); +} + +function hasHostedWebSearchDeclaration(body: Record, protocol: GatewayProviderProtocol): boolean { + if (protocol === "anthropic_messages") { + return hasAnthropicHostedWebSearchTool(body.tools); + } + if (protocol === "openai_chat_completions" || protocol === "openai_responses") { + return hasOpenAiHostedWebSearchDeclaration(body); + } + if (protocol === "gemini_generate_content") { + return hasGeminiHostedWebSearchTool(body.tools); + } + return false; +} + +function hasOpenAiHostedWebSearchDeclaration(body: Record): boolean { + if (body.web_search_options !== undefined || body.webSearchOptions !== undefined) { + return true; + } + return Array.isArray(body.tools) && body.tools.some(isOpenAiHostedWebSearchTool); +} + +function hasGeminiHostedWebSearchTool(tools: unknown): boolean { + if (!Array.isArray(tools)) { + return false; + } + return tools.some((tool) => { + if (!isRecord(tool)) { + return false; + } + if (tool.google_search !== undefined || tool.googleSearch !== undefined || tool.google_search_retrieval !== undefined || tool.googleSearchRetrieval !== undefined) { + return true; + } + return false; + }); +} + +function isAnthropicHostedWebSearchTool(tool: unknown): boolean { + if (!isRecord(tool)) { + return false; + } + return anthropicHostedWebSearchType(stringValue(tool.type)); +} + +function isOpenAiHostedWebSearchTool(tool: unknown): boolean { + if (!isRecord(tool)) { + return false; + } + return openAiHostedWebSearchType(stringValue(tool.type)); +} + +function openAiToolChoiceNamesWebSearch(value: unknown): boolean { + if (typeof value === "string") { + return openAiHostedWebSearchType(value); + } + if (!isRecord(value)) { + return false; + } + return openAiHostedWebSearchType(stringValue(value.type)); +} + +function anthropicHostedWebSearchType(value: string | undefined): boolean { + const normalized = normalizedToolProtocolName(value); + return normalized === "web_search" || normalized === "web_search_20250305"; +} + +function openAiHostedWebSearchType(value: string | undefined): boolean { + const normalized = normalizedToolProtocolName(value); + return normalized === "web_search" || + normalized === "web_search_preview" || + normalized.startsWith("web_search_preview_"); +} + +function normalizedToolProtocolName(value: string | undefined): string { + return value?.trim().toLowerCase().replace(/[-.]/g, "_") ?? ""; +} + +function readAnthropicWebSearchMaxUses(tools: unknown): number | undefined { + if (!Array.isArray(tools)) { + return undefined; + } + const tool = tools.find((item) => isRecord(item) && stringValue(item.type)?.toLowerCase() === "web_search_20250305"); + return isRecord(tool) ? numberValue(tool.max_uses ?? tool.maxUses) : undefined; +} + +function readHostedWebSearchMaxUses(body: Record, protocol: GatewayProviderProtocol): number | undefined { + if (protocol === "anthropic_messages") { + return readAnthropicWebSearchMaxUses(body.tools); + } + if (protocol === "openai_chat_completions" || protocol === "openai_responses") { + const tool = Array.isArray(body.tools) ? body.tools.find(isOpenAiHostedWebSearchTool) : undefined; + return isRecord(tool) ? numberValue(tool.max_uses ?? tool.maxUses) : undefined; + } + return undefined; +} + +export function extractHostedWebSearchQueryHint(body: Record, protocol: GatewayProviderProtocol): string | undefined { + if (protocol === "anthropic_messages") { + return extractAnthropicWebSearchQueryHint(body); + } + if (protocol === "openai_chat_completions") { + return normalizedWebSearchQueryHintFromParts(textPartsFromOpenAiChatMessages(body.messages)); + } + if (protocol === "openai_responses") { + return normalizedWebSearchQueryHintFromParts(textPartsFromOpenAiResponsesInput(body.input)); + } + if (protocol === "gemini_generate_content") { + return normalizedWebSearchQueryHintFromParts(textPartsFromGeminiContents(body.contents)); + } + return undefined; +} + +function extractAnthropicWebSearchQueryHint(body: Record): string | undefined { + const userTexts = Array.isArray(body.messages) + ? body.messages.flatMap((message) => { + if (!isRecord(message) || stringValue(message.role) !== "user") { + return []; + } + return textPartsFromAnthropicContent(message.content); + }) + : []; + return normalizedWebSearchQueryHintFromParts(userTexts); +} + +function normalizedWebSearchQueryHintFromParts(parts: string[]): string | undefined { + const candidates = parts + .map((part) => part.trim()) + .filter(Boolean); + for (const candidate of [...candidates].reverse()) { + const explicit = extractExplicitWebSearchQuery(candidate); + if (explicit) { + return normalizedWebSearchQueryHint(explicit); + } + } + for (const candidate of [...candidates].reverse()) { + if (isRuntimeContextText(candidate)) { + continue; + } + return normalizedWebSearchQueryHint(stripSearchIntentPrefix(candidate)); + } + return normalizedWebSearchQueryHint(candidates.join("\n")); +} + +function normalizedWebSearchQueryHint(value: string | undefined): string | undefined { + if (!value) { + return undefined; + } + const joined = value.trim(); + if (!joined) { + return undefined; + } + return joined.trim().slice(0, 500); +} + +function extractExplicitWebSearchQuery(value: string): string | undefined { + const explicit = /perform\s+a\s+web\s+search\s+for\s+the\s+query:\s*([\s\S]+)$/i.exec(value.trim()); + return normalizedWebSearchQueryHint(explicit?.[1]); +} + +function stripSearchIntentPrefix(value: string): string { + const trimmed = value.trim(); + const match = /^(?:请)?(?:帮我)?(?:搜索|查询|查一下|帮我查一下|搜一下)\s*[::]?\s*([\s\S]+)$/i.exec(trimmed); + return (match?.[1] || trimmed).trim(); +} + +function isRuntimeContextText(value: string): boolean { + const trimmed = value.trim(); + if (!trimmed) { + return false; + } + if (/^<(?:environment_context|permissions instructions|collaboration_mode|skills_instructions|plugins_instructions|apps_instructions)>/i.test(trimmed)) { + return true; + } + return ( + trimmed.includes("") || + trimmed.includes("") || + trimmed.includes("") || + trimmed.includes("") + ); +} + +function textPartsFromAnthropicContent(content: unknown): string[] { + if (typeof content === "string") { + return [content]; + } + if (!Array.isArray(content)) { + return []; + } + return content.flatMap((part) => isRecord(part) && typeof part.text === "string" ? [part.text] : []); +} + +function textPartsFromOpenAiChatMessages(messages: unknown): string[] { + if (!Array.isArray(messages)) { + return []; + } + return messages.flatMap((message) => { + if (!isRecord(message) || stringValue(message.role)?.toLowerCase() !== "user") { + return []; + } + return textPartsFromOpenAiContent(message.content); + }); +} + +function textPartsFromOpenAiContent(content: unknown): string[] { + if (typeof content === "string") { + return [content]; + } + if (!Array.isArray(content)) { + return []; + } + return content.flatMap((part) => { + if (!isRecord(part)) { + return []; + } + const type = stringValue(part.type); + if (type === "text" || type === "input_text" || type === "output_text") { + return stringValue(part.text) ? [stringValue(part.text) as string] : []; + } + return []; + }); +} + +function textPartsFromOpenAiResponsesInput(input: unknown): string[] { + if (typeof input === "string") { + return [input]; + } + if (!Array.isArray(input)) { + return []; + } + return input.flatMap((item) => { + if (!isRecord(item)) { + return []; + } + const role = stringValue(item.role)?.toLowerCase(); + if (role && role !== "user") { + return []; + } + return textPartsFromOpenAiContent(item.content); + }); +} + +function textPartsFromGeminiContents(contents: unknown): string[] { + if (!Array.isArray(contents)) { + return []; + } + return contents.flatMap((content) => { + if (!isRecord(content)) { + return []; + } + const role = stringValue(content.role)?.toLowerCase(); + if (role && role !== "user") { + return []; + } + const parts = Array.isArray(content.parts) ? content.parts : []; + return parts.flatMap((part) => isRecord(part) && typeof part.text === "string" ? [part.text] : []); + }); +} + +export function fusionWebSearchToolNameForRequest(config: AppConfig, model: string | undefined): string | undefined { + const normalizedModel = model ? fusionModelNameFromSelector(model) : ""; + const rawProfiles = Array.isArray(config.virtualModelProfiles) ? config.virtualModelProfiles : []; + const profiles = normalizeCoreGatewayVirtualModelProfiles( + withCodexCompatibleVirtualModelProfiles(withFusionVirtualModelAliases(rawProfiles)), + config + ); + for (const profile of profiles) { + if (!isRecord(profile) || profile.enabled === false) { + continue; + } + const metadata = isRecord(profile.metadata) ? profile.metadata : undefined; + const fusionWebSearch = isRecord(metadata?.fusionWebSearch) ? metadata.fusionWebSearch : undefined; + const webSearchConfig = readFusionWebSearchConfig(fusionWebSearch); + if (!webSearchConfig?.toolName || webSearchConfig.provider !== "browser") { + continue; + } + const match = isRecord(profile.match) ? profile.match : undefined; + const aliases = uniqueStrings([ + stringValue(profile.id), + stringValue(profile.key), + stringValue(profile.displayName), + ...stringListValue(match?.exactAliases) + ].filter((item): item is string => Boolean(item))); + if (!normalizedModel || aliases.some((alias) => fusionModelNameFromSelector(alias).toLowerCase() === normalizedModel.toLowerCase())) { + return webSearchConfig.toolName; + } + } + return undefined; +} + +async function selectHostedWebSearchProtocolRecords( + context: HostedWebSearchProtocolContext, + integration: BrowserWebSearchMcpIntegration +): Promise { + const records = [ + ...(context.records ?? []), + ...(integration.recentBrowserWebSearchResults?.({ sinceMs: context.sinceMs, toolName: context.toolName }) ?? []) + ] + .filter((record) => record.results.length > 0) + .filter(uniqueSearchRecordFilter()) + .sort((left, right) => { + const queryScoreDelta = queryMatchScore(context.queryHint, right.query) - queryMatchScore(context.queryHint, left.query); + return queryScoreDelta || left.completedAtMs - right.completedAtMs; + }); + if (records.length > 0) { + return records.slice(0, 8); + } + if (!context.queryHint || !integration.runBrowserWebSearch) { + return []; + } + const record = await integration.runBrowserWebSearch({ + count: Math.trunc(clampNumber(context.maxUses ?? 5, 1, 10)), + prompt: context.queryHint, + timeoutMs: 30_000, + toolName: context.toolName + }); + return record?.results.length ? [record] : []; +} + +async function selectAnthropicWebSearchProtocolRecords( + context: AnthropicWebSearchProtocolContext, + integration: BrowserWebSearchMcpIntegration +): Promise { + return selectHostedWebSearchProtocolRecords(context, integration); +} + +function uniqueSearchRecordFilter(): (record: BrowserWebSearchProtocolRecord) => boolean { + const seen = new Set(); + return (record) => { + const key = `${record.toolName}\n${record.query}\n${record.searchUrl}`; + if (seen.has(key)) { + return false; + } + seen.add(key); + return true; + }; +} + +function queryMatchScore(queryHint: string | undefined, query: string): number { + if (!queryHint) { + return 0; + } + const left = normalizeSearchComparisonText(queryHint); + const right = normalizeSearchComparisonText(query); + if (!left || !right) { + return 0; + } + if (left === right) { + return 4; + } + if (left.includes(right) || right.includes(left)) { + return 3; + } + const leftTerms = new Set(left.split(" ").filter((item) => item.length > 2)); + const rightTerms = right.split(" ").filter((item) => item.length > 2); + return rightTerms.reduce((score, term) => score + (leftTerms.has(term) ? 1 : 0), 0); +} + +function normalizeSearchComparisonText(value: string): string { + return value.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").replace(/\s+/g, " ").trim(); +} + +function responseValueContainsAnthropicWebSearchBlocks(value: Record): boolean { + return Array.isArray(value.content) && value.content.some((block) => { + const type = isRecord(block) ? stringValue(block.type) : undefined; + return type === "server_tool_use" || type === "web_search_tool_result"; + }); +} + +function responseValueContainsVisibleText(value: Record): boolean { + return Array.isArray(value.content) && value.content.some((block) => { + if (!isRecord(block) || stringValue(block.type) !== "text") { + return false; + } + return Boolean(stringValue(block.text)?.trim()); + }); +} + +function leadingThinkingBlockCount(content: unknown[]): number { + let index = 0; + while (index < content.length) { + const block = content[index]; + if (!isRecord(block) || stringValue(block.type) !== "thinking") { + break; + } + index += 1; + } + return index; +} + +function webSearchProtocolInsertIndex(content: unknown[], hasWebSearchBlocks: boolean): number { + let index = leadingThinkingBlockCount(content); + if (!hasWebSearchBlocks) { + return index; + } + while (index < content.length) { + const block = content[index]; + const type = isRecord(block) ? stringValue(block.type) : undefined; + if (type !== "server_tool_use" && type !== "web_search_tool_result") { + break; + } + index += 1; + } + return index; +} + +function mergeAnthropicWebSearchUsage(usage: unknown, searchCount: number): Record { + const nextUsage = isRecord(usage) ? { ...usage } : {}; + const serverToolUse = isRecord(nextUsage.server_tool_use) ? { ...nextUsage.server_tool_use } : {}; + const webSearchRequests = Math.max(1, Math.trunc(searchCount)); + serverToolUse.web_search_requests = Math.max(numberValue(serverToolUse.web_search_requests) ?? 0, webSearchRequests); + nextUsage.server_tool_use = serverToolUse; + return nextUsage; +} + +function sseEventsContainAnthropicWebSearchBlocks(events: ParsedSseEvent[]): boolean { + return events.some((event) => { + const data = isRecord(event.data) ? event.data : undefined; + const block = isRecord(data?.content_block) ? data.content_block : undefined; + const type = stringValue(block?.type) || stringValue(data?.type); + return type === "server_tool_use" || type === "web_search_tool_result"; + }); +} + +function sseEventsContainVisibleText(events: ParsedSseEvent[]): boolean { + return events.some((event) => { + const data = isRecord(event.data) ? event.data : undefined; + if (!data) { + return false; + } + const block = isRecord(data.content_block) ? data.content_block : undefined; + if (stringValue(data.type) === "content_block_start" && stringValue(block?.type) === "text") { + return Boolean(stringValue(block?.text)?.trim()); + } + const delta = isRecord(data.delta) ? data.delta : undefined; + return stringValue(data.type) === "content_block_delta" && + stringValue(delta?.type) === "text_delta" && + Boolean(stringValue(delta?.text)?.trim()); + }); +} + +function anthropicWebSearchSseEventsForBlock(block: Record, index: number): ParsedSseEvent[] { + if (stringValue(block.type) === "text") { + const text = stringValue(block.text) ?? ""; + return [ + sseEventFromValue({ + content_block: { text: "", type: "text" }, + index, + type: "content_block_start" + }), + sseEventFromValue({ + delta: { text, type: "text_delta" }, + index, + type: "content_block_delta" + }), + sseEventFromValue({ + index, + type: "content_block_stop" + }) + ]; + } + return [ + sseEventFromValue({ + content_block: block, + index, + type: "content_block_start" + }), + sseEventFromValue({ + index, + type: "content_block_stop" + }) + ]; +} + +function updateAnthropicWebSearchSseUsage(event: ParsedSseEvent, searchCount: number, didSynthesizeAnswer: boolean): ParsedSseEvent { + if (!isRecord(event.data) || stringValue(event.data.type) !== "message_delta") { + return event; + } + const delta = isRecord(event.data.delta) ? { ...event.data.delta } : event.data.delta; + const nextData: Record = { + ...event.data, + usage: mergeAnthropicWebSearchUsage(event.data.usage, searchCount) + }; + if (didSynthesizeAnswer && isRecord(delta) && stringValue(delta.stop_reason) === "max_tokens") { + nextData.delta = { ...delta, stop_reason: "end_turn" }; + } + return { + ...event, + data: nextData + }; +} + +function synthesizeWebSearchAnswer(records: BrowserWebSearchProtocolRecord[], queryHint: string | undefined): string | undefined { + const query = queryHint || records.map((record) => record.query).find(Boolean) || ""; + const weatherAnswer = synthesizeWeatherWebSearchAnswer(records, query); + if (weatherAnswer) { + return weatherAnswer; + } + const evidence = topWebSearchEvidenceSentences(records, query, 3); + if (evidence.length === 0) { + const sources = webSearchSourceNames(records, 3); + if (!sources) { + return undefined; + } + return containsCjkText(query) + ? `搜索已完成,但页面可提取正文不足。较相关的来源包括:${sources}。` + : `The search completed, but the pages did not expose enough extractable text. The most relevant sources are: ${sources}.`; + } + const sources = webSearchSourceNames(records, 3); + return containsCjkText(query) + ? `根据搜索结果,${evidence.join(";")}。${sources ? `来源:${sources}。` : ""}` + : `Based on the search results, ${evidence.join("; ")}.${sources ? ` Sources: ${sources}.` : ""}`; +} + +function synthesizeWeatherWebSearchAnswer(records: BrowserWebSearchProtocolRecord[], query: string): string | undefined { + if (!/天气|气温|温度|weather|forecast|temperature/i.test(query)) { + return undefined; + } + const items = webSearchEvidenceItems(records); + const text = items.map((item) => item.text).join(" "); + if (!text) { + return undefined; + } + const cjk = containsCjkText(query); + const location = extractWeatherLocation(query); + const temperatureRange = weatherTemperatureRange(text); + const currentTemperature = firstRegexGroup(text, [ + /(?:当前|现在|实时|实况|气温|温度)[^。;,,\d-]{0,12}(-?\d{1,2}(?:\.\d+)?)\s*℃/i, + location ? new RegExp(`${escapeRegExp(location)}\\s+(-?\\d{1,2}(?:\\.\\d+)?)\\s*℃`) : undefined + ]); + const feelsLike = firstRegexGroup(text, [/体感温度[::\s]*(-?\d{1,2}(?:\.\d+)?)\s*℃/]); + const high = firstRegexGroup(text, [/最高气温[::\s]*(-?\d{1,2}(?:\.\d+)?)\s*℃/]); + const low = firstRegexGroup(text, [/最低气温[::\s]*(-?\d{1,2}(?:\.\d+)?)\s*℃/]); + const humidity = firstRegexGroup(text, [/(?:最大相对湿度|相对湿度)[::\s]*(-?\d{1,3}(?:\.\d+)?%)/]); + const aqi = firstRegexGroup(text, [/AQI最高值[::\s]*(\d{1,3})/i]); + const airQuality = firstRegexGroup(text, [/空气质量[::\s]*([^\s,。;,;]{1,12})/]); + const rain = firstRegexGroup(text, [/(?:过去24小时总降水量|总降水量|降水量)[::\s]*(-?\d+(?:\.\d+)?mm)/i]); + const wind = firstRegexGroup(text, [/最大风力[::\s]*([<>]?\d+级|微风)/, /(东风|东南风|南风|西南风|西风|西北风|北风|东北风)\s*([<>]?\d+级|微风)/]); + + const facts = [ + currentTemperature ? (cjk ? `当前约 ${currentTemperature}℃` : `currently about ${currentTemperature}°C`) : undefined, + !currentTemperature && temperatureRange ? (cjk ? `气温约 ${temperatureRange}` : `temperatures are around ${temperatureRange}`) : undefined, + feelsLike ? (cjk ? `体感约 ${feelsLike}℃` : `feels like about ${feelsLike}°C`) : undefined, + high || low ? (cjk + ? `过去24小时${high ? `最高 ${high}℃` : ""}${high && low ? "、" : ""}${low ? `最低 ${low}℃` : ""}` + : `over the past 24 hours ${high ? `the high was ${high}°C` : ""}${high && low ? " and " : ""}${low ? `the low was ${low}°C` : ""}`) : undefined, + humidity ? (cjk ? `相对湿度最高 ${humidity}` : `relative humidity reached ${humidity}`) : undefined, + aqi ? (cjk ? `AQI 最高 ${aqi}` : `AQI reached ${aqi}`) : undefined, + airQuality && !aqi ? (cjk ? `空气质量 ${airQuality}` : `air quality is ${airQuality}`) : undefined, + rain ? (cjk ? `过去24小时降水量 ${rain}` : `24-hour rainfall is ${rain}`) : undefined, + wind ? (cjk ? `风力 ${wind}` : `wind ${wind}`) : undefined + ].filter((item): item is string => Boolean(item)); + + if (facts.length === 0) { + return undefined; + } + const sources = webSearchSourceNames(records, 2); + if (cjk) { + return `${location ? `${location}天气` : "天气"}:${facts.slice(0, 6).join(",")}。${sources ? `来源:${sources}。` : ""}`; + } + return `${location ? `${location} weather` : "Weather"}: ${facts.slice(0, 6).join(", ")}.${sources ? ` Sources: ${sources}.` : ""}`; +} + +function webSearchEvidenceItems(records: BrowserWebSearchProtocolRecord[]): Array<{ source: string; text: string; url: string }> { + return records.flatMap((record) => record.results.map((result) => ({ + source: result.title || hostnameFromUrl(result.url) || record.engine, + text: sanitizeWebSearchEvidenceText(result.content || result.snippet || ""), + url: result.url + }))).filter((item) => item.text); +} + +function topWebSearchEvidenceSentences(records: BrowserWebSearchProtocolRecord[], query: string, limit: number): string[] { + const terms = relevantSearchTerms(query); + const scored = webSearchEvidenceItems(records).flatMap((item, itemIndex) => { + const sentences = splitEvidenceSentences(item.text).slice(0, 12); + return sentences.map((sentence, sentenceIndex) => { + const normalizedSentence = normalizeSearchComparisonText(sentence); + const termScore = terms.reduce((score, term) => score + (normalizedSentence.includes(term) ? 2 : 0), 0); + const sourceBonus = itemIndex === 0 ? 2 : itemIndex === 1 ? 1 : 0; + const positionBonus = Math.max(0, 4 - sentenceIndex) / 4; + return { + score: termScore + sourceBonus + positionBonus, + sentence + }; + }); + }).filter((item) => item.sentence.length >= 12 && item.sentence.length <= 260); + scored.sort((left, right) => right.score - left.score || left.sentence.length - right.sentence.length); + const seen = new Set(); + return scored.flatMap((item) => { + const key = normalizeSearchComparisonText(item.sentence).slice(0, 120); + if (!key || seen.has(key)) { + return []; + } + seen.add(key); + return [item.sentence]; + }).slice(0, limit); +} + +function splitEvidenceSentences(text: string): string[] { + return text + .replace(/\s+/g, " ") + .split(/[。!?!?]\s*|\n+/g) + .map((sentence) => sentence.trim().replace(/[,,;;::]\s*$/, "")) + .filter((sentence) => sentence && !looksLikeNavigationText(sentence)); +} + +function looksLikeNavigationText(text: string): boolean { + const punctuationCount = (text.match(/[,,。;;::]/g) ?? []).length; + const digitCount = (text.match(/\d/g) ?? []).length; + return text.length > 160 && punctuationCount < 2 && digitCount < 2; +} + +function relevantSearchTerms(query: string): string[] { + const normalizedTerms = normalizeSearchComparisonText(query) + .split(" ") + .filter((term) => term.length >= 2); + const cjkTerms = query.match(/[\p{Script=Han}]{2,}/gu) ?? []; + return uniqueStrings([...normalizedTerms, ...cjkTerms].map((term) => term.toLowerCase())); +} + +function weatherTemperatureRange(text: string): string | undefined { + const values = Array.from(text.matchAll(/(-?\d{1,2}(?:\.\d+)?)\s*℃/g)) + .map((match) => Number(match[1])) + .filter((value) => Number.isFinite(value) && value > -80 && value < 60) + .slice(0, 8); + if (values.length === 0) { + return undefined; + } + const min = Math.min(...values); + const max = Math.max(...values); + const format = (value: number) => Number.isInteger(value) ? String(value) : value.toFixed(1); + return min === max ? `${format(min)}℃` : `${format(min)}-${format(max)}℃`; +} + +function extractWeatherLocation(query: string): string | undefined { + const cleaned = query + .replace(/perform\s+a\s+web\s+search\s+for\s+the\s+query:\s*/i, "") + .replace(/天气预报|天气|气温|温度|怎么样|如何|查询|搜索|今天|今日|现在|当前|请问|weather|forecast|temperature/gi, " ") + .replace(/\s+/g, " ") + .trim(); + if (!cleaned || cleaned.length > 24) { + return undefined; + } + return cleaned; +} + +function firstRegexGroup(text: string, patterns: Array): string | undefined { + for (const pattern of patterns) { + if (!pattern) { + continue; + } + const match = pattern.exec(text); + const value = match?.[1]; + if (value) { + return value.trim(); + } + } + return undefined; +} + +function sanitizeWebSearchEvidenceText(text: string): string { + return text.replace(/\s+/g, " ").trim(); +} + +function containsCjkText(text: string): boolean { + return /\p{Script=Han}/u.test(text); +} + +function webSearchSourceNames(records: BrowserWebSearchProtocolRecord[], limit: number): string { + return uniqueStrings(records.flatMap((record) => record.results.map((result) => { + const title = result.title?.trim(); + return title || hostnameFromUrl(result.url) || record.engine; + }))).slice(0, limit).join("、"); +} + +function hostnameFromUrl(value: string): string | undefined { + try { + return new URL(value).hostname.replace(/^www\./, ""); + } catch { + return undefined; + } +} + +function escapeRegExp(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function anthropicWebSearchProtocolBlocks(records: BrowserWebSearchProtocolRecord[], requestId: string): Record[] { + const blocks: Record[] = []; + records.forEach((record, index) => { + const toolUseId = `srvtoolu_${sanitizeAnthropicToolUseId(requestId)}_${index + 1}`; + blocks.push({ + id: toolUseId, + input: { query: record.query }, + name: "web_search", + type: "server_tool_use" + }); + blocks.push({ + content: record.results.map(anthropicWebSearchResultBlock), + tool_use_id: toolUseId, + type: "web_search_tool_result" + }); + }); + return blocks; +} + +function anthropicWebSearchResultBlock(result: BrowserWebSearchProtocolResult): Record { + const snippet = result.snippet || result.content; + return { + encrypted_content: "", + ...(snippet ? { snippet: snippet.slice(0, 1_200) } : {}), + title: result.title, + type: "web_search_result", + url: result.url + }; +} + +function sanitizeAnthropicToolUseId(value: string): string { + return value.replace(/[^a-zA-Z0-9]/g, "").slice(0, 24) || randomBytes(8).toString("hex"); +} + +type ParsedSseEvent = { + data?: unknown; + event?: string; + raw?: string; +}; + +function parseSseEvents(body: string): ParsedSseEvent[] { + return body + .split(/\r?\n\r?\n/g) + .filter((block) => block.trim()) + .map((raw) => { + const lines = raw.split(/\r?\n/g); + const event = lines + .filter((line) => line.startsWith("event:")) + .map((line) => line.slice(6).trim()) + .find(Boolean); + const data = lines + .filter((line) => line.startsWith("data:")) + .map((line) => line.slice(5).replace(/^ /, "")) + .join("\n"); + if (!data || data === "[DONE]") { + return { event, raw }; + } + try { + return { data: JSON.parse(data) as unknown, event, raw }; + } catch { + return { event, raw }; + } + }); +} + +function shiftSseContentBlockIndex(event: ParsedSseEvent, startIndex: number, delta: number): ParsedSseEvent { + if (!isRecord(event.data) || !Number.isFinite(event.data.index) || Number(event.data.index) < startIndex) { + return event; + } + return { + ...event, + data: { + ...event.data, + index: Number(event.data.index) + delta + } + }; +} + +function sseEventFromValue(data: Record): ParsedSseEvent { + return { + data, + event: stringValue(data.type) + }; +} + +function serializeSseEvent(event: ParsedSseEvent): string { + if (event.data === undefined) { + return event.raw ?? ""; + } + const type = isRecord(event.data) ? stringValue(event.data.type) : undefined; + return [ + event.event || type ? `event: ${event.event || type}` : undefined, + `data: ${JSON.stringify(event.data)}` + ].filter(Boolean).join("\n"); +} + function requestProtocolForPath(path: string): GatewayProviderProtocol | undefined { const normalized = path.toLowerCase(); if (normalized === "/v1/messages" || normalized === "/messages" || normalized.endsWith("/v1/messages")) { @@ -3159,6 +5237,16 @@ function createGatewayProcessEnv(config: AppConfig, upstreamProxyUrl: string | u PORT: String(config.gateway.corePort) }; + const noProxy = mergeNoProxy(env.NO_PROXY || env.no_proxy, [ + "127.0.0.1", + "localhost", + "::1", + config.gateway.host, + config.gateway.coreHost + ]); + env.NO_PROXY = noProxy; + env.no_proxy = noProxy; + if (!upstreamProxyUrl) { return env; } @@ -3166,13 +5254,9 @@ function createGatewayProcessEnv(config: AppConfig, upstreamProxyUrl: string | u env.HTTP_PROXY = upstreamProxyUrl; env.HTTPS_PROXY = upstreamProxyUrl; env.ALL_PROXY = upstreamProxyUrl; - env.NO_PROXY = mergeNoProxy(env.NO_PROXY, [ - "127.0.0.1", - "localhost", - "::1", - config.gateway.host, - config.gateway.coreHost - ]); + env.http_proxy = upstreamProxyUrl; + env.https_proxy = upstreamProxyUrl; + env.all_proxy = upstreamProxyUrl; env.CCR_UPSTREAM_PROXY_URL = upstreamProxyUrl; env.CCR_UNDICI_MODULE = requireFromHere.resolve("undici"); return env; @@ -3187,8 +5271,79 @@ function writeGatewayProxyPreloadFile(config: AppConfig, upstreamProxyUrl: strin "const proxyUrl = process.env.CCR_UPSTREAM_PROXY_URL;", "if (proxyUrl) {", " const undiciModule = process.env.CCR_UNDICI_MODULE || \"undici\";", - " const { ProxyAgent, setGlobalDispatcher } = require(undiciModule);", - " setGlobalDispatcher(new ProxyAgent(proxyUrl));", + " const { Agent, ProxyAgent, setGlobalDispatcher } = require(undiciModule);", + " const directAgent = new Agent();", + " const proxyAgent = new ProxyAgent(proxyUrl);", + " const noProxyEntries = String(process.env.NO_PROXY || process.env.no_proxy || \"\")", + " .split(\",\")", + " .map((entry) => entry.trim())", + " .filter(Boolean);", + " function normalizeHost(host) {", + " return String(host || \"\").replace(/^\\[/, \"\").replace(/\\]$/, \"\").replace(/\\.$/, \"\").toLowerCase();", + " }", + " function isLoopbackHost(host) {", + " return host === \"localhost\" || host === \"::1\" || host === \"0:0:0:0:0:0:0:1\" || host === \"127.0.0.1\" || host.startsWith(\"127.\");", + " }", + " function splitNoProxyEntry(entry) {", + " let value = String(entry || \"\").trim().toLowerCase();", + " if (!value || value === \"*\") {", + " return { host: value, port: \"\" };", + " }", + " if (value.startsWith(\"[\")) {", + " const end = value.indexOf(\"]\");", + " if (end >= 0) {", + " const remainder = value.slice(end + 1);", + " return { host: normalizeHost(value.slice(1, end)), port: remainder.startsWith(\":\") ? remainder.slice(1) : \"\" };", + " }", + " }", + " const colon = value.lastIndexOf(\":\");", + " if (colon > 0 && value.indexOf(\":\") === colon) {", + " return { host: normalizeHost(value.slice(0, colon)), port: value.slice(colon + 1) };", + " }", + " return { host: normalizeHost(value), port: \"\" };", + " }", + " function noProxyMatches(host, port, entry) {", + " const parsed = splitNoProxyEntry(entry);", + " if (parsed.host === \"*\") {", + " return true;", + " }", + " if (!parsed.host || (parsed.port && parsed.port !== port)) {", + " return false;", + " }", + " if (parsed.host.startsWith(\"*.\")) {", + " const suffix = parsed.host.slice(1);", + " return host.endsWith(suffix);", + " }", + " if (parsed.host.startsWith(\".\")) {", + " return host === parsed.host.slice(1) || host.endsWith(parsed.host);", + " }", + " return host === parsed.host || host.endsWith(`.${parsed.host}`);", + " }", + " function shouldBypassProxy(origin) {", + " try {", + " const url = new URL(String(origin));", + " const host = normalizeHost(url.hostname);", + " const port = url.port || (url.protocol === \"https:\" ? \"443\" : url.protocol === \"http:\" ? \"80\" : \"\");", + " return isLoopbackHost(host) || noProxyEntries.some((entry) => noProxyMatches(host, port, entry));", + " } catch {", + " return false;", + " }", + " }", + " setGlobalDispatcher({", + " dispatch(options, handler) {", + " return shouldBypassProxy(options && options.origin)", + " ? directAgent.dispatch(options, handler)", + " : proxyAgent.dispatch(options, handler);", + " },", + " close(callback) {", + " Promise.all([directAgent.close(), proxyAgent.close()]).then(() => callback && callback(), callback);", + " },", + " destroy(error, callback) {", + " directAgent.destroy(error);", + " proxyAgent.destroy(error);", + " if (callback) callback();", + " }", + " });", "}" ].join("\n"), "utf8" diff --git a/src/server/mcp/browser-web-search-proxy-mcp.ts b/src/server/mcp/browser-web-search-proxy-mcp.ts new file mode 100644 index 0000000..d034adf --- /dev/null +++ b/src/server/mcp/browser-web-search-proxy-mcp.ts @@ -0,0 +1,146 @@ +const targetUrl = process.env.BROWSER_WEB_SEARCH_MCP_URL?.trim(); +const requestTimeoutMs = clampInteger(Number(process.env.BROWSER_WEB_SEARCH_PROXY_TIMEOUT_MS), 1_000, 600_000, 120_000); + +let stdinBuffer = Buffer.alloc(0); + +if (!targetUrl) { + process.stderr.write("BROWSER_WEB_SEARCH_MCP_URL is required.\n"); + process.exit(1); +} + +process.stdin.on("data", (chunk: Buffer | string) => { + stdinBuffer = Buffer.concat([stdinBuffer, typeof chunk === "string" ? Buffer.from(chunk) : chunk]); + void drainFrames(); +}); + +process.stdin.on("end", () => { + process.exit(0); +}); + +async function drainFrames(): Promise { + while (true) { + const headerEnd = stdinBuffer.indexOf("\r\n\r\n"); + if (headerEnd < 0) { + return; + } + + const headerText = stdinBuffer.slice(0, headerEnd).toString("utf8"); + const contentLength = readContentLength(headerText); + if (contentLength === undefined || contentLength < 0) { + stdinBuffer = Buffer.alloc(0); + writeFrame(jsonRpcError(null, -32600, "Invalid MCP frame header.")); + return; + } + + const payloadStart = headerEnd + 4; + const payloadEnd = payloadStart + contentLength; + if (stdinBuffer.length < payloadEnd) { + return; + } + + const body = stdinBuffer.slice(payloadStart, payloadEnd).toString("utf8"); + stdinBuffer = stdinBuffer.slice(payloadEnd); + await forwardPayload(body); + } +} + +async function forwardPayload(body: string): Promise { + const requestId = readJsonRpcId(body); + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), requestTimeoutMs); + try { + const response = await fetch(targetUrl!, { + body, + headers: { + accept: "application/json, text/event-stream", + "content-type": "application/json" + }, + method: "POST", + signal: controller.signal + }); + + if (response.status === 204) { + return; + } + + const text = await response.text(); + if (!response.ok) { + writeFrame(jsonRpcError(requestId, -32603, text || `In-app browser MCP returned HTTP ${response.status}.`)); + return; + } + if (text.trim()) { + writeRawFrame(text); + } + } catch (error) { + writeFrame(jsonRpcError(requestId, -32603, formatError(error))); + } finally { + clearTimeout(timeout); + } +} + +function readContentLength(headerText: string): number | undefined { + for (const line of headerText.split(/\r?\n/)) { + const match = /^content-length\s*:\s*(\d+)\s*$/i.exec(line); + if (match) { + return Number(match[1]); + } + } + return undefined; +} + +function readJsonRpcId(body: string): null | number | string { + try { + const payload = JSON.parse(body) as unknown; + if (isRecord(payload)) { + return isJsonRpcId(payload.id) ? payload.id : null; + } + if (Array.isArray(payload)) { + const first = payload.find((item) => isRecord(item) && isJsonRpcId(item.id)); + return isRecord(first) && isJsonRpcId(first.id) ? first.id : null; + } + } catch { + // The upstream MCP endpoint will return the parse error for valid JSON-RPC framing. + } + return null; +} + +function isJsonRpcId(value: unknown): value is null | number | string { + return value === null || typeof value === "number" || typeof value === "string"; +} + +function jsonRpcError(id: null | number | string, code: number, message: string): Record { + return { + error: { + code, + message + }, + id, + jsonrpc: "2.0" + }; +} + +function writeFrame(payload: Record): void { + writeRawFrame(JSON.stringify(payload)); +} + +function writeRawFrame(body: string): void { + process.stdout.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`); +} + +function clampInteger(value: number, min: number, max: number, fallback: number): number { + if (!Number.isFinite(value)) { + return fallback; + } + return Math.min(max, Math.max(min, Math.trunc(value))); +} + +function isRecord(value: unknown): value is Record { + return Boolean(value && typeof value === "object" && !Array.isArray(value)); +} + +function formatError(error: unknown): string { + if (error instanceof Error) { + return error.name === "AbortError" ? "In-app browser MCP proxy request timed out." : error.message; + } + return String(error); +} diff --git a/src/server/mcp/fusion-vision-mcp.ts b/src/server/mcp/fusion-vision-mcp.ts index c6a285e..956866c 100644 --- a/src/server/mcp/fusion-vision-mcp.ts +++ b/src/server/mcp/fusion-vision-mcp.ts @@ -90,6 +90,10 @@ const visionTool = { const webSearchTool = { description: "Search the web with this Fusion profile's configured search provider.", inputSchema: objectSchema({ + allowedDomains: { items: { type: "string" }, type: "array" }, + allowed_domains: { items: { type: "string" }, type: "array" }, + blockedDomains: { items: { type: "string" }, type: "array" }, + blocked_domains: { items: { type: "string" }, type: "array" }, count: { maximum: 20, minimum: 1, type: "number" }, country: { type: "string" }, excludeDomains: { items: { type: "string" }, type: "array" }, @@ -280,9 +284,17 @@ async function analyzeWebSearch(args: Record): Promise const input = { count, country: readString(args.country), - excludeDomains: readStringArray(args.excludeDomains), + excludeDomains: uniqueStrings([ + ...readStringArray(args.excludeDomains), + ...readStringArray(args.blockedDomains), + ...readStringArray(args.blocked_domains) + ]), freshness: readString(args.freshness), - includeDomains: readStringArray(args.includeDomains), + includeDomains: uniqueStrings([ + ...readStringArray(args.includeDomains), + ...readStringArray(args.allowedDomains), + ...readStringArray(args.allowed_domains) + ]), includeRaw: args.includeRaw === true, language: readString(args.language), prompt, @@ -651,12 +663,19 @@ function isSearchResult(value: SearchResult): value is Required item.trim()).filter(Boolean); + } if (!Array.isArray(value)) { return []; } return value.map(readString).filter((item): item is string => Boolean(item)); } +function uniqueStrings(values: string[]): string[] { + return [...new Set(values.filter(Boolean))]; +} + function textResult(text: string): ToolCallResult { return { content: [{ text, type: "text" }] diff --git a/src/shared/app.ts b/src/shared/app.ts index bc91702..56bd581 100644 --- a/src/shared/app.ts +++ b/src/shared/app.ts @@ -675,6 +675,7 @@ export type VirtualModelFusionVisionConfig = { }; export type VirtualModelFusionWebSearchProvider = + | "browser" | "brave" | "bing" | "google_cse" diff --git a/tests/main/codex-model-catalog.test.mjs b/tests/main/codex-model-catalog.test.mjs index 263c2b5..b08f94f 100644 --- a/tests/main/codex-model-catalog.test.mjs +++ b/tests/main/codex-model-catalog.test.mjs @@ -193,7 +193,7 @@ test("codex catalog marks prefixed Fusion virtual models with legacy web search key: "web-prefix", match: { exactAliases: [], prefixes: ["web-"], suffixes: [] }, materialization: { enabled: true, includeInGatewayModels: true }, - tools: [{ name: "web_search", visibility: "internal" }] + tools: [{ name: "web_prefix_web_search", visibility: "internal" }] } ] }, "deepseek/web-deepseek-chat"); diff --git a/tests/main/gateway-virtual-models.test.mjs b/tests/main/gateway-virtual-models.test.mjs index 4931a1f..2f0898c 100644 --- a/tests/main/gateway-virtual-models.test.mjs +++ b/tests/main/gateway-virtual-models.test.mjs @@ -1,6 +1,22 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { normalizeCoreGatewayVirtualModelProfiles } from "../../src/server/gateway/service.ts"; +import { + fusionFallbackToolDefinitions, + fusionWebSearchToolNameForRequest, + fusionToolNamesBackedByMcpServers, + extractHostedWebSearchQueryHint, + prepareAnthropicWebSearchProtocolRequestBody, + prepareHostedWebSearchProtocolRequestBody, + transformAnthropicWebSearchProtocolResponseValue, + transformAnthropicWebSearchProtocolSseText, + transformGeminiHostedWebSearchResponseValue, + transformGeminiHostedWebSearchSseText, + transformOpenAiChatHostedWebSearchResponseValue, + transformOpenAiChatHostedWebSearchSseText, + transformOpenAiResponsesHostedWebSearchResponseValue, + transformOpenAiResponsesHostedWebSearchSseText, + normalizeCoreGatewayVirtualModelProfiles +} from "../../src/server/gateway/service.ts"; test("gateway config rewrites Fusion fixed base and vision models to core provider selectors", () => { const providerName = "Zhipu AI (China) - Coding Plan"; @@ -59,3 +75,561 @@ test("gateway config rewrites Fusion fixed base and vision models to core provid ); assert.equal(profiles[0].baseModel.fixedModel, `${providerName}/glm-5.2`); }); + +test("gateway config normalizes Fusion web search tool names for native Anthropic search triggers", () => { + const profiles = [ + { + displayName: "Kimi Search", + enabled: true, + execution: { + clientToolsPolicy: "allow", + matchWebSearch: true, + maxToolCalls: 8, + maxTurns: 6, + mode: "tool_loop", + streamMode: "optimistic" + }, + id: "kimisearch", + key: "kimisearch", + match: { exactAliases: ["kimisearch"], prefixes: [], suffixes: [] }, + materialization: { enabled: true, includeInGatewayModels: true }, + metadata: { + fusionWebSearch: { provider: "browser", toolName: "web_search_kimisearch" } + }, + tools: [{ name: "web_search_kimisearch", visibility: "internal" }] + } + ]; + + const [profile] = normalizeCoreGatewayVirtualModelProfiles(profiles, { + Providers: [], + Router: { fallback: { mode: "off", models: [], retryCount: 0 } }, + gateway: {} + }); + + assert.equal(profile.metadata.fusionWebSearch.toolName, "kimisearch_web_search"); + assert.deepEqual(profile.tools.map((tool) => tool.name), ["kimisearch_web_search"]); + assert.match(profile.instructions.append, /call the kimisearch_web_search function tool before answering/); +}); + +test("gateway resolves normalized Fusion web search tool names for Anthropic protocol bridging", () => { + const config = { + Providers: [], + Router: { fallback: { mode: "off", models: [], retryCount: 0 } }, + gateway: {}, + virtualModelProfiles: [ + { + displayName: "Kimisearch", + enabled: true, + execution: { + clientToolsPolicy: "allow", + matchWebSearch: true, + maxToolCalls: 8, + maxTurns: 6, + mode: "tool_loop", + streamMode: "optimistic" + }, + id: "fusion-2", + key: "kimisearch", + match: { exactAliases: ["kimisearch", "Fusion/kimisearch"], prefixes: [], suffixes: [] }, + materialization: { enabled: true, includeInGatewayModels: true }, + metadata: { + fusionWebSearch: { provider: "browser", toolName: "web_search_fusion_2" } + }, + tools: [{ name: "web_search_fusion_2", visibility: "internal" }] + } + ] + }; + + assert.equal(fusionWebSearchToolNameForRequest(config, "Fusion/kimisearch"), "fusion_2_web_search"); +}); + +test("gateway resolves only browser-backed Fusion web search tools for hosted protocol bridging", () => { + const config = { + Providers: [], + Router: { fallback: { mode: "off", models: [], retryCount: 0 } }, + gateway: {}, + virtualModelProfiles: [ + { + displayName: "Research", + enabled: true, + execution: { + clientToolsPolicy: "allow", + matchWebSearch: true, + maxToolCalls: 8, + maxTurns: 6, + mode: "tool_loop", + streamMode: "optimistic" + }, + id: "research", + key: "research", + match: { exactAliases: ["research", "Fusion/research"], prefixes: [], suffixes: [] }, + materialization: { enabled: true, includeInGatewayModels: true }, + metadata: { + fusionWebSearch: { provider: "brave", toolName: "research_web_search" } + }, + tools: [{ name: "research_web_search", visibility: "internal" }] + } + ] + }; + + assert.equal(fusionWebSearchToolNameForRequest(config, "Fusion/research"), undefined); + assert.equal(fusionWebSearchToolNameForRequest(config, "gpt-5"), undefined); +}); + +test("gateway config does not create fallback tools for MCP-backed Fusion tools", () => { + const profiles = [ + { + enabled: true, + tools: [ + { description: "Browser search", name: "fusion_2_web_search", visibility: "internal" }, + { description: "Vision", name: "vision_understand_glm_5_2v", visibility: "internal" }, + { description: "Missing", name: "missing_fusion_tool", visibility: "internal" } + ] + } + ]; + const servers = [ + { name: "fusion_2_web_search" }, + { env: { FUSION_TOOL_NAME: "vision_understand_glm_5_2v" }, name: "fusion-vision-glm-5.2v" } + ]; + + const definitions = fusionFallbackToolDefinitions(profiles, fusionToolNamesBackedByMcpServers(servers)); + + assert.deepEqual(definitions.map((definition) => definition.name), ["missing_fusion_tool"]); +}); + +test("gateway response injects Anthropic web search protocol blocks into JSON responses", () => { + const response = { + content: [ + { thinking: "searched", type: "thinking" }, + { text: "answer", type: "text" } + ], + id: "msg_1", + role: "assistant", + type: "message", + usage: { server_tool_use: { web_search_requests: 1 } } + }; + const transformed = transformAnthropicWebSearchProtocolResponseValue(response, [sampleSearchRecord()], "req-1"); + + assert.equal(transformed.changed, true); + assert.deepEqual( + transformed.value.content.map((block) => block.type), + ["thinking", "server_tool_use", "web_search_tool_result", "text"] + ); + assert.equal(transformed.value.content[1].name, "web_search"); + assert.equal(transformed.value.content[2].content[0].type, "web_search_result"); + assert.equal(transformed.value.content[2].content[0].snippet, "Spot gold traded near $3,340 per ounce."); +}); + +test("gateway injects prefetched web search evidence into Anthropic requests", () => { + const body = Buffer.from(JSON.stringify({ + messages: [{ role: "user", content: [{ type: "text", text: "北京天气怎么样" }] }], + model: "Fusion/kimisearch", + output_config: { effort: "high" }, + system: [{ type: "text", text: "Answer in Chinese." }], + thinking: { type: "enabled" }, + tools: [{ name: "web_search", type: "web_search_20250305" }] + })); + const record = sampleSearchRecord(); + record.results[0].content = "北京市当前天气晴,气温 28 摄氏度,空气质量良。"; + + const transformed = prepareAnthropicWebSearchProtocolRequestBody(body, [record], { queryHint: "北京天气怎么样" }); + const parsed = JSON.parse(transformed.toString("utf8")); + + assert.match(parsed.system[1].text, /Use the evidence below to answer the user's question directly/); + assert.equal(parsed.tools, undefined); + assert.equal(parsed.tool_choice, undefined); + assert.equal(parsed.output_config.effort, "low"); + assert.equal(parsed.thinking, undefined); + assert.match(parsed.system[1].text, /北京市当前天气晴/); +}); + +test("gateway synthesizes final Anthropic text when web search response has no visible answer", () => { + const response = { + content: [ + { thinking: "searched but did not answer", type: "thinking" } + ], + id: "msg_1", + role: "assistant", + stop_reason: "max_tokens", + type: "message", + usage: { output_tokens: 0 } + }; + const transformed = transformAnthropicWebSearchProtocolResponseValue( + response, + [sampleSearchRecord()], + "req-1", + "today gold price per ounce USD July 2026" + ); + + assert.equal(transformed.changed, true); + assert.deepEqual( + transformed.value.content.map((block) => block.type), + ["thinking", "server_tool_use", "web_search_tool_result", "text"] + ); + assert.match(transformed.value.content[3].text, /Spot gold traded near \$3,340 per ounce/); + assert.equal(transformed.value.usage.server_tool_use.web_search_requests, 1); + assert.equal(transformed.value.stop_reason, "end_turn"); +}); + +test("gateway response injects Anthropic web search protocol blocks into SSE responses", () => { + const sse = [ + sseEvent({ type: "message_start", message: { content: [], id: "msg_1", role: "assistant", type: "message" } }), + sseEvent({ type: "content_block_start", index: 0, content_block: { type: "thinking", thinking: "" } }), + sseEvent({ type: "content_block_stop", index: 0 }), + sseEvent({ type: "content_block_start", index: 1, content_block: { type: "text", text: "" } }), + sseEvent({ type: "content_block_delta", index: 1, delta: { type: "text_delta", text: "answer" } }), + sseEvent({ type: "content_block_stop", index: 1 }), + sseEvent({ type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { server_tool_use: { web_search_requests: 1 } } }), + sseEvent({ type: "message_stop" }) + ].join("\n\n") + "\n\n"; + + const transformed = transformAnthropicWebSearchProtocolSseText(sse, [sampleSearchRecord()], "req-1"); + + assert.match(transformed, /"type":"server_tool_use"/); + assert.match(transformed, /"type":"web_search_tool_result"/); + assert.match(transformed, /"type":"web_search_result"/); + assert.match(transformed, /"index":3,"content_block":\{"type":"text"/); + assert.match(transformed, /"server_tool_use":\{"web_search_requests":1\}/); +}); + +test("gateway synthesizes final Anthropic SSE text when model exhausts tokens in thinking", () => { + const sse = [ + sseEvent({ type: "message_start", message: { content: [], id: "msg_1", role: "assistant", type: "message" } }), + sseEvent({ type: "content_block_start", index: 0, content_block: { type: "thinking", thinking: "" } }), + sseEvent({ type: "content_block_delta", index: 0, delta: { type: "thinking_delta", thinking: "long reasoning" } }), + sseEvent({ type: "content_block_stop", index: 0 }), + sseEvent({ type: "message_delta", delta: { stop_reason: "max_tokens" }, usage: { output_tokens: 0 } }), + sseEvent({ type: "message_stop" }) + ].join("\n\n") + "\n\n"; + + const transformed = transformAnthropicWebSearchProtocolSseText( + sse, + [sampleSearchRecord()], + "req-1", + "today gold price per ounce USD July 2026" + ); + + assert.match(transformed, /"type":"server_tool_use"/); + assert.match(transformed, /"type":"web_search_tool_result"/); + assert.match(transformed, /"delta":\{"text":"Based on the search results, Spot gold traded near \$3,340 per ounce/); + assert.match(transformed, /"stop_reason":"end_turn"/); + assert.match(transformed, /"server_tool_use":\{"web_search_requests":1\}/); +}); + +test("gateway injects prefetched web search evidence into OpenAI chat requests", () => { + const body = Buffer.from(JSON.stringify({ + messages: [{ role: "user", content: "Perform a web search for the query: today gold price per ounce USD July 2026" }], + model: "Fusion/kimisearch", + tool_choice: "auto", + tools: [{ type: "web_search_preview" }], + web_search_options: { search_context_size: "low" } + })); + + const transformed = prepareHostedWebSearchProtocolRequestBody(body, [sampleSearchRecord()], { + protocol: "openai_chat_completions", + queryHint: "today gold price per ounce USD July 2026" + }); + const parsed = JSON.parse(transformed.toString("utf8")); + + assert.equal(parsed.tools, undefined); + assert.equal(parsed.tool_choice, undefined); + assert.equal(parsed.web_search_options, undefined); + assert.equal(parsed.messages[0].role, "system"); + assert.match(parsed.messages[0].content, /hidden in-app browser web search/); +}); + +test("gateway hosted web search rewrites preserve custom web_search-named tools", () => { + const anthropicBody = Buffer.from(JSON.stringify({ + messages: [{ role: "user", content: "search docs" }], + model: "Fusion/kimisearch", + tool_choice: { name: "web_search_docs", type: "tool" }, + tools: [ + { input_schema: { type: "object" }, name: "web_search_docs" }, + { name: "web_search", type: "web_search_20250305" } + ] + })); + const anthropicParsed = JSON.parse(prepareAnthropicWebSearchProtocolRequestBody( + anthropicBody, + [sampleSearchRecord()], + { queryHint: "search docs" } + ).toString("utf8")); + + assert.deepEqual(anthropicParsed.tools.map((tool) => tool.name), ["web_search_docs"]); + assert.equal(anthropicParsed.tool_choice.name, "web_search_docs"); + + const openAiBody = Buffer.from(JSON.stringify({ + messages: [{ role: "user", content: "search docs" }], + model: "Fusion/kimisearch", + parallel_tool_calls: true, + tool_choice: { function: { name: "web_search_docs" }, type: "function" }, + tools: [ + { function: { name: "web_search_docs", parameters: { type: "object" } }, type: "function" }, + { type: "web_search_preview" } + ], + web_search_options: { search_context_size: "low" } + })); + const openAiParsed = JSON.parse(prepareHostedWebSearchProtocolRequestBody( + openAiBody, + [sampleSearchRecord()], + { protocol: "openai_chat_completions", queryHint: "search docs" } + ).toString("utf8")); + + assert.deepEqual(openAiParsed.tools.map((tool) => tool.function.name), ["web_search_docs"]); + assert.equal(openAiParsed.tool_choice.function.name, "web_search_docs"); + assert.equal(openAiParsed.parallel_tool_calls, true); + + const geminiBody = Buffer.from(JSON.stringify({ + contents: [{ role: "user", parts: [{ text: "search docs" }] }], + tools: [ + { + functionDeclarations: [{ name: "web_search_docs", parameters: { type: "object" } }], + google_search: {} + } + ] + })); + const geminiParsed = JSON.parse(prepareHostedWebSearchProtocolRequestBody( + geminiBody, + [sampleSearchRecord()], + { protocol: "gemini_generate_content", queryHint: "search docs" } + ).toString("utf8")); + + assert.deepEqual(geminiParsed.tools[0].functionDeclarations.map((declaration) => declaration.name), ["web_search_docs"]); + assert.equal(geminiParsed.tools[0].google_search, undefined); +}); + +test("gateway synthesizes OpenAI chat final text for hosted web search fallback", () => { + const response = { + choices: [ + { finish_reason: "length", index: 0, message: { role: "assistant", content: "" } } + ], + id: "chatcmpl_1", + object: "chat.completion" + }; + const transformed = transformOpenAiChatHostedWebSearchResponseValue( + response, + [sampleSearchRecord()], + "today gold price per ounce USD July 2026" + ); + + assert.equal(transformed.changed, true); + assert.match(transformed.value.choices[0].message.content, /Spot gold traded near \$3,340 per ounce/); + assert.equal(transformed.value.choices[0].finish_reason, "stop"); +}); + +test("gateway synthesizes OpenAI chat SSE final text for hosted web search fallback", () => { + const sse = [ + openAiSseEvent({ id: "chatcmpl_1", object: "chat.completion.chunk", choices: [{ index: 0, delta: { role: "assistant" }, finish_reason: null }] }), + openAiSseEvent({ id: "chatcmpl_1", object: "chat.completion.chunk", choices: [{ index: 0, delta: {}, finish_reason: "length" }] }), + "data: [DONE]" + ].join("\n\n") + "\n\n"; + + const transformed = transformOpenAiChatHostedWebSearchSseText( + sse, + [sampleSearchRecord()], + "today gold price per ounce USD July 2026" + ); + + assert.match(transformed, /"delta":\{"content":"Based on the search results, Spot gold traded near \$3,340 per ounce/); + assert.match(transformed, /"finish_reason":"stop"/); +}); + +test("gateway injects prefetched web search evidence into OpenAI responses requests", () => { + const body = Buffer.from(JSON.stringify({ + input: "Perform a web search for the query: today gold price per ounce USD July 2026", + model: "Fusion/kimisearch", + tools: [{ type: "web_search_preview" }], + tool_choice: "auto" + })); + + const transformed = prepareHostedWebSearchProtocolRequestBody(body, [sampleSearchRecord()], { + protocol: "openai_responses", + queryHint: "today gold price per ounce USD July 2026" + }); + const parsed = JSON.parse(transformed.toString("utf8")); + + assert.equal(parsed.tools, undefined); + assert.equal(parsed.tool_choice, undefined); + assert.match(parsed.instructions, /hidden in-app browser web search/); +}); + +test("gateway extracts OpenAI responses web search query after Codex runtime context", () => { + const body = { + input: [ + { + type: "message", + role: "developer", + content: [{ type: "input_text", text: "\nNetwork access is restricted.\n" }] + }, + { + type: "message", + role: "user", + content: [{ type: "input_text", text: "\n /tmp/project\n 2026-07-02\n \n" }] + }, + { + type: "message", + role: "assistant", + content: [{ type: "output_text", text: "之前的回答" }] + }, + { + type: "message", + role: "user", + content: [{ type: "input_text", text: "搜索今天黄金价格" }] + } + ], + model: "Fusion/kimisearch", + tools: [{ type: "web_search_preview" }] + }; + + assert.equal(extractHostedWebSearchQueryHint(body, "openai_responses"), "今天黄金价格"); +}); + +test("gateway synthesizes OpenAI responses output for hosted web search fallback", () => { + const response = { + id: "resp_1", + output: [], + status: "incomplete", + incomplete_details: { reason: "max_output_tokens" } + }; + const transformed = transformOpenAiResponsesHostedWebSearchResponseValue( + response, + [sampleSearchRecord()], + "req-1", + "today gold price per ounce USD July 2026" + ); + + assert.equal(transformed.changed, true); + assert.deepEqual(transformed.value.output.map((item) => item.type), ["web_search_call", "message"]); + assert.match(transformed.value.output[1].content[0].text, /Spot gold traded near \$3,340 per ounce/); + assert.equal(transformed.value.status, "completed"); +}); + +test("gateway synthesizes OpenAI responses SSE output for hosted web search fallback", () => { + const sse = [ + openAiSseEvent({ type: "response.created", response: { id: "resp_1", status: "in_progress" } }), + openAiSseEvent({ type: "response.completed", response: { id: "resp_1", status: "incomplete", incomplete_details: { reason: "max_output_tokens" } } }), + "data: [DONE]" + ].join("\n\n") + "\n\n"; + + const transformed = transformOpenAiResponsesHostedWebSearchSseText( + sse, + [sampleSearchRecord()], + "req-1", + "today gold price per ounce USD July 2026" + ); + + assert.match(transformed, /event: response.output_text.delta/); + assert.match(transformed, /"type":"response.output_text.delta"/); + assert.match(transformed, /Spot gold traded near \$3,340 per ounce/); + assert.match(transformed, /"status":"completed"/); +}); + +test("gateway normalizes OpenAI responses SSE with visible text for hosted web search", () => { + const answer = "杭州今天湿润有雨,气温约24℃到29.3℃。"; + const sse = [ + openAiSseEvent({ type: "response.created", response: { id: "resp_1", status: "in_progress", output: [] } }), + openAiSseEvent({ type: "response.output_item.added", output_index: 0, item: { id: "rs_1", type: "reasoning", status: "in_progress", content: [] } }), + openAiSseEvent({ type: "response.reasoning_text.delta", output_index: 0, content_index: 0, item_id: "rs_1", delta: "hidden reasoning" }), + openAiSseEvent({ type: "response.output_item.added", output_index: 1, item: { id: "msg_1", type: "message", role: "assistant", status: "in_progress", content: [] } }), + openAiSseEvent({ type: "response.content_part.added", output_index: 1, content_index: 0, item_id: "msg_1", part: { type: "output_text", text: "", annotations: [] } }), + openAiSseEvent({ type: "response.output_text.delta", output_index: 1, content_index: 0, item_id: "msg_1", delta: answer }), + openAiSseEvent({ type: "response.output_text.done", output_index: 1, content_index: 0, item_id: "msg_1", text: answer }), + openAiSseEvent({ type: "response.output_item.done", output_index: 0, item: { id: "rs_1", type: "reasoning", status: "completed", content: [{ type: "reasoning_text", text: "hidden reasoning" }] } }), + openAiSseEvent({ type: "response.output_item.done", output_index: 1, item: { id: "msg_1", type: "message", role: "assistant", status: "completed", content: [{ type: "output_text", text: answer, annotations: [] }] } }), + openAiSseEvent({ + type: "response.completed", + response: { + id: "resp_1", + status: "completed", + output_text: answer, + output: [ + { id: "rs_1", type: "reasoning", status: "completed", content: [{ type: "reasoning_text", text: "hidden reasoning" }] }, + { id: "msg_1", type: "message", role: "assistant", status: "completed", content: [{ type: "output_text", text: answer, annotations: [] }] } + ] + } + }), + "data: [DONE]" + ].join("\n\n") + "\n\n"; + + const transformed = transformOpenAiResponsesHostedWebSearchSseText( + sse, + [sampleSearchRecord()], + "req-1", + "杭州天气怎么样" + ); + + assert.match(transformed, /event: response.output_text.delta/); + assert.match(transformed, /"output_index":0/); + assert.match(transformed, /"output_text":"杭州今天湿润有雨/); + assert.doesNotMatch(transformed, /"output_index":1/); + assert.doesNotMatch(transformed, /response.reasoning_text.delta/); + assert.doesNotMatch(transformed, /"type":"reasoning"/); +}); + +test("gateway injects prefetched web search evidence into Gemini requests", () => { + const body = Buffer.from(JSON.stringify({ + contents: [{ role: "user", parts: [{ text: "Perform a web search for the query: today gold price per ounce USD July 2026" }] }], + tools: [{ google_search: {} }] + })); + + const transformed = prepareHostedWebSearchProtocolRequestBody(body, [sampleSearchRecord()], { + protocol: "gemini_generate_content", + queryHint: "today gold price per ounce USD July 2026" + }); + const parsed = JSON.parse(transformed.toString("utf8")); + + assert.equal(parsed.tools, undefined); + assert.match(parsed.systemInstruction.parts[0].text, /hidden in-app browser web search/); +}); + +test("gateway synthesizes Gemini response text for hosted web search fallback", () => { + const response = { candidates: [{ content: { parts: [], role: "model" }, finishReason: "MAX_TOKENS", index: 0 }] }; + const transformed = transformGeminiHostedWebSearchResponseValue( + response, + [sampleSearchRecord()], + "today gold price per ounce USD July 2026" + ); + + assert.equal(transformed.changed, true); + assert.match(transformed.value.candidates[0].content.parts[0].text, /Spot gold traded near \$3,340 per ounce/); + assert.equal(transformed.value.candidates[0].finishReason, "STOP"); +}); + +test("gateway synthesizes Gemini SSE text for hosted web search fallback", () => { + const sse = [ + openAiSseEvent({ candidates: [{ content: { parts: [], role: "model" }, finishReason: "MAX_TOKENS", index: 0 }] }), + "data: [DONE]" + ].join("\n\n") + "\n\n"; + + const transformed = transformGeminiHostedWebSearchSseText( + sse, + [sampleSearchRecord()], + "today gold price per ounce USD July 2026" + ); + + assert.match(transformed, /"candidates":\[\{"content":\{"parts":\[\{"text":"Based on the search results, Spot gold traded near \$3,340 per ounce/); +}); + +function sampleSearchRecord() { + return { + completedAtMs: Date.now(), + engine: "google", + query: "today gold price per ounce USD July 2026", + results: [ + { + snippet: "Spot gold traded near $3,340 per ounce.", + title: "Gold Price Today", + url: "https://example.test/gold" + } + ], + searchUrl: "https://www.google.com/search?q=gold", + toolName: "fusion_2_web_search" + }; +} + +function sseEvent(value) { + return `event: ${value.type}\ndata: ${JSON.stringify(value)}`; +} + +function openAiSseEvent(value) { + return `data: ${JSON.stringify(value)}`; +} diff --git a/tests/renderer/virtual-models.test.ts b/tests/renderer/virtual-models.test.ts index 5377160..1617e19 100644 --- a/tests/renderer/virtual-models.test.ts +++ b/tests/renderer/virtual-models.test.ts @@ -27,7 +27,7 @@ test("Fusion draft saves multiple selected tools into one profile", () => { assert.deepEqual(profile.tools.map((tool) => tool.name), [ "vision_understand_fusion_plus", - "web_search_fusion_plus", + "fusion_plus_web_search", "lookup_customer" ]); assert.equal(profile.execution.matchMultimodal, true); @@ -36,7 +36,7 @@ test("Fusion draft saves multiple selected tools into one profile", () => { assert.equal(profile.execution.clientToolsPolicy, "allow"); assert.equal(profile.execution.streamMode, "optimistic"); assert.equal(metadataString(profile.metadata, "fusionVision", "toolName"), "vision_understand_fusion_plus"); - assert.equal(metadataString(profile.metadata, "fusionWebSearch", "toolName"), "web_search_fusion_plus"); + assert.equal(metadataString(profile.metadata, "fusionWebSearch", "toolName"), "fusion_plus_web_search"); assert.equal(metadataString(profile.metadata, "fusionTool", "mcpServerName"), "customer-tools"); }); From ccd2694d43c2fdc52ab10c15b22778be3488ebf3 Mon Sep 17 00:00:00 2001 From: musistudio Date: Thu, 2 Jul 2026 18:30:24 +0800 Subject: [PATCH 04/62] Open main window from tray and hide it on close --- src/main/tray-controller.ts | 14 ++++++++++++++ src/main/windows.ts | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/main/tray-controller.ts b/src/main/tray-controller.ts index 11d5b1e..7379dd0 100644 --- a/src/main/tray-controller.ts +++ b/src/main/tray-controller.ts @@ -6,6 +6,7 @@ import { loadAppConfig } from "./config"; import { APP_NAME } from "./constants"; import { getProviderAccountSnapshots } from "./provider-account-service"; import { getTodayUsageTotals, onUsageRecorded } from "./usage-store"; +import windowsManager from "./windows"; import type { AppConfig, ProviderAccountMeter, TrayBalanceProgressConfig, TrayIconPreference } from "../shared/app"; const popoverMenuWidth = 420; @@ -58,6 +59,10 @@ class TrayController { this.suppressMainWindowActivation(); this.togglePopover(); }); + this.tray.on("double-click", () => { + this.suppressMainWindowActivation(); + this.showMainWindow(); + }); this.tray.on("right-click", () => { this.suppressMainWindowActivation(); this.showContextMenu(); @@ -275,6 +280,10 @@ class TrayController { label: formatTokenTitle(this.trayTotalTokens) }, { type: "separator" }, + { + click: () => this.showMainWindow(), + label: `Open ${APP_NAME}` + }, { click: () => this.showPopover(), label: "Show Usage" @@ -289,6 +298,11 @@ class TrayController { this.tray?.popUpContextMenu(menu); } + private showMainWindow(): void { + this.hidePopover(); + windowsManager.showMainWindow(); + } + private showDetailPopover(provider?: string): void { if (!this.popover || this.popover.isDestroyed() || !this.popover.isVisible()) { return; diff --git a/src/main/windows.ts b/src/main/windows.ts index e010725..634ec51 100644 --- a/src/main/windows.ts +++ b/src/main/windows.ts @@ -57,6 +57,13 @@ class WindowsManager { window.show(); } }); + window.on("close", (event) => { + if (!shouldHideMainWindowOnClose()) { + return; + } + event.preventDefault(); + window.hide(); + }); window.on("closed", () => this.windows.delete("main")); window.webContents.on("page-title-updated", (_event, title) => { window.setTitle(title || APP_NAME); @@ -115,10 +122,17 @@ const windowsManager = new WindowsManager(); export default windowsManager; +let appIsQuitting = false; + app.on("before-quit", () => { + appIsQuitting = true; windowsManager.broadcast(IPC_CHANNELS.appBeforeQuit); }); +function shouldHideMainWindowOnClose(): boolean { + return process.platform === "win32" && !appIsQuitting; +} + function fitWindowSize(preferred: number, minimum: number, available: number): number { return Math.max(minimum, Math.min(preferred, available > 0 ? available : preferred)); } From 01b3c3bc3baba87d2e8a89afe2c4ec2594681630 Mon Sep 17 00:00:00 2001 From: musistudio Date: Thu, 2 Jul 2026 20:28:25 +0800 Subject: [PATCH 05/62] Add router support for multi-provider model handling --- src/main/claude-app-launch.ts | 30 + src/main/codex-cli-middleware-runtime.ts | 144 ++- src/main/profile-launch-core.ts | 30 + src/main/profile-service.ts | 87 +- .../gateway/claude-code-router-plugin.ts | 14 +- src/server/gateway/service.ts | 994 ++++++++++++++++-- .../codex-cli-middleware-runtime.test.mjs | 86 ++ tests/main/gateway-virtual-models.test.mjs | 376 ++++++- tests/main/profile-launch-core.test.mjs | 5 + tests/main/profile-service.test.mjs | 108 ++ tests/main/router-builtins.test.mjs | 34 +- 11 files changed, 1789 insertions(+), 119 deletions(-) create mode 100644 tests/main/codex-cli-middleware-runtime.test.mjs create mode 100644 tests/main/profile-service.test.mjs diff --git a/src/main/claude-app-launch.ts b/src/main/claude-app-launch.ts index ec7adce..dc67f1b 100644 --- a/src/main/claude-app-launch.ts +++ b/src/main/claude-app-launch.ts @@ -55,6 +55,7 @@ export async function launchClaudeAppProfile(configDir: string, profile: Profile const env: NodeJS.ProcessEnv = { ...process.env, ...profileEnv(profile), + ...claudeCodeModelEnv(profile), ...(config ? botGatewayProfileEnv(config, profile, "app") : {}), CLAUDE_CONFIG_DIR: settingsDir, CLAUDE_USER_DATA_DIR: userDataDir, @@ -314,6 +315,35 @@ function profileEnv(profile: ProfileConfig): Record { }, {}); } +function claudeCodeModelEnv(profile: ProfileConfig): Record { + const env: Record = {}; + const model = normalizeClientModel(profile.model); + if (model) { + env.ANTHROPIC_MODEL = model; + env.CCR_CLAUDE_CODE_MODEL = model; + env.CODEXL_CLAUDE_CODE_MODEL = model; + } + const smallFastModel = normalizeClientModel(profile.smallFastModel); + if (smallFastModel) { + env.ANTHROPIC_SMALL_FAST_MODEL = smallFastModel; + } + return env; +} + +function normalizeClientModel(value: string | undefined): string { + const trimmed = value?.trim() || ""; + if (!trimmed) { + return ""; + } + const commaIndex = trimmed.indexOf(","); + if (commaIndex > 0 && commaIndex < trimmed.length - 1) { + const provider = trimmed.slice(0, commaIndex).trim(); + const model = trimmed.slice(commaIndex + 1).trim(); + return provider && model ? `${provider}/${model}` : ""; + } + return trimmed; +} + function isEnvName(value: string): boolean { return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value); } diff --git a/src/main/codex-cli-middleware-runtime.ts b/src/main/codex-cli-middleware-runtime.ts index 5b61cb9..2f7b30b 100644 --- a/src/main/codex-cli-middleware-runtime.ts +++ b/src/main/codex-cli-middleware-runtime.ts @@ -87,7 +87,8 @@ async function main() { async function runClaudeCodeCliWrapper(args) { const realCli = expandHome(nonEmptyEnv("CCR_REAL_CLAUDE_CODE_BIN") || nonEmptyEnv("CCR_CLAUDE_CODE_BIN") || nonEmptyEnv("CODEXL_CLAUDE_CODE_BIN") || "claude"); - log("claude_code_wrapper_start", { realCli, args }); + const realArgs = claudeCodeCliWrapperArgs(args); + log("claude_code_wrapper_start", { realCli, args, realArgs }); const captureStdout = shouldCaptureClaudeCodeCliStdout(args); const remoteSync = createRemoteSyncClient({ args, @@ -96,7 +97,7 @@ async function runClaudeCodeCliWrapper(args) { title: nonEmptyEnv("CCR_REMOTE_SYNC_PROFILE_NAME") || "Claude Code" }); const injectRemoteStdin = boolEnv("CCR_REMOTE_SYNC_INJECT_STDIN"); - const child = childProcess.spawn(realCli, args, { + const child = childProcess.spawn(realCli, realArgs, { env: { ...withoutKeys(process.env, ["CCR_CLAUDE_CODE_WRAPPER", "CCR_REAL_CLAUDE_CODE_BIN"]), ...claudeCodeUtcTimezoneEnvOverride() @@ -145,6 +146,79 @@ async function runClaudeCodeCliWrapper(args) { process.exitCode = code; } +function claudeCodeCliWrapperArgs(args) { + const model = nonEmptyEnv("CCR_CLAUDE_CODE_MODEL") || nonEmptyEnv("CODEXL_CLAUDE_CODE_MODEL") || nonEmptyEnv("ANTHROPIC_MODEL"); + if (!model || claudeCodeArgsHaveModel(args) || claudeCodeArgsShouldSkipModelInjection(args)) { + return args; + } + return ["--model", model, ...args]; +} + +function claudeCodeArgsHaveModel(args) { + for (const arg of args) { + if (arg === "--model" || arg === "-m" || arg.startsWith("--model=")) { + return true; + } + } + return false; +} + +function claudeCodeArgsShouldSkipModelInjection(args) { + if (args.some((arg) => arg === "--help" || arg === "-h" || arg === "--version" || arg === "-v")) { + return true; + } + const command = firstClaudeCodePositionalArg(args); + return Boolean(command && new Set([ + "config", + "doctor", + "help", + "install", + "login", + "logout", + "mcp", + "update", + "upgrade", + "version" + ]).has(command.toLowerCase())); +} + +function firstClaudeCodePositionalArg(args) { + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + if (arg === "--") { + return undefined; + } + if (!arg.startsWith("-")) { + return arg; + } + if (claudeCodeOptionTakesValue(arg) && !arg.includes("=")) { + index += 1; + } + } + return undefined; +} + +function claudeCodeOptionTakesValue(arg) { + return new Set([ + "--add-dir", + "--append-system-prompt", + "--config", + "--continue", + "--debug-to", + "--fallback-model", + "--model", + "--output-format", + "--permission-mode", + "--resume", + "--settings", + "--system-prompt", + "-c", + "-m", + "-p", + "-r" + ]).has(arg); +} + function shouldCaptureClaudeCodeCliStdout(args) { if (boolEnv("CCR_CLAUDE_CODE_CAPTURE_STDOUT") || boolEnv("CODEXL_CLAUDE_CODE_CAPTURE_STDOUT")) { return true; @@ -736,7 +810,14 @@ async function runClaudeCodeBotWorker(args) { try { const server = new ClaudeCodeAppServer(options); server.ensureBotBridgeRegistered(); - log("claude_bot_worker_start", { workspaceName: options.workspaceName, pid: process.pid, lockPath: lock.path }); + log("claude_bot_worker_start", { + workspaceName: options.workspaceName, + pid: process.pid, + lockPath: lock.path, + claudeConfigDir: nonEmptyEnv("CLAUDE_CONFIG_DIR"), + claudeUserDataDir: currentClaudeAppUserDataDir(), + model: nonEmptyEnv("CCR_CLAUDE_CODE_MODEL") || nonEmptyEnv("CODEXL_CLAUDE_CODE_MODEL") || agentEnv(codexRuntimeAgent(), "MODEL") || "" + }); await waitForTerminationSignal(); await botBridge().stop(); log("claude_bot_worker_stop", { pid: process.pid }); @@ -1295,13 +1376,23 @@ class ClaudeCodeAppServer { return null; } if (!entry.claudeSessionId && !entry.claudeAppSessionId) return null; + const appSession = readClaudeAppLocalAgentSession(entry.claudeAppSessionFile || ""); + if (!botSessionEntryMatchesCurrentProfile(entry, appSession)) { + log("bot_gateway_session_scope_skip", { + conversationKeyPrefix: key.slice(0, 80), + threadId: entry.threadId || "", + claudeConfigDir: entry.claudeConfigDir || appSession.claudeConfigDir || "", + claudeAppSessionFile: entry.claudeAppSessionFile || "", + expectedUserDataDir: currentClaudeAppUserDataDir() + }); + return null; + } const thread = this.createThread({ cwd: entry.cwd || process.cwd(), model: entry.model || undefined, workspaceKind: "local", claudeConfigDir: entry.claudeConfigDir || null }); - const appSession = readClaudeAppLocalAgentSession(entry.claudeAppSessionFile || ""); this.replaceThreadId(thread, entry.threadId || thread.id); thread.sessionId = entry.sessionId || thread.id; thread.claudeSessionId = entry.claudeSessionId || appSession.cliSessionId || null; @@ -1537,7 +1628,15 @@ class ClaudeCodeAppServer { if (!thread || !turn) return; const started = Date.now(); const command = claudeCommand(work); - log("claude_turn_spawn", { threadId: work.threadId, turnId: work.turnId, command: command.command, args: command.args }); + log("claude_turn_spawn", { + threadId: work.threadId, + turnId: work.turnId, + command: command.command, + args: command.args, + cwd: work.cwd, + claudeConfigDir: work.claudeConfigDir || "", + expectedUserDataDir: currentClaudeAppUserDataDir() + }); const child = childProcess.spawn(command.command, command.args, { cwd: work.cwd, env: command.env, @@ -3414,9 +3513,9 @@ function latestClaudeAppLocalAgentSession() { } function claudeAppLocalAgentSessions() { - const baseDir = nonEmptyEnv("CCR_CLAUDE_APP_USER_DATA_PATH") || nonEmptyEnv("CLAUDE_USER_DATA_DIR"); + const baseDir = currentClaudeAppUserDataDir(); if (!baseDir) return []; - const root = path.join(expandHome(baseDir), "local-agent-mode-sessions"); + const root = path.join(baseDir, "local-agent-mode-sessions"); const files = listClaudeAppSessionFiles(root, 6); const sessions = []; for (const file of files) { @@ -3449,6 +3548,37 @@ function claudeAppLocalAgentSessions() { return sessions; } +function currentClaudeAppUserDataDir() { + return expandHome(nonEmptyEnv("CCR_CLAUDE_APP_USER_DATA_PATH") || nonEmptyEnv("CLAUDE_USER_DATA_DIR") || ""); +} + +function botSessionEntryMatchesCurrentProfile(entry, appSession) { + const expectedUserDataDir = currentClaudeAppUserDataDir(); + if (!expectedUserDataDir) return true; + const candidates = [ + entry && entry.claudeConfigDir, + entry && entry.claudeAppSessionFile, + entry && entry.cwd, + appSession && appSession.claudeConfigDir + ]; + return candidates.some((candidate) => pathIsInside(candidate, expectedUserDataDir)); +} + +function pathIsInside(candidate, parentDir) { + const child = expandHome(String(candidate || "")); + const parent = expandHome(String(parentDir || "")); + if (!child || !parent) return false; + const childPath = normalizeComparablePath(path.resolve(child)); + const parentPath = normalizeComparablePath(path.resolve(parent)); + if (childPath === parentPath) return true; + const relative = path.relative(parentPath, childPath); + return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative); +} + +function normalizeComparablePath(value) { + return process.platform === "win32" ? value.toLowerCase() : value; +} + function resolveClaudeAppLocalAgentSession(selector) { const query = String(selector || "").trim(); if (!query) return null; diff --git a/src/main/profile-launch-core.ts b/src/main/profile-launch-core.ts index 0220187..ea5bea2 100644 --- a/src/main/profile-launch-core.ts +++ b/src/main/profile-launch-core.ts @@ -182,6 +182,7 @@ function buildClaudeCodeLaunchPlan( env: { CLAUDE_CONFIG_DIR: path.dirname(settingsFile), CCR_PROFILE_SURFACE: surface, + ...claudeCodeModelEnv(profile), ...claudeCodeUtcTimezoneEnvOverride() }, profile, @@ -219,6 +220,35 @@ function normalizeProfileSurface(value: ProfileConfig["surface"]): "auto" | "cli return value === "cli" || value === "app" ? value : "auto"; } +function claudeCodeModelEnv(profile: ProfileConfig): Record { + const env: Record = {}; + const model = normalizeClientModel(profile.model); + if (model) { + env.ANTHROPIC_MODEL = model; + env.CCR_CLAUDE_CODE_MODEL = model; + env.CODEXL_CLAUDE_CODE_MODEL = model; + } + const smallFastModel = normalizeClientModel(profile.smallFastModel); + if (smallFastModel) { + env.ANTHROPIC_SMALL_FAST_MODEL = smallFastModel; + } + return env; +} + +function normalizeClientModel(value: string | undefined): string { + const trimmed = value?.trim() || ""; + if (!trimmed) { + return ""; + } + const commaIndex = trimmed.indexOf(","); + if (commaIndex > 0 && commaIndex < trimmed.length - 1) { + const provider = trimmed.slice(0, commaIndex).trim(); + const model = trimmed.slice(commaIndex + 1).trim(); + return provider && model ? `${provider}/${model}` : ""; + } + return trimmed; +} + function isGeneratedProfileScope(value: ProfileConfig["scope"]): boolean { return value === "ccr" || value === "custom"; } diff --git a/src/main/profile-service.ts b/src/main/profile-service.ts index 87be49b..5c814c7 100644 --- a/src/main/profile-service.ts +++ b/src/main/profile-service.ts @@ -60,6 +60,7 @@ export async function applyProfileConfig(config: AppConfig): Promise key !== "CCR_CLAUDE_CODE_BIN") .map(([key, value]) => `export ${key}=${shellQuote(value)}`); @@ -540,6 +547,7 @@ function claudeCodeWrapperShellScript(config: AppConfig, profile: ProfileConfig, return [ "#!/bin/sh", ...envExports, + ...shellEnvExports(claudeCodeRuntimeEnv(config, profile, settingsDir)), ...shellEnvExports(claudeCodeUtcTimezoneEnvOverride()), `: "\${CCR_PROFILE_SURFACE:=${surface}}"`, "export CCR_PROFILE_SURFACE", @@ -562,6 +570,7 @@ function claudeCodeWrapperCmdScript(config: AppConfig, profile: ProfileConfig, r const realClaude = profile.env?.CCR_CLAUDE_CODE_BIN?.trim() || "claude"; const surface = normalizeProfileSurface(profile.surface); const remoteEndpoint = `${gatewayEndpoint(config)}/__ccr/remote`; + const settingsDir = path.dirname(resolveClaudeCodeSettingsFile(profile)); const envExports = Object.entries(profileEnv(profile)) .filter(([key]) => key !== "CCR_CLAUDE_CODE_BIN") .map(([key, value]) => cmdSetLine(key, value)); @@ -569,6 +578,7 @@ function claudeCodeWrapperCmdScript(config: AppConfig, profile: ProfileConfig, r return [ "@echo off", ...envExports, + ...cmdEnvExports(claudeCodeRuntimeEnv(config, profile, settingsDir)), ...cmdEnvExports(claudeCodeUtcTimezoneEnvOverride()), `if not defined CCR_PROFILE_SURFACE ${cmdSetLine("CCR_PROFILE_SURFACE", surface)}`, ...botEnvExports, @@ -617,6 +627,27 @@ function writeCodexCliMiddleware( }; } +function claudeCodeRuntimeEnv(config: AppConfig, profile: ProfileConfig, settingsDir: string): Record { + const endpoint = gatewayEndpoint(config); + const env: Record = { + ANTHROPIC_API_BASE_URL: endpoint, + ANTHROPIC_BASE_URL: endpoint, + CLAUDE_AGENT_API_BASE_URL: endpoint, + CLAUDE_CONFIG_DIR: settingsDir + }; + const model = normalizeClientModel(profile.model); + if (model) { + env.ANTHROPIC_MODEL = model; + env.CCR_CLAUDE_CODE_MODEL = model; + env.CODEXL_CLAUDE_CODE_MODEL = model; + } + const smallFastModel = normalizeClientModel(profile.smallFastModel); + if (smallFastModel) { + env.ANTHROPIC_SMALL_FAST_MODEL = smallFastModel; + } + return env; +} + function codexMiddlewareRuntimeFilename(): string { return "ccr-codex-cli-middleware.js"; } @@ -1032,6 +1063,60 @@ function disabledProfileStatus(profile: ProfileConfig): ProfileClientApplyStatus ); } +export function restoreInactiveGlobalProfileConfigs(profiles: ProfileConfig[]): ProfileClientApplyStatus[] { + const statuses: ProfileClientApplyStatus[] = []; + if (!profiles.some((profile) => profile.agent === "claude-code" && profile.enabled && isGlobalProfile(profile))) { + for (const file of uniqueResolvedPaths([ + "~/.claude/settings.json", + ...profiles + .filter((profile) => profile.agent === "claude-code") + .map((profile) => profile.settingsFile || "") + .filter(Boolean) + ])) { + const restoreResult = restoreGlobalConfigFile(file, { + isManagedContent: isManagedClaudeCodeSettingsContent, + mode: privateFileMode + }); + if (restoreResult.changed || restoreResult.missingBackup) { + statuses.push(inactiveGlobalCleanupStatus("claude-code", file, restoreResult)); + } + } + } + return statuses; +} + +function inactiveGlobalCleanupStatus( + client: ProfileClientKind, + file: string, + restoreResult: RestoreFileResult +): ProfileClientApplyStatus { + return { + backupFile: restoreResult.backupFile, + client, + enabled: false, + message: restoreResult.missingBackup + ? `No active global ${codexCompatibleClientName(client)} profile is configured, but the global config is managed by CCR and no original backup was found.` + : `${codexCompatibleClientName(client)} global config was restored because no active global profile is configured.`, + ok: !restoreResult.missingBackup, + path: resolveUserPath(file) + }; +} + +function uniqueResolvedPaths(paths: string[]): string[] { + const seen = new Set(); + const result: string[] = []; + for (const item of paths) { + const resolved = resolveUserPath(item); + const key = process.platform === "win32" ? resolved.toLowerCase() : resolved; + if (seen.has(key)) { + continue; + } + seen.add(key); + result.push(resolved); + } + return result; +} + function restoreDisabledZcodeProfile(profile: ProfileConfig, configFile: string): ProfileClientApplyStatus { const disabledMessage = "ZCode profile is disabled."; if (!isGlobalProfile(profile)) { diff --git a/src/server/gateway/claude-code-router-plugin.ts b/src/server/gateway/claude-code-router-plugin.ts index a824ced..89726b3 100644 --- a/src/server/gateway/claude-code-router-plugin.ts +++ b/src/server/gateway/claude-code-router-plugin.ts @@ -2,7 +2,7 @@ import { createRequire } from "node:module"; import { EventEmitter } from "node:events"; import os from "node:os"; import path from "node:path"; -import type { AppConfig, RouterBuiltInAgentRuleId, RouterConfig, RouterFallbackConfig, RouterRule, RouterRuleCondition, RouterRuleRewrite } from "../../shared/app"; +import { availableGatewayModelIds, type AppConfig, type RouterBuiltInAgentRuleId, type RouterConfig, type RouterFallbackConfig, type RouterRule, type RouterRuleCondition, type RouterRuleRewrite } from "../../shared/app"; import { CONFIGDIR } from "../../main/constants"; type HeaderValue = string | string[] | undefined; @@ -1055,16 +1055,22 @@ export function normalizeRouteSelector(value: string | undefined): string | unde } function isKnownInlineRoute(model: string | undefined, config: AppConfig): boolean { - if (!model) { + const normalizedModel = normalizeRouteSelector(model); + if (!normalizedModel) { return false; } - const separator = model.indexOf("/"); + const normalizedModelLower = normalizedModel.toLowerCase(); + if (availableGatewayModelIds(config).some((id) => id.toLowerCase() === normalizedModelLower)) { + return true; + } + + const separator = normalizedModel.indexOf("/"); if (separator <= 0) { return false; } - const providerName = model.slice(0, separator).trim().toLowerCase(); + const providerName = normalizedModel.slice(0, separator).trim().toLowerCase(); return config.Providers.some((provider) => provider.name.trim().toLowerCase() === providerName); } diff --git a/src/server/gateway/service.ts b/src/server/gateway/service.ts index f4ab704..56b9e1b 100644 --- a/src/server/gateway/service.ts +++ b/src/server/gateway/service.ts @@ -113,6 +113,12 @@ type HostedWebSearchProtocolContext = { type AnthropicWebSearchProtocolContext = HostedWebSearchProtocolContext; +type ClaudeCodeWebSearchContinuationContext = { + queryHint?: string; + sinceMs: number; + toolName: string; +}; + export type BrowserWebSearchMcpRegistration = { env?: Record; name: string; @@ -823,6 +829,33 @@ class GatewayService { } } + const claudeCodeWebSearchContinuationContext = !hostedWebSearchProtocolContext && this.browserWebSearchMcpIntegration + ? createClaudeCodeWebSearchContinuationContext({ + body: bodyToForward, + config: this.config, + method, + path, + routedModel, + sinceMs: startedAt - 5 * 60_000 + }) + : undefined; + if (claudeCodeWebSearchContinuationContext && this.browserWebSearchMcpIntegration) { + const records = selectClaudeCodeWebSearchContinuationRecords( + claudeCodeWebSearchContinuationContext, + this.browserWebSearchMcpIntegration + ); + const webSearchContinuationBody = prepareClaudeCodeWebSearchContinuationRequestBody( + bodyToForward, + records, + claudeCodeWebSearchContinuationContext + ); + if (webSearchContinuationBody) { + bodyToForward = webSearchContinuationBody; + headers["content-type"] = "application/json"; + headers["x-ccr-claude-code-web-search-continuation"] = records.length > 0 ? "in-app-browser-evidence" : "tool-result-evidence"; + } + } + delete headers["content-length"]; const upstreamUrl = new URL(request.url || "/", this.status.coreEndpoint).toString(); let upstreamResult: UpstreamFetchResult; @@ -882,7 +915,8 @@ class GatewayService { const hostedWebSearchResponseContentType = responseHeaders.get("content-type")?.toLowerCase() ?? ""; if ( hostedWebSearchProtocolContext && - hostedWebSearchResponseContentType.includes("application/json") && + (hostedWebSearchResponseContentType.includes("application/json") || + hostedWebSearchResponseContentType.includes("text/event-stream")) && (this.browserWebSearchMcpIntegration?.recentBrowserWebSearchResults || this.browserWebSearchMcpIntegration?.runBrowserWebSearch) ) { responseHeaders.delete("content-length"); @@ -2628,6 +2662,32 @@ function createAnthropicWebSearchProtocolContext(input: { return context?.protocol === "anthropic_messages" ? context : undefined; } +function createClaudeCodeWebSearchContinuationContext(input: { + body: Buffer | undefined; + config: AppConfig; + method: string; + path: string; + routedModel?: string; + sinceMs: number; +}): ClaudeCodeWebSearchContinuationContext | undefined { + if (input.method !== "POST" || requestProtocolForPath(input.path) !== "anthropic_messages") { + return undefined; + } + const body = parseJsonObjectSafe(input.body); + if (!body || claudeCodeWebSearchToolResultTexts(body).length === 0) { + return undefined; + } + const toolName = fusionWebSearchToolNameForRequest(input.config, stringValue(body.model) || input.routedModel); + if (!toolName) { + return undefined; + } + return { + queryHint: extractClaudeCodeWebSearchToolResultQuery(body) || extractAnthropicWebSearchQueryHint(body), + sinceMs: input.sinceMs, + toolName + }; +} + export function prepareHostedWebSearchProtocolRequestBody( body: Buffer | undefined, records: BrowserWebSearchProtocolRecord[], @@ -2675,6 +2735,31 @@ export function prepareAnthropicWebSearchProtocolRequestBody( return Buffer.from(`${JSON.stringify(next)}\n`, "utf8"); } +export function prepareClaudeCodeWebSearchContinuationRequestBody( + body: Buffer | undefined, + records: BrowserWebSearchProtocolRecord[], + context: Pick +): Buffer | undefined { + const parsed = parseJsonObjectSafe(body); + if (!parsed) { + return undefined; + } + const toolResultTexts = claudeCodeWebSearchToolResultTexts(parsed); + if (toolResultTexts.length === 0) { + return undefined; + } + const queryHint = context.queryHint || extractClaudeCodeWebSearchToolResultQuery(parsed) || extractAnthropicWebSearchQueryHint(parsed); + const evidence = claudeCodeWebSearchContinuationEvidenceText(records, queryHint, toolResultTexts); + if (!evidence) { + return undefined; + } + const next = applyAnthropicWebSearchSynthesisControls(stripClaudeCodeWebSearchContinuationTools({ + ...parsed, + system: appendAnthropicSystemText(parsed.system, evidence) + })); + return Buffer.from(`${JSON.stringify(next)}\n`, "utf8"); +} + function applyAnthropicWebSearchSynthesisControls(body: Record): Record { const next = { ...body }; const outputConfig = isRecord(next.output_config) ? { ...next.output_config } : {}; @@ -2741,6 +2826,16 @@ function stripAnthropicHostedWebSearchTools(body: Record): Reco return next; } +function stripClaudeCodeWebSearchContinuationTools(body: Record): Record { + if (!Array.isArray(body.tools)) { + return body; + } + const next = { ...body }; + delete next.tools; + delete next.tool_choice; + return next; +} + function stripOpenAiHostedWebSearchTools(body: Record): Record { const next = { ...body }; let removedTools = false; @@ -2877,6 +2972,29 @@ function hostedWebSearchEvidenceText(records: BrowserWebSearchProtocolRecord[], ].filter(Boolean).join("\n\n").slice(0, 10_000); } +function claudeCodeWebSearchContinuationEvidenceText( + records: BrowserWebSearchProtocolRecord[], + queryHint: string | undefined, + toolResultTexts: string[] +): string { + const browserEvidence = records.length > 0 ? hostedWebSearchEvidenceText(records, queryHint) : ""; + const toolResultEvidence = toolResultTexts + .map((text) => text.trim()) + .filter(Boolean) + .join("\n\n---\n\n") + .slice(0, 12_000); + if (!browserEvidence && !toolResultEvidence) { + return ""; + } + return [ + "A Claude Code WebSearch tool result has already been returned for this turn.", + "Answer the user's search question directly in the visible final response. Do not call any tool. Do not merely list links or ask the user to open links. Include the sources you used as markdown links.", + queryHint ? `Original search intent: ${queryHint}` : "", + browserEvidence ? `In-app browser extracted evidence:\n\n${browserEvidence}` : "", + toolResultEvidence ? `Previous WebSearch tool result:\n\n${toolResultEvidence}` : "" + ].filter(Boolean).join("\n\n"); +} + function focusedWebSearchContent(content: string | undefined, queryHint: string | undefined): string | undefined { const text = content?.replace(/\s+/g, " ").trim(); if (!text) { @@ -2901,7 +3019,7 @@ function focusedWebSearchContent(content: string | undefined, queryHint: string return `${start > 0 ? "..." : ""}${text.slice(start, start + 1_200)}${start + 1_200 < text.length ? "..." : ""}`; } -function hostedWebSearchProtocolResponseStream( +export function hostedWebSearchProtocolResponseStream( input: Readable, headers: Headers, context: HostedWebSearchProtocolContext, @@ -2912,7 +3030,10 @@ function hostedWebSearchProtocolResponseStream( } const contentType = headers.get("content-type")?.toLowerCase() ?? ""; if (contentType.includes("text/event-stream")) { - return input; + if (context.protocol === "anthropic_messages") { + return anthropicHostedWebSearchProtocolSseStream(input, context, integration); + } + return hostedWebSearchProtocolSseStream(input, context, integration); } if (!contentType.includes("application/json")) { return input; @@ -2944,6 +3065,416 @@ function hostedWebSearchProtocolResponseStream( })); } +function hostedWebSearchProtocolSseStream( + input: Readable, + context: HostedWebSearchProtocolContext, + integration: BrowserWebSearchMcpIntegration +): Readable { + const recordsPromise = selectHostedWebSearchProtocolRecords(context, integration); + let records: BrowserWebSearchProtocolRecord[] | undefined; + let pending = ""; + let passThrough = false; + const state: HostedWebSearchSseState = { + done: false, + maxOutputIndex: -1, + visibleText: false + }; + + async function ensureRecords() { + if (records || passThrough) { + return; + } + records = await recordsPromise; + passThrough = records.length === 0; + } + + return input.pipe(new Transform({ + transform(chunk, _encoding, callback) { + const text = chunk.toString(); + const rawText = pending + text; + void (async () => { + await ensureRecords(); + if (passThrough || !records) { + this.push(text); + return; + } + pending += text; + drainHostedWebSearchSseBlocks(this, pending, state, records, context, false); + pending = sseTrailingPartialBlock(pending); + })().catch((error) => { + console.warn(`[gateway] Hosted web search protocol bridge failed: ${formatError(error)}`); + this.push(rawText); + pending = ""; + passThrough = true; + }).finally(() => callback()); + }, + flush(callback) { + void (async () => { + await ensureRecords(); + if (passThrough || !records) { + if (pending) { + this.push(pending); + } + return; + } + drainHostedWebSearchSseBlocks(this, pending, state, records, context, true); + pending = ""; + })().catch((error) => { + console.warn(`[gateway] Hosted web search protocol bridge failed: ${formatError(error)}`); + if (pending) { + this.push(pending); + } + }).finally(() => callback()); + } + })); +} + +type HostedWebSearchSseState = { + done: boolean; + maxOutputIndex: number; + visibleText: boolean; +}; + +function drainHostedWebSearchSseBlocks( + stream: Transform, + text: string, + state: HostedWebSearchSseState, + records: BrowserWebSearchProtocolRecord[], + context: Pick, + flush: boolean +): void { + let cursor = 0; + for (const match of text.matchAll(/\r?\n\r?\n/g)) { + const index = match.index ?? 0; + const delimiter = match[0]; + const block = text.slice(cursor, index); + cursor = index + delimiter.length; + if (!block.trim()) { + stream.push(delimiter); + continue; + } + writeHostedWebSearchSseEvent(stream, parseSseEventBlock(block), delimiter, state, records, context); + } + if (flush) { + const block = text.slice(cursor); + if (block.trim()) { + writeHostedWebSearchSseEvent(stream, parseSseEventBlock(block), "", state, records, context); + } else if (block) { + stream.push(block); + } + writeHostedWebSearchSseFallback(stream, state, records, context); + } +} + +function writeHostedWebSearchSseEvent( + stream: Transform, + event: ParsedSseEvent, + delimiter: string, + state: HostedWebSearchSseState, + records: BrowserWebSearchProtocolRecord[], + context: Pick +): void { + updateHostedWebSearchSseState(event, state, context.protocol); + const isDone = sseEventIsDone(event); + const isOpenAiResponsesCompleted = context.protocol === "openai_responses" && + isRecord(event.data) && + stringValue(event.data.type) === "response.completed"; + if ((isDone || isOpenAiResponsesCompleted) && !state.done) { + writeHostedWebSearchSseFallback(stream, state, records, context); + } + const nextEvent = context.protocol === "openai_chat_completions" + ? updateOpenAiChatSseFinishReason(event) + : context.protocol === "openai_responses" + ? updateOpenAiResponsesCompletedStatus(event) + : event; + stream.push(`${serializeSseEvent(nextEvent)}${delimiter}`); +} + +function updateHostedWebSearchSseState( + event: ParsedSseEvent, + state: HostedWebSearchSseState, + protocol: GatewayProviderProtocol +): void { + if (protocol === "openai_chat_completions") { + state.visibleText ||= openAiChatSseContainsVisibleText([event]); + return; + } + if (protocol === "openai_responses") { + state.visibleText ||= openAiResponsesSseContainsVisibleText([event]); + if (isRecord(event.data)) { + const outputIndex = numberValue(event.data.output_index); + if (outputIndex !== undefined) { + state.maxOutputIndex = Math.max(state.maxOutputIndex, outputIndex); + } + } + return; + } + if (protocol === "gemini_generate_content") { + state.visibleText ||= geminiSseContainsVisibleText([event]); + } +} + +function writeHostedWebSearchSseFallback( + stream: Transform, + state: HostedWebSearchSseState, + records: BrowserWebSearchProtocolRecord[], + context: Pick +): void { + if (state.done || state.visibleText) { + return; + } + const answer = synthesizeWebSearchAnswer(records, context.queryHint); + if (!answer) { + state.done = true; + return; + } + for (const event of hostedWebSearchSseFallbackEvents(answer, state, context)) { + stream.push(`${serializeSseEvent(event)}\n\n`); + } + state.visibleText = true; + state.done = true; +} + +function hostedWebSearchSseFallbackEvents( + answer: string, + state: HostedWebSearchSseState, + context: Pick +): ParsedSseEvent[] { + if (context.protocol === "openai_chat_completions") { + return [sseEventFromValue({ + object: "chat.completion.chunk", + choices: [ + { + delta: { content: answer }, + finish_reason: null, + index: 0 + } + ] + })]; + } + if (context.protocol === "openai_responses") { + return openAiResponsesSseAnswerEvents(answer, context.requestId, state.maxOutputIndex + 1); + } + if (context.protocol === "gemini_generate_content") { + return [sseEventFromValue(geminiAnswerCandidateChunk(answer))]; + } + return []; +} + +function anthropicHostedWebSearchProtocolSseStream( + input: Readable, + context: HostedWebSearchProtocolContext, + integration: BrowserWebSearchMcpIntegration +): Readable { + const recordsPromise = selectHostedWebSearchProtocolRecords(context, integration); + let records: BrowserWebSearchProtocolRecord[] | undefined; + let pending = ""; + let passThrough = false; + const state: AnthropicHostedWebSearchSseState = { + answerInjected: false, + hasClientToolUse: false, + hasWebSearchBlocks: false, + injectedBlockCount: 0, + insertedSearchBlocks: false, + maxIndex: -1, + visibleText: false + }; + + async function ensureRecords() { + if (records || passThrough) { + return; + } + records = await recordsPromise; + passThrough = records.length === 0; + } + + return input.pipe(new Transform({ + transform(chunk, _encoding, callback) { + const text = chunk.toString(); + const rawText = pending + text; + void (async () => { + await ensureRecords(); + if (passThrough || !records) { + this.push(text); + return; + } + pending += text; + drainAnthropicHostedWebSearchSseBlocks(this, pending, state, records, context, false); + pending = sseTrailingPartialBlock(pending); + })().catch((error) => { + console.warn(`[gateway] Hosted web search protocol bridge failed: ${formatError(error)}`); + this.push(rawText); + pending = ""; + passThrough = true; + }).finally(() => callback()); + }, + flush(callback) { + void (async () => { + await ensureRecords(); + if (passThrough || !records) { + if (pending) { + this.push(pending); + } + return; + } + drainAnthropicHostedWebSearchSseBlocks(this, pending, state, records, context, true); + pending = ""; + })().catch((error) => { + console.warn(`[gateway] Hosted web search protocol bridge failed: ${formatError(error)}`); + if (pending) { + this.push(pending); + } + }).finally(() => callback()); + } + })); +} + +type AnthropicHostedWebSearchSseState = { + answerInjected: boolean; + hasClientToolUse: boolean; + hasWebSearchBlocks: boolean; + injectedBlockCount: number; + insertedSearchBlocks: boolean; + insertIndex?: number; + maxIndex: number; + visibleText: boolean; +}; + +function drainAnthropicHostedWebSearchSseBlocks( + stream: Transform, + text: string, + state: AnthropicHostedWebSearchSseState, + records: BrowserWebSearchProtocolRecord[], + context: Pick, + flush: boolean +): void { + let cursor = 0; + for (const match of text.matchAll(/\r?\n\r?\n/g)) { + const index = match.index ?? 0; + const delimiter = match[0]; + const block = text.slice(cursor, index); + cursor = index + delimiter.length; + if (!block.trim()) { + stream.push(delimiter); + continue; + } + writeAnthropicHostedWebSearchSseEvent( + stream, + parseSseEventBlock(block), + delimiter, + state, + records, + context + ); + } + if (flush) { + const block = text.slice(cursor); + if (block.trim()) { + writeAnthropicHostedWebSearchSseEvent( + stream, + parseSseEventBlock(block), + "", + state, + records, + context + ); + } else if (block) { + stream.push(block); + } + } +} + +function sseTrailingPartialBlock(text: string): string { + let cursor = 0; + for (const match of text.matchAll(/\r?\n\r?\n/g)) { + cursor = (match.index ?? 0) + match[0].length; + } + return text.slice(cursor); +} + +function writeAnthropicHostedWebSearchSseEvent( + stream: Transform, + event: ParsedSseEvent, + delimiter: string, + state: AnthropicHostedWebSearchSseState, + records: BrowserWebSearchProtocolRecord[], + context: Pick +): void { + updateAnthropicHostedWebSearchSseState(event, state); + const textStartIndex = anthropicSseTextBlockStartIndex(event); + const isMessageEnd = sseEventIsAnthropicMessageEnd(event); + const answer = !state.hasClientToolUse && !state.visibleText && !state.answerInjected && isMessageEnd + ? synthesizeWebSearchAnswer(records, context.queryHint) + : undefined; + + if (!state.insertedSearchBlocks && (textStartIndex !== undefined || isMessageEnd)) { + const insertIndex = textStartIndex ?? state.maxIndex + 1; + insertAnthropicHostedWebSearchSseBlocks(stream, state, records, context.requestId, insertIndex); + } + if (answer && isMessageEnd) { + const answerIndex = state.maxIndex + state.injectedBlockCount + 1; + insertAnthropicHostedWebSearchSseAnswer(stream, state, answer, answerIndex); + } + + const nextEvent = updateAnthropicWebSearchSseUsage( + shiftAnthropicHostedWebSearchSseEvent(event, state), + records.length, + Boolean(answer), + state.hasClientToolUse + ); + stream.push(`${serializeSseEvent(nextEvent)}${delimiter}`); +} + +function updateAnthropicHostedWebSearchSseState(event: ParsedSseEvent, state: AnthropicHostedWebSearchSseState): void { + if (isRecord(event.data) && Number.isFinite(event.data.index)) { + state.maxIndex = Math.max(state.maxIndex, Number(event.data.index)); + } + state.hasWebSearchBlocks ||= sseEventContainsAnthropicWebSearchBlock(event); + state.hasClientToolUse ||= sseEventContainsAnthropicClientToolUse(event); + state.visibleText ||= sseEventContainsVisibleText(event); +} + +function insertAnthropicHostedWebSearchSseBlocks( + stream: Transform, + state: AnthropicHostedWebSearchSseState, + records: BrowserWebSearchProtocolRecord[], + requestId: string, + insertIndex: number +): void { + state.insertedSearchBlocks = true; + state.insertIndex = insertIndex; + if (state.hasWebSearchBlocks) { + return; + } + const blocks = anthropicWebSearchProtocolBlocks(records, requestId); + for (const event of blocks.flatMap((block, offset) => anthropicWebSearchSseEventsForBlock(block, insertIndex + offset))) { + stream.push(`${serializeSseEvent(event)}\n\n`); + } + state.injectedBlockCount += blocks.length; +} + +function insertAnthropicHostedWebSearchSseAnswer( + stream: Transform, + state: AnthropicHostedWebSearchSseState, + answer: string, + answerIndex: number +): void { + state.answerInjected = true; + for (const event of anthropicWebSearchSseEventsForBlock({ text: answer, type: "text" }, answerIndex)) { + stream.push(`${serializeSseEvent(event)}\n\n`); + } +} + +function shiftAnthropicHostedWebSearchSseEvent( + event: ParsedSseEvent, + state: AnthropicHostedWebSearchSseState +): ParsedSseEvent { + if (!state.insertedSearchBlocks || state.insertIndex === undefined || state.injectedBlockCount === 0) { + return event; + } + return shiftSseContentBlockIndex(event, state.insertIndex, state.injectedBlockCount); +} + function transformHostedWebSearchProtocolResponseValue( value: unknown, records: BrowserWebSearchProtocolRecord[], @@ -2995,7 +3526,8 @@ export function transformAnthropicWebSearchProtocolResponseValue( } const hasWebSearchBlocks = responseValueContainsAnthropicWebSearchBlocks(value); const blocks = hasWebSearchBlocks ? [] : anthropicWebSearchProtocolBlocks(records, requestId); - const answer = responseValueContainsVisibleText(value) + const hasClientToolUse = responseValueContainsAnthropicClientToolUse(value); + const answer = hasClientToolUse || responseValueContainsVisibleText(value) ? undefined : synthesizeWebSearchAnswer(records, queryHint); const injectedBlocks = [ @@ -3010,7 +3542,7 @@ export function transformAnthropicWebSearchProtocolResponseValue( const nextValue = { ...value, ...(shouldUpdateUsage ? { usage: mergeAnthropicWebSearchUsage(value.usage, records.length) } : {}), - ...(answer && stringValue(value.stop_reason) === "max_tokens" ? { stop_reason: "end_turn" } : {}), + ...(shouldEndAnthropicHostedWebSearchTurn(value.stop_reason, Boolean(answer), hasClientToolUse) ? { stop_reason: "end_turn" } : {}), content: injectedBlocks.length > 0 ? [ ...value.content.slice(0, insertAt), @@ -3037,7 +3569,8 @@ export function transformAnthropicWebSearchProtocolSseText( } const hasWebSearchBlocks = sseEventsContainAnthropicWebSearchBlocks(events); const blocks = hasWebSearchBlocks ? [] : anthropicWebSearchProtocolBlocks(records, requestId); - const answer = sseEventsContainVisibleText(events) + const hasClientToolUse = sseEventsContainAnthropicClientToolUse(events); + const answer = hasClientToolUse || sseEventsContainVisibleText(events) ? undefined : synthesizeWebSearchAnswer(records, queryHint); const injectedBlocks = [ @@ -3076,7 +3609,7 @@ export function transformAnthropicWebSearchProtocolSseText( const shiftedEvents = events .map((event) => shiftSseContentBlockIndex(event, insertIndex, injectedBlocks.length)) - .map((event) => updateAnthropicWebSearchSseUsage(event, records.length, Boolean(answer))); + .map((event) => updateAnthropicWebSearchSseUsage(event, records.length, Boolean(answer), hasClientToolUse)); const injectedEvents = injectedBlocks.flatMap((block, offset) => anthropicWebSearchSseEventsForBlock(block, insertIndex + offset)); shiftedEvents.splice(insertPosition, 0, ...injectedEvents); return `${shiftedEvents.map(serializeSseEvent).join("\n\n")}\n\n`; @@ -3199,59 +3732,7 @@ export function transformOpenAiResponsesHostedWebSearchSseText( return serializeOpenAiResponsesSseEvents(normalizeOpenAiResponsesSseEvents(events)); } const outputIndex = nextOpenAiResponsesOutputIndex(events); - const itemId = `msg_${sanitizeAnthropicToolUseId(requestId)}_web_search_answer`; - const contentIndex = 0; - const injected = [ - sseEventFromValue({ - item: { - id: itemId, - role: "assistant", - status: "in_progress", - type: "message" - }, - output_index: outputIndex, - type: "response.output_item.added" - }), - sseEventFromValue({ - content_index: contentIndex, - item_id: itemId, - output_index: outputIndex, - part: { annotations: [], text: "", type: "output_text" }, - type: "response.content_part.added" - }), - sseEventFromValue({ - content_index: contentIndex, - delta: answer, - item_id: itemId, - output_index: outputIndex, - type: "response.output_text.delta" - }), - sseEventFromValue({ - content_index: contentIndex, - item_id: itemId, - output_index: outputIndex, - text: answer, - type: "response.output_text.done" - }), - sseEventFromValue({ - content_index: contentIndex, - item_id: itemId, - output_index: outputIndex, - part: { annotations: [], text: answer, type: "output_text" }, - type: "response.content_part.done" - }), - sseEventFromValue({ - item: { - content: [{ annotations: [], text: answer, type: "output_text" }], - id: itemId, - role: "assistant", - status: "completed", - type: "message" - }, - output_index: outputIndex, - type: "response.output_item.done" - }) - ]; + const injected = openAiResponsesSseAnswerEvents(answer, requestId, outputIndex); const shifted = events.map(updateOpenAiResponsesCompletedStatus); const insertAt = shifted.findIndex((event) => isRecord(event.data) && stringValue(event.data.type) === "response.completed"); shifted.splice(insertAt >= 0 ? insertAt : doneSseEventIndex(shifted) >= 0 ? doneSseEventIndex(shifted) : shifted.length, 0, ...injected); @@ -3446,6 +3927,62 @@ function nextOpenAiResponsesOutputIndex(events: ParsedSseEvent[]): number { return indexes.length === 0 ? 0 : Math.max(...indexes) + 1; } +function openAiResponsesSseAnswerEvents(answer: string, requestId: string, outputIndex: number): ParsedSseEvent[] { + const itemId = `msg_${sanitizeAnthropicToolUseId(requestId)}_web_search_answer`; + const contentIndex = 0; + return [ + sseEventFromValue({ + item: { + id: itemId, + role: "assistant", + status: "in_progress", + type: "message" + }, + output_index: outputIndex, + type: "response.output_item.added" + }), + sseEventFromValue({ + content_index: contentIndex, + item_id: itemId, + output_index: outputIndex, + part: { annotations: [], text: "", type: "output_text" }, + type: "response.content_part.added" + }), + sseEventFromValue({ + content_index: contentIndex, + delta: answer, + item_id: itemId, + output_index: outputIndex, + type: "response.output_text.delta" + }), + sseEventFromValue({ + content_index: contentIndex, + item_id: itemId, + output_index: outputIndex, + text: answer, + type: "response.output_text.done" + }), + sseEventFromValue({ + content_index: contentIndex, + item_id: itemId, + output_index: outputIndex, + part: { annotations: [], text: answer, type: "output_text" }, + type: "response.content_part.done" + }), + sseEventFromValue({ + item: { + content: [{ annotations: [], text: answer, type: "output_text" }], + id: itemId, + role: "assistant", + status: "completed", + type: "message" + }, + output_index: outputIndex, + type: "response.output_item.done" + }) + ]; +} + function updateOpenAiResponsesCompletedStatus(event: ParsedSseEvent): ParsedSseEvent { if (!isRecord(event.data) || stringValue(event.data.type) !== "response.completed") { return event; @@ -3602,6 +4139,10 @@ function doneSseEventIndex(events: ParsedSseEvent[]): number { return events.findIndex((event) => event.raw?.includes("[DONE]")); } +function sseEventIsDone(event: ParsedSseEvent): boolean { + return Boolean(event.raw?.includes("[DONE]")); +} + function hasAnthropicHostedWebSearchTool(tools: unknown): boolean { if (!Array.isArray(tools)) { return false; @@ -3731,6 +4272,20 @@ function extractAnthropicWebSearchQueryHint(body: Record): stri return normalizedWebSearchQueryHintFromParts(userTexts); } +function extractClaudeCodeWebSearchToolResultQuery(body: Record): string | undefined { + for (const text of claudeCodeWebSearchToolResultTexts(body)) { + const quoted = /Web search results for query:\s*"([^"]+)"/i.exec(text); + if (quoted?.[1]) { + return normalizedWebSearchQueryHint(quoted[1]); + } + const unquoted = /Web search results for query:\s*([^\n]+)/i.exec(text); + if (unquoted?.[1]) { + return normalizedWebSearchQueryHint(unquoted[1].replace(/^["']|["']$/g, "")); + } + } + return undefined; +} + function normalizedWebSearchQueryHintFromParts(parts: string[]): string | undefined { const candidates = parts .map((part) => part.trim()) @@ -3799,6 +4354,75 @@ function textPartsFromAnthropicContent(content: unknown): string[] { return content.flatMap((part) => isRecord(part) && typeof part.text === "string" ? [part.text] : []); } +function claudeCodeWebSearchToolResultTexts(body: Record): string[] { + if (!Array.isArray(body.messages)) { + return []; + } + const lastMessage = body.messages.at(-1); + if (!isRecord(lastMessage) || stringValue(lastMessage.role) !== "user" || !Array.isArray(lastMessage.content)) { + return []; + } + const latestToolResults = lastMessage.content.filter((part) => isRecord(part) && stringValue(part.type) === "tool_result"); + if (latestToolResults.length === 0) { + return []; + } + const webSearchToolUseIds = new Set(); + for (let index = body.messages.length - 2; index >= 0; index -= 1) { + const message = body.messages[index]; + if (!isRecord(message) || stringValue(message.role) !== "assistant" || !Array.isArray(message.content)) { + continue; + } + for (const part of message.content) { + if (!isRecord(part) || stringValue(part.type) !== "tool_use" || stringValue(part.name)?.toLowerCase() !== "websearch") { + continue; + } + const id = stringValue(part.id); + if (id) { + webSearchToolUseIds.add(id); + } + } + break; + } + if (webSearchToolUseIds.size === 0) { + return []; + } + const texts: string[] = []; + for (const part of latestToolResults) { + if (!isRecord(part)) { + continue; + } + const toolUseId = stringValue(part.tool_use_id); + if (!toolUseId || !webSearchToolUseIds.has(toolUseId)) { + continue; + } + const text = anthropicToolResultContentText(part.content); + if (text) { + texts.push(text); + } + } + return texts; +} + +function anthropicToolResultContentText(content: unknown): string { + if (typeof content === "string") { + return content; + } + if (!Array.isArray(content)) { + return ""; + } + return content.flatMap((part) => { + if (!isRecord(part)) { + return []; + } + const type = stringValue(part.type); + if (type === "text" || type === "input_text" || type === "output_text") { + const text = stringValue(part.text); + return text ? [text] : []; + } + return []; + }).join("\n"); +} + function textPartsFromOpenAiChatMessages(messages: unknown): string[] { if (!Array.isArray(messages)) { return []; @@ -3868,11 +4492,21 @@ function textPartsFromGeminiContents(contents: unknown): string[] { export function fusionWebSearchToolNameForRequest(config: AppConfig, model: string | undefined): string | undefined { const normalizedModel = model ? fusionModelNameFromSelector(model) : ""; + for (const candidate of fusionBrowserWebSearchToolCandidates(config)) { + if (!normalizedModel || candidate.aliases.some((alias) => fusionModelNameFromSelector(alias).toLowerCase() === normalizedModel.toLowerCase())) { + return candidate.toolName; + } + } + return undefined; +} + +function fusionBrowserWebSearchToolCandidates(config: AppConfig): Array<{ aliases: string[]; toolName: string }> { const rawProfiles = Array.isArray(config.virtualModelProfiles) ? config.virtualModelProfiles : []; const profiles = normalizeCoreGatewayVirtualModelProfiles( withCodexCompatibleVirtualModelProfiles(withFusionVirtualModelAliases(rawProfiles)), config ); + const candidates: Array<{ aliases: string[]; toolName: string }> = []; for (const profile of profiles) { if (!isRecord(profile) || profile.enabled === false) { continue; @@ -3890,11 +4524,9 @@ export function fusionWebSearchToolNameForRequest(config: AppConfig, model: stri stringValue(profile.displayName), ...stringListValue(match?.exactAliases) ].filter((item): item is string => Boolean(item))); - if (!normalizedModel || aliases.some((alias) => fusionModelNameFromSelector(alias).toLowerCase() === normalizedModel.toLowerCase())) { - return webSearchConfig.toolName; - } + candidates.push({ aliases, toolName: webSearchConfig.toolName }); } - return undefined; + return candidates; } async function selectHostedWebSearchProtocolRecords( @@ -3926,6 +4558,24 @@ async function selectHostedWebSearchProtocolRecords( return record?.results.length ? [record] : []; } +function selectClaudeCodeWebSearchContinuationRecords( + context: ClaudeCodeWebSearchContinuationContext, + integration: BrowserWebSearchMcpIntegration +): BrowserWebSearchProtocolRecord[] { + const records = integration.recentBrowserWebSearchResults?.({ + sinceMs: context.sinceMs, + toolName: context.toolName + }) ?? []; + return records + .filter((record) => record.results.length > 0) + .filter(uniqueSearchRecordFilter()) + .sort((left, right) => { + const queryScoreDelta = queryMatchScore(context.queryHint, right.query) - queryMatchScore(context.queryHint, left.query); + return queryScoreDelta || right.completedAtMs - left.completedAtMs; + }) + .slice(0, 3); +} + async function selectAnthropicWebSearchProtocolRecords( context: AnthropicWebSearchProtocolContext, integration: BrowserWebSearchMcpIntegration @@ -3985,6 +4635,12 @@ function responseValueContainsVisibleText(value: Record): boole }); } +function responseValueContainsAnthropicClientToolUse(value: Record): boolean { + return Array.isArray(value.content) && value.content.some((block) => { + return isRecord(block) && stringValue(block.type) === "tool_use"; + }); +} + function leadingThinkingBlockCount(content: unknown[]): number { let index = 0; while (index < content.length) { @@ -4024,28 +4680,59 @@ function mergeAnthropicWebSearchUsage(usage: unknown, searchCount: number): Reco function sseEventsContainAnthropicWebSearchBlocks(events: ParsedSseEvent[]): boolean { return events.some((event) => { - const data = isRecord(event.data) ? event.data : undefined; - const block = isRecord(data?.content_block) ? data.content_block : undefined; - const type = stringValue(block?.type) || stringValue(data?.type); - return type === "server_tool_use" || type === "web_search_tool_result"; + return sseEventContainsAnthropicWebSearchBlock(event); }); } function sseEventsContainVisibleText(events: ParsedSseEvent[]): boolean { - return events.some((event) => { - const data = isRecord(event.data) ? event.data : undefined; - if (!data) { - return false; - } - const block = isRecord(data.content_block) ? data.content_block : undefined; - if (stringValue(data.type) === "content_block_start" && stringValue(block?.type) === "text") { - return Boolean(stringValue(block?.text)?.trim()); - } - const delta = isRecord(data.delta) ? data.delta : undefined; - return stringValue(data.type) === "content_block_delta" && - stringValue(delta?.type) === "text_delta" && - Boolean(stringValue(delta?.text)?.trim()); - }); + return events.some(sseEventContainsVisibleText); +} + +function sseEventContainsAnthropicWebSearchBlock(event: ParsedSseEvent): boolean { + const data = isRecord(event.data) ? event.data : undefined; + const block = isRecord(data?.content_block) ? data.content_block : undefined; + const type = stringValue(block?.type) || stringValue(data?.type); + return type === "server_tool_use" || type === "web_search_tool_result"; +} + +function sseEventContainsVisibleText(event: ParsedSseEvent): boolean { + const data = isRecord(event.data) ? event.data : undefined; + if (!data) { + return false; + } + const block = isRecord(data.content_block) ? data.content_block : undefined; + if (stringValue(data.type) === "content_block_start" && stringValue(block?.type) === "text") { + return Boolean(stringValue(block?.text)?.trim()); + } + const delta = isRecord(data.delta) ? data.delta : undefined; + return stringValue(data.type) === "content_block_delta" && + stringValue(delta?.type) === "text_delta" && + Boolean(stringValue(delta?.text)?.trim()); +} + +function sseEventsContainAnthropicClientToolUse(events: ParsedSseEvent[]): boolean { + return events.some(sseEventContainsAnthropicClientToolUse); +} + +function sseEventContainsAnthropicClientToolUse(event: ParsedSseEvent): boolean { + const data = isRecord(event.data) ? event.data : undefined; + const block = isRecord(data?.content_block) ? data.content_block : undefined; + return stringValue(data?.type) === "content_block_start" && stringValue(block?.type) === "tool_use"; +} + +function anthropicSseTextBlockStartIndex(event: ParsedSseEvent): number | undefined { + const data = isRecord(event.data) ? event.data : undefined; + const block = isRecord(data?.content_block) ? data.content_block : undefined; + if (stringValue(data?.type) !== "content_block_start" || stringValue(block?.type) !== "text") { + return undefined; + } + const index = numberValue(data?.index); + return index === undefined ? undefined : index; +} + +function sseEventIsAnthropicMessageEnd(event: ParsedSseEvent): boolean { + const type = isRecord(event.data) ? stringValue(event.data.type) : undefined; + return type === "message_delta" || type === "message_stop"; } function anthropicWebSearchSseEventsForBlock(block: Record, index: number): ParsedSseEvent[] { @@ -4081,7 +4768,12 @@ function anthropicWebSearchSseEventsForBlock(block: Record, ind ]; } -function updateAnthropicWebSearchSseUsage(event: ParsedSseEvent, searchCount: number, didSynthesizeAnswer: boolean): ParsedSseEvent { +function updateAnthropicWebSearchSseUsage( + event: ParsedSseEvent, + searchCount: number, + didSynthesizeAnswer: boolean, + hasClientToolUse: boolean +): ParsedSseEvent { if (!isRecord(event.data) || stringValue(event.data.type) !== "message_delta") { return event; } @@ -4090,7 +4782,7 @@ function updateAnthropicWebSearchSseUsage(event: ParsedSseEvent, searchCount: nu ...event.data, usage: mergeAnthropicWebSearchUsage(event.data.usage, searchCount) }; - if (didSynthesizeAnswer && isRecord(delta) && stringValue(delta.stop_reason) === "max_tokens") { + if (isRecord(delta) && shouldEndAnthropicHostedWebSearchTurn(delta.stop_reason, didSynthesizeAnswer, hasClientToolUse)) { nextData.delta = { ...delta, stop_reason: "end_turn" }; } return { @@ -4099,12 +4791,28 @@ function updateAnthropicWebSearchSseUsage(event: ParsedSseEvent, searchCount: nu }; } +function shouldEndAnthropicHostedWebSearchTurn( + stopReason: unknown, + didSynthesizeAnswer: boolean, + hasClientToolUse: boolean +): boolean { + if (hasClientToolUse) { + return false; + } + const normalized = stringValue(stopReason); + return normalized === "tool_use" || (didSynthesizeAnswer && normalized === "max_tokens"); +} + function synthesizeWebSearchAnswer(records: BrowserWebSearchProtocolRecord[], queryHint: string | undefined): string | undefined { const query = queryHint || records.map((record) => record.query).find(Boolean) || ""; const weatherAnswer = synthesizeWeatherWebSearchAnswer(records, query); if (weatherAnswer) { return weatherAnswer; } + const componentChangelogAnswer = synthesizeComponentChangelogWebSearchAnswer(records, query); + if (componentChangelogAnswer) { + return componentChangelogAnswer; + } const evidence = topWebSearchEvidenceSentences(records, query, 3); if (evidence.length === 0) { const sources = webSearchSourceNames(records, 3); @@ -4121,6 +4829,74 @@ function synthesizeWebSearchAnswer(records: BrowserWebSearchProtocolRecord[], qu : `Based on the search results, ${evidence.join("; ")}.${sources ? ` Sources: ${sources}.` : ""}`; } +function synthesizeComponentChangelogWebSearchAnswer(records: BrowserWebSearchProtocolRecord[], query: string): string | undefined { + const normalizedQuery = normalizeSearchComparisonText(query); + const asksForComponents = /component|components|组件/i.test(query); + const asksForNewOrChangelog = /new|latest|recent|changelog|release|新增|新组件|最新|更新|官方/i.test(query); + if (!asksForComponents || !asksForNewOrChangelog) { + return undefined; + } + const items = webSearchEvidenceItems(records); + const preferred = items.find((item) => { + const normalized = normalizeSearchComparisonText(`${item.source} ${item.url} ${item.text.slice(0, 500)}`); + return normalized.includes("changelog") || normalized.includes("official") || normalized.includes("docs") || normalizedQuery.includes("official"); + }) ?? items[0]; + if (!preferred) { + return undefined; + } + const release = extractComponentReleaseTitle(preferred.text); + const components = extractLikelyComponentNames(preferred.text); + const sources = webSearchSourceNames(records, 2); + const cjk = containsCjkText(query); + if (!release && components.length === 0) { + return undefined; + } + if (cjk) { + return [ + release ? `官方相关条目是 ${release}` : "官方页面包含新增组件相关内容", + components.length > 0 ? `可提取到的相关组件包括 ${components.join("、")}` : "", + sources ? `来源:${sources}。` : "" + ].filter(Boolean).join(";"); + } + return [ + release ? `The relevant official entry is ${release}` : "The official page contains new component information", + components.length > 0 ? `extractable related components include ${components.join(", ")}` : "", + sources ? `Sources: ${sources}.` : "" + ].filter(Boolean).join("; "); +} + +function extractComponentReleaseTitle(text: string): string | undefined { + const patterns = [ + /((?:January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{4}\s*-\s*Components?[^.。!?]{0,90})/i, + /(\d{4}[-/]\d{1,2}[^.。!?]{0,60}Components?[^.。!?]{0,60})/i + ]; + for (const pattern of patterns) { + const match = pattern.exec(text); + const title = match?.[1]?.replace(/\s+/g, " ").trim(); + if (title) { + return title; + } + } + return undefined; +} + +function extractLikelyComponentNames(text: string): string[] { + const knownNames = [ + "Message Scroller", + "Message", + "Attachment", + "Bubble", + "Marker", + "Empty", + "Item", + "Field", + "Input OTP", + "Button Group" + ]; + const lower = text.toLowerCase(); + return knownNames.filter((name) => lower.includes(name.toLowerCase())).slice(0, 10); +} + function synthesizeWeatherWebSearchAnswer(records: BrowserWebSearchProtocolRecord[], query: string): string | undefined { if (!/天气|气温|温度|weather|forecast|temperature/i.test(query)) { return undefined; @@ -4314,7 +5090,7 @@ function anthropicWebSearchProtocolBlocks(records: BrowserWebSearchProtocolRecor } function anthropicWebSearchResultBlock(result: BrowserWebSearchProtocolResult): Record { - const snippet = result.snippet || result.content; + const snippet = anthropicWebSearchResultSnippet(result); return { encrypted_content: "", ...(snippet ? { snippet: snippet.slice(0, 1_200) } : {}), @@ -4324,6 +5100,14 @@ function anthropicWebSearchResultBlock(result: BrowserWebSearchProtocolResult): }; } +function anthropicWebSearchResultSnippet(result: BrowserWebSearchProtocolResult): string | undefined { + const parts = [ + result.snippet ? `Search snippet: ${sanitizeWebSearchEvidenceText(result.snippet)}` : "", + result.content ? `Extracted page content: ${sanitizeWebSearchEvidenceText(result.content)}` : "" + ].filter(Boolean); + return parts.length > 0 ? parts.join("\n") : undefined; +} + function sanitizeAnthropicToolUseId(value: string): string { return value.replace(/[^a-zA-Z0-9]/g, "").slice(0, 24) || randomBytes(8).toString("hex"); } @@ -4338,25 +5122,27 @@ function parseSseEvents(body: string): ParsedSseEvent[] { return body .split(/\r?\n\r?\n/g) .filter((block) => block.trim()) - .map((raw) => { - const lines = raw.split(/\r?\n/g); - const event = lines - .filter((line) => line.startsWith("event:")) - .map((line) => line.slice(6).trim()) - .find(Boolean); - const data = lines - .filter((line) => line.startsWith("data:")) - .map((line) => line.slice(5).replace(/^ /, "")) - .join("\n"); - if (!data || data === "[DONE]") { - return { event, raw }; - } - try { - return { data: JSON.parse(data) as unknown, event, raw }; - } catch { - return { event, raw }; - } - }); + .map(parseSseEventBlock); +} + +function parseSseEventBlock(raw: string): ParsedSseEvent { + const lines = raw.split(/\r?\n/g); + const event = lines + .filter((line) => line.startsWith("event:")) + .map((line) => line.slice(6).trim()) + .find(Boolean); + const data = lines + .filter((line) => line.startsWith("data:")) + .map((line) => line.slice(5).replace(/^ /, "")) + .join("\n"); + if (!data || data === "[DONE]") { + return { event, raw }; + } + try { + return { data: JSON.parse(data) as unknown, event, raw }; + } catch { + return { event, raw }; + } } function shiftSseContentBlockIndex(event: ParsedSseEvent, startIndex: number, delta: number): ParsedSseEvent { diff --git a/tests/main/codex-cli-middleware-runtime.test.mjs b/tests/main/codex-cli-middleware-runtime.test.mjs new file mode 100644 index 0000000..4a06989 --- /dev/null +++ b/tests/main/codex-cli-middleware-runtime.test.mjs @@ -0,0 +1,86 @@ +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { chmodSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { codexCliMiddlewareRuntimeScript } from "../../src/main/codex-cli-middleware-runtime.ts"; + +test("generated Codex CLI middleware runtime is valid JavaScript", () => { + const dir = mkdtempSync(path.join(os.tmpdir(), "ccr-runtime-check-")); + const file = path.join(dir, "ccr-codex-cli-middleware.js"); + writeFileSync(file, codexCliMiddlewareRuntimeScript()); + execFileSync(process.execPath, ["--check", file], { stdio: "pipe" }); +}); + +test("Claude Code wrapper injects the scoped profile model into real CLI args", { skip: process.platform === "win32" }, () => { + const dir = mkdtempSync(path.join(os.tmpdir(), "ccr-runtime-wrapper-")); + const runtimeFile = writeRuntimeScript(dir); + const { fakeCli, outputFile } = writeFakeClaudeCli(dir); + + execFileSync(process.execPath, [runtimeFile, "-p", "hi"], { + env: { + ...process.env, + ANTHROPIC_MODEL: "Fusion/kimisearch", + CCR_CLAUDE_CODE_MODEL: "Fusion/kimisearch", + CCR_CLAUDE_CODE_WRAPPER: "1", + CCR_FAKE_CLAUDE_OUT: outputFile, + CCR_REAL_CLAUDE_CODE_BIN: fakeCli, + CCR_REMOTE_SYNC_ENABLED: "0" + }, + stdio: "pipe" + }); + + const observed = JSON.parse(readFileSync(outputFile, "utf8")); + assert.deepEqual(observed.argv, ["--model", "Fusion/kimisearch", "-p", "hi"]); + assert.equal(observed.env.ANTHROPIC_MODEL, "Fusion/kimisearch"); + assert.equal(observed.env.CCR_CLAUDE_CODE_MODEL, "Fusion/kimisearch"); +}); + +test("Claude Code wrapper does not duplicate an explicit model argument", { skip: process.platform === "win32" }, () => { + const dir = mkdtempSync(path.join(os.tmpdir(), "ccr-runtime-wrapper-")); + const runtimeFile = writeRuntimeScript(dir); + const { fakeCli, outputFile } = writeFakeClaudeCli(dir); + + execFileSync(process.execPath, [runtimeFile, "--model", "Provider/manual", "-p", "hi"], { + env: { + ...process.env, + ANTHROPIC_MODEL: "Fusion/kimisearch", + CCR_CLAUDE_CODE_MODEL: "Fusion/kimisearch", + CCR_CLAUDE_CODE_WRAPPER: "1", + CCR_FAKE_CLAUDE_OUT: outputFile, + CCR_REAL_CLAUDE_CODE_BIN: fakeCli, + CCR_REMOTE_SYNC_ENABLED: "0" + }, + stdio: "pipe" + }); + + const observed = JSON.parse(readFileSync(outputFile, "utf8")); + assert.deepEqual(observed.argv, ["--model", "Provider/manual", "-p", "hi"]); +}); + +function writeRuntimeScript(dir) { + const file = path.join(dir, "ccr-codex-cli-middleware.js"); + writeFileSync(file, codexCliMiddlewareRuntimeScript()); + chmodSync(file, 0o700); + return file; +} + +function writeFakeClaudeCli(dir) { + const fakeCli = path.join(dir, "fake-claude"); + const outputFile = path.join(dir, "fake-claude-output.json"); + writeFileSync(fakeCli, [ + "#!/usr/bin/env node", + "const fs = require('node:fs');", + "fs.writeFileSync(process.env.CCR_FAKE_CLAUDE_OUT, JSON.stringify({", + " argv: process.argv.slice(2),", + " env: {", + " ANTHROPIC_MODEL: process.env.ANTHROPIC_MODEL || '',", + " CCR_CLAUDE_CODE_MODEL: process.env.CCR_CLAUDE_CODE_MODEL || ''", + " }", + "}));", + "" + ].join("\n")); + chmodSync(fakeCli, 0o700); + return { fakeCli, outputFile }; +} diff --git a/tests/main/gateway-virtual-models.test.mjs b/tests/main/gateway-virtual-models.test.mjs index 2f0898c..b449381 100644 --- a/tests/main/gateway-virtual-models.test.mjs +++ b/tests/main/gateway-virtual-models.test.mjs @@ -1,11 +1,14 @@ import assert from "node:assert/strict"; +import { PassThrough, Readable } from "node:stream"; import test from "node:test"; import { fusionFallbackToolDefinitions, fusionWebSearchToolNameForRequest, fusionToolNamesBackedByMcpServers, extractHostedWebSearchQueryHint, + hostedWebSearchProtocolResponseStream, prepareAnthropicWebSearchProtocolRequestBody, + prepareClaudeCodeWebSearchContinuationRequestBody, prepareHostedWebSearchProtocolRequestBody, transformAnthropicWebSearchProtocolResponseValue, transformAnthropicWebSearchProtocolSseText, @@ -143,6 +146,76 @@ test("gateway resolves normalized Fusion web search tool names for Anthropic pro assert.equal(fusionWebSearchToolNameForRequest(config, "Fusion/kimisearch"), "fusion_2_web_search"); }); +test("gateway does not route hosted web search through an unrelated Fusion search profile", () => { + const config = { + Providers: [ + { + baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4", + credentials: [{ apiKey: "test-key", id: "test-1" }], + models: ["glm-5.2", "glm-5v-turbo"], + name: "Zhipu AI (China) - Coding Plan", + type: "openai_chat_completions" + }, + { + baseUrl: "https://api.moonshot.cn/anthropic", + models: ["kimi-for-coding"], + name: "Kimi Code - Coding Plan", + type: "openai_chat_completions" + } + ], + Router: { fallback: { mode: "off", models: [], retryCount: 0 } }, + gateway: {}, + virtualModelProfiles: [ + { + baseModel: { fixedModel: "Zhipu AI (China) - Coding Plan/glm-5.2", mode: "fixed" }, + displayName: "GLM 5 2V", + enabled: true, + execution: { + clientToolsPolicy: "deny", + matchMultimodal: true, + matchWebSearch: false, + maxToolCalls: 8, + maxTurns: 6, + mode: "tool_loop", + streamMode: "buffered" + }, + id: "glm-5.2v", + key: "glm-5.2v", + match: { exactAliases: ["GLM-5.2V"], prefixes: [], suffixes: [] }, + materialization: { enabled: true, includeInGatewayModels: true }, + metadata: { + fusionVision: { modelSelector: "Zhipu AI (China) - Coding Plan/glm-5v-turbo", toolName: "vision_understand_glm_5_2v" } + }, + tools: [{ name: "vision_understand_glm_5_2v", visibility: "internal" }] + }, + { + baseModel: { fixedModel: "Kimi Code - Coding Plan/kimi-for-coding", mode: "fixed" }, + displayName: "Kimisearch", + enabled: true, + execution: { + clientToolsPolicy: "allow", + matchWebSearch: true, + maxToolCalls: 8, + maxTurns: 6, + mode: "tool_loop", + streamMode: "optimistic" + }, + id: "fusion-2", + key: "kimisearch", + match: { exactAliases: ["kimisearch"], prefixes: [], suffixes: [] }, + materialization: { enabled: true, includeInGatewayModels: true }, + metadata: { + fusionWebSearch: { provider: "browser", toolName: "web_search_fusion_2" } + }, + tools: [{ name: "web_search_fusion_2", visibility: "internal" }] + } + ] + }; + + assert.equal(fusionWebSearchToolNameForRequest(config, "Fusion/GLM-5.2V"), undefined); + assert.equal(fusionWebSearchToolNameForRequest(config, "Fusion/kimisearch"), "fusion_2_web_search"); +}); + test("gateway resolves only browser-backed Fusion web search tools for hosted protocol bridging", () => { const config = { Providers: [], @@ -205,6 +278,7 @@ test("gateway response injects Anthropic web search protocol blocks into JSON re ], id: "msg_1", role: "assistant", + stop_reason: "tool_use", type: "message", usage: { server_tool_use: { web_search_requests: 1 } } }; @@ -217,7 +291,30 @@ test("gateway response injects Anthropic web search protocol blocks into JSON re ); assert.equal(transformed.value.content[1].name, "web_search"); assert.equal(transformed.value.content[2].content[0].type, "web_search_result"); - assert.equal(transformed.value.content[2].content[0].snippet, "Spot gold traded near $3,340 per ounce."); + assert.equal(transformed.value.content[2].content[0].snippet, "Search snippet: Spot gold traded near $3,340 per ounce."); + assert.equal(transformed.value.stop_reason, "end_turn"); +}); + +test("gateway preserves Anthropic tool_use stop reason when client tools remain", () => { + const response = { + content: [ + { thinking: "searched", type: "thinking" }, + { id: "toolu_1", input: { command: "pwd" }, name: "Bash", type: "tool_use" } + ], + id: "msg_1", + role: "assistant", + stop_reason: "tool_use", + type: "message", + usage: {} + }; + const transformed = transformAnthropicWebSearchProtocolResponseValue(response, [sampleSearchRecord()], "req-1"); + + assert.equal(transformed.changed, true); + assert.deepEqual( + transformed.value.content.map((block) => block.type), + ["thinking", "server_tool_use", "web_search_tool_result", "tool_use"] + ); + assert.equal(transformed.value.stop_reason, "tool_use"); }); test("gateway injects prefetched web search evidence into Anthropic requests", () => { @@ -243,6 +340,107 @@ test("gateway injects prefetched web search evidence into Anthropic requests", ( assert.match(parsed.system[1].text, /北京市当前天气晴/); }); +test("gateway forces Claude Code WebSearch continuations to answer without tools", () => { + const body = Buffer.from(JSON.stringify({ + messages: [ + { role: "user", content: [{ type: "text", text: "搜索 shadcn官方有哪些New Components" }] }, + { + role: "assistant", + content: [ + { + id: "tool_search_1", + input: { query: "shadcn UI new components 2025 2026 official" }, + name: "WebSearch", + type: "tool_use" + } + ] + }, + { + role: "user", + content: [ + { + content: "Web search results for query: \"shadcn UI new components 2025 2026 official\"\n\nLinks: [{\"title\":\"Changelog - Shadcn UI\",\"url\":\"https://ui.shadcn.com/docs/changelog\"}]\n\nThe official shadcn/ui changelog lists June 2026 chat interface components. REMINDER: You MUST include the sources above in your response to the user using markdown hyperlinks.", + tool_use_id: "tool_search_1", + type: "tool_result" + } + ] + } + ], + model: "Fusion/kimisearch", + output_config: { effort: "high" }, + system: [{ type: "text", text: "You are Claude Code." }], + thinking: { type: "enabled" }, + tools: [ + { name: "WebFetch", input_schema: { type: "object" } }, + { name: "WebSearch", input_schema: { type: "object" } }, + { name: "Bash", input_schema: { type: "object" } } + ] + })); + + const transformed = prepareClaudeCodeWebSearchContinuationRequestBody( + body, + [sampleSearchRecord()], + { queryHint: "shadcn UI new components 2025 2026 official" } + ); + assert.ok(transformed); + const parsed = JSON.parse(transformed.toString("utf8")); + + assert.equal(parsed.tools, undefined); + assert.equal(parsed.tool_choice, undefined); + assert.equal(parsed.output_config.effort, "low"); + assert.equal(parsed.thinking, undefined); + assert.match(parsed.system.at(-1).text, /Do not call any tool/); + assert.match(parsed.system.at(-1).text, /In-app browser extracted evidence/); + assert.match(parsed.system.at(-1).text, /Previous WebSearch tool result/); + assert.match(parsed.system.at(-1).text, /Spot gold traded near \$3,340 per ounce/); +}); + +test("gateway ignores stale Claude Code WebSearch results on later turns", () => { + const body = Buffer.from(JSON.stringify({ + messages: [ + { role: "user", content: [{ type: "text", text: "搜索 shadcn官方有哪些New Components" }] }, + { + role: "assistant", + content: [ + { + id: "tool_search_1", + input: { query: "shadcn UI new components 2025 2026 official" }, + name: "WebSearch", + type: "tool_use" + } + ] + }, + { + role: "user", + content: [ + { + content: "Web search results for query: \"shadcn UI new components 2025 2026 official\"\n\nLinks: []", + tool_use_id: "tool_search_1", + type: "tool_result" + } + ] + }, + { role: "assistant", content: [{ type: "text", text: "The changelog has new chat components." }] }, + { role: "user", content: [{ type: "text", text: "Now inspect package.json with Bash." }] } + ], + model: "Fusion/kimisearch", + system: [{ type: "text", text: "You are Claude Code." }], + tools: [ + { name: "WebFetch", input_schema: { type: "object" } }, + { name: "WebSearch", input_schema: { type: "object" } }, + { name: "Bash", input_schema: { type: "object" } } + ] + })); + + const transformed = prepareClaudeCodeWebSearchContinuationRequestBody( + body, + [sampleSearchRecord()], + { queryHint: undefined } + ); + + assert.equal(transformed, undefined); +}); + test("gateway synthesizes final Anthropic text when web search response has no visible answer", () => { const response = { content: [ @@ -271,6 +469,32 @@ test("gateway synthesizes final Anthropic text when web search response has no v assert.equal(transformed.value.stop_reason, "end_turn"); }); +test("gateway synthesizes useful component changelog answers from extracted pages", () => { + const response = { + content: [ + { thinking: "searched but did not answer", type: "thinking" } + ], + id: "msg_1", + role: "assistant", + stop_reason: "max_tokens", + type: "message", + usage: { output_tokens: 0 } + }; + const transformed = transformAnthropicWebSearchProtocolResponseValue( + response, + [sampleShadcnSearchRecord()], + "req-1", + "shadcn ui new components 2025 2026 official registry" + ); + + assert.equal(transformed.changed, true); + assert.match(transformed.value.content[2].content[0].snippet, /Extracted page content:/); + assert.match(transformed.value.content[3].text, /June 2026 - Components for Chat Interfaces/); + assert.match(transformed.value.content[3].text, /Message Scroller/); + assert.match(transformed.value.content[3].text, /Attachment/); + assert.doesNotMatch(transformed.value.content[3].text, /Morning, shadcn/); +}); + test("gateway response injects Anthropic web search protocol blocks into SSE responses", () => { const sse = [ sseEvent({ type: "message_start", message: { content: [], id: "msg_1", role: "assistant", type: "message" } }), @@ -279,7 +503,7 @@ test("gateway response injects Anthropic web search protocol blocks into SSE res sseEvent({ type: "content_block_start", index: 1, content_block: { type: "text", text: "" } }), sseEvent({ type: "content_block_delta", index: 1, delta: { type: "text_delta", text: "answer" } }), sseEvent({ type: "content_block_stop", index: 1 }), - sseEvent({ type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { server_tool_use: { web_search_requests: 1 } } }), + sseEvent({ type: "message_delta", delta: { stop_reason: "tool_use" }, usage: { server_tool_use: { web_search_requests: 1 } } }), sseEvent({ type: "message_stop" }) ].join("\n\n") + "\n\n"; @@ -290,6 +514,102 @@ test("gateway response injects Anthropic web search protocol blocks into SSE res assert.match(transformed, /"type":"web_search_result"/); assert.match(transformed, /"index":3,"content_block":\{"type":"text"/); assert.match(transformed, /"server_tool_use":\{"web_search_requests":1\}/); + assert.match(transformed, /"stop_reason":"end_turn"/); +}); + +test("gateway preserves Anthropic SSE tool_use stop reason when client tools remain", () => { + const sse = [ + sseEvent({ type: "message_start", message: { content: [], id: "msg_1", role: "assistant", type: "message" } }), + sseEvent({ type: "content_block_start", index: 0, content_block: { type: "thinking", thinking: "" } }), + sseEvent({ type: "content_block_stop", index: 0 }), + sseEvent({ type: "content_block_start", index: 1, content_block: { id: "toolu_1", input: { command: "pwd" }, name: "Bash", type: "tool_use" } }), + sseEvent({ type: "content_block_stop", index: 1 }), + sseEvent({ type: "message_delta", delta: { stop_reason: "tool_use" }, usage: {} }), + sseEvent({ type: "message_stop" }) + ].join("\n\n") + "\n\n"; + + const transformed = transformAnthropicWebSearchProtocolSseText(sse, [sampleSearchRecord()], "req-1"); + + assert.match(transformed, /"type":"server_tool_use"/); + assert.match(transformed, /"type":"tool_use"/); + assert.match(transformed, /"stop_reason":"tool_use"/); + assert.doesNotMatch(transformed, /"stop_reason":"end_turn"/); +}); + +test("gateway hosted web search response stream transforms Anthropic SSE responses", async () => { + const sse = [ + sseEvent({ type: "message_start", message: { content: [], id: "msg_1", role: "assistant", type: "message" } }), + sseEvent({ type: "content_block_start", index: 0, content_block: { type: "thinking", thinking: "" } }), + sseEvent({ type: "content_block_delta", index: 0, delta: { type: "thinking_delta", thinking: "reasoning" } }), + sseEvent({ type: "content_block_stop", index: 0 }), + sseEvent({ type: "content_block_start", index: 1, content_block: { type: "text", text: "" } }), + sseEvent({ type: "content_block_delta", index: 1, delta: { type: "text_delta", text: "answer" } }), + sseEvent({ type: "content_block_stop", index: 1 }), + sseEvent({ type: "message_delta", delta: { stop_reason: "tool_use" }, usage: {} }), + sseEvent({ type: "message_stop" }) + ].join("\n\n") + "\n\n"; + const stream = hostedWebSearchProtocolResponseStream( + Readable.from([Buffer.from(sse, "utf8")]), + new Headers({ "content-type": "text/event-stream; charset=utf-8" }), + { + protocol: "anthropic_messages", + queryHint: "today gold price per ounce USD July 2026", + records: [sampleSearchRecord()], + requestId: "req-1", + sinceMs: Date.now() - 1000, + toolName: "fusion_2_web_search" + }, + { + recentBrowserWebSearchResults: () => [], + stopBrowserWebSearchMcpServers: async () => {} + } + ); + + const transformed = await readStreamText(stream); + + assert.match(transformed, /"type":"server_tool_use"/); + assert.match(transformed, /"type":"web_search_tool_result"/); + assert.match(transformed, /"type":"web_search_result"/); + assert.match(transformed, /"server_tool_use":\{"web_search_requests":1\}/); + assert.match(transformed, /"stop_reason":"end_turn"/); +}); + +test("gateway hosted web search Anthropic SSE stream emits before upstream ends", async () => { + const input = new PassThrough(); + const stream = hostedWebSearchProtocolResponseStream( + input, + new Headers({ "content-type": "text/event-stream; charset=utf-8" }), + { + protocol: "anthropic_messages", + queryHint: "today gold price per ounce USD July 2026", + records: [sampleSearchRecord()], + requestId: "req-1", + sinceMs: Date.now() - 1000, + toolName: "fusion_2_web_search" + }, + { + recentBrowserWebSearchResults: () => [], + stopBrowserWebSearchMcpServers: async () => {} + } + ); + + const injectedData = waitForStreamDataMatching(stream, /"type":"server_tool_use"/, 500); + input.write([ + sseEvent({ type: "message_start", message: { content: [], id: "msg_1", role: "assistant", type: "message" } }), + sseEvent({ type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }) + ].join("\n\n") + "\n\n"); + const chunk = await injectedData; + assert.ok(chunk); + assert.match(chunk.toString("utf8"), /"type":"server_tool_use"/); + + input.end([ + sseEvent({ type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "answer" } }), + sseEvent({ type: "content_block_stop", index: 0 }), + sseEvent({ type: "message_delta", delta: { stop_reason: "tool_use" }, usage: {} }), + sseEvent({ type: "message_stop" }) + ].join("\n\n") + "\n\n"); + const rest = await readStreamText(stream); + assert.match(`${chunk.toString("utf8")}${rest}`, /"stop_reason":"end_turn"/); }); test("gateway synthesizes final Anthropic SSE text when model exhausts tokens in thinking", () => { @@ -626,6 +946,28 @@ function sampleSearchRecord() { }; } +function sampleShadcnSearchRecord() { + return { + completedAtMs: Date.now(), + engine: "google", + query: "shadcn ui new components 2025 2026 official registry", + results: [ + { + content: "Sections Introduction Components Attachment Avatar Badge Bubble Button Button Group Empty Field Input Input Group Input OTP Item Marker Message Message Scroller Native Select Changelog RSS Latest updates and announcements. June 2026 - Components for Chat Interfaces New Chat How can I help you today? Morning, shadcn! What are we working on today?", + title: "Changelog - Shadcn UI", + url: "https://ui.shadcn.com/docs/changelog" + }, + { + snippet: "YouTube · Web Dev Simplified 26.3K+ views · 11 months ago", + title: "How I Built My Own Shadcn Library", + url: "https://www.youtube.com/watch?v=example" + } + ], + searchUrl: "https://www.google.com/search?q=shadcn", + toolName: "fusion_2_web_search" + }; +} + function sseEvent(value) { return `event: ${value.type}\ndata: ${JSON.stringify(value)}`; } @@ -633,3 +975,33 @@ function sseEvent(value) { function openAiSseEvent(value) { return `data: ${JSON.stringify(value)}`; } + +async function readStreamText(stream) { + const chunks = []; + for await (const chunk of stream) { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + } + return Buffer.concat(chunks).toString("utf8"); +} + +function waitForStreamDataMatching(stream, pattern, timeoutMs) { + return new Promise((resolve) => { + const timer = setTimeout(() => { + cleanup(); + resolve(null); + }, timeoutMs); + const onData = (chunk) => { + const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + if (!pattern.test(buffer.toString("utf8"))) { + return; + } + cleanup(); + resolve(buffer); + }; + const cleanup = () => { + clearTimeout(timer); + stream.off("data", onData); + }; + stream.on("data", onData); + }); +} diff --git a/tests/main/profile-launch-core.test.mjs b/tests/main/profile-launch-core.test.mjs index d93aeda..1401e58 100644 --- a/tests/main/profile-launch-core.test.mjs +++ b/tests/main/profile-launch-core.test.mjs @@ -20,6 +20,7 @@ const claudeProfile = { model: "provider,model", name: "Claude Main", scope: "ccr", + smallFastModel: "provider,small", surface: "auto" }; @@ -82,6 +83,10 @@ test("buildProfileLaunchPlan creates CCR-managed launcher paths", () => { assert.equal(path.basename(claudePlan.command), process.platform === "win32" ? "ccr-claude-code-wrapper-claude-main.cmd" : "ccr-claude-code-wrapper-claude-main"); assert.equal(claudePlan.env.CCR_PROFILE_SURFACE, "cli"); assert.match(claudePlan.env.CLAUDE_CONFIG_DIR, /claude$/); + assert.equal(claudePlan.env.ANTHROPIC_MODEL, "provider/model"); + assert.equal(claudePlan.env.CCR_CLAUDE_CODE_MODEL, "provider/model"); + assert.equal(claudePlan.env.CODEXL_CLAUDE_CODE_MODEL, "provider/model"); + assert.equal(claudePlan.env.ANTHROPIC_SMALL_FAST_MODEL, "provider/small"); assert.throws(() => buildProfileLaunchPlan(configDir, claudeProfile, "app"), /Claude App opening/); }); diff --git a/tests/main/profile-service.test.mjs b/tests/main/profile-service.test.mjs new file mode 100644 index 0000000..e271ccf --- /dev/null +++ b/tests/main/profile-service.test.mjs @@ -0,0 +1,108 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { restoreInactiveGlobalProfileConfigs } from "../../src/main/profile-service.ts"; + +test("profile service restores managed global Claude settings when only CCR-scoped Claude profiles are active", () => { + const previousHome = process.env.HOME; + const home = mkdtempSync(path.join(os.tmpdir(), "ccr-profile-home-")); + process.env.HOME = home; + try { + const settingsFile = path.join(home, ".claude", "settings.json"); + mkdirSync(path.dirname(settingsFile), { recursive: true }); + const originalSettings = { + env: { + USER_VALUE: "kept" + }, + theme: "dark" + }; + writeFileSync(`${settingsFile}.ccr-backup-2026-01-01T00-00-00-000Z`, `${JSON.stringify(originalSettings, null, 2)}\n`); + writeFileSync(settingsFile, `${JSON.stringify({ + apiKeyHelper: "/tmp/ccr-claude-code-api-key-claude-code", + env: { + ANTHROPIC_API_BASE_URL: "http://127.0.0.1:3456", + ANTHROPIC_BASE_URL: "http://127.0.0.1:3456", + ANTHROPIC_MODEL: "Fusion/GLM-5.2V", + CLAUDE_AGENT_API_BASE_URL: "http://127.0.0.1:3456" + } + }, null, 2)}\n`); + + const statuses = restoreInactiveGlobalProfileConfigs([ + { + agent: "claude-code", + enabled: true, + env: { CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY: "1" }, + id: "claude-code-2", + model: "Fusion/kimisearch", + name: "Claude Code", + scope: "ccr", + settingsFile: "~/.claude/settings.json", + smallFastModel: "", + surface: "auto" + } + ]); + + const restored = JSON.parse(readFileSync(settingsFile, "utf8")); + assert.equal(statuses.length, 1); + assert.equal(statuses[0].client, "claude-code"); + assert.equal(statuses[0].ok, true); + assert.equal(restored.env.USER_VALUE, "kept"); + assert.equal(restored.env.ANTHROPIC_MODEL, undefined); + assert.equal(restored.env.CCR_CLAUDE_CODE_MODEL, undefined); + assert.equal(restored.env.CODEXL_CLAUDE_CODE_MODEL, undefined); + } finally { + if (previousHome === undefined) { + delete process.env.HOME; + } else { + process.env.HOME = previousHome; + } + rmSync(home, { force: true, recursive: true }); + } +}); + +test("profile service keeps managed global Claude settings when a global Claude profile is active", () => { + const previousHome = process.env.HOME; + const home = mkdtempSync(path.join(os.tmpdir(), "ccr-profile-home-")); + process.env.HOME = home; + try { + const settingsFile = path.join(home, ".claude", "settings.json"); + mkdirSync(path.dirname(settingsFile), { recursive: true }); + writeFileSync(settingsFile, `${JSON.stringify({ + apiKeyHelper: "/tmp/ccr-claude-code-api-key-claude-code", + env: { + ANTHROPIC_API_BASE_URL: "http://127.0.0.1:3456", + ANTHROPIC_BASE_URL: "http://127.0.0.1:3456", + ANTHROPIC_MODEL: "Fusion/GLM-5.2V", + CLAUDE_AGENT_API_BASE_URL: "http://127.0.0.1:3456" + } + }, null, 2)}\n`); + + const statuses = restoreInactiveGlobalProfileConfigs([ + { + agent: "claude-code", + enabled: true, + env: {}, + id: "claude-code", + model: "Fusion/GLM-5.2V", + name: "Claude Code", + scope: "global", + settingsFile: "~/.claude/settings.json", + smallFastModel: "", + surface: "auto" + } + ]); + + const current = JSON.parse(readFileSync(settingsFile, "utf8")); + assert.equal(statuses.length, 0); + assert.equal(current.env.ANTHROPIC_MODEL, "Fusion/GLM-5.2V"); + } finally { + if (previousHome === undefined) { + delete process.env.HOME; + } else { + process.env.HOME = previousHome; + } + rmSync(home, { force: true, recursive: true }); + } +}); diff --git a/tests/main/router-builtins.test.mjs b/tests/main/router-builtins.test.mjs index 33f7cc3..ccc40cc 100644 --- a/tests/main/router-builtins.test.mjs +++ b/tests/main/router-builtins.test.mjs @@ -35,7 +35,8 @@ function createRouterPlugin(options = {}) { scope: "global" } ] - } + }, + virtualModelProfiles: options.virtualModelProfiles ?? [] }); } @@ -58,6 +59,37 @@ test("built-in Claude Code route matches user-agent case-insensitively", async ( assert.equal(result.decision.reason, "builtin:claude-code"); }); +test("built-in Claude Code route preserves explicit virtual gateway models", async () => { + const plugin = createRouterPlugin({ + profileModel: "Provider/claude-sonnet", + virtualModelProfiles: [ + { + displayName: "Kimisearch", + enabled: true, + id: "fusion-search", + key: "kimisearch", + match: { exactAliases: ["kimisearch"], prefixes: [], suffixes: [] }, + materialization: { enabled: true, includeInGatewayModels: true } + } + ] + }); + const result = await plugin.routeRequest({ + body: { + messages: [], + model: "Fusion/kimisearch" + }, + headers: { + "user-agent": "claude-code/1.0" + }, + method: "POST", + url: "/v1/messages" + }); + + assert.equal(result.body.model, "Fusion/kimisearch"); + assert.equal(result.decision.model, "Fusion/kimisearch"); + assert.equal(result.decision.reason, "inline-model"); +}); + test("built-in Codex route stays inactive when profile model is unset", async () => { const plugin = createRouterPlugin({ agent: "codex" From ec99720037865f94b6eeb20981da0c6bc97a1a59 Mon Sep 17 00:00:00 2001 From: musi Date: Thu, 2 Jul 2026 22:08:17 +0800 Subject: [PATCH 06/62] chore: release 3.0.6 --- package-lock.json | 4 ++-- package.json | 2 +- src/renderer/pages/home/components/providers.tsx | 7 +------ 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index d7dd9fd..890dd6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-code-router", - "version": "3.0.5", + "version": "3.0.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-code-router", - "version": "3.0.5", + "version": "3.0.6", "license": "MIT", "dependencies": { "@the-next-ai/ai-gateway": "^1.0.3", diff --git a/package.json b/package.json index d8d9cc7..c817649 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-code-router", - "version": "3.0.5", + "version": "3.0.6", "license": "MIT", "description": "Local Claude Code Router gateway with CLI and web management UI.", "repository": { diff --git a/src/renderer/pages/home/components/providers.tsx b/src/renderer/pages/home/components/providers.tsx index bde3e90..70cf187 100644 --- a/src/renderer/pages/home/components/providers.tsx +++ b/src/renderer/pages/home/components/providers.tsx @@ -348,7 +348,7 @@ export function ModelsView({ {visibleRows.map((row) => (
@@ -358,11 +358,6 @@ export function ModelsView({
{row.providerName ? `${row.providerName}/${row.model}` : row.model}
- {row.providerName ? ( - - {row.providerName} - - ) : null}
{row.providerIndex !== undefined ? ( From 5f7345fdc8f5ed4cfcec32d5d0696ce9fa6cdb16 Mon Sep 17 00:00:00 2001 From: 810senpai114514 <810senpai114514@users.noreply.github.com> Date: Fri, 3 Jul 2026 00:20:26 +0800 Subject: [PATCH 07/62] fix(gateway): proxy passthrough via globalThis.fetch and fix NO_PROXY parsing Replace setGlobalDispatcher with globalThis.fetch wrapper to work around bundled undici instance isolation in ai-gateway subprocess. Also: - Extract shared readEnvProxyUrl() to eliminate duplicated env proxy logic - Fix IPv6 loopback detection (URL.hostname returns [::1] with brackets) - Fix URL object handling in preload bypass check - Fix *.domain wildcard and host:port NO_PROXY matching - Fix inverted fail-safe: bypass on parse error instead of proxying - Add writable guard for globalThis.fetch assignment --- src/main/system-proxy-fetch.ts | 14 +++-- src/server/gateway/service.ts | 111 +++++++++++---------------------- 2 files changed, 47 insertions(+), 78 deletions(-) diff --git a/src/main/system-proxy-fetch.ts b/src/main/system-proxy-fetch.ts index dfb7538..fd6d941 100644 --- a/src/main/system-proxy-fetch.ts +++ b/src/main/system-proxy-fetch.ts @@ -38,10 +38,18 @@ export async function fetchWithSystemProxy(input: RequestInfo | URL, init?: Requ } as FetchInitWithDispatcher); } +export function readEnvProxyUrl(): string | undefined { + const envProxy = process.env.HTTPS_PROXY || process.env.https_proxy || + process.env.HTTP_PROXY || process.env.http_proxy; + return envProxy ? envProxy.trim() : undefined; +} + export async function getSystemProxyUrlForProtocol(protocol: "http" | "https" = "https"): Promise { const cache = await readSystemProxy(); const server = proxyServerForRequest(cache.upstreamProxy, protocol); - return server ? formatProxyUrl(server) : undefined; + if (server) return formatProxyUrl(server); + + return readEnvProxyUrl(); } async function systemProxyUrlForRequest(url: URL): Promise { @@ -49,9 +57,7 @@ async function systemProxyUrlForRequest(url: URL): Promise { const server = proxyServerForRequest(cache.upstreamProxy, url.protocol === "https:" ? "https" : "http"); if (server) return formatProxyUrl(server); - const envProxy = process.env.HTTPS_PROXY || process.env.https_proxy || - process.env.HTTP_PROXY || process.env.http_proxy; - return envProxy ? envProxy.trim() : undefined; + return readEnvProxyUrl(); } async function readSystemProxy(): Promise { diff --git a/src/server/gateway/service.ts b/src/server/gateway/service.ts index 56b9e1b..9a3323a 100644 --- a/src/server/gateway/service.ts +++ b/src/server/gateway/service.ts @@ -6054,82 +6054,45 @@ function writeGatewayProxyPreloadFile(config: AppConfig, upstreamProxyUrl: strin file, [ "\"use strict\";", - "const proxyUrl = process.env.CCR_UPSTREAM_PROXY_URL;", - "if (proxyUrl) {", - " const undiciModule = process.env.CCR_UNDICI_MODULE || \"undici\";", - " const { Agent, ProxyAgent, setGlobalDispatcher } = require(undiciModule);", - " const directAgent = new Agent();", - " const proxyAgent = new ProxyAgent(proxyUrl);", - " const noProxyEntries = String(process.env.NO_PROXY || process.env.no_proxy || \"\")", - " .split(\",\")", - " .map((entry) => entry.trim())", - " .filter(Boolean);", - " function normalizeHost(host) {", - " return String(host || \"\").replace(/^\\[/, \"\").replace(/\\]$/, \"\").replace(/\\.$/, \"\").toLowerCase();", - " }", - " function isLoopbackHost(host) {", - " return host === \"localhost\" || host === \"::1\" || host === \"0:0:0:0:0:0:0:1\" || host === \"127.0.0.1\" || host.startsWith(\"127.\");", - " }", - " function splitNoProxyEntry(entry) {", - " let value = String(entry || \"\").trim().toLowerCase();", - " if (!value || value === \"*\") {", - " return { host: value, port: \"\" };", - " }", - " if (value.startsWith(\"[\")) {", - " const end = value.indexOf(\"]\");", - " if (end >= 0) {", - " const remainder = value.slice(end + 1);", - " return { host: normalizeHost(value.slice(1, end)), port: remainder.startsWith(\":\") ? remainder.slice(1) : \"\" };", - " }", - " }", - " const colon = value.lastIndexOf(\":\");", - " if (colon > 0 && value.indexOf(\":\") === colon) {", - " return { host: normalizeHost(value.slice(0, colon)), port: value.slice(colon + 1) };", - " }", - " return { host: normalizeHost(value), port: \"\" };", - " }", - " function noProxyMatches(host, port, entry) {", - " const parsed = splitNoProxyEntry(entry);", - " if (parsed.host === \"*\") {", - " return true;", - " }", - " if (!parsed.host || (parsed.port && parsed.port !== port)) {", - " return false;", - " }", - " if (parsed.host.startsWith(\"*.\")) {", - " const suffix = parsed.host.slice(1);", - " return host.endsWith(suffix);", - " }", - " if (parsed.host.startsWith(\".\")) {", - " return host === parsed.host.slice(1) || host.endsWith(parsed.host);", - " }", - " return host === parsed.host || host.endsWith(`.${parsed.host}`);", - " }", - " function shouldBypassProxy(origin) {", + "const up = process.env.CCR_UPSTREAM_PROXY_URL;", + "const um = process.env.CCR_UNDICI_MODULE;", + "if (up && um) {", + " const { ProxyAgent } = require(um);", + " const agent = new ProxyAgent(up);", + " const realFetch = globalThis.fetch.bind(globalThis);", + " const raw = (process.env.NO_PROXY || process.env.no_proxy || '').toLowerCase();", + " const byp = raw.split(',').map((s) => s.trim()).filter(Boolean);", + " const norm = (h) => h.replace(/^\\[/, '').replace(/\\]$/, '').replace(/\\.$/, '');", + " const isLP = (h) => h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '0:0:0:0:0:0:0:1' || h === '0.0.0.0' || h.startsWith('127.');", + " const shouldBypass = (input) => {", + " let h;", " try {", - " const url = new URL(String(origin));", - " const host = normalizeHost(url.hostname);", - " const port = url.port || (url.protocol === \"https:\" ? \"443\" : url.protocol === \"http:\" ? \"80\" : \"\");", - " return isLoopbackHost(host) || noProxyEntries.some((entry) => noProxyMatches(host, port, entry));", - " } catch {", - " return false;", - " }", + " const u = typeof input === 'string' ? new URL(input) : input instanceof URL ? input : new URL(input && input.url ? input.url : String(input));", + " h = norm(u.hostname);", + " } catch { return true; }", + " if (!h) return false;", + " if (isLP(h)) return true;", + " return byp.some((p) => {", + " if (p === '*') return true;", + " const s = p.split(':');", + " const ph = norm(s[0]);", + " if (s.length === 2 && s[1]) {", + " if (h !== ph) return false;", + " try { return new URL(input).port === s[1]; } catch { return false; }", + " }", + " if (ph.startsWith('*.')) return h.endsWith(ph.slice(1));", + " if (ph.startsWith('.')) return h.endsWith(ph) || h === ph.slice(1);", + " return h === ph;", + " });", + " };", + " const patched = function(input, init) {", + " if (init && init.dispatcher) return realFetch(input, init);", + " if (shouldBypass(input)) return realFetch(input, init);", + " return realFetch(input, Object.assign({}, init, { dispatcher: agent }));", + " };", + " if (Object.getOwnPropertyDescriptor(globalThis, 'fetch')?.writable) {", + " globalThis.fetch = patched;", " }", - " setGlobalDispatcher({", - " dispatch(options, handler) {", - " return shouldBypassProxy(options && options.origin)", - " ? directAgent.dispatch(options, handler)", - " : proxyAgent.dispatch(options, handler);", - " },", - " close(callback) {", - " Promise.all([directAgent.close(), proxyAgent.close()]).then(() => callback && callback(), callback);", - " },", - " destroy(error, callback) {", - " directAgent.destroy(error);", - " proxyAgent.destroy(error);", - " if (callback) callback();", - " }", - " });", "}" ].join("\n"), "utf8" From f13e3b4598769be2800797992b98a99f9b152495 Mon Sep 17 00:00:00 2001 From: musi Date: Fri, 3 Jul 2026 10:01:48 +0800 Subject: [PATCH 08/62] fix: avoid false protocol detection for Gemini --- src/main/provider-probe.ts | 25 +++++++++-- src/server/gateway/service.ts | 43 ++++++++++++++++++- tests/main/gateway-virtual-models.test.mjs | 50 ++++++++++++++++++++++ tests/main/provider-probe.test.mjs | 34 +++++++++++++++ tests/renderer/providers.test.ts | 22 ++++++++++ 5 files changed, 169 insertions(+), 5 deletions(-) create mode 100644 tests/main/provider-probe.test.mjs create mode 100644 tests/renderer/providers.test.ts diff --git a/src/main/provider-probe.ts b/src/main/provider-probe.ts index 91ed2d4..917e540 100644 --- a/src/main/provider-probe.ts +++ b/src/main/provider-probe.ts @@ -487,7 +487,7 @@ async function probeProtocolSupport( for (const candidate of endpoints) { const result = await requestJson(candidate.endpoint, requestForProtocolSupport(protocol, apiKey)); const message = readResponseMessage(result); - const supported = isProtocolEndpointSupported(result.status, message); + const supported = isProviderProtocolEndpointSupportedForProbe(result.status, message, protocol, parsed.hints); const probeResult = { baseUrl: candidate.baseUrl, endpoint: candidate.endpoint, @@ -1003,19 +1003,38 @@ function isProtocolSupported(status: number | undefined, message: string): boole return false; } -function isProtocolEndpointSupported(status: number | undefined, message: string): boolean { +export function isProviderProtocolEndpointSupportedForProbe( + status: number | undefined, + message: string, + protocol: GatewayProviderProtocol, + hints: GatewayProviderProtocol[] = [] +): boolean { if (isProtocolSupported(status, message)) { return true; } if (status === 401 || status === 403) { const normalized = message.toLowerCase(); - return !/not found|unknown endpoint|unknown route|no route/.test(normalized); + return (hints.length === 0 || protocolMatchesHints(protocol, hints)) && + !/not found|unknown endpoint|unknown route|no route/.test(normalized); } return false; } +function protocolMatchesHints(protocol: GatewayProviderProtocol, hints: GatewayProviderProtocol[]): boolean { + if (hints.includes(protocol)) { + return true; + } + if (protocol === "openai_chat_completions") { + return hints.includes("openai_responses"); + } + if (protocol === "openai_responses") { + return hints.includes("openai_chat_completions"); + } + return false; +} + function readResponseMessage(result: FetchJsonResult): string { if (result.status === undefined) { return result.text || "Request failed."; diff --git a/src/server/gateway/service.ts b/src/server/gateway/service.ts index 56b9e1b..efc56ac 100644 --- a/src/server/gateway/service.ts +++ b/src/server/gateway/service.ts @@ -37,7 +37,7 @@ import { ROUTER_FALLBACK_MAX_RETRY_COUNT, hasAvailableGatewayModels } from "../../shared/app"; -import { providerApiKeySafetyIssue } from "../../main/presets"; +import { findProviderPresetByBaseUrl, providerApiKeySafetyIssue } from "../../main/presets"; import { normalizeProviderBaseUrl as normalizeProviderBaseUrlInput } from "../../shared/provider-url"; import { backendService } from "../backend-service"; import { RAW_TRACE_SPOOL_DIR } from "../../main/constants"; @@ -6249,7 +6249,46 @@ function normalizedProviderCapabilities(provider: GatewayProviderConfig): Gatewa normalized.push(selected); } } - return normalized; + return applyPresetProtocolLock(provider, normalized); +} + +function applyPresetProtocolLock( + provider: GatewayProviderConfig, + capabilities: GatewayProviderCapability[] +): GatewayProviderCapability[] { + const lockedProtocol = lockedProviderPresetProtocol(provider, capabilities); + if (!lockedProtocol) { + return capabilities; + } + + const lockedCapabilities = capabilities.filter((capability) => capability.type === lockedProtocol); + if (lockedCapabilities.length > 0) { + return lockedCapabilities; + } + + const baseUrl = readBaseUrl(provider); + const normalizedBaseUrl = normalizeProviderRuntimeBaseUrl(baseUrl, lockedProtocol); + return normalizedBaseUrl + ? [{ baseUrl: normalizedBaseUrl, source: "preset", type: lockedProtocol }] + : []; +} + +function lockedProviderPresetProtocol( + provider: GatewayProviderConfig, + capabilities: GatewayProviderCapability[] +): GatewayProviderProtocol | undefined { + const baseUrls = [ + readBaseUrl(provider), + ...capabilities.map((capability) => capability.baseUrl) + ].filter((value): value is string => Boolean(value?.trim())); + + for (const baseUrl of baseUrls) { + if (findProviderPresetByBaseUrl(baseUrl)?.id === "gemini") { + return "gemini_generate_content"; + } + } + + return undefined; } function providerCapabilityPriority(capability: GatewayProviderCapability): number { diff --git a/tests/main/gateway-virtual-models.test.mjs b/tests/main/gateway-virtual-models.test.mjs index b449381..7b51808 100644 --- a/tests/main/gateway-virtual-models.test.mjs +++ b/tests/main/gateway-virtual-models.test.mjs @@ -79,6 +79,56 @@ test("gateway config rewrites Fusion fixed base and vision models to core provid assert.equal(profiles[0].baseModel.fixedModel, `${providerName}/glm-5.2`); }); +test("gateway ignores non-Gemini capabilities on Gemini preset providers", () => { + const providerName = "Google Gemini"; + const config = { + Providers: [ + { + api_base_url: "https://generativelanguage.googleapis.com", + capabilities: [ + { baseUrl: "https://generativelanguage.googleapis.com", source: "preset", type: "gemini_generate_content" }, + { baseUrl: "https://generativelanguage.googleapis.com", source: "preset", type: "openai_chat_completions" }, + { baseUrl: "https://generativelanguage.googleapis.com", source: "preset", type: "openai_responses" }, + { baseUrl: "https://generativelanguage.googleapis.com", source: "preset", type: "anthropic_messages" } + ], + credentials: [{ apiKey: "test-key", id: "test-1" }], + id: "provider-google-gemini-1785c39128", + models: ["gemini-2.5-pro"], + name: providerName, + type: "gemini_generate_content" + } + ], + Router: { fallback: { mode: "off", models: [], retryCount: 0 } }, + gateway: {} + }; + const profiles = [ + { + baseModel: { fixedModel: `${providerName}/gemini-2.5-pro`, mode: "fixed" }, + displayName: "Gemini Fusion", + enabled: true, + execution: { + clientToolsPolicy: "allow", + maxToolCalls: 8, + maxTurns: 6, + mode: "tool_loop", + streamMode: "optimistic" + }, + id: "gemini-fusion", + key: "gemini-fusion", + match: { exactAliases: ["gemini-fusion"], prefixes: [], suffixes: [] }, + materialization: { enabled: true, includeInGatewayModels: true }, + tools: [] + } + ]; + + const [profile] = normalizeCoreGatewayVirtualModelProfiles(profiles, config); + + assert.equal( + profile.baseModel.fixedModel, + "provider-google-gemini-1785c39128::gemini_generate_content::cred:test-1/gemini-2.5-pro" + ); +}); + test("gateway config normalizes Fusion web search tool names for native Anthropic search triggers", () => { const profiles = [ { diff --git a/tests/main/provider-probe.test.mjs b/tests/main/provider-probe.test.mjs new file mode 100644 index 0000000..3038f7f --- /dev/null +++ b/tests/main/provider-probe.test.mjs @@ -0,0 +1,34 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { isProviderProtocolEndpointSupportedForProbe } from "../../src/main/provider-probe.ts"; + +test("protocol support probe does not treat Gemini auth errors as every protocol", () => { + const message = "HTTP 403: API key not valid. Please pass a valid API key."; + const hints = ["gemini_generate_content"]; + + assert.equal( + isProviderProtocolEndpointSupportedForProbe(403, message, "anthropic_messages", hints), + false + ); + assert.equal( + isProviderProtocolEndpointSupportedForProbe(403, message, "openai_chat_completions", hints), + false + ); + assert.equal( + isProviderProtocolEndpointSupportedForProbe(403, message, "openai_responses", hints), + false + ); + assert.equal( + isProviderProtocolEndpointSupportedForProbe(403, message, "gemini_generate_content", hints), + true + ); +}); + +test("protocol support probe keeps auth-only fallback for unhinted endpoints", () => { + const message = "HTTP 401: Unauthorized"; + + assert.equal( + isProviderProtocolEndpointSupportedForProbe(401, message, "openai_chat_completions", []), + true + ); +}); diff --git a/tests/renderer/providers.test.ts b/tests/renderer/providers.test.ts new file mode 100644 index 0000000..6a59f8f --- /dev/null +++ b/tests/renderer/providers.test.ts @@ -0,0 +1,22 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { geminiProviderPreset } from "../../src/main/presets/gemini/index.ts"; +import { + createProviderDraft, + providerProtocolOptions, + providerProbeCandidates, + setProviderPresets +} from "../../src/renderer/pages/home/shared/index.tsx"; + +test("Gemini preset keeps full protocol probing candidates", () => { + setProviderPresets([geminiProviderPreset]); + const draft = { + ...createProviderDraft([]), + presetId: "gemini" + }; + + const candidates = providerProbeCandidates(draft); + + assert.equal(candidates.length, 1); + assert.deepEqual(candidates[0].protocols, providerProtocolOptions.map((option) => option.value)); +}); From d45e88387aed62bc897cfb6f977644ca34d2b56a Mon Sep 17 00:00:00 2001 From: musistudio Date: Fri, 3 Jul 2026 10:31:39 +0800 Subject: [PATCH 09/62] Refine protocol probe filtering for Gemini --- src/main/provider-probe.ts | 14 +++-- src/renderer/pages/home/App.tsx | 2 +- .../pages/home/components/providers.tsx | 2 +- src/renderer/pages/home/shared/providers.ts | 7 +-- tests/main/provider-probe.test.mjs | 13 +++++ tests/renderer/providers.test.ts | 51 +++++++++++++++++++ 6 files changed, 78 insertions(+), 11 deletions(-) diff --git a/src/main/provider-probe.ts b/src/main/provider-probe.ts index 917e540..b1d7389 100644 --- a/src/main/provider-probe.ts +++ b/src/main/provider-probe.ts @@ -535,7 +535,7 @@ async function probeProtocolConnectivity( for (const candidate of endpoints) { const result = await requestJson(candidate.endpoint, requestForProtocol(protocol, model, apiKey)); const message = readResponseMessage(result); - const supported = isProtocolSupported(result.status, message); + const supported = isProtocolSupported(result.status, message, protocol); const probeResult = { baseUrl: candidate.baseUrl, endpoint: candidate.endpoint, @@ -982,7 +982,11 @@ function protocolHints(value: string): GatewayProviderProtocol[] { return hints; } -function isProtocolSupported(status: number | undefined, message: string): boolean { +function isProtocolSupported( + status: number | undefined, + message: string, + protocol?: GatewayProviderProtocol +): boolean { if (status === undefined) { return false; } @@ -997,7 +1001,9 @@ function isProtocolSupported(status: number | undefined, message: string): boole if (status === 400) { const normalized = message.toLowerCase(); - return /model|max_tokens|max output|messages|input|required/.test(normalized) && !/not found|unknown endpoint|unknown route|no route/.test(normalized); + const schemaError = /model|max_tokens|max output|messages|input|required/.test(normalized) || + (protocol === "gemini_generate_content" && /contents|generatecontentrequest|generationconfig/.test(normalized)); + return schemaError && !/not found|unknown endpoint|unknown route|no route/.test(normalized); } return false; @@ -1009,7 +1015,7 @@ export function isProviderProtocolEndpointSupportedForProbe( protocol: GatewayProviderProtocol, hints: GatewayProviderProtocol[] = [] ): boolean { - if (isProtocolSupported(status, message)) { + if (isProtocolSupported(status, message, protocol)) { return true; } diff --git a/src/renderer/pages/home/App.tsx b/src/renderer/pages/home/App.tsx index 6fcdbf2..99fdea4 100644 --- a/src/renderer/pages/home/App.tsx +++ b/src/renderer/pages/home/App.tsx @@ -1361,7 +1361,7 @@ function App() { const fallbackBaseUrl = probe?.normalizedBaseUrl || providerDraft.baseUrl; const selectableProtocols = providerSelectableProtocolsFromProbe(probe); const selectedProtocols = providerDraft.selectedProtocols.length > 0 - ? providerDraft.selectedProtocols.filter((protocol) => selectableProtocols.length === 0 || selectableProtocols.includes(protocol)) + ? providerDraft.selectedProtocols.filter((protocol) => !probe || selectableProtocols.includes(protocol)) : []; if (selectableProtocols.length > 0 && selectedProtocols.length === 0) { setProviderProbeError(t("Select at least one protocol.")); diff --git a/src/renderer/pages/home/components/providers.tsx b/src/renderer/pages/home/components/providers.tsx index 70cf187..8c50a3c 100644 --- a/src/renderer/pages/home/components/providers.tsx +++ b/src/renderer/pages/home/components/providers.tsx @@ -1680,7 +1680,7 @@ export function AddProviderForm({
{probe.protocols.map((item) => { const selectable = item.supported && selectableProtocols.includes(item.protocol); - const checked = draft.selectedProtocols.includes(item.protocol); + const checked = selectable && draft.selectedProtocols.includes(item.protocol); return (
capability.type), - ...probe.protocols.filter((item) => item.supported).map((item) => item.protocol) - ]); + return uniqueProviderProtocols(probe.protocols.filter((item) => item.supported).map((item) => item.protocol)); } export function selectedProviderProtocolsFromCapabilities( @@ -1772,7 +1769,7 @@ export function selectedProviderProtocolsForProbe( ): GatewayProviderProtocol[] { const selectable = providerSelectableProtocolsFromProbe(probe); if (selectable.length === 0) { - return selectedProtocols.length > 0 ? uniqueProviderProtocols(selectedProtocols) : [fallback]; + return []; } if (selectedProtocolsMatchPresetDefault(selectedProtocols, presetId)) { diff --git a/tests/main/provider-probe.test.mjs b/tests/main/provider-probe.test.mjs index 3038f7f..1291117 100644 --- a/tests/main/provider-probe.test.mjs +++ b/tests/main/provider-probe.test.mjs @@ -32,3 +32,16 @@ test("protocol support probe keeps auth-only fallback for unhinted endpoints", ( true ); }); + +test("protocol support probe treats Gemini contents validation as Gemini support only", () => { + const message = "HTTP 400: * GenerateContentRequest.contents: contents is not specified"; + + assert.equal( + isProviderProtocolEndpointSupportedForProbe(400, message, "gemini_generate_content", ["gemini_generate_content"]), + true + ); + assert.equal( + isProviderProtocolEndpointSupportedForProbe(400, message, "openai_chat_completions", ["openai_chat_completions"]), + false + ); +}); diff --git a/tests/renderer/providers.test.ts b/tests/renderer/providers.test.ts index 6a59f8f..5f0a1f0 100644 --- a/tests/renderer/providers.test.ts +++ b/tests/renderer/providers.test.ts @@ -2,6 +2,7 @@ import assert from "node:assert/strict"; import test from "node:test"; import { geminiProviderPreset } from "../../src/main/presets/gemini/index.ts"; import { + applyProviderProbeResult, createProviderDraft, providerProtocolOptions, providerProbeCandidates, @@ -20,3 +21,53 @@ test("Gemini preset keeps full protocol probing candidates", () => { assert.equal(candidates.length, 1); assert.deepEqual(candidates[0].protocols, providerProtocolOptions.map((option) => option.value)); }); + +test("provider probe result drops unavailable selected protocols", () => { + const draft = { + ...createProviderDraft([]), + baseUrl: "https://generativelanguage.googleapis.com", + protocol: "gemini_generate_content", + selectedProtocols: providerProtocolOptions.map((option) => option.value) + }; + + const next = applyProviderProbeResult(draft, { + capabilities: [], + detectedProtocol: "gemini_generate_content", + models: [], + normalizedBaseUrl: draft.baseUrl, + protocols: providerProtocolOptions.map((option) => ({ + endpoint: draft.baseUrl, + message: "HTTP 404", + protocol: option.value, + status: 404, + supported: false + })) + }); + + assert.deepEqual(next.selectedProtocols, []); +}); + +test("provider probe result keeps only supported selected protocols", () => { + const draft = { + ...createProviderDraft([]), + baseUrl: "https://generativelanguage.googleapis.com", + protocol: "gemini_generate_content", + selectedProtocols: providerProtocolOptions.map((option) => option.value) + }; + + const next = applyProviderProbeResult(draft, { + capabilities: [], + detectedProtocol: "gemini_generate_content", + models: [], + normalizedBaseUrl: draft.baseUrl, + protocols: providerProtocolOptions.map((option) => ({ + endpoint: draft.baseUrl, + message: option.value === "gemini_generate_content" ? "HTTP 400: contents is not specified" : "HTTP 404", + protocol: option.value, + status: option.value === "gemini_generate_content" ? 400 : 404, + supported: option.value === "gemini_generate_content" + })) + }); + + assert.deepEqual(next.selectedProtocols, ["gemini_generate_content"]); +}); From 89d2d2394c660ef568a60d85a77854bd7085fc7b Mon Sep 17 00:00:00 2001 From: musistudio Date: Fri, 3 Jul 2026 11:12:14 +0800 Subject: [PATCH 10/62] Add launch-at-login support and route merge handling --- src/main/config.ts | 4 + src/main/ipc.ts | 14 ++ src/main/launch-at-login.ts | 28 +++ src/main/main-app.ts | 6 + src/main/web-management-server.ts | 1 + src/renderer/pages/home/App.tsx | 11 +- .../pages/home/components/settings.tsx | 28 ++- src/renderer/pages/home/shared/config.ts | 1 + src/renderer/pages/home/shared/fallbacks.ts | 1 + src/renderer/pages/home/shared/i18n.tsx | 6 + .../gateway/claude-code-router-plugin.ts | 34 ++- src/server/gateway/service.ts | 52 ++++ src/shared/app.ts | 2 + src/shared/default-config.ts | 1 + tests/main/router-builtins.test.mjs | 230 +++++++++++++++++- 15 files changed, 409 insertions(+), 10 deletions(-) create mode 100644 src/main/launch-at-login.ts diff --git a/src/main/config.ts b/src/main/config.ts index bccbc7d..c0816cb 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -579,6 +579,10 @@ function pickConfig(value: Partial): LoadedAppConfig { if (typeof value.autoStart === "boolean") { config.autoStart = value.autoStart; } + const launchAtLogin = (value as Record).launchAtLogin; + if (typeof launchAtLogin === "boolean") { + config.launchAtLogin = launchAtLogin; + } if (isObject(value.gateway)) { const gateway = value.gateway as Record; const gatewayConfig: Partial = {}; diff --git a/src/main/ipc.ts b/src/main/ipc.ts index bdf8cd2..64f01db 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -18,6 +18,7 @@ import { getProviderAccountSnapshots, invalidateProviderAccountSnapshotCache, te import { detectProviderIcon } from "./provider-icons"; import { fetchProviderManifest } from "./provider-manifest-service"; import { getLocalAgentProviderCandidates, importLocalAgentProvider } from "./local-agent-provider-service"; +import { isLaunchAtLoginSupported, syncLaunchAtLogin } from "./launch-at-login"; import { getProviderCatalogModels } from "./provider-model-catalog"; import { getProviderPresets } from "./presets"; import { checkGatewayProviderConnectivity, probeGatewayProvider, probeGatewayProviderCandidates } from "./provider-probe"; @@ -62,6 +63,7 @@ ipcMain.handle(IPC_CHANNELS.appGetInfo, () => { configFile: CONFIG_FILE, dataDir: DATADIR, gatewayConfigFile: GATEWAY_CONFIG_FILE, + launchAtLoginSupported: isLaunchAtLoginSupported(), name: APP_NAME, platform: process.platform, requestLogsDbFile: REQUEST_LOGS_DB_FILE, @@ -253,7 +255,19 @@ ipcMain.handle(IPC_CHANNELS.appSaveConfig, async (_event, config: AppConfig, opt throw new Error(certificateStatus.message); } } + const launchAtLoginChanged = Boolean(config.launchAtLogin) !== Boolean(previousConfig.launchAtLogin); let savedConfig = await saveAppConfig(config); + if (launchAtLoginChanged) { + try { + syncLaunchAtLogin(savedConfig); + } catch (error) { + await saveAppConfig({ + ...savedConfig, + launchAtLogin: previousConfig.launchAtLogin + }); + throw error; + } + } const syncedClaudeAppConfig = await syncClaudeAppGatewayConfig(savedConfig); savedConfig = syncedClaudeAppConfig.config; let runtimeStatus = gatewayService.getStatus(); diff --git a/src/main/launch-at-login.ts b/src/main/launch-at-login.ts new file mode 100644 index 0000000..f79ea65 --- /dev/null +++ b/src/main/launch-at-login.ts @@ -0,0 +1,28 @@ +import { app } from "electron"; +import type { AppConfig } from "../shared/app"; + +export function isLaunchAtLoginSupported(platform = process.platform): boolean { + return platform === "darwin" || platform === "win32"; +} + +export function syncLaunchAtLogin(config: Pick): void { + if (!isLaunchAtLoginSupported()) { + return; + } + app.setLoginItemSettings(loginItemSettings(Boolean(config.launchAtLogin))); +} + +function loginItemSettings(openAtLogin: boolean): Electron.Settings { + const settings: Electron.Settings = { + openAtLogin + }; + + if (process.platform === "darwin") { + settings.openAsHidden = false; + return settings; + } + + settings.path = process.execPath; + settings.args = process.defaultApp ? [app.getAppPath()] : []; + return settings; +} diff --git a/src/main/main-app.ts b/src/main/main-app.ts index a065668..eb7f7f0 100644 --- a/src/main/main-app.ts +++ b/src/main/main-app.ts @@ -7,6 +7,7 @@ import { gatewayService } from "../server/gateway/service"; import "./ipc"; import { applyProfileConfig } from "./profile-service"; import { ensureCcrCliLauncher } from "./profile-launch-service"; +import { syncLaunchAtLogin } from "./launch-at-login"; import { proxyService } from "../server/proxy/service"; import trayController from "./tray-controller"; import { appUpdateService } from "./update-service"; @@ -182,6 +183,11 @@ function startConfiguredServices(reason: string): Promise { } catch (error) { console.error(`Failed to sync Claude App gateway config during ${reason}: ${formatError(error)}`); } + try { + syncLaunchAtLogin(config); + } catch (error) { + console.error(`Failed to sync launch-at-login setting during ${reason}: ${formatError(error)}`); + } const status = await gatewayService.start(config); if (status.state === "error") { console.error(`Failed to start gateway during ${reason}: ${status.lastError}`); diff --git a/src/main/web-management-server.ts b/src/main/web-management-server.ts index 40d7f2b..6a4924d 100644 --- a/src/main/web-management-server.ts +++ b/src/main/web-management-server.ts @@ -497,6 +497,7 @@ function getCliAppInfo(): AppInfo { configFile: CONFIG_FILE, dataDir: DATADIR, gatewayConfigFile: GATEWAY_CONFIG_FILE, + launchAtLoginSupported: false, name: APP_NAME, platform: process.platform, requestLogsDbFile: REQUEST_LOGS_DB_FILE, diff --git a/src/renderer/pages/home/App.tsx b/src/renderer/pages/home/App.tsx index 99fdea4..be2163d 100644 --- a/src/renderer/pages/home/App.tsx +++ b/src/renderer/pages/home/App.tsx @@ -2040,6 +2040,13 @@ function App() { })); } + function changeLaunchAtLogin(launchAtLogin: boolean) { + updateConfig((config) => ({ + ...config, + launchAtLogin + })); + } + function changeTrayIconPreference(value: string) { const trayIcon = normalizeTrayIconPreference(value); if (trayIcon === "progress" && !normalizeTrayBalanceProgressConfig(draftConfig.trayBalanceProgress)) { @@ -3120,9 +3127,10 @@ function App() { botConfigs: draftConfig.botConfigs, copy, initialPage: settingsInitialPage, - traySupported, languagePreference, + launchAtLogin: Boolean(draftConfig.launchAtLogin), onChangeBotConfigs: changeBotConfigs, + onChangeLaunchAtLogin: changeLaunchAtLogin, onChangeLanguage: changeLanguagePreference, onChangeObservability: changeObservabilityConfig, onChangeTheme: changeThemePreference, @@ -3138,6 +3146,7 @@ function App() { providerAccountSnapshots, trayBalanceProgress: normalizeTrayBalanceProgressConfig(draftConfig.trayBalanceProgress), trayIconPreference: draftConfig.trayIcon || "random", + traySupported, trayWidgets: normalizeTrayWidgets(draftConfig.trayWidgets ?? DEFAULT_TRAY_WIDGETS, draftConfig.trayWindowModules, draftConfig.trayComponentVariants) } : undefined} update={updateDialogOpen ? { diff --git a/src/renderer/pages/home/components/settings.tsx b/src/renderer/pages/home/components/settings.tsx index 0c9a051..4fba842 100644 --- a/src/renderer/pages/home/components/settings.tsx +++ b/src/renderer/pages/home/components/settings.tsx @@ -22,7 +22,9 @@ export function AppSettingsDialog({ copy, initialPage = "appearance", languagePreference, + launchAtLogin, onChangeBotConfigs, + onChangeLaunchAtLogin, onChangeObservability, onChangeTrayBalanceProgress, onChangeLanguage, @@ -47,7 +49,9 @@ export function AppSettingsDialog({ copy: AppCopy; initialPage?: SettingsPageId; languagePreference: AppLanguagePreference; + launchAtLogin: boolean; onChangeBotConfigs: (configs: BotGatewaySavedConfig[]) => void; + onChangeLaunchAtLogin: (checked: boolean) => void; onChangeObservability: (patch: Partial) => void; onChangeTrayBalanceProgress: (config: TrayBalanceProgressConfig) => void; onChangeLanguage: (value: string) => void; @@ -77,6 +81,9 @@ export function AppSettingsDialog({ void; onChangeLanguage: (value: string) => void; onChangeTheme: (value: string) => void; systemLanguage: ResolvedLanguage; @@ -296,6 +309,15 @@ function AppearanceSettingsPage({ + {launchAtLoginSupported ? ( + + ) : null}
); @@ -314,14 +336,14 @@ function ObservabilitySettingsPage({

{copy.settings.observability}

- onChange({ requestLogs })} /> - = { languageChinese: "Chinese", languageEnglish: "English", languageSystem: "System", + launchAtLogin: "Launch at login", + launchAtLoginDescription: "Open Claude Code Router automatically when you sign in to this computer.", observability: "Logs & Observability", requestLogs: "Request logs", requestLogsDescription: "Record gateway requests and show the Logs page.", @@ -461,6 +465,8 @@ export const appCopy: Record = { languageChinese: "中文", languageEnglish: "英文", languageSystem: "跟随系统", + launchAtLogin: "开机自启", + launchAtLoginDescription: "登录系统后自动打开 Claude Code Router。", observability: "日志与观测", requestLogs: "请求日志", requestLogsDescription: "记录网关请求并显示日志页。", diff --git a/src/server/gateway/claude-code-router-plugin.ts b/src/server/gateway/claude-code-router-plugin.ts index 89726b3..18d6d51 100644 --- a/src/server/gateway/claude-code-router-plugin.ts +++ b/src/server/gateway/claude-code-router-plugin.ts @@ -198,21 +198,45 @@ function resolveConfiguredRouteDecision( const router = config.Router; const builtInDecision = resolveBuiltInAgentRouteDecision(request, config); - if (builtInDecision) { - return builtInDecision; - } - const rules = router.rules ?? []; for (const rule of rules) { const decision = resolveRouterRule(rule, request, router); if (decision) { - return decision; + return builtInDecision ? mergeConfiguredRouteDecisions(builtInDecision, decision) : decision; } } + if (builtInDecision) { + return builtInDecision; + } + return { fallback: router.fallback, model: explicitModel, reason: "default" }; } +function mergeConfiguredRouteDecisions( + base: ConfiguredRouteDecision, + override: ConfiguredRouteDecision +): ConfiguredRouteDecision { + const rewrites = [ + ...configuredRouteDecisionRewrites(base), + ...configuredRouteDecisionRewrites(override) + ]; + return { + fallback: override.fallback ?? base.fallback, + model: override.model ?? base.model, + reason: override.reason, + ...(rewrites.length === 1 ? { rewrite: rewrites[0] } : {}), + ...(rewrites.length > 0 ? { rewrites } : {}) + }; +} + +function configuredRouteDecisionRewrites(decision: ConfiguredRouteDecision): RouterRuleRewrite[] { + if (decision.rewrites?.length) { + return decision.rewrites; + } + return decision.rewrite ? [decision.rewrite] : []; +} + function resolveBuiltInClaudeCodeSubagentRouteDecision( request: MutableRequestLike, config: AppConfig diff --git a/src/server/gateway/service.ts b/src/server/gateway/service.ts index efc56ac..7780980 100644 --- a/src/server/gateway/service.ts +++ b/src/server/gateway/service.ts @@ -2192,6 +2192,58 @@ function applyProviderCapabilityRouting(input: { }; } +export function prepareGatewayUpstreamAttemptForTest(input: { + body: Record; + config: AppConfig; + fallback?: RouterFallbackConfig; + headers: Record; + method: string; + path: string; + routedModel?: string; +}): { + body?: Record; + credentialChain?: string[]; + credentialIds?: string[]; + credentialProtocol?: GatewayProviderProtocol; + fallback: RouterFallbackConfig; + headers?: Record; + logicalProvider?: string; + model?: string; + routedModel?: string; +} { + const headers = { ...input.headers }; + const providerCapabilityRouting = applyProviderCapabilityRouting({ + body: Buffer.from(`${JSON.stringify(input.body)}\n`, "utf8"), + config: input.config, + fallback: input.fallback ?? input.config.Router.fallback, + headers, + path: input.path, + routedModel: input.routedModel + }); + const attempt = prepareUpstreamCredentialAttempt({ + attempt: { + body: providerCapabilityRouting.body, + index: 0, + model: normalizeRouteSelector(providerCapabilityRouting.routedModel) + }, + config: input.config, + headers, + method: input.method, + path: input.path + }); + return { + body: parseJsonObjectSafe(attempt.body), + credentialChain: attempt.credentialChain, + credentialIds: attempt.credentialIds, + credentialProtocol: attempt.credentialProtocol, + fallback: providerCapabilityRouting.fallback, + headers: attempt.headers, + logicalProvider: attempt.logicalProvider, + model: attempt.model, + routedModel: providerCapabilityRouting.routedModel + }; +} + export function prepareCodexApplyPatchBridgeRequest(input: { body?: Buffer; config: AppConfig; diff --git a/src/shared/app.ts b/src/shared/app.ts index 56bd581..17254ad 100644 --- a/src/shared/app.ts +++ b/src/shared/app.ts @@ -5,6 +5,7 @@ export type AppInfo = { configFile: string; dataDir: string; gatewayConfigFile: string; + launchAtLoginSupported: boolean; requestLogsDbFile: string; name: string; platform: string; @@ -1352,6 +1353,7 @@ export type AppConfig = { botConfigs: BotGatewaySavedConfig[]; botGateway: BotGatewayRuntimeConfig; gateway: GatewayRuntimeConfig; + launchAtLogin: boolean; observability: ObservabilityConfig; preferredProvider: string; plugins: GatewayPluginConfig[]; diff --git a/src/shared/default-config.ts b/src/shared/default-config.ts index 3eb66c8..c7953cd 100644 --- a/src/shared/default-config.ts +++ b/src/shared/default-config.ts @@ -90,6 +90,7 @@ export function createDefaultAppConfig(options: DefaultAppConfigOptions): AppCon host: "127.0.0.1", port: 3456 }, + launchAtLogin: false, observability: { agentAnalysis: false, requestLogs: false diff --git a/tests/main/router-builtins.test.mjs b/tests/main/router-builtins.test.mjs index ccc40cc..095b842 100644 --- a/tests/main/router-builtins.test.mjs +++ b/tests/main/router-builtins.test.mjs @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; import { ClaudeCodeRouterPlugin } from "../../src/server/gateway/claude-code-router-plugin.ts"; +import { prepareGatewayUpstreamAttemptForTest } from "../../src/server/gateway/service.ts"; function createRouterPlugin(options = {}) { const agent = options.agent ?? "claude-code"; @@ -21,7 +22,7 @@ function createRouterPlugin(options = {}) { codex: { enabled: options.codexRuleEnabled ?? true } }, fallback: { mode: "off", models: [], retryCount: 1 }, - rules: [] + rules: options.routerRules ?? [] }, profile: { enabled: options.profileRuntimeEnabled ?? true, @@ -40,6 +41,115 @@ function createRouterPlugin(options = {}) { }); } +function createIssue1480RouterConfig() { + return { + CUSTOM_ROUTER_PATH: "", + Providers: [ + { + api_base_url: "https://nebulacoder.example/v1/chat/completions", + credentials: [{ apiKey: "nebulacoder-key", id: "nebulacoder-main" }], + modelDescriptions: { + "nebulacoder-v8.0": "Code reading model.", + "nebulacoder-cot-v8.0": "Code reading reasoning model." + }, + models: ["nebulacoder-v8.0", "nebulacoder-cot-v8.0"], + name: "NebulaCoder" + }, + { + api_base_url: "https://coclaw.example/v1/chat/completions", + credentials: [{ apiKey: "coclaw-key", id: "coclaw-main" }], + modelDescriptions: { + "Qwen3-235B-A22B": "Background task model." + }, + models: ["Qwen3-235B-A22B"], + name: "CoClaw" + }, + { + api_base_url: "https://opencode.ai/zen/go/v1/chat/completions", + capabilities: [ + { baseUrl: "https://opencode.ai/zen/go/v1/chat/completions", type: "openai_chat_completions" } + ], + credentials: [{ apiKey: "opencode-key", id: "opencode-main" }], + modelDescriptions: { + "deepseek-v4-flash": "Default route model.", + "deepseek-v4-pro": "Thinking route model.", + "kimi-k2.6": "Claude Code profile model." + }, + models: ["kimi-k2.6", "deepseek-v4-pro", "deepseek-v4-flash"], + name: "OpenCode" + } + ], + Router: { + builtInRules: { + "claude-code": { enabled: true }, + codex: { enabled: true } + }, + fallback: { mode: "retry", models: [], retryCount: 1 }, + rules: [ + { + condition: { + left: "request.url", + operator: "contains", + right: "/v1" + }, + enabled: true, + id: "rule-default", + name: "Default route", + rewrites: [ + { key: "request.header.x-target-provider", operation: "set", value: "OpenCode" }, + { key: "request.body.model", operation: "set", value: "deepseek-v4-flash" } + ], + type: "condition" + }, + { + condition: { + left: "request.header.anthropic-background", + operator: "==", + right: "true" + }, + enabled: true, + id: "rule-background", + name: "Background tasks", + rewrites: [ + { key: "request.header.x-target-provider", operation: "set", value: "CoClaw" }, + { key: "request.body.model", operation: "set", value: "Qwen3-235B-A22B" } + ], + type: "condition" + }, + { + condition: { + left: "request.header.anthropic-thinking", + operator: "==", + right: "true" + }, + enabled: true, + id: "rule-thinking", + name: "Thinking/reasoning tasks", + rewrites: [ + { key: "request.header.x-target-provider", operation: "set", value: "OpenCode" }, + { key: "request.body.model", operation: "set", value: "deepseek-v4-pro" } + ], + type: "condition" + } + ] + }, + profile: { + enabled: true, + profiles: [ + { + agent: "claude-code", + enabled: true, + id: "claude-code-main", + model: "kimi-k2.6", + name: "Claude Code", + scope: "global" + } + ] + }, + virtualModelProfiles: [] + }; +} + test("built-in Claude Code route matches user-agent case-insensitively", async () => { const plugin = createRouterPlugin({ profileModel: "Provider/claude-sonnet" }); const result = await plugin.routeRequest({ @@ -59,6 +169,124 @@ test("built-in Claude Code route matches user-agent case-insensitively", async ( assert.equal(result.decision.reason, "builtin:claude-code"); }); +test("router rules override the built-in Claude Code profile route", async () => { + const plugin = createRouterPlugin({ + profileModel: "Provider/claude-sonnet", + routerRules: [ + { + condition: { + left: "request.url", + operator: "contains", + right: "/v1" + }, + enabled: true, + id: "default", + name: "Default", + rewrites: [ + { key: "request.header.x-target-provider", operation: "set", value: "OverrideProvider" }, + { key: "request.body.model", operation: "set", value: "Provider/gpt-5-codex" } + ], + type: "condition" + } + ] + }); + const headers = { + "user-agent": "claude-code/1.0" + }; + const result = await plugin.routeRequest({ + body: { + messages: [], + model: "claude-default" + }, + headers, + method: "POST", + url: "/v1/messages" + }); + + assert.equal(headers["x-target-provider"], "OverrideProvider"); + assert.equal(result.body.model, "Provider/gpt-5-codex"); + assert.equal(result.decision.model, "Provider/gpt-5-codex"); + assert.equal(result.decision.reason, "rule:default"); +}); + +test("router rules can add headers after the built-in Claude Code profile route", async () => { + const plugin = createRouterPlugin({ + profileModel: "Provider/claude-sonnet", + routerRules: [ + { + condition: { + left: "request.url", + operator: "contains", + right: "/v1" + }, + enabled: true, + id: "target-provider", + name: "Target provider", + rewrites: [ + { key: "request.header.x-target-provider", operation: "set", value: "Provider" } + ], + type: "condition" + } + ] + }); + const headers = { + "user-agent": "Claude Code" + }; + const result = await plugin.routeRequest({ + body: { + messages: [], + model: "claude-default" + }, + headers, + method: "POST", + url: "/v1/messages" + }); + + assert.equal(headers["x-target-provider"], "Provider"); + assert.equal(result.body.model, "Provider/claude-sonnet"); + assert.equal(result.decision.model, "Provider/claude-sonnet"); + assert.equal(result.decision.reason, "rule:target-provider"); +}); + +test("issue 1480 config routes Claude Code profile traffic through the user default OpenCode rule", async () => { + const config = createIssue1480RouterConfig(); + const plugin = new ClaudeCodeRouterPlugin(config); + const headers = { + "user-agent": "claude-cli/2.1.187 (external, cli)" + }; + const result = await plugin.routeRequest({ + body: { + messages: [], + model: "claude-sonnet-4-6", + tools: [] + }, + headers, + method: "POST", + url: "/v1/messages?beta=true" + }); + + assert.equal(headers["x-target-provider"], "OpenCode"); + assert.equal(result.body.model, "deepseek-v4-flash"); + assert.equal(result.decision.model, "deepseek-v4-flash"); + assert.equal(result.decision.reason, "rule:rule-default"); + + const upstreamAttempt = prepareGatewayUpstreamAttemptForTest({ + body: result.body, + config, + fallback: result.decision.fallback, + headers, + method: "POST", + path: "/v1/messages", + routedModel: result.decision.model + }); + + assert.equal(upstreamAttempt.logicalProvider, "OpenCode"); + assert.equal(upstreamAttempt.credentialProtocol, "openai_chat_completions"); + assert.equal(upstreamAttempt.body.model, "deepseek-v4-flash"); + assert.match(upstreamAttempt.headers["x-target-providers"], /^provider-opencode-[a-f0-9]{10}::openai_chat_completions::cred:opencode-main$/); + assert.doesNotMatch(upstreamAttempt.headers["x-target-providers"], /nebulacoder/i); +}); + test("built-in Claude Code route preserves explicit virtual gateway models", async () => { const plugin = createRouterPlugin({ profileModel: "Provider/claude-sonnet", From 0d51897d18fdf01522a6e9007dad5a129906b6fd Mon Sep 17 00:00:00 2001 From: musistudio Date: Fri, 3 Jul 2026 11:44:47 +0800 Subject: [PATCH 11/62] Add Gemini interactions protocol support --- .../en/configuration/provider-deeplink.md | 4 +- .../zh/configuration/provider-deeplink.md | 4 +- src/main/codex-model-catalog.ts | 21 ++++- src/main/config.ts | 10 +++ src/main/presets/gemini/index.ts | 2 +- src/main/provider-probe.ts | 73 ++++++++++++++-- src/main/usage-normalization.ts | 14 +++- src/renderer/pages/home/shared/i18n.tsx | 1 + src/renderer/pages/home/shared/options.ts | 3 +- src/renderer/pages/home/shared/providers.ts | 2 +- src/server/gateway/service.ts | 55 ++++++++++-- src/shared/app.ts | 3 +- src/shared/deep-link.ts | 1 + src/shared/provider-url.ts | 8 +- tests/main/codex-model-catalog.test.mjs | 10 +++ tests/main/gateway-virtual-models.test.mjs | 84 +++++++++++++++++++ tests/main/provider-probe.test.mjs | 17 ++++ tests/main/provider-url.test.mjs | 13 +++ 18 files changed, 294 insertions(+), 31 deletions(-) diff --git a/docs/src/content/docs/en/configuration/provider-deeplink.md b/docs/src/content/docs/en/configuration/provider-deeplink.md index 6932c81..6a3ba3a 100644 --- a/docs/src/content/docs/en/configuration/provider-deeplink.md +++ b/docs/src/content/docs/en/configuration/provider-deeplink.md @@ -133,7 +133,7 @@ For larger configs, pass a manifest: | `name` | Provider display name | | `base_url` | Provider API Base URL, required for direct imports | | `api_key` | Optional provider API key | -| `protocol` | Protocol, one of `openai_chat_completions`, `openai_responses`, `anthropic_messages`, `gemini_generate_content` | +| `protocol` | Protocol, one of `openai_chat_completions`, `openai_responses`, `anthropic_messages`, `gemini_generate_content`, `gemini_interactions` | | `models` | Model list. Use comma/newline-separated text in HTML, or a string/array in JavaScript | | `icon` | Provider icon URL | | `source` | Provider website or config source | @@ -261,7 +261,7 @@ Complete manifest example: | `name` | Provider display name | | `base_url` | Provider API Base URL, required | | `api_key` | Optional provider API key | -| `protocol` | Protocol, one of `openai_chat_completions`, `openai_responses`, `anthropic_messages`, `gemini_generate_content` | +| `protocol` | Protocol, one of `openai_chat_completions`, `openai_responses`, `anthropic_messages`, `gemini_generate_content`, `gemini_interactions` | | `models` | Model list, comma-separated, newline-separated, or repeated | | `icon` | Provider icon URL | | `source` | Provider website or config source | diff --git a/docs/src/content/docs/zh/configuration/provider-deeplink.md b/docs/src/content/docs/zh/configuration/provider-deeplink.md index f2d2537..12546d2 100644 --- a/docs/src/content/docs/zh/configuration/provider-deeplink.md +++ b/docs/src/content/docs/zh/configuration/provider-deeplink.md @@ -133,7 +133,7 @@ CCR 也提供了一个无框架的按钮脚本,供应商可以嵌入到自己 | `name` | Provider 展示名称 | | `base_url` | Provider API Base URL,直链导入时必填 | | `api_key` | 可选 Provider API Key | -| `protocol` | 协议类型,支持 `openai_chat_completions`、`openai_responses`、`anthropic_messages`、`gemini_generate_content` | +| `protocol` | 协议类型,支持 `openai_chat_completions`、`openai_responses`、`anthropic_messages`、`gemini_generate_content`、`gemini_interactions` | | `models` | 模型列表。HTML 中用逗号或换行分隔,JS 中可传字符串或数组 | | `icon` | Provider 图标 URL | | `source` | Provider 官网或配置来源 | @@ -261,7 +261,7 @@ Manifest 可以把供应商信息放在顶层 `provider` 对象中: | `name` | Provider 展示名称 | | `base_url` | Provider API Base URL,必填 | | `api_key` | 可选 Provider API Key | -| `protocol` | 协议类型,支持 `openai_chat_completions`、`openai_responses`、`anthropic_messages`、`gemini_generate_content` | +| `protocol` | 协议类型,支持 `openai_chat_completions`、`openai_responses`、`anthropic_messages`、`gemini_generate_content`、`gemini_interactions` | | `models` | 模型列表,支持逗号或换行分隔,也可以重复传入 | | `icon` | Provider 图标 URL | | `source` | Provider 官网或配置来源 | diff --git a/src/main/codex-model-catalog.ts b/src/main/codex-model-catalog.ts index 841f553..07ba04d 100644 --- a/src/main/codex-model-catalog.ts +++ b/src/main/codex-model-catalog.ts @@ -178,7 +178,11 @@ function codexModelCapabilityProfile( supportsFusionWebSearch || ( readCatalogCapability(capabilities, "webSearch") && - (providerProtocol === "openai_responses" || providerProtocol === "anthropic_messages") + ( + providerProtocol === "openai_responses" || + providerProtocol === "anthropic_messages" || + providerProtocol === "gemini_interactions" + ) ); return { @@ -231,7 +235,7 @@ function findConfiguredProvider( function codexProviderProtocol(provider: GatewayProviderConfig): GatewayProviderProtocol | undefined { const capabilityProtocols = uniqueProviderProtocols((provider.capabilities ?? []).map((capability) => normalizeProviderProtocol(capability.type))); - for (const protocol of ["openai_responses", "openai_chat_completions", "anthropic_messages", "gemini_generate_content"] as GatewayProviderProtocol[]) { + for (const protocol of ["openai_responses", "openai_chat_completions", "anthropic_messages", "gemini_generate_content", "gemini_interactions"] as GatewayProviderProtocol[]) { if (capabilityProtocols.includes(protocol)) { return protocol; } @@ -253,6 +257,9 @@ function inferProviderProtocol(provider: GatewayProviderConfig): GatewayProvider if (providerEndpointLooksLikeResponses(provider)) { return "openai_responses"; } + if (url.includes("/interactions") || transformer.includes("gemini_interactions")) { + return "gemini_interactions"; + } if (url.includes("generativelanguage.googleapis.com") || transformer.includes("gemini")) { return "gemini_generate_content"; } @@ -308,6 +315,16 @@ function normalizeProviderProtocol(value: unknown): GatewayProviderProtocol | un if (normalized === "gemini" || normalized === "gemini_generate_content") { return "gemini_generate_content"; } + if ( + normalized === "gemini_interactions" || + normalized === "gemini-interactions" || + normalized === "google_interactions" || + normalized === "google-interactions" || + normalized === "interactions" || + normalized === "interaction" + ) { + return "gemini_interactions"; + } return undefined; } diff --git a/src/main/config.ts b/src/main/config.ts index c0816cb..fa13597 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -1185,6 +1185,16 @@ function parseProviderCapabilityProtocol(value: string | undefined): GatewayProv if (normalized === "gemini" || normalized === "gemini_generate_content") { return "gemini_generate_content"; } + if ( + normalized === "gemini_interactions" || + normalized === "gemini-interactions" || + normalized === "google_interactions" || + normalized === "google-interactions" || + normalized === "interactions" || + normalized === "interaction" + ) { + return "gemini_interactions"; + } return undefined; } diff --git a/src/main/presets/gemini/index.ts b/src/main/presets/gemini/index.ts index 432d4ff..4d92ad5 100644 --- a/src/main/presets/gemini/index.ts +++ b/src/main/presets/gemini/index.ts @@ -6,7 +6,7 @@ export const geminiProviderPreset: ProviderPreset = { endpoints: [ { baseUrl: "https://generativelanguage.googleapis.com", - protocols: ["gemini_generate_content"] + protocols: ["gemini_generate_content", "gemini_interactions"] } ], id: "gemini", diff --git a/src/main/provider-probe.ts b/src/main/provider-probe.ts index b1d7389..aaf6d1f 100644 --- a/src/main/provider-probe.ts +++ b/src/main/provider-probe.ts @@ -59,7 +59,8 @@ const protocolOrder: GatewayProviderProtocol[] = [ "openai_responses", "openai_chat_completions", "anthropic_messages", - "gemini_generate_content" + "gemini_generate_content", + "gemini_interactions" ]; const modelSourceOrder: ModelSource[] = ["openai", "anthropic", "gemini"]; @@ -608,6 +609,24 @@ function requestForProtocol(protocol: GatewayProviderProtocol, model: string, ap }; } + if (protocol === "gemini_interactions") { + return { + body: JSON.stringify({ + generation_config: { + max_output_tokens: probeOutputTokenLimit + }, + input: "ping", + model, + store: false + }), + headers: { + "content-type": "application/json", + ...geminiHeaders(apiKey) + }, + method: "POST" + }; + } + return { body: JSON.stringify({ contents: [{ parts: [{ text: "ping" }], role: "user" }], @@ -701,6 +720,19 @@ function endpointsForProtocol( ])); } + if (protocol === "gemini_interactions") { + return [ + { + baseUrl: parsed.geminiBaseUrl, + endpoint: `${parsed.geminiBaseUrl}/v1beta/interactions` + }, + { + baseUrl: parsed.geminiBaseUrl, + endpoint: `${parsed.geminiBaseUrl}/v1/interactions` + } + ]; + } + const encodedModel = encodeURIComponent(stripGeminiModelPrefix(model || "model")); return [ { @@ -747,7 +779,7 @@ function headersForProtocol(protocol: GatewayProviderProtocol, apiKey: string | if (protocol === "anthropic_messages") { return anthropicHeaders(apiKey); } - if (protocol === "gemini_generate_content") { + if (protocol === "gemini_generate_content" || protocol === "gemini_interactions") { return geminiHeaders(apiKey); } return openAiHeaders(apiKey); @@ -834,7 +866,7 @@ function pickProbeModel(models: string[], protocol: GatewayProviderProtocol): st return undefined; } - if (protocol === "gemini_generate_content") { + if (protocol === "gemini_generate_content" || protocol === "gemini_interactions") { return candidates.find((model) => model.toLowerCase().includes("gemini")) ?? candidates[0]; } @@ -883,7 +915,7 @@ function protocolModelSource(protocol: GatewayProviderProtocol): ModelSource { if (protocol === "anthropic_messages") { return "anthropic"; } - if (protocol === "gemini_generate_content") { + if (protocol === "gemini_generate_content" || protocol === "gemini_interactions") { return "gemini"; } return "openai"; @@ -921,8 +953,13 @@ function detectProtocol( return "anthropic_messages"; } - if (modelSource === "gemini" && protocolIsAllowed("gemini_generate_content", allowedProtocols)) { - return "gemini_generate_content"; + if (modelSource === "gemini") { + const geminiProtocols = orderedProtocols(parsed, allowedProtocols).filter((protocol) => + protocol === "gemini_generate_content" || protocol === "gemini_interactions" + ); + return geminiProtocols.find((protocol) => parsed.hints.includes(protocol)) ?? + geminiProtocols.find((protocol) => protocol === "gemini_generate_content") ?? + geminiProtocols[0]; } if (modelSource === "openai") { @@ -975,9 +1012,15 @@ function protocolHints(value: string): GatewayProviderProtocol[] { if (normalized.includes("anthropic") || normalized.includes("/messages")) { hints.push("anthropic_messages"); } + if (normalized.includes("interactions") || normalized.includes("gemini_interactions") || normalized.includes("google_interactions")) { + hints.push("gemini_interactions"); + } if (normalized.includes("generativelanguage.googleapis.com") || normalized.includes("gemini") || normalized.includes("generatecontent")) { hints.push("gemini_generate_content"); } + if (normalized.includes("generativelanguage.googleapis.com")) { + hints.push("gemini_interactions"); + } return hints; } @@ -1001,9 +1044,21 @@ function isProtocolSupported( if (status === 400) { const normalized = message.toLowerCase(); - const schemaError = /model|max_tokens|max output|messages|input|required/.test(normalized) || - (protocol === "gemini_generate_content" && /contents|generatecontentrequest|generationconfig/.test(normalized)); - return schemaError && !/not found|unknown endpoint|unknown route|no route/.test(normalized); + if (/not found|unknown endpoint|unknown route|no route/.test(normalized)) { + return false; + } + if (protocol === "openai_responses") { + return /model|max_output|max output|input|required/.test(normalized); + } + if (protocol === "openai_chat_completions" || protocol === "anthropic_messages") { + return /model|max_tokens|messages|required/.test(normalized); + } + if (protocol === "gemini_generate_content") { + return /contents|generatecontentrequest|generationconfig/.test(normalized); + } + if (protocol === "gemini_interactions") { + return /model|input|required|interaction|generation_config|generationconfig|system_instruction/.test(normalized); + } } return false; diff --git a/src/main/usage-normalization.ts b/src/main/usage-normalization.ts index 03ca697..a3dfa0b 100644 --- a/src/main/usage-normalization.ts +++ b/src/main/usage-normalization.ts @@ -60,7 +60,12 @@ function inputIncludesCacheTokensForProtocol(protocol: GatewayProviderProtocol | if (protocol === "anthropic_messages") { return false; } - if (protocol === "openai_chat_completions" || protocol === "openai_responses" || protocol === "gemini_generate_content") { + if ( + protocol === "openai_chat_completions" || + protocol === "openai_responses" || + protocol === "gemini_generate_content" || + protocol === "gemini_interactions" + ) { return true; } return undefined; @@ -71,7 +76,12 @@ function inputIncludesCacheTokensForPath(path: string | undefined): boolean | un if (!normalized) { return undefined; } - if (normalized.includes("/chat/completions") || normalized.includes("/responses") || normalized.includes(":generatecontent")) { + if ( + normalized.includes("/chat/completions") || + normalized.includes("/responses") || + normalized.includes(":generatecontent") || + normalized.includes("/interactions") + ) { return true; } if (normalized.includes("/messages")) { diff --git a/src/renderer/pages/home/shared/i18n.tsx b/src/renderer/pages/home/shared/i18n.tsx index f412f2e..d84b7f7 100644 --- a/src/renderer/pages/home/shared/i18n.tsx +++ b/src/renderer/pages/home/shared/i18n.tsx @@ -1444,6 +1444,7 @@ export const appCopy: Record = { "OpenAI Responses": "OpenAI Responses", "Anthropic Messages": "Anthropic Messages", "Gemini Generate": "Gemini 生成", + "Gemini Interactions": "Gemini Interactions", "Model required before protocol verification.": "需要先填写模型,才能验证协议。", "No endpoint candidates available.": "没有可用的端点候选。", "Request failed.": "请求失败。", diff --git a/src/renderer/pages/home/shared/options.ts b/src/renderer/pages/home/shared/options.ts index a47466f..8eb75a9 100644 --- a/src/renderer/pages/home/shared/options.ts +++ b/src/renderer/pages/home/shared/options.ts @@ -144,7 +144,8 @@ export const providerProtocolOptions: Array<{ label: string; value: GatewayProvi { label: "OpenAI Chat", value: "openai_chat_completions" }, { label: "OpenAI Responses", value: "openai_responses" }, { label: "Anthropic Messages", value: "anthropic_messages" }, - { label: "Gemini Generate", value: "gemini_generate_content" } + { label: "Gemini Generate", value: "gemini_generate_content" }, + { label: "Gemini Interactions", value: "gemini_interactions" } ]; export const providerAccountModeOptions: Array<{ label: string; value: ProviderAccountDraftMode }> = [ diff --git a/src/renderer/pages/home/shared/providers.ts b/src/renderer/pages/home/shared/providers.ts index 5aba189..bbf03cb 100644 --- a/src/renderer/pages/home/shared/providers.ts +++ b/src/renderer/pages/home/shared/providers.ts @@ -1948,7 +1948,7 @@ export function recommendedModelRank(model: string, protocol?: GatewayProviderPr if (normalized.includes("opus")) return 1; if (normalized.includes("haiku")) return 2; } - if (protocol === "gemini_generate_content" || normalized.includes("gemini")) { + if (protocol === "gemini_generate_content" || protocol === "gemini_interactions" || normalized.includes("gemini")) { if (normalized.includes("pro")) return 0; if (normalized.includes("flash")) return 1; } diff --git a/src/server/gateway/service.ts b/src/server/gateway/service.ts index 7780980..59d0b97 100644 --- a/src/server/gateway/service.ts +++ b/src/server/gateway/service.ts @@ -302,7 +302,8 @@ const gatewayProviderProtocolFallbackOrder: GatewayProviderProtocol[] = [ "anthropic_messages", "openai_chat_completions", "openai_responses", - "gemini_generate_content" + "gemini_generate_content", + "gemini_interactions" ]; const privateDirMode = 0o700; const privateFileMode = 0o600; @@ -2184,6 +2185,7 @@ function applyProviderCapabilityRouting(input: { const routedModel = rewriteModelSelectorForProtocol(input.routedModel, input.config, protocol); const fallback = rewriteFallbackForProtocol(input.fallback, input.config, protocol); const body = rewriteBodyModelForProtocol(input.body, input.config, protocol); + clearTargetProviderHeadersForModelSelector(input.headers, input.config, body, routedModel); return { body, @@ -5242,6 +5244,9 @@ function requestProtocolForPath(path: string): GatewayProviderProtocol | undefin if (/\/v1(?:beta)?\/models\/[^/]+:(?:generatecontent|streamgeneratecontent)$/i.test(normalized)) { return "gemini_generate_content"; } + if (/\/v1(?:beta)?\/interactions(?:\/[^/]+(?:\/cancel)?)?$/i.test(normalized)) { + return "gemini_interactions"; + } return undefined; } @@ -5304,6 +5309,23 @@ function rewriteBodyModelForProtocol(body: Buffer | undefined, config: AppConfig return Buffer.from(`${JSON.stringify({ ...parsedBody, model: rewrittenModel })}\n`, "utf8"); } +function clearTargetProviderHeadersForModelSelector( + headers: Record, + config: AppConfig, + body: Buffer | undefined, + routedModel: string | undefined +): void { + const parsedBody = parseJsonObjectSafe(body); + const model = stringValue(parsedBody?.model) || routedModel; + if (!resolveConfiguredProviderModelSelector(model, config)) { + return; + } + + delete headers["x-target-provider"]; + delete headers["x-target-providers"]; + delete headers["x-gateway-target-provider"]; +} + function rewriteModelSelectorForProtocol( model: string | undefined, config: AppConfig, @@ -5356,7 +5378,7 @@ function providerProtocolForClientProtocol( function providerProtocolPreferenceForClient(clientProtocol: GatewayProviderProtocol): GatewayProviderProtocol[] { if (clientProtocol === "openai_responses") { - return ["openai_responses", "openai_chat_completions", "anthropic_messages"]; + return ["openai_responses", "openai_chat_completions", "anthropic_messages", "gemini_interactions"]; } if (clientProtocol === "anthropic_messages") { return uniqueProviderProtocols([clientProtocol, ...gatewayProviderProtocolFallbackOrder]); @@ -6308,16 +6330,18 @@ function applyPresetProtocolLock( provider: GatewayProviderConfig, capabilities: GatewayProviderCapability[] ): GatewayProviderCapability[] { - const lockedProtocol = lockedProviderPresetProtocol(provider, capabilities); - if (!lockedProtocol) { + const lockedProtocols = lockedProviderPresetProtocols(provider, capabilities); + if (lockedProtocols.length === 0) { return capabilities; } - const lockedCapabilities = capabilities.filter((capability) => capability.type === lockedProtocol); + const lockedProtocolSet = new Set(lockedProtocols); + const lockedCapabilities = capabilities.filter((capability) => lockedProtocolSet.has(capability.type)); if (lockedCapabilities.length > 0) { return lockedCapabilities; } + const lockedProtocol = lockedProtocols[0]; const baseUrl = readBaseUrl(provider); const normalizedBaseUrl = normalizeProviderRuntimeBaseUrl(baseUrl, lockedProtocol); return normalizedBaseUrl @@ -6325,10 +6349,10 @@ function applyPresetProtocolLock( : []; } -function lockedProviderPresetProtocol( +function lockedProviderPresetProtocols( provider: GatewayProviderConfig, capabilities: GatewayProviderCapability[] -): GatewayProviderProtocol | undefined { +): GatewayProviderProtocol[] { const baseUrls = [ readBaseUrl(provider), ...capabilities.map((capability) => capability.baseUrl) @@ -6336,11 +6360,11 @@ function lockedProviderPresetProtocol( for (const baseUrl of baseUrls) { if (findProviderPresetByBaseUrl(baseUrl)?.id === "gemini") { - return "gemini_generate_content"; + return ["gemini_generate_content", "gemini_interactions"]; } } - return undefined; + return []; } function providerCapabilityPriority(capability: GatewayProviderCapability): number { @@ -6488,12 +6512,25 @@ function normalizeProviderProtocol(value: unknown): GatewayProviderProtocol | un if (normalized === "gemini" || normalized === "gemini_generate_content") { return "gemini_generate_content"; } + if ( + normalized === "gemini_interactions" || + normalized === "gemini-interactions" || + normalized === "google_interactions" || + normalized === "google-interactions" || + normalized === "interactions" || + normalized === "interaction" + ) { + return "gemini_interactions"; + } return undefined; } function inferProtocol(provider: GatewayProviderConfig): GatewayProviderProtocol { const url = readBaseUrl(provider)?.toLowerCase() ?? ""; const transformerNames = JSON.stringify(provider.transformer ?? "").toLowerCase(); + if (url.includes("/interactions") || transformerNames.includes("gemini_interactions")) { + return "gemini_interactions"; + } if (url.includes("generativelanguage.googleapis.com") || transformerNames.includes("gemini")) { return "gemini_generate_content"; } diff --git a/src/shared/app.ts b/src/shared/app.ts index 17254ad..922147f 100644 --- a/src/shared/app.ts +++ b/src/shared/app.ts @@ -113,7 +113,8 @@ export type GatewayProviderProtocol = | "openai_responses" | "openai_chat_completions" | "anthropic_messages" - | "gemini_generate_content"; + | "gemini_generate_content" + | "gemini_interactions"; export type GatewayProviderConfig = { account?: ProviderAccountConfig; diff --git a/src/shared/deep-link.ts b/src/shared/deep-link.ts index f74e97f..5b9999e 100644 --- a/src/shared/deep-link.ts +++ b/src/shared/deep-link.ts @@ -26,6 +26,7 @@ const maxModels = 300; const providerProtocols = new Set([ "anthropic_messages", "gemini_generate_content", + "gemini_interactions", "openai_chat_completions", "openai_responses" ]); diff --git a/src/shared/provider-url.ts b/src/shared/provider-url.ts index fce24da..b105f2b 100644 --- a/src/shared/provider-url.ts +++ b/src/shared/provider-url.ts @@ -102,6 +102,9 @@ function stripProviderEndpointPath(pathname: string): string { [/\/messages$/i, ""], [/\/v1beta\/models\/[^/]+:(generateContent|streamGenerateContent)$/i, "/v1beta"], [/\/v1\/models\/[^/]+:(generateContent|streamGenerateContent)$/i, "/v1"], + [/\/v1beta\/interactions(?:\/[^/]+(?:\/cancel)?)?$/i, "/v1beta"], + [/\/v1\/interactions(?:\/[^/]+(?:\/cancel)?)?$/i, "/v1"], + [/\/interactions(?:\/[^/]+(?:\/cancel)?)?$/i, ""], [/\/v1beta\/models$/i, "/v1beta"], [/\/v1\/models$/i, "/v1"], [/\/models$/i, ""] @@ -178,10 +181,13 @@ function normalizeProviderBaseUrlText(value: string, protocol?: GatewayProviderP if (protocol === "anthropic_messages") { return normalized.replace(/\/v1\/messages$/i, "").replace(/\/messages$/i, "").replace(/\/v1$/i, ""); } - if (protocol === "gemini_generate_content") { + if (protocol === "gemini_generate_content" || protocol === "gemini_interactions") { return normalized .replace(/\/v1beta\/models\/[^/]+:(generateContent|streamGenerateContent)$/i, "") .replace(/\/v1\/models\/[^/]+:(generateContent|streamGenerateContent)$/i, "") + .replace(/\/v1beta\/interactions(?:\/[^/]+(?:\/cancel)?)?$/i, "") + .replace(/\/v1\/interactions(?:\/[^/]+(?:\/cancel)?)?$/i, "") + .replace(/\/interactions(?:\/[^/]+(?:\/cancel)?)?$/i, "") .replace(/\/v1beta\/models$/i, "") .replace(/\/v1\/models$/i, "") .replace(/\/v1beta$/i, "") diff --git a/tests/main/codex-model-catalog.test.mjs b/tests/main/codex-model-catalog.test.mjs index b08f94f..91e250c 100644 --- a/tests/main/codex-model-catalog.test.mjs +++ b/tests/main/codex-model-catalog.test.mjs @@ -61,6 +61,16 @@ test("codex catalog enables multimodal reasoning and search when provider protoc assert.equal(model.apply_patch_tool_type, "freeform"); }); +test("codex catalog enables native search for Gemini Interactions providers", () => { + const model = catalogModelFor({ + Providers: [ + { name: "gemini", type: "gemini_interactions", models: ["gemini-2.5-pro"] } + ] + }, "gemini/gemini-2.5-pro"); + + assert.equal(model.supports_search_tool, true); +}); + test("codex catalog does not expose native search through chat-completions-only providers", () => { const model = catalogModelFor({ Providers: [ diff --git a/tests/main/gateway-virtual-models.test.mjs b/tests/main/gateway-virtual-models.test.mjs index 7b51808..06e9895 100644 --- a/tests/main/gateway-virtual-models.test.mjs +++ b/tests/main/gateway-virtual-models.test.mjs @@ -7,6 +7,7 @@ import { fusionToolNamesBackedByMcpServers, extractHostedWebSearchQueryHint, hostedWebSearchProtocolResponseStream, + prepareGatewayUpstreamAttemptForTest, prepareAnthropicWebSearchProtocolRequestBody, prepareClaudeCodeWebSearchContinuationRequestBody, prepareHostedWebSearchProtocolRequestBody, @@ -129,6 +130,89 @@ test("gateway ignores non-Gemini capabilities on Gemini preset providers", () => ); }); +test("gateway keeps Gemini Interactions capability on Gemini preset providers", () => { + const providerName = "Google Gemini"; + const config = { + Providers: [ + { + api_base_url: "https://generativelanguage.googleapis.com", + capabilities: [ + { baseUrl: "https://generativelanguage.googleapis.com", source: "preset", type: "gemini_generate_content" }, + { baseUrl: "https://generativelanguage.googleapis.com", source: "preset", type: "gemini_interactions" }, + { baseUrl: "https://generativelanguage.googleapis.com", source: "preset", type: "openai_responses" } + ], + models: ["gemini-3.5-flash"], + name: providerName, + type: "gemini_generate_content" + } + ], + Router: { fallback: { mode: "off", models: [], retryCount: 0 } }, + gateway: {} + }; + + const attempt = prepareGatewayUpstreamAttemptForTest({ + body: { + input: "Say hello", + model: `${providerName}/gemini-3.5-flash` + }, + config, + headers: { + "x-target-provider": providerName + }, + method: "POST", + path: "/v1/responses" + }); + + assert.equal(attempt.headers["x-target-provider"], undefined); + assert.match(attempt.body.model, /^provider-google-gemini-[a-f0-9]{10}::gemini_interactions\/gemini-3\.5-flash$/); +}); + +test("gateway lets explicit Gemini Interactions model selectors override generic target provider lists", () => { + const geminiName = "Google Gemini"; + const config = { + Providers: [ + { + api_base_url: "https://generativelanguage.googleapis.com", + capabilities: [ + { baseUrl: "https://generativelanguage.googleapis.com", source: "preset", type: "gemini_interactions" } + ], + models: ["gemma-4-31b-it"], + name: geminiName, + type: "gemini_interactions" + }, + { + api_base_url: "https://open.bigmodel.cn/api/coding/paas/v4", + capabilities: [ + { baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4", type: "openai_chat_completions" }, + { baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4", type: "anthropic_messages" } + ], + credentials: [{ apiKey: "test-key", id: "test-1" }], + models: ["glm-5.2", "glm-4.5-air", "glm-5v-turbo"], + name: "Zhipu AI (China) - Coding Plan", + type: "openai_chat_completions" + } + ], + Router: { fallback: { mode: "off", models: [], retryCount: 0 } }, + gateway: {} + }; + + const attempt = prepareGatewayUpstreamAttemptForTest({ + body: { + input: "Say hello", + model: `${geminiName}/gemma-4-31b-it` + }, + config, + headers: { + "x-target-providers": "openai,anthropic" + }, + method: "POST", + path: "/v1/responses" + }); + + assert.equal(attempt.headers["x-target-providers"], undefined); + assert.match(attempt.body.model, /^provider-google-gemini-[a-f0-9]{10}::gemini_interactions\/gemma-4-31b-it$/); +}); + test("gateway config normalizes Fusion web search tool names for native Anthropic search triggers", () => { const profiles = [ { diff --git a/tests/main/provider-probe.test.mjs b/tests/main/provider-probe.test.mjs index 1291117..676db01 100644 --- a/tests/main/provider-probe.test.mjs +++ b/tests/main/provider-probe.test.mjs @@ -22,6 +22,10 @@ test("protocol support probe does not treat Gemini auth errors as every protocol isProviderProtocolEndpointSupportedForProbe(403, message, "gemini_generate_content", hints), true ); + assert.equal( + isProviderProtocolEndpointSupportedForProbe(403, message, "gemini_interactions", hints), + false + ); }); test("protocol support probe keeps auth-only fallback for unhinted endpoints", () => { @@ -45,3 +49,16 @@ test("protocol support probe treats Gemini contents validation as Gemini support false ); }); + +test("protocol support probe treats Gemini Interactions input validation as Interactions support", () => { + const message = "HTTP 400: Gemini Interactions request requires input."; + + assert.equal( + isProviderProtocolEndpointSupportedForProbe(400, message, "gemini_interactions", ["gemini_interactions"]), + true + ); + assert.equal( + isProviderProtocolEndpointSupportedForProbe(400, message, "gemini_generate_content", ["gemini_generate_content"]), + false + ); +}); diff --git a/tests/main/provider-url.test.mjs b/tests/main/provider-url.test.mjs index 633a8c8..f1e4d46 100644 --- a/tests/main/provider-url.test.mjs +++ b/tests/main/provider-url.test.mjs @@ -16,6 +16,7 @@ test("provider URL parsing strips endpoint paths and unsafe URL parts", () => { assert.equal(providerBaseUrlForProtocol(parsed, "openai_responses"), "https://api.example.com/v1"); assert.equal(providerBaseUrlForProtocol(parsed, "anthropic_messages"), "https://api.example.com"); assert.equal(providerBaseUrlForProtocol(parsed, "gemini_generate_content"), "https://api.example.com"); + assert.equal(providerBaseUrlForProtocol(parsed, "gemini_interactions"), "https://api.example.com"); }); test("provider URL parsing handles local and Gemini endpoint variants", () => { @@ -26,6 +27,18 @@ test("provider URL parsing handles local and Gemini endpoint variants", () => { assert.equal(parsed.geminiBaseUrl, "http://localhost:8787"); }); +test("provider URL parsing handles Gemini Interactions endpoint variants", () => { + const parsed = parseProviderBaseUrl("localhost:8787/v1/interactions/interaction-123/cancel"); + + assert.equal(parsed.normalizedInputBaseUrl, "http://localhost:8787/v1"); + assert.equal(parsed.rootBaseUrl, "http://localhost:8787"); + assert.equal(parsed.geminiBaseUrl, "http://localhost:8787"); + assert.equal( + normalizeProviderBaseUrl("localhost:8787/v1beta/interactions", "gemini_interactions"), + "http://localhost:8787" + ); +}); + test("provider URL normalization chooses protocol-specific bases", () => { assert.equal(providerUrlWithDefaultScheme("127.0.0.1:3456/v1"), "http://127.0.0.1:3456/v1"); assert.equal(providerUrlWithDefaultScheme("api.example.com/v1"), "https://api.example.com/v1"); From 15fcaaecf53f8234e510379b38ff8f41ad45aa4b Mon Sep 17 00:00:00 2001 From: musistudio Date: Fri, 3 Jul 2026 11:58:49 +0800 Subject: [PATCH 12/62] Document in-app browser search provider --- .../docs/en/configuration/fusion-web-search.md | 15 ++++++++++++++- .../docs/zh/configuration/fusion-web-search.md | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/src/content/docs/en/configuration/fusion-web-search.md b/docs/src/content/docs/en/configuration/fusion-web-search.md index ac4461b..d12fff4 100644 --- a/docs/src/content/docs/en/configuration/fusion-web-search.md +++ b/docs/src/content/docs/en/configuration/fusion-web-search.md @@ -11,7 +11,20 @@ Use `ccr-fusion-builtins / web_search`. ## Search Providers -Supported providers include Brave, Bing, Google CSE, Serper, SerpAPI, Tavily, and Exa. +Supported providers include In-app Browser, Brave, Bing, Google CSE, Serper, SerpAPI, Tavily, and Exa. + +## In-app Browser + +`In-app Browser` runs searches through a hidden built-in browser window in CCR Desktop, opens result pages, extracts visible content, and passes that evidence to the Fusion model. It does not require an external search API key, so it is useful when you want desktop-side browser retrieval. + +Configuration options include search engine, language, country or region, and safe-search level: + +- Search engine: Bing, Google, or DuckDuckGo. +- Language: for example `en` or `zh-CN`. +- Country or region: for example `US` or `CN`. +- Safe search: default, moderate, strict, or off. + +> Note: `In-app Browser` depends on CCR Desktop's Electron built-in browser capability and is only available in the desktop app. CLI, server deployments, and pure web environments do not have the built-in browser integration; use Brave, Bing, Google CSE, Serper, SerpAPI, Tavily, or Exa instead. ## Troubleshooting diff --git a/docs/src/content/docs/zh/configuration/fusion-web-search.md b/docs/src/content/docs/zh/configuration/fusion-web-search.md index 1862677..a8be16c 100644 --- a/docs/src/content/docs/zh/configuration/fusion-web-search.md +++ b/docs/src/content/docs/zh/configuration/fusion-web-search.md @@ -11,7 +11,20 @@ lead: 使用 CCR 内置 Web Search 工具为模型提供联网检索能力。 ## 搜索服务 -支持 Brave、Bing、Google CSE、Serper、SerpAPI、Tavily、Exa 等搜索服务。 +支持 In-app Browser、Brave、Bing、Google CSE、Serper、SerpAPI、Tavily、Exa 等搜索服务。 + +## In-app Browser + +`In-app Browser` 会通过 CCR Desktop 的隐藏内置浏览器窗口执行搜索,打开搜索结果页面并提取可见内容,再把证据提供给 Fusion 模型。它不需要外部搜索 API Key,适合希望用桌面端内置浏览器完成联网检索的场景。 + +可配置项包括搜索引擎、语言、地区和安全搜索级别: + +- 搜索引擎:Bing、Google、DuckDuckGo。 +- 语言:例如 `en`、`zh-CN`。 +- 地区:例如 `US`、`CN`。 +- 安全搜索:默认、中等、严格或关闭。 + +> 注意:`In-app Browser` 依赖 CCR Desktop 的 Electron 内置浏览器能力,只在桌面端可用。CLI、服务器部署或纯 Web 环境没有内置浏览器集成,请改用 Brave、Bing、Google CSE、Serper、SerpAPI、Tavily 或 Exa 等搜索服务。 ## 排查要点 From b6f8bb3c52d4e08efa588f1c9c8404f7d47f1b24 Mon Sep 17 00:00:00 2001 From: musi Date: Fri, 3 Jul 2026 13:58:05 +0800 Subject: [PATCH 13/62] Add Codex manual reset account metrics --- src/main/local-agent-providers/codex.ts | 117 ++++++++++++++++-- src/main/provider-account-service.ts | 35 +++++- .../pages/home/components/dashboard.tsx | 17 ++- src/renderer/pages/home/shared/i18n.tsx | 9 ++ .../pages/home/shared/provider-accounts.ts | 25 ++-- src/renderer/pages/home/shared/providers.ts | 2 +- src/renderer/pages/tray/shared.tsx | 34 +++-- src/shared/app.ts | 11 +- .../main/local-agent-provider-codex.test.mjs | 41 ++++++ tests/renderer/overview-components.test.tsx | 79 +++++++++++- tests/renderer/tray-components.test.tsx | 53 ++++++++ 11 files changed, 389 insertions(+), 34 deletions(-) create mode 100644 tests/main/local-agent-provider-codex.test.mjs diff --git a/src/main/local-agent-providers/codex.ts b/src/main/local-agent-providers/codex.ts index cf705e6..e353493 100644 --- a/src/main/local-agent-providers/codex.ts +++ b/src/main/local-agent-providers/codex.ts @@ -39,21 +39,124 @@ const codexAccountRateLimitMapping: ProviderAccountMappingConfig = { kind: "quota", label: "Primary quota", limit: 100, - remaining: "100 - $.rate_limit.primary_window.used_percent", - resetAt: "$.rate_limit.primary_window.reset_at", + remaining: [ + "100 - $.rate_limit.primary_window.used_percent", + "100 - $.rate_limits.primary.used_percent" + ], + resetAt: [ + "$.rate_limit.primary_window.reset_at", + "$.rate_limit.primary_window.resets_at", + "$.rate_limits.primary.resets_at" + ], unit: "%", - used: "$.rate_limit.primary_window.used_percent", + used: [ + "$.rate_limit.primary_window.used_percent", + "$.rate_limits.primary.used_percent" + ], window: "primary" }, + { + id: "codex_manual_resets", + kind: "requests", + label: "Manual resets", + remaining: [ + "$.resetsAvailable", + "$.availableRateLimitResetCount", + "$.rate_limit.resetsAvailable", + "$.rate_limits.resetsAvailable", + "$.rate_limit.manual_resets.remaining", + "$.rate_limit.manual_resets.resetsAvailable", + "$.rate_limit.manual_reset.remaining", + "$.rate_limit.manual_reset.resetsAvailable", + "$.rate_limits.manual_resets.remaining", + "$.rate_limits.manual_resets.resetsAvailable", + "$.rate_limits.manual_reset.remaining", + "$.rate_limits.manual_reset.resetsAvailable", + "$.manual_resets.remaining", + "$.manual_resets.resetsAvailable", + "$.manual_reset.remaining", + "$.manual_reset.resetsAvailable", + "$.resets.remaining", + "$.resets.resetsAvailable", + "$.rate_limit.manual_resets.available", + "$.rate_limit.manual_reset.available", + "$.rate_limit.resets.available", + "$.rate_limits.resets.available", + "$.manual_resets.available", + "$.manual_reset.available", + "$.resets.available" + ], + resetAt: [ + "$.resetExpires", + "$.expires_at", + "$.resets_at", + "$.rate_limit.manual_resets.expires_at", + "$.rate_limit.manual_resets.expire_at", + "$.rate_limit.manual_resets.reset_at", + "$.rate_limit.manual_resets.resets_at", + "$.rate_limit.manual_reset.expires_at", + "$.rate_limit.manual_reset.expire_at", + "$.rate_limit.manual_reset.reset_at", + "$.rate_limit.manual_reset.resets_at", + "$.rate_limits.manual_resets.expires_at", + "$.rate_limits.manual_resets.expire_at", + "$.rate_limits.manual_resets.reset_at", + "$.rate_limits.manual_resets.resets_at", + "$.rate_limits.manual_reset.expires_at", + "$.rate_limits.manual_reset.expire_at", + "$.rate_limits.manual_reset.reset_at", + "$.rate_limits.manual_reset.resets_at", + "$.rate_limit.resets.expires_at", + "$.rate_limit.resets.expire_at", + "$.rate_limit.resets.reset_at", + "$.rate_limit.resets.resets_at", + "$.rate_limits.resets.expires_at", + "$.rate_limits.resets.expire_at", + "$.rate_limits.resets.reset_at", + "$.rate_limits.resets.resets_at", + "$.manual_resets.expires_at", + "$.manual_resets.expire_at", + "$.manual_resets.reset_at", + "$.manual_resets.resets_at", + "$.manual_reset.expires_at", + "$.manual_reset.expire_at", + "$.manual_reset.reset_at", + "$.manual_reset.resets_at", + "$.resets.expires_at", + "$.resets.expire_at", + "$.resets.reset_at", + "$.resets.resets_at" + ], + unit: "resets", + used: [ + "$.rate_limit.manual_resets.used", + "$.rate_limit.manual_reset.used", + "$.rate_limits.manual_resets.used", + "$.manual_resets.used", + "$.manual_reset.used", + "$.resets.used" + ], + window: "manual-reset" + }, { id: "codex_secondary_quota", kind: "quota", label: "Secondary quota", limit: 100, - remaining: "100 - $.rate_limit.secondary_window.used_percent", - resetAt: "$.rate_limit.secondary_window.reset_at", + remaining: [ + "100 - $.rate_limit.secondary_window.used_percent", + "100 - $.rate_limits.secondary.used_percent" + ], + resetAt: [ + "$.rate_limit.secondary_window.reset_at", + "$.rate_limit.secondary_window.resets_at", + "$.rate_limits.secondary.resets_at" + ], unit: "%", - used: "$.rate_limit.secondary_window.used_percent", + used: [ + "$.rate_limit.secondary_window.used_percent", + "$.rate_limits.secondary.used_percent" + ], window: "secondary" }, { @@ -164,7 +267,7 @@ export function readCodexAuth(): OAuthTokenSet | undefined { }; } -function codexProviderAccountConfig(): ProviderAccountConfig { +export function codexProviderAccountConfig(): ProviderAccountConfig { return { connectors: [ { diff --git a/src/main/provider-account-service.ts b/src/main/provider-account-service.ts index 68ba07e..9539081 100644 --- a/src/main/provider-account-service.ts +++ b/src/main/provider-account-service.ts @@ -18,6 +18,8 @@ import type { ProviderAccountLocalEstimateConnectorConfig, ProviderAccountLocalWindowConfig, ProviderAccountMappedMeterConfig, + ProviderAccountMappedNumberExpression, + ProviderAccountMappedStringExpression, ProviderAccountMeter, ProviderAccountMeterKind, ProviderAccountMeterUnit, @@ -1345,7 +1347,16 @@ type JsonPathFilterCondition = { path: string[]; }; -function readMappedNumber(value: number | string | undefined, payload: unknown): number | undefined { +function readMappedNumber(value: ProviderAccountMappedNumberExpression | undefined, payload: unknown): number | undefined { + if (Array.isArray(value)) { + for (const candidate of value) { + const resolved = readMappedNumber(candidate, payload); + if (resolved !== undefined) { + return resolved; + } + } + return undefined; + } if (typeof value === "number") { return normalizeNumber(value); } @@ -1382,7 +1393,16 @@ function resolveMappedNumberTerm(term: string, payload: unknown): unknown { return trimmed.startsWith("$") ? readJsonPath(payload, trimmed) : trimmed; } -function readMappedString(value: string | undefined, payload: unknown): string | undefined { +function readMappedString(value: ProviderAccountMappedStringExpression | undefined, payload: unknown): string | undefined { + if (Array.isArray(value)) { + for (const candidate of value) { + const resolved = readMappedString(candidate, payload); + if (resolved !== undefined) { + return resolved; + } + } + return undefined; + } if (!value) { return undefined; } @@ -1390,7 +1410,16 @@ function readMappedString(value: string | undefined, payload: unknown): string | return readString(resolved); } -function readMappedDateString(value: string | undefined, payload: unknown): string | undefined { +function readMappedDateString(value: ProviderAccountMappedStringExpression | undefined, payload: unknown): string | undefined { + if (Array.isArray(value)) { + for (const candidate of value) { + const resolved = readMappedDateString(candidate, payload); + if (resolved !== undefined) { + return resolved; + } + } + return undefined; + } if (!value) { return undefined; } diff --git a/src/renderer/pages/home/components/dashboard.tsx b/src/renderer/pages/home/components/dashboard.tsx index 1031518..c97eba0 100644 --- a/src/renderer/pages/home/components/dashboard.tsx +++ b/src/renderer/pages/home/components/dashboard.tsx @@ -12,7 +12,7 @@ import { metricToneBar, metricToneStroke, motion, normalizeAgentFilterValue, normalizeOverviewWidget, normalizeOverviewWidgets, OverviewMetricKind, overviewMetricOptions, overviewWidgetCollisionDetection, OverviewWidgetConfig, OverviewWidgetSize, overviewWidgetSizeOptions, OverviewWidgetType, OverviewWidgetVariant, Pencil, Pie, PieChart, Plus, - PointerSensor, primaryProviderAccountMeter, providerAccountMeterProgress, providerAccountMetersForDisplay, providerAccountProgressClass, + PointerSensor, primaryProviderAccountMeter, providerAccountMeterProgress, providerAccountMetersForDisplay, providerAccountProgressClass, isProviderAccountManualResetMeter, providerAccountSnapshotKey, providerAccountSnapshotLabel, ProviderAccountMeter, ProviderAccountSnapshot, ReactNode, ReactPointerEvent, rectSortingStrategy, RefreshCw, Select, SelectControl, SortableContext, sortableKeyboardCoordinates, systemStatusIconClass, systemStatusPointTooltip, systemStatusSegmentClass, @@ -2432,7 +2432,15 @@ function primaryProviderAccountBalanceMeter(account: ProviderAccountSnapshot): P } function providerAccountMetersForDisplayOrdered(account: ProviderAccountSnapshot, maxCount: number): ProviderAccountMeter[] { - const ordered = [...providerAccountQuotaMeters(account), ...providerAccountBalanceMeters(account)]; + const quotaMeters = providerAccountQuotaMeters(account); + const manualResetMeters = account.meters.filter(isProviderAccountManualResetMeter); + const leadingQuotaCount = manualResetMeters.length > 0 ? Math.min(quotaMeters.length, maxCount <= 2 ? 1 : 2) : quotaMeters.length; + const ordered = [ + ...quotaMeters.slice(0, leadingQuotaCount), + ...manualResetMeters, + ...providerAccountBalanceMeters(account), + ...quotaMeters.slice(leadingQuotaCount) + ]; const seen = new Set(); const unique = ordered.filter((meter) => { const key = `${meter.id}:${meter.kind}:${meter.window ?? ""}`; @@ -2470,10 +2478,11 @@ function compareProviderAccountQuotaMeters(a: ProviderAccountMeter, b: ProviderA } function providerAccountMeterWindowRank(meter: ProviderAccountMeter): number { - if (meter.window === "5h" || meter.id.toLowerCase().includes("5h") || meter.label.toLowerCase().includes("5h")) { + const text = `${meter.window ?? ""} ${meter.id} ${meter.label}`.toLowerCase(); + if (meter.window === "5h" || text.includes("5h") || text.includes("primary")) { return 0; } - if (meter.window === "weekly" || meter.id.toLowerCase().includes("weekly") || meter.label.toLowerCase().includes("weekly")) { + if (meter.window === "weekly" || text.includes("weekly") || text.includes("secondary")) { return 1; } if (meter.window === "daily") return 2; diff --git a/src/renderer/pages/home/shared/i18n.tsx b/src/renderer/pages/home/shared/i18n.tsx index d84b7f7..681ff8d 100644 --- a/src/renderer/pages/home/shared/i18n.tsx +++ b/src/renderer/pages/home/shared/i18n.tsx @@ -1357,12 +1357,21 @@ export const appCopy: Record = { "Wrapper plugin": "包装插件", "5h quota": "5 小时额度", "Granted balance": "赠送余额", + "Individual limit": "个人额度", + "Lifetime tokens": "累计令牌", + "Manual resets": "主动重置次数", "Monthly budget": "月度预算", + "Peak daily tokens": "日峰值令牌", + "Primary quota": "主额度", + "Secondary quota": "副额度", "Topped-up balance": "充值余额", "Total credits": "总额度", "Total usage": "总用量", "Voucher balance": "代金券余额", "Weekly quota": "周额度", + "expired": "已过期", + "expires in": "剩余", + "resets": "次", "All": "全部", "API endpoint": "API 地址", "Available": "可用", diff --git a/src/renderer/pages/home/shared/provider-accounts.ts b/src/renderer/pages/home/shared/provider-accounts.ts index dfcdc8e..aae1324 100644 --- a/src/renderer/pages/home/shared/provider-accounts.ts +++ b/src/renderer/pages/home/shared/provider-accounts.ts @@ -410,7 +410,12 @@ export function primaryProviderAccountMeter(account: ProviderAccountSnapshot): P } export function providerAccountMetersForDisplay(account: ProviderAccountSnapshot, maxCount: number): ProviderAccountMeter[] { - return account.meters.slice(0, maxCount); + const meters = account.meters.slice(0, maxCount); + const manualResetMeter = account.meters.find(isProviderAccountManualResetMeter); + if (!manualResetMeter || meters.includes(manualResetMeter) || meters.length < maxCount) { + return meters; + } + return [...meters.slice(0, Math.max(0, maxCount - 1)), manualResetMeter]; } export function providerAccountMeterRemainingRatio(meter: ProviderAccountMeter): number | undefined { @@ -483,26 +488,32 @@ export function formatProviderAccountNumber(value: number): string { return new Intl.NumberFormat(undefined, { maximumFractionDigits: 6 }).format(value); } -export function formatProviderAccountReset(value: string): string { +export function formatProviderAccountReset(value: string, translate: (value: string) => string = (item) => item): string { const timestamp = new Date(value).getTime(); if (!Number.isFinite(timestamp)) { return value; } const minutes = Math.round((timestamp - Date.now()) / 60000); if (minutes <= 0) { - return "soon"; + return translate("expired"); } + const prefix = translate("expires in"); if (minutes < 60) { - return `${minutes}m`; + return `${prefix} ${minutes}m`; } const hours = Math.round(minutes / 60); if (hours < 48) { - return `${hours}h`; + return `${prefix} ${hours}h`; } - return `${Math.round(hours / 24)}d`; + return `${prefix} ${Math.round(hours / 24)}d`; } export function formatProviderAccountMeterTitle(meter: ProviderAccountMeter, translate: (value: string) => string): string { const label = translate(meter.label); - return meter.resetAt ? `${label} (${formatProviderAccountReset(meter.resetAt)})` : label; + return meter.resetAt ? `${label} (${formatProviderAccountReset(meter.resetAt, translate)})` : label; +} + +export function isProviderAccountManualResetMeter(meter: ProviderAccountMeter): boolean { + const text = `${meter.id} ${meter.label} ${meter.window ?? ""}`.toLowerCase(); + return text.includes("manual_reset") || text.includes("manual reset") || text.includes("manual-reset"); } diff --git a/src/renderer/pages/home/shared/providers.ts b/src/renderer/pages/home/shared/providers.ts index bbf03cb..bd3c4f4 100644 --- a/src/renderer/pages/home/shared/providers.ts +++ b/src/renderer/pages/home/shared/providers.ts @@ -1286,7 +1286,7 @@ export function createProviderAccountDraftFromConfig(account: ProviderAccountCon usageStatusPath: httpJsonConnector.mapping.status ?? "", usageSubscriptionLimitPath: stringValue(subscriptionMeter?.limit) || "", usageSubscriptionRemainingPath: stringValue(subscriptionMeter?.remaining) || "", - usageSubscriptionResetPath: subscriptionMeter?.resetAt ?? "", + usageSubscriptionResetPath: stringValue(subscriptionMeter?.resetAt) || "", usageSubscriptionUnit: stringValue(subscriptionMeter?.unit) || "tokens" }; } diff --git a/src/renderer/pages/tray/shared.tsx b/src/renderer/pages/tray/shared.tsx index 7b98b62..abc3373 100644 --- a/src/renderer/pages/tray/shared.tsx +++ b/src/renderer/pages/tray/shared.tsx @@ -71,9 +71,12 @@ export const trayText: Record> = { "F": "五", "Granted balance": "赠送余额", "Input": "输入", + "Individual limit": "个人额度", "Less": "少", + "Lifetime tokens": "累计令牌", "Longest streak": "最长连续", "M": "一", + "Manual resets": "主动重置次数", "Monthly budget": "月度预算", "Model Share": "模型占比", "More": "多", @@ -84,8 +87,11 @@ export const trayText: Record> = { "Output": "输出", "Overview": "概览", "Open CCR": "打开 CCR", + "Peak daily tokens": "日峰值令牌", + "Primary quota": "主额度", "Quit": "退出", "Refresh": "刷新", + "Secondary quota": "副额度", "Subscription": "订阅", "Success": "成功", "Success rate": "成功率", @@ -112,10 +118,13 @@ export const trayText: Record> = { "day": "天", "days": "天", "error": "错误", + "expired": "已过期", + "expires in": "剩余", "hours": "小时", "minutes": "分钟", "ok": "正常", "requests": "请求", + "resets": "次", "soon": "即将", "tokens": "令牌", "unsupported": "不支持", @@ -652,7 +661,17 @@ export function accountStatusRank(status: ProviderAccountSnapshot["status"]): nu } export function accountMetersForDisplay(snapshot: ProviderAccountSnapshot, maxCount: number): ProviderAccountMeter[] { - return snapshot.meters.slice(0, maxCount); + const meters = snapshot.meters.slice(0, maxCount); + const manualResetMeter = snapshot.meters.find(isAccountManualResetMeter); + if (!manualResetMeter || meters.includes(manualResetMeter) || meters.length < maxCount) { + return meters; + } + return [...meters.slice(0, Math.max(0, maxCount - 1)), manualResetMeter]; +} + +function isAccountManualResetMeter(meter: ProviderAccountMeter): boolean { + const text = `${meter.id} ${meter.label} ${meter.window ?? ""}`.toLowerCase(); + return text.includes("manual_reset") || text.includes("manual reset") || text.includes("manual-reset"); } export function meterRemainingRatio(meter: ProviderAccountMeter): number | undefined { @@ -706,28 +725,29 @@ export function formatMeterNumber(value: number): string { return new Intl.NumberFormat(undefined, { maximumFractionDigits: 6 }).format(value); } -export function formatAccountReset(value: string): string { +export function formatAccountReset(value: string, translate: (value: string) => string = (item) => item): string { const timestamp = new Date(value).getTime(); if (!Number.isFinite(timestamp)) { return value; } const minutes = Math.round((timestamp - Date.now()) / 60000); if (minutes <= 0) { - return "soon"; + return translate("expired"); } + const prefix = translate("expires in"); if (minutes < 60) { - return `${minutes}m`; + return `${prefix} ${minutes}m`; } const hours = Math.round(minutes / 60); if (hours < 48) { - return `${hours}h`; + return `${prefix} ${hours}h`; } - return `${Math.round(hours / 24)}d`; + return `${prefix} ${Math.round(hours / 24)}d`; } export function formatAccountMeterTitle(meter: ProviderAccountMeter, translate: (value: string) => string): string { const label = translateAccountMeterLabel(meter.label, translate); - return meter.resetAt ? `${label} (${formatAccountReset(meter.resetAt)})` : label; + return meter.resetAt ? `${label} (${formatAccountReset(meter.resetAt, translate)})` : label; } export function accountStatusClass(status: ProviderAccountSnapshot["status"]): string { diff --git a/src/shared/app.ts b/src/shared/app.ts index 922147f..a4f9e28 100644 --- a/src/shared/app.ts +++ b/src/shared/app.ts @@ -223,15 +223,18 @@ export type ProviderAccountMappingConfig = { status?: string; }; +export type ProviderAccountMappedNumberExpression = number | string | Array; +export type ProviderAccountMappedStringExpression = string | string[]; + export type ProviderAccountMappedMeterConfig = { id: string; kind?: ProviderAccountMeterKind; label: string; - limit?: number | string; - remaining?: number | string; - resetAt?: string; + limit?: ProviderAccountMappedNumberExpression; + remaining?: ProviderAccountMappedNumberExpression; + resetAt?: ProviderAccountMappedStringExpression; unit?: ProviderAccountMeterUnit; - used?: number | string; + used?: ProviderAccountMappedNumberExpression; window?: ProviderAccountMeterWindow; }; diff --git a/tests/main/local-agent-provider-codex.test.mjs b/tests/main/local-agent-provider-codex.test.mjs new file mode 100644 index 0000000..3e47a9d --- /dev/null +++ b/tests/main/local-agent-provider-codex.test.mjs @@ -0,0 +1,41 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { codexProviderAccountConfig } from "../../src/main/local-agent-providers/codex.ts"; + +test("Codex provider account config includes manual reset meter", () => { + const config = codexProviderAccountConfig(); + const usageConnector = config.connectors.find((connector) => connector.type === "http-json" && connector.endpoint.endsWith("/wham/usage")); + assert.ok(usageConnector); + + const meters = usageConnector.mapping.meters; + const manualResetMeter = meters.find((meter) => meter.id === "codex_manual_resets"); + assert.ok(manualResetMeter); + assert.equal(manualResetMeter.label, "Manual resets"); + assert.equal(manualResetMeter.unit, "resets"); + assert.equal(manualResetMeter.window, "manual-reset"); + assert.ok(Array.isArray(manualResetMeter.remaining)); + assert.ok(manualResetMeter.remaining.includes("$.resetsAvailable")); + assert.ok(manualResetMeter.remaining.includes("$.availableRateLimitResetCount")); + assert.ok(manualResetMeter.remaining.includes("$.rate_limit.manual_resets.remaining")); + assert.ok(Array.isArray(manualResetMeter.resetAt)); + assert.ok(manualResetMeter.resetAt.includes("$.resetExpires")); + assert.ok(manualResetMeter.resetAt.includes("$.expires_at")); + assert.ok(manualResetMeter.resetAt.includes("$.rate_limit.manual_resets.expires_at")); + assert.ok(manualResetMeter.resetAt.includes("$.manual_resets.reset_at")); +}); + +test("Codex quota meters accept reset_at and resets_at usage shapes", () => { + const config = codexProviderAccountConfig(); + const usageConnector = config.connectors.find((connector) => connector.type === "http-json" && connector.endpoint.endsWith("/wham/usage")); + const primaryQuota = usageConnector.mapping.meters.find((meter) => meter.id === "codex_primary_quota"); + const secondaryQuota = usageConnector.mapping.meters.find((meter) => meter.id === "codex_secondary_quota"); + + assert.ok(Array.isArray(primaryQuota.resetAt)); + assert.ok(primaryQuota.resetAt.includes("$.rate_limit.primary_window.reset_at")); + assert.ok(primaryQuota.resetAt.includes("$.rate_limit.primary_window.resets_at")); + assert.ok(primaryQuota.resetAt.includes("$.rate_limits.primary.resets_at")); + assert.ok(Array.isArray(secondaryQuota.resetAt)); + assert.ok(secondaryQuota.resetAt.includes("$.rate_limit.secondary_window.reset_at")); + assert.ok(secondaryQuota.resetAt.includes("$.rate_limit.secondary_window.resets_at")); + assert.ok(secondaryQuota.resetAt.includes("$.rate_limits.secondary.resets_at")); +}); diff --git a/tests/renderer/overview-components.test.tsx b/tests/renderer/overview-components.test.tsx index 4d8ee12..7a20916 100644 --- a/tests/renderer/overview-components.test.tsx +++ b/tests/renderer/overview-components.test.tsx @@ -3,7 +3,7 @@ import test from "node:test"; import * as React from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { OverviewView } from "../../src/renderer/pages/home/components/dashboard.tsx"; -import type { OverviewWidgetConfig } from "../../src/shared/app.ts"; +import type { OverviewWidgetConfig, ProviderAccountSnapshot } from "../../src/shared/app.ts"; import { accountSnapshots, installBrowserGlobals, usageStats } from "./fixtures.ts"; installBrowserGlobals(); @@ -82,3 +82,80 @@ test("OverviewView renders the empty widget layout state", () => { assert.match(html, /No widgets configured/); assert.match(html, /aria-label="Edit widgets"/); }); + +test("OverviewView prioritizes Codex manual resets before folded balance meters", () => { + const resetAt = new Date(Date.now() + 72 * 60 * 60 * 1000).toISOString(); + const codexAccount: ProviderAccountSnapshot = { + meters: [ + { + id: "codex_primary_quota", + kind: "quota", + label: "Primary quota", + limit: 100, + remaining: 96, + resetAt, + unit: "%", + window: "primary" + }, + { + id: "codex_secondary_quota", + kind: "quota", + label: "Secondary quota", + limit: 100, + remaining: 68, + resetAt, + unit: "%", + window: "secondary" + }, + { + id: "codex_individual_limit", + kind: "quota", + label: "Individual limit", + limit: 100, + remaining: 42, + resetAt, + unit: "credits", + window: "monthly" + }, + { + id: "codex_credit_balance", + kind: "balance", + label: "Credit balance", + remaining: 0, + unit: "credits" + }, + { + id: "codex_manual_resets", + kind: "requests", + label: "Manual resets", + remaining: 2, + resetAt, + unit: "resets", + window: "manual-reset" + } + ], + provider: "Codex API", + source: "http-json", + status: "ok", + updatedAt: new Date().toISOString() + }; + + const html = renderToStaticMarkup( + undefined} + setUsageRange={() => undefined} + usageRange="30d" + usageStats={usageStats("30d")} + onWidgetsChange={() => undefined} + /> + ); + + assert.match(html, /Primary quota/); + assert.match(html, /Secondary quota/); + assert.match(html, /Manual resets/); + assert.match(html, /expires in/); + assert.match(html, /2 resets/); + assert.doesNotMatch(html, /Credit balance/); +}); diff --git a/tests/renderer/tray-components.test.tsx b/tests/renderer/tray-components.test.tsx index 6e55776..3d94409 100644 --- a/tests/renderer/tray-components.test.tsx +++ b/tests/renderer/tray-components.test.tsx @@ -144,6 +144,59 @@ test("AccountSummaryPanel covers empty and metered account states", () => { assert.match(meteredHtml, /style="\s*width:42%"/); }); +test("AccountSummaryPanel prioritizes Codex manual reset meter with expiration", () => { + const resetAt = new Date(Date.now() + 72 * 60 * 60 * 1000).toISOString(); + const html = renderToStaticMarkup( + + ); + + assert.match(html, /Primary quota/); + assert.match(html, /Manual resets/); + assert.match(html, /expires in/); + assert.match(html, /2 resets/); + assert.doesNotMatch(html, /Secondary quota/); +}); + test("RangeSwitch renders every usage range option", () => { const html = renderToStaticMarkup( undefined} />); From 43948b4c2a444135d1b89b67ba4dc690b8395097 Mon Sep 17 00:00:00 2001 From: musistudio Date: Fri, 3 Jul 2026 15:11:52 +0800 Subject: [PATCH 14/62] Refactor router configuration and expand provider support --- src/main/config.ts | 3 +- src/main/ipc.ts | 7 +- src/main/local-agent-providers/codex.ts | 285 ++++++++- src/main/preload.ts | 3 + src/main/provider-account-service.ts | 119 +++- src/main/web-client-bridge.ts | 1 + src/main/web-management-server.ts | 4 +- .../pages/home/components/dashboard.tsx | 567 +++++++++++++++++- src/renderer/pages/home/shared/i18n.tsx | 12 +- .../pages/home/shared/provider-accounts.ts | 58 ++ .../pages/tray/components/account-panel.tsx | 6 +- src/renderer/pages/tray/shared.tsx | 29 + src/renderer/types/electron.d.ts | 3 + src/shared/app.ts | 23 + src/shared/ipc-channels.ts | 1 + .../main/local-agent-provider-codex.test.mjs | 167 +++++- tests/renderer/overview-components.test.tsx | 85 +++ tests/renderer/tray-components.test.tsx | 9 + 18 files changed, 1356 insertions(+), 26 deletions(-) diff --git a/src/main/config.ts b/src/main/config.ts index fa13597..ef2702a 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -3,6 +3,7 @@ import { existsSync, readFileSync } from "node:fs"; import { loadPersistedAppConfig, replacePersistedAppConfig } from "./app-config-store"; import { loadPersistedApiKeys, replacePersistedApiKeys } from "./api-key-store"; import { CONFIG_FILE, GATEWAY_CONFIG_FILE, LEGACY_CONFIG_FILE, LEGACY_WINDOWS_CONFIG_FILE } from "./constants"; +import { normalizeCodexProviderAccountConfig } from "./local-agent-providers/codex"; import { CLAUDE_CODE_DEFAULT_ENV, CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV, DEFAULT_OVERVIEW_WIDGETS, DEFAULT_TRAY_COMPONENT_VARIANTS, DEFAULT_TRAY_WIDGETS, DEFAULT_TRAY_WINDOW_MODULES, OVERVIEW_WIDGET_SIZE_VALUES, ROUTER_FALLBACK_MAX_RETRY_COUNT, TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS, enforceSingleEnabledGlobalProfilePerAgent } from "../shared/app"; import { createDefaultAppConfig } from "../shared/default-config"; import { findProviderPresetByBaseUrl, providerApiKeySafetyIssue, providerEndpointCanReceiveProviderApiKey } from "./presets"; @@ -1000,7 +1001,7 @@ function parseProviders(value: unknown): GatewayProviderConfig[] | undefined { transformer: item.transformer, type: readString(item.type) }; - return provider; + return normalizeCodexProviderAccountConfig(provider); }) .filter((item): item is GatewayProviderConfig => Boolean(item)); diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 64f01db..12cf675 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -14,7 +14,7 @@ import { loadAppConfig, saveApiKeysConfig, saveAppConfig } from "./config"; import { API_KEYS_DB_FILE, APP_CONFIG_DB_FILE, APP_NAME, CONFIGDIR, CONFIG_FILE, DATADIR, GATEWAY_CONFIG_FILE, IPC_CHANNELS, LEGACY_CONFIG_FILE, ONBOARDING_FINISHED_FILE, PROXY_CA_CERT_FILE, REQUEST_LOGS_DB_FILE, USAGE_DB_FILE } from "./constants"; import { deepLinkService } from "./deep-link"; import { gatewayService } from "../server/gateway/service"; -import { getProviderAccountSnapshots, invalidateProviderAccountSnapshotCache, testProviderAccountConnector } from "./provider-account-service"; +import { getProviderAccountSnapshots, invalidateProviderAccountSnapshotCache, resetCodexRateLimitCredit, testProviderAccountConnector } from "./provider-account-service"; import { detectProviderIcon } from "./provider-icons"; import { fetchProviderManifest } from "./provider-manifest-service"; import { getLocalAgentProviderCandidates, importLocalAgentProvider } from "./local-agent-provider-service"; @@ -32,7 +32,7 @@ import trayController from "./tray-controller"; import { appUpdateService } from "./update-service"; import { getUsageStats } from "./usage-store"; import windowsManager from "./windows"; -import type { AgentAnalysisFilter, AgentAnalysisTracePayloadRequest, ApiKeyConfig, AppCaptureElementPngRequest, AppCaptureElementPngResult, AppConfig, AppDataExportResult, AppImageExportTargetRequest, AppImageExportTargetResult, AppInfo, AppRenderHtmlPngRequest, AppRenderHtmlPngResult, AppSaveConfigOptions, BotGatewayQrLoginCancelRequest, BotGatewayQrLoginStartRequest, BotGatewayQrLoginWaitRequest, BotGatewayQrWindowCloseRequest, BotGatewayQrWindowOpenRequest, GatewayPluginAppConfig, GatewayProviderConnectivityCheckRequest, GatewayProviderProbeCandidatesRequest, GatewayProviderProbeRequest, GatewayStatus, LocalAgentProviderImportRequest, PluginDependency, PluginDirectorySelection, PluginMarketplaceEntry, ProfileApplyResult, ProfileOpenRequest, ProviderAccountSnapshotRequestOptions, ProviderAccountTestRequest, ProviderCatalogModelsRequest, ProviderIconDetectionRequest, ProviderManifestFetchRequest, RequestLogListFilter, UsageStatsFilter, UsageStatsRange } from "../shared/app"; +import type { AgentAnalysisFilter, AgentAnalysisTracePayloadRequest, ApiKeyConfig, AppCaptureElementPngRequest, AppCaptureElementPngResult, AppConfig, AppDataExportResult, AppImageExportTargetRequest, AppImageExportTargetResult, AppInfo, AppRenderHtmlPngRequest, AppRenderHtmlPngResult, AppSaveConfigOptions, BotGatewayQrLoginCancelRequest, BotGatewayQrLoginStartRequest, BotGatewayQrLoginWaitRequest, BotGatewayQrWindowCloseRequest, BotGatewayQrWindowOpenRequest, GatewayPluginAppConfig, GatewayProviderConnectivityCheckRequest, GatewayProviderProbeCandidatesRequest, GatewayProviderProbeRequest, GatewayStatus, LocalAgentProviderImportRequest, PluginDependency, PluginDirectorySelection, PluginMarketplaceEntry, ProfileApplyResult, ProfileOpenRequest, ProviderAccountResetRequest, ProviderAccountSnapshotRequestOptions, ProviderAccountTestRequest, ProviderCatalogModelsRequest, ProviderIconDetectionRequest, ProviderManifestFetchRequest, RequestLogListFilter, UsageStatsFilter, UsageStatsRange } from "../shared/app"; const pluginMarketplace: PluginMarketplaceEntry[] = [ { @@ -234,6 +234,9 @@ ipcMain.handle(IPC_CHANNELS.appProbeProvider, (_event, request: GatewayProviderP ipcMain.handle(IPC_CHANNELS.appProbeProviderCandidates, (_event, request: GatewayProviderProbeCandidatesRequest) => { return probeGatewayProviderCandidates(request); }); +ipcMain.handle(IPC_CHANNELS.appResetCodexRateLimitCredit, (_event, request: ProviderAccountResetRequest) => { + return resetCodexRateLimitCredit(request); +}); ipcMain.handle(IPC_CHANNELS.appTestProviderAccountConnector, (_event, request: ProviderAccountTestRequest) => { return testProviderAccountConnector(request); }); diff --git a/src/main/local-agent-providers/codex.ts b/src/main/local-agent-providers/codex.ts index e353493..eb50bbc 100644 --- a/src/main/local-agent-providers/codex.ts +++ b/src/main/local-agent-providers/codex.ts @@ -3,11 +3,17 @@ import path from "node:path"; import type { LocalAgentProviderCandidate, LocalAgentProviderImportResult, + GatewayProviderConfig, ProviderAccountConfig, - ProviderAccountMappingConfig + ProviderAccountConnectorConfig, + ProviderAccountMappingConfig, + ProviderAccountMeter, + ProviderAccountMeterDetail } from "../../shared/app"; +import { normalizeProviderBaseUrl } from "../../shared/provider-url"; import { isRecord, + localAgentProviderApiKey, missingCandidate, modelDisplayNamesForModels, providerInternalNamePlaceholder, @@ -62,6 +68,7 @@ const codexAccountRateLimitMapping: ProviderAccountMappingConfig = { remaining: [ "$.resetsAvailable", "$.availableRateLimitResetCount", + "$.rate_limit_reset_credits.available_count", "$.rate_limit.resetsAvailable", "$.rate_limits.resetsAvailable", "$.rate_limit.manual_resets.remaining", @@ -84,7 +91,8 @@ const codexAccountRateLimitMapping: ProviderAccountMappingConfig = { "$.rate_limits.resets.available", "$.manual_resets.available", "$.manual_reset.available", - "$.resets.available" + "$.resets.available", + 0 ], resetAt: [ "$.resetExpires", @@ -180,6 +188,29 @@ const codexAccountRateLimitMapping: ProviderAccountMappingConfig = { ] }; +const codexAccountRateLimitResetCreditsMapping: ProviderAccountMappingConfig = { + meters: [ + { + id: "codex_manual_resets", + kind: "requests", + label: "Manual resets", + remaining: [ + "$.available_count", + "$.rate_limit_reset_credits.available_count", + 0 + ], + resetAt: [ + "$.expires_at", + "$.expiresAt", + "$.reset_at", + "$.resetAt" + ], + unit: "resets", + window: "manual-reset" + } + ] +}; + const codexAccountTokenUsageMapping: ProviderAccountMappingConfig = { meters: [ { @@ -279,6 +310,15 @@ export function codexProviderAccountConfig(): ProviderAccountConfig { mapping: codexAccountRateLimitMapping, type: "http-json" }, + { + auth: "provider-api-key", + endpoint: `${codexAccountBaseUrl}/wham/rate-limit-reset-credits`, + headers: { + "User-Agent": "codex-cli" + }, + mapping: codexAccountRateLimitResetCreditsMapping, + type: "http-json" + }, { auth: "provider-api-key", endpoint: `${codexAccountBaseUrl}/wham/profiles/me`, @@ -293,6 +333,247 @@ export function codexProviderAccountConfig(): ProviderAccountConfig { }; } +export function attachCodexRateLimitResetCreditDetails(meters: ProviderAccountMeter[], payload: unknown): ProviderAccountMeter[] { + const details = codexRateLimitResetCreditDetails(payload); + if (details.length === 0) { + return meters; + } + const resetAt = firstCodexResetCreditExpiry(details); + return meters.map((meter) => { + if (!isCodexManualResetMeter(meter)) { + return meter; + } + return { + ...meter, + details, + resetAt: meter.resetAt ?? resetAt + }; + }); +} + +export function codexRateLimitResetCreditDetails(payload: unknown): ProviderAccountMeterDetail[] { + const records = codexRateLimitResetCreditRecords(payload); + if (records.length === 0) { + return []; + } + const availableRecords = records.filter(isAvailableCodexResetCreditRecord); + const sourceRecords = availableRecords.length > 0 ? availableRecords : records; + return sourceRecords + .map(codexRateLimitResetCreditDetail) + .filter((detail): detail is ProviderAccountMeterDetail => Boolean(detail)) + .sort(compareCodexResetCreditDetails); +} + +export function normalizeCodexProviderAccountConfig(provider: GatewayProviderConfig): GatewayProviderConfig { + if (!isLocalCodexProvider(provider) || !shouldUseCurrentCodexAccountConfig(provider.account)) { + return provider; + } + const account = codexProviderAccountConfig(); + return { + ...provider, + account: { + ...account, + refreshIntervalMs: provider.account?.refreshIntervalMs ?? account.refreshIntervalMs + } + }; +} + +function isLocalCodexProvider(provider: GatewayProviderConfig): boolean { + return ( + providerApiKey(provider) === localAgentProviderApiKey && + normalizeProviderBaseUrl(providerBaseUrl(provider)) === normalizeProviderBaseUrl(codexDefaultBaseUrl) + ); +} + +function shouldUseCurrentCodexAccountConfig(account: ProviderAccountConfig | undefined): boolean { + if (account?.enabled === false) { + return false; + } + const connectors = account?.connectors ?? []; + if (connectors.length === 0) { + return true; + } + return connectors.every(isCodexAccountConnector); +} + +function isCodexAccountConnector(connector: ProviderAccountConnectorConfig): boolean { + if (connector.type === "standard") { + return !connector.endpoint?.trim() && !connector.endpoints?.length && !connector.headers && !connector.id; + } + if (connector.type !== "http-json") { + return false; + } + return /^https:\/\/chatgpt\.com\/backend-api\/wham\//i.test(connector.endpoint.trim()); +} + +function codexRateLimitResetCreditRecords(payload: unknown): Record[] { + if (!isRecord(payload)) { + return []; + } + const containers = [ + payload.rate_limit_reset_credits, + payload.rateLimitResetCredits, + payload + ].filter(isRecord); + for (const container of containers) { + const candidates = [ + container.credits, + container.items, + container.data, + container.available, + container.available_credits, + container.availableCredits, + container.reset_credits, + container.resetCredits + ]; + for (const candidate of candidates) { + const records = readCodexResetCreditRecordArray(candidate); + if (records.length > 0) { + return records; + } + } + } + return []; +} + +function readCodexResetCreditRecordArray(value: unknown): Record[] { + if (Array.isArray(value)) { + return value.filter(isRecord); + } + if (!isRecord(value)) { + return []; + } + const nested = [value.credits, value.items, value.data]; + for (const candidate of nested) { + if (Array.isArray(candidate)) { + return candidate.filter(isRecord); + } + } + return []; +} + +function isAvailableCodexResetCreditRecord(record: Record): boolean { + const status = readCodexStringFromKeys(record, ["status", "state"])?.toLowerCase(); + return !status || status === "available" || status === "active"; +} + +function codexRateLimitResetCreditDetail(record: Record, index: number): ProviderAccountMeterDetail | undefined { + const id = readCodexStringFromKeys(record, ["id", "credit_id", "creditId"]); + const status = readCodexStringFromKeys(record, ["status", "state"]); + const effectiveAt = readCodexDateFromKeys(record, [ + "effective_at", + "effectiveAt", + "start_date", + "startDate", + "valid_from", + "validFrom", + "starts_at", + "startsAt", + "start_at", + "startAt", + "available_at", + "availableAt", + "granted_at", + "grantedAt", + "created_at", + "createdAt" + ]); + const expiresAt = readCodexDateFromKeys(record, [ + "expires_at", + "expiresAt", + "expire_at", + "expireAt", + "expiration_at", + "expirationAt", + "valid_until", + "validUntil", + "end_date", + "endDate", + "ends_at", + "endsAt", + "end_at", + "endAt" + ]); + if (!effectiveAt && !expiresAt) { + return undefined; + } + return { + description: readCodexStringFromKeys(record, ["description", "message"]), + effectiveAt, + expiresAt, + id: id ?? `codex-reset-credit-${index + 1}`, + label: readCodexStringFromKeys(record, ["label", "name", "title"]), + redeemable: Boolean(id) && isAvailableCodexResetCreditRecord(record), + status + }; +} + +function readCodexStringFromKeys(record: Record, keys: string[]): string | undefined { + for (const key of keys) { + const value = readString(record[key]); + if (value) { + return value; + } + } + return undefined; +} + +function readCodexDateFromKeys(record: Record, keys: string[]): string | undefined { + for (const key of keys) { + const value = codexDateString(record[key]); + if (value) { + return value; + } + } + return undefined; +} + +function codexDateString(value: unknown): string | undefined { + if (typeof value === "number" && Number.isFinite(value)) { + const timestamp = value > 1_000_000_000_000 ? value : value * 1000; + return new Date(timestamp).toISOString(); + } + const text = readString(value); + if (!text) { + return undefined; + } + const timestamp = new Date(text).getTime(); + return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : text; +} + +function compareCodexResetCreditDetails(a: ProviderAccountMeterDetail, b: ProviderAccountMeterDetail): number { + return codexDetailTimestamp(a.expiresAt) - codexDetailTimestamp(b.expiresAt) + || codexDetailTimestamp(a.effectiveAt) - codexDetailTimestamp(b.effectiveAt); +} + +function codexDetailTimestamp(value: string | undefined): number { + if (!value) { + return Number.MAX_SAFE_INTEGER; + } + const timestamp = new Date(value).getTime(); + return Number.isFinite(timestamp) ? timestamp : Number.MAX_SAFE_INTEGER; +} + +function firstCodexResetCreditExpiry(details: ProviderAccountMeterDetail[]): string | undefined { + return [...details] + .sort(compareCodexResetCreditDetails) + .find((detail) => detail.expiresAt) + ?.expiresAt; +} + +function isCodexManualResetMeter(meter: ProviderAccountMeter): boolean { + const text = `${meter.id} ${meter.label} ${meter.window ?? ""}`.toLowerCase(); + return text.includes("manual_reset") || text.includes("manual reset") || text.includes("manual-reset"); +} + +function providerBaseUrl(provider: GatewayProviderConfig): string { + return provider.api_base_url || provider.baseUrl || provider.baseurl || ""; +} + +function providerApiKey(provider: GatewayProviderConfig): string { + return provider.api_key || provider.apiKey || provider.apikey || ""; +} + function codexOauthPlugin(suffix: string, providerName = providerNamePlaceholder): Record { return { key: `ccr-local-agent-${providerNameSlugPlaceholder}-${suffix}`, diff --git a/src/main/preload.ts b/src/main/preload.ts index 812d823..4f45200 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -48,6 +48,8 @@ import type { ProfileOpenResult, ProfileRuntimeStatus, ProfileStopResult, + ProviderAccountResetRequest, + ProviderAccountResetResult, ProviderAccountSnapshotRequestOptions, ProviderAccountTestRequest, ProviderAccountTestResult, @@ -134,6 +136,7 @@ contextBridge.exposeInMainWorld("ccr", { quitApp: () => invoke(IPC_CHANNELS.appQuit) as Promise, revealProxyCertificate: () => invoke(IPC_CHANNELS.appRevealProxyCertificate) as Promise, renderHtmlPng: (request: AppRenderHtmlPngRequest) => invoke(IPC_CHANNELS.appRenderHtmlPng, request) as Promise, + resetCodexRateLimitCredit: (request: ProviderAccountResetRequest) => invoke(IPC_CHANNELS.appResetCodexRateLimitCredit, request) as Promise, restartGateway: () => invoke(IPC_CHANNELS.appRestartGateway) as Promise, restartProxy: () => invoke(IPC_CHANNELS.appRestartProxy) as Promise, saveApiKeys: (apiKeys: ApiKeyConfig[]) => invoke(IPC_CHANNELS.appSaveApiKeys, apiKeys) as Promise, diff --git a/src/main/provider-account-service.ts b/src/main/provider-account-service.ts index 9539081..094d36b 100644 --- a/src/main/provider-account-service.ts +++ b/src/main/provider-account-service.ts @@ -1,5 +1,6 @@ -import { createHash } from "node:crypto"; +import { createHash, randomUUID } from "node:crypto"; import { loadAppConfig } from "./config"; +import { attachCodexRateLimitResetCreditDetails } from "./local-agent-providers/codex"; import { localAgentProviderApiKey, readCodexAuth } from "./local-agent-provider-service"; import { pluginService } from "./plugins/service"; import { getUsageTotalsSince } from "./usage-store"; @@ -21,11 +22,14 @@ import type { ProviderAccountMappedNumberExpression, ProviderAccountMappedStringExpression, ProviderAccountMeter, + ProviderAccountMeterDetail, ProviderAccountMeterKind, ProviderAccountMeterUnit, ProviderAccountPluginConnectorConfig, ProviderAccountSnapshot, ProviderAccountSnapshotRequestOptions, + ProviderAccountResetRequest, + ProviderAccountResetResult, ProviderAccountTestPath, ProviderAccountTestRequest, ProviderAccountTestResult, @@ -65,6 +69,7 @@ const maxErrorRefreshIntervalMs = 60 * 1000; const maxStaleAccountSnapshotMs = 2 * 60 * 1000; const maxCacheEntries = 500; const standardAccountPaths = ["/.well-known/ccr/account", "/v1/account/limits"]; +const codexRateLimitResetCreditConsumeEndpoint = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume"; const cache = new Map(); const inFlightRefreshes = new Map>(); let cacheGeneration = 0; @@ -142,9 +147,7 @@ export async function testProviderAccountConnector(request: ProviderAccountTestR }; } - const meters = connector.mapping.meters - .map((meter) => mappedMeterFromPayload(meter, payload)) - .filter((meter): meter is ProviderAccountMeter => Boolean(meter)); + const meters = mappedMetersFromPayload(connector, payload); return { meters, @@ -155,6 +158,42 @@ export async function testProviderAccountConnector(request: ProviderAccountTestR }; } +export async function resetCodexRateLimitCredit(request: ProviderAccountResetRequest): Promise { + const providerName = request.provider?.trim(); + const creditId = request.creditId?.trim(); + if (!providerName) { + throw new Error("Provider is required."); + } + if (!creditId) { + throw new Error("Reset credit id is required."); + } + + const config = await loadAppConfig(); + const provider = codexResetProvider(config, providerName, request.credentialId); + const materialized = materializeProviderAccountRequest(config, provider); + const payload = await fetchJson( + codexRateLimitResetCreditConsumeEndpoint, + materialized.provider, + "provider-api-key", + { + "User-Agent": "codex-cli", + ...(materialized.headers ?? {}) + }, + "POST", + { + credit_id: creditId, + redeem_request_id: randomUUID() + } + ); + + invalidateProviderAccountSnapshotCache(providerName); + return { + code: isRecord(payload) ? readString(payload.code) : undefined, + creditId, + ok: true + }; +} + async function resolveProviderAccountSnapshot( config: AppConfig, target: ProviderAccountTarget, @@ -315,6 +354,36 @@ function providerAccountTargets(provider: GatewayProviderConfig): ProviderAccoun return providerAccount && providerApiKey(provider) ? [{ account: providerAccount, provider }] : []; } +function codexResetProvider(config: AppConfig, providerName: string, credentialId: string | undefined): GatewayProviderConfig { + const normalizedProviderName = normalizeProviderName(providerName); + const provider = config.Providers.find((candidate) => normalizeProviderName(candidate.name) === normalizedProviderName); + if (!provider) { + throw new Error("Provider account was not found."); + } + + const target = providerAccountTargets(provider).find((candidate) => { + const candidateCredentialId = candidate.credential ? providerCredentialRuntimeId(candidate.provider, candidate.credential) : undefined; + return !credentialId || candidateCredentialId === credentialId; + }); + if (!target) { + throw new Error("Provider account credential was not found."); + } + if (!providerAccountHasCodexResetCreditsConnector(target.account)) { + throw new Error("Provider account does not support Codex manual reset credits."); + } + + return target.credential + ? providerWithCredentialApiKey(target.provider, target.credential, target.account) + : { ...target.provider, account: target.account }; +} + +function providerAccountHasCodexResetCreditsConnector(account: ProviderAccountConfig): boolean { + return normalizeConnectors(account).some((connector) => + connector.type === "http-json" && + /\/wham\/rate-limit-reset-credits(?:$|[?#/])/i.test(connector.endpoint.trim()) + ); +} + function providerAccountUnavailableSnapshots(provider: GatewayProviderConfig): ProviderAccountSnapshot[] { const credentials = activeProviderCredentials(provider); if (credentials.length > 0) { @@ -518,9 +587,7 @@ async function resolveHttpJsonConnector( }; } - const meters = connector.mapping.meters - .map((meter) => mappedMeterFromPayload(meter, payload)) - .filter((meter): meter is ProviderAccountMeter => Boolean(meter)); + const meters = mappedMetersFromPayload(connector, payload); return { errors: [], meters, @@ -906,6 +973,13 @@ function mappedMeterFromPayload(config: ProviderAccountMappedMeterConfig, payloa }, "http-json"); } +function mappedMetersFromPayload(connector: ProviderAccountHttpJsonConnectorConfig, payload: unknown): ProviderAccountMeter[] { + const meters = connector.mapping.meters + .map((meter) => mappedMeterFromPayload(meter, payload)) + .filter((meter): meter is ProviderAccountMeter => Boolean(meter)); + return attachCodexRateLimitResetCreditDetails(meters, payload); +} + function normalizeMeter(value: unknown, source: ProviderAccountConnectorSource): ProviderAccountMeter | undefined { if (!isRecord(value)) { return undefined; @@ -919,7 +993,9 @@ function normalizeMeter(value: unknown, source: ProviderAccountConnectorSource): const limit = normalizeNumber(value.limit); const used = normalizeNumber(value.used); const remaining = normalizeNumber(value.remaining) ?? (limit !== undefined && used !== undefined ? limit - used : undefined); + const details = normalizeMeterDetails(value.details); return { + ...(details.length > 0 ? { details } : {}), id, kind: normalizeMeterKind(readString(value.kind)) ?? inferMeterKind(unit), label, @@ -933,6 +1009,31 @@ function normalizeMeter(value: unknown, source: ProviderAccountConnectorSource): }; } +function normalizeMeterDetails(value: unknown): ProviderAccountMeterDetail[] { + if (!Array.isArray(value)) { + return []; + } + return value + .map(normalizeMeterDetail) + .filter((detail): detail is ProviderAccountMeterDetail => Boolean(detail)); +} + +function normalizeMeterDetail(value: unknown): ProviderAccountMeterDetail | undefined { + if (!isRecord(value)) { + return undefined; + } + const detail: ProviderAccountMeterDetail = { + description: readString(value.description), + effectiveAt: readString(value.effectiveAt), + expiresAt: readString(value.expiresAt), + id: readString(value.id), + label: readString(value.label), + redeemable: readBoolean(value.redeemable), + status: readString(value.status) + }; + return detail.description || detail.effectiveAt || detail.expiresAt || detail.id || detail.label || detail.redeemable !== undefined || detail.status ? detail : undefined; +} + function materializeProviderAccountRequest( config: AppConfig, provider: GatewayProviderConfig @@ -1677,6 +1778,10 @@ function readString(value: unknown): string | undefined { return typeof value === "string" && value.trim() ? value.trim() : undefined; } +function readBoolean(value: unknown): boolean | undefined { + return typeof value === "boolean" ? value : undefined; +} + function joinUrlPath(basePath: string, suffix: string): string { const normalizedBase = basePath === "/" ? "" : basePath.replace(/\/+$/, ""); const normalizedSuffix = suffix.startsWith("/") ? suffix : `/${suffix}`; diff --git a/src/main/web-client-bridge.ts b/src/main/web-client-bridge.ts index e5bf48c..57fc840 100644 --- a/src/main/web-client-bridge.ts +++ b/src/main/web-client-bridge.ts @@ -137,6 +137,7 @@ window.ccr = { restartGateway: () => rpc("restartGateway") as ReturnType["restartGateway"]>, restartProxy: () => rpc("restartProxy") as ReturnType["restartProxy"]>, revealProxyCertificate: () => rpc("revealProxyCertificate") as ReturnType["revealProxyCertificate"]>, + resetCodexRateLimitCredit: (request) => rpc("resetCodexRateLimitCredit", [request]) as ReturnType["resetCodexRateLimitCredit"]>, saveApiKeys: (apiKeys) => rpc("saveApiKeys", [apiKeys]) as ReturnType["saveApiKeys"]>, saveConfig: (config, options) => rpc("saveConfig", [config, options]) as ReturnType["saveConfig"]>, scanBotHandoffBluetoothTargets: () => rpc("scanBotHandoffBluetoothTargets") as ReturnType["scanBotHandoffBluetoothTargets"]>, diff --git a/src/main/web-management-server.ts b/src/main/web-management-server.ts index 6a4924d..6b31ff3 100644 --- a/src/main/web-management-server.ts +++ b/src/main/web-management-server.ts @@ -27,7 +27,7 @@ import { listMcpServerTools } from "../server/mcp/tool-discovery"; import { getAgentAnalysis, getAgentTracePayload, getRequestLogDetail, getRequestLogs } from "./request-log-store"; import { getUsageStats } from "./usage-store"; import { gatewayService } from "../server/gateway/service"; -import { getProviderAccountSnapshots, invalidateProviderAccountSnapshotCache, testProviderAccountConnector } from "./provider-account-service"; +import { getProviderAccountSnapshots, invalidateProviderAccountSnapshotCache, resetCodexRateLimitCredit, testProviderAccountConnector } from "./provider-account-service"; import type { AgentAnalysisFilter, AgentAnalysisTracePayloadRequest, @@ -52,6 +52,7 @@ import type { PluginMarketplaceEntry, ProfileApplyResult, ProfileOpenRequest, + ProviderAccountResetRequest, ProviderAccountSnapshotRequestOptions, ProviderAccountTestRequest, ProviderCatalogModelsRequest, @@ -354,6 +355,7 @@ const rpcHandlers: Record = { ensureProxyCertificateAuthority(); await revealFile(PROXY_CA_CERT_FILE); }, + resetCodexRateLimitCredit: (request) => resetCodexRateLimitCredit(request as ProviderAccountResetRequest), saveApiKeys: async (apiKeys) => { const savedConfig = await saveApiKeysConfig(apiKeys as ApiKeyConfig[]); const syncedClaudeAppConfig = await syncClaudeAppGatewayConfig(savedConfig); diff --git a/src/renderer/pages/home/components/dashboard.tsx b/src/renderer/pages/home/components/dashboard.tsx index c97eba0..a557a0e 100644 --- a/src/renderer/pages/home/components/dashboard.tsx +++ b/src/renderer/pages/home/components/dashboard.tsx @@ -1,18 +1,19 @@ import { agentAnalysisRangeOptions, AgentAnalysisSessionSelection, AgentAnalysisSnapshot, AgentAnalysisTracePayloadFullResult, AgentAnalysisTracePayloadRequest, AgentAnalysisTraceRun, agentFilterOptions, AgentFilterValue, agentKindLabel, + AnimatePresence, AnimatedDisclosure, AnimatedIconSwap, Area, arrayMove, Badge, Bar, BarChart, Button, Card, CardContent, CardHeader, CardTitle, CartesianGrid, Cell, constrainOverviewWidgetSize, Check, ChevronDown, ChevronLeft, ChevronRight, CircleAlert, cn, compactId, compactUserAgent, compareProviderAccountSnapshots, ComposedChart, CSS, DEFAULT_OVERVIEW_WIDGETS, DndContext, Dialog, DialogBody, DialogContent, DialogHeader, DialogTitle, DragEndEvent, DragOverEvent, DragOverlay, DragStartEvent, Field, formatAxisNumber, formatBytes, - formatCompactNumber, formatDuration, formatLogDateTime, formatPercent, formatProviderAccountMeterTitle, formatProviderAccountMeterValue, + formatCompactNumber, formatDuration, formatLogDateTime, formatPercent, formatProviderAccountDetailDate, formatProviderAccountMeterTitle, formatProviderAccountMeterValue, formatStatusBucketDate, formatStatusCodeCounts, formatSystemStatusRange, formatToolCounts, formatUsdCost, KeyboardSensor, LabelList, LayoutGroup, Line, LoaderCircle, MeasuringStrategy, MetricCard, MetricTone, metricToneBar, metricToneStroke, motion, normalizeAgentFilterValue, normalizeOverviewWidget, normalizeOverviewWidgets, OverviewMetricKind, overviewMetricOptions, overviewWidgetCollisionDetection, OverviewWidgetConfig, OverviewWidgetSize, overviewWidgetSizeOptions, OverviewWidgetType, OverviewWidgetVariant, Pencil, Pie, PieChart, Plus, - PointerSensor, primaryProviderAccountMeter, providerAccountMeterProgress, providerAccountMetersForDisplay, providerAccountProgressClass, isProviderAccountManualResetMeter, + PointerSensor, primaryProviderAccountMeter, providerAccountMeterDetailValidityProgress, providerAccountMeterProgress, providerAccountMetersForDisplay, providerAccountProgressClass, isProviderAccountManualResetMeter, providerAccountSnapshotKey, providerAccountSnapshotLabel, ProviderAccountMeter, ProviderAccountSnapshot, ReactNode, ReactPointerEvent, rectSortingStrategy, RefreshCw, Select, SelectControl, SortableContext, sortableKeyboardCoordinates, systemStatusIconClass, systemStatusPointTooltip, systemStatusSegmentClass, @@ -23,6 +24,7 @@ import { } from "../shared"; import { buildTokenActivity, type TokenActivityCell } from "@/lib/usage-activity"; import { ShareCardWidget } from "./share-cards"; +import { Cloud, Rocket } from "lucide-react"; export function OverviewView({ onWidgetsChange, overviewWidgets, @@ -2129,7 +2131,7 @@ function ProviderAccountSinglePanel({ ) : meters.length > 0 ? (
{meters.map((meter) => ( - + ))} {providerAccountShowExtraCount(dimensions) && account.meters.length > meters.length ? (
+{account.meters.length - meters.length}
@@ -2181,7 +2183,7 @@ function ProviderAccountSummaryCard({ ) : meters.length > 0 ? (
{meters.map((meter) => ( - + ))} {providerAccountShowExtraCount(dimensions) && account.meters.length > meters.length ? (
+{account.meters.length - meters.length}
@@ -2244,31 +2246,580 @@ function ProviderAccountMeterLine({ account, dimensions, meter, + onRefresh, single = false }: { account: ProviderAccountSnapshot; dimensions: OverviewWidgetDimensions; meter: ReturnType[number]; + onRefresh?: () => void | Promise; single?: boolean; }) { const t = useAppText(); const progress = isProviderAccountQuotaMeter(meter) ? providerAccountMeterProgress(meter) : undefined; + const canExpandDetails = dimensions.height >= 2 && isProviderAccountManualResetMeter(meter) && (meter.details?.length ?? 0) > 0; + const [detailsOpen, setDetailsOpen] = useState(false); + const [resetDialogDetail, setResetDialogDetail] = useState[number]>(); + const title = formatProviderAccountMeterTitle(meter, t); + const detailsId = `provider-account-meter-${providerAccountSnapshotKey(account)}-${meter.id}-details`.replace(/[^a-zA-Z0-9_-]/g, "-"); + const titleClassName = cn("min-w-0 truncate font-medium text-muted-foreground", single && dimensions.height >= 2 ? "text-[13px]" : "text-[12px]"); + const valueClassName = cn("shrink-0 font-semibold tracking-tight", single && dimensions.height >= 2 ? "text-[18px]" : "text-[15px]"); + const meterSummary = ( + <> +
+ {canExpandDetails ? ( + + {detailsOpen ? + ) : null} +
{title}
+
+
{formatProviderAccountMeterValue(meter)}
+ + ); return (
-
-
= 2 ? "text-[13px]" : "text-[12px]")}>{formatProviderAccountMeterTitle(meter, t)}
-
= 2 ? "text-[18px]" : "text-[15px]")}>{formatProviderAccountMeterValue(meter)}
-
+ {canExpandDetails ? ( + + ) : ( +
+ {meterSummary} +
+ )} {progress !== undefined && providerAccountShowProgress(dimensions) ? (
) : null} + + {canExpandDetails && detailsOpen ? ( + + + + ) : null} + + setResetDialogDetail(undefined)} + onResetComplete={onRefresh} + />
); } +function ProviderAccountMeterDetails({ + account, + detailsId, + meter, + onReset +}: { + account: ProviderAccountSnapshot; + detailsId: string; + meter: ProviderAccountMeter; + onReset: (detail: NonNullable[number]) => void; +}) { + const t = useAppText(); + const details = meter.details ?? []; + + return ( +
+ {details.map((detail, index) => { + const detailProgress = providerAccountMeterDetailValidityProgress(detail); + const label = providerAccountMeterDetailLabel(detail, index, t); + const status = providerAccountMeterDetailStatusLabel(detail.status, t); + return ( +
+
+
+
{label}
+
+
+ {status ?
{status}
: null} + {detail.redeemable ? ( + + ) : null} +
+
+
+
{t("Effective")}: {formatProviderAccountDetailDate(detail.effectiveAt)}
+
{t("Expires")}: {formatProviderAccountDetailDate(detail.expiresAt)}
+
+ {detailProgress !== undefined ? ( +
+
+
+ ) : null} +
+ ); + })} +
+ ); +} + +function providerAccountMeterDetailLabel( + detail: NonNullable[number], + index: number, + t: (value: string) => string +): string { + return detail.label ? t(detail.label) : `#${index + 1}`; +} + +function providerAccountMeterDetailStatusLabel(status: string | undefined, t: (value: string) => string): string { + const normalized = status?.trim().toLowerCase(); + if (!normalized) { + return ""; + } + if (normalized === "available") return t("Available"); + if (normalized === "active") return t("Active"); + if (normalized === "expired") return t("Expired"); + if (normalized === "used") return t("Used"); + return t(status ?? ""); +} + +type CodexResetLaunchStatus = "idle" | "launching" | "launched"; + +function CodexResetCreditDialog({ + account, + detail, + onClose, + onResetComplete, + open +}: { + account: ProviderAccountSnapshot; + detail: NonNullable[number] | undefined; + onClose: () => void; + onResetComplete?: () => void | Promise; + open: boolean; +}) { + const t = useAppText(); + const [status, setStatus] = useState("idle"); + const [error, setError] = useState(""); + const detailLabel = detail ? providerAccountMeterDetailLabel(detail, 0, t) : ""; + const detailStatus = providerAccountMeterDetailStatusLabel(detail?.status, t); + + useEffect(() => { + if (!open) { + setStatus("idle"); + setError(""); + } + }, [open, detail?.id]); + + async function resetCredit() { + if (status !== "idle" || !detail?.id) { + return; + } + if (!window.ccr?.resetCodexRateLimitCredit) { + setError(t("Reset is unavailable.")); + return; + } + + setError(""); + setStatus("launching"); + const ignition = delay(2000); + try { + await window.ccr.resetCodexRateLimitCredit({ + credentialId: account.credentialId, + creditId: detail.id, + provider: account.provider + }); + await ignition; + setStatus("launched"); + await onResetComplete?.(); + window.setTimeout(() => { + onClose(); + setStatus("idle"); + }, 1800); + } catch (resetError) { + await ignition.catch(() => undefined); + setError(formatDialogError(resetError)); + setStatus("idle"); + } + } + + return ( + { if (!nextOpen && status !== "launching") onClose(); }}> + + + +
+
+
+
+
+ {codexResetSpaceDust.map((dust) => ( +
+ ))} + {codexResetLaunchStars.map((star) => ( +
+ ))} + {codexResetShootingStars.map((meteor) => ( +
+ ))} +
+ +
+
{t("Manual reset credit")}
+
{detailLabel || "-"}
+
+ {t("Expires")}: {formatProviderAccountDetailDate(detail?.expiresAt)} + {detailStatus ? {detailStatus} : null} +
+
+
+
+ void resetCredit()} + transition={status === "launching" ? { duration: 0.3, repeat: Infinity } : {}} + type="button" + whileHover={status === "idle" ? { scale: 1.05 } : {}} + whileTap={status === "idle" ? { scale: 0.95 } : {}} + > +
+ + {status === "idle" ? ( + + + {t("Reset")} + + ) : null} + {status === "launching" ? ( + + + + + {t("Resetting")} + + ) : null} + {status === "launched" ? ( + + {t("Reset complete")} + + ) : null} + +
+ + + {(status === "launching" || status === "launched") ? ( + + ) : null} + + + + {status === "launched" ? ( + +
+ + +
+
+ ) : null} +
+
+ + + {status !== "idle" ? ( + +
+ + + + + + + + + +
+
+ ) : null} +
+
+
+ {error ?
{error}
: null} + + +
+ ); +} + +const codexResetStarfieldDense = [ + "radial-gradient(circle at 12% 18%, rgba(255,255,255,0.75) 0 1px, transparent 1.4px)", + "radial-gradient(circle at 42% 36%, rgba(186,230,253,0.65) 0 1px, transparent 1.6px)", + "radial-gradient(circle at 74% 22%, rgba(255,255,255,0.6) 0 1px, transparent 1.5px)", + "radial-gradient(circle at 18% 72%, rgba(226,232,240,0.55) 0 1px, transparent 1.4px)", + "radial-gradient(circle at 88% 78%, rgba(147,197,253,0.5) 0 1px, transparent 1.6px)" +].join(", "); + +const codexResetStarfieldWide = [ + "radial-gradient(circle at 18% 28%, rgba(255,255,255,0.5) 0 1.4px, transparent 2px)", + "radial-gradient(circle at 58% 16%, rgba(219,234,254,0.45) 0 1.2px, transparent 2px)", + "radial-gradient(circle at 78% 62%, rgba(255,255,255,0.42) 0 1.3px, transparent 2px)", + "radial-gradient(circle at 34% 82%, rgba(125,211,252,0.35) 0 1.2px, transparent 2px)" +].join(", "); + +const codexResetLaunchStars = Array.from({ length: 76 }, (_, index) => ({ + delay: (index % 9) * 0.18, + duration: 2 + (index % 7) * 0.35, + id: index, + left: (index * 37) % 100, + opacity: 0.45 + (index % 5) * 0.11, + size: `${1.2 + (index % 4) * 0.65}px`, + top: (index * 53) % 100 +})); + +const codexResetSpaceDust = Array.from({ length: 26 }, (_, index) => ({ + delay: (index % 8) * 0.55, + duration: 5.5 + (index % 6) * 0.75, + id: index, + left: (index * 41) % 100, + size: `${2 + (index % 3)}px`, + top: (index * 29) % 100 +})); + +const codexResetShootingStars = Array.from({ length: 5 }, (_, index) => ({ + delay: 1.4 + index * 2.8, + drop: 14 + (index % 3) * 5, + duration: 5.8 + index * 0.7, + id: index, + opacity: 0.55 + (index % 3) * 0.12, + repeatDelay: 8 + index * 1.4, + rotate: 14 + (index % 2) * 6, + top: 12 + index * 15 +})); + +const codexResetSpaceAnimationCss = ` + .ccr-codex-starfield-slow { + animation: ccr-codex-starfield-slow 9s linear infinite; + will-change: background-position; + } + + .ccr-codex-starfield-deep { + animation: ccr-codex-starfield-deep 15s linear infinite; + will-change: background-position; + } + + .ccr-codex-nebula { + background: + radial-gradient(circle at 24% 52%, rgba(239, 68, 68, 0.2), transparent 32%), + radial-gradient(circle at 70% 42%, rgba(59, 130, 246, 0.18), transparent 36%), + radial-gradient(circle at 54% 76%, rgba(20, 184, 166, 0.12), transparent 38%); + animation: ccr-codex-nebula 6s ease-in-out infinite alternate; + will-change: opacity, transform; + } + + .ccr-codex-twinkle-star { + animation-name: ccr-codex-twinkle-star; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; + opacity: 0.24; + will-change: opacity, transform; + } + + .ccr-codex-space-dust { + animation-name: ccr-codex-space-dust; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; + opacity: 0; + will-change: opacity, transform; + } + + .ccr-codex-meteor { + left: -32%; + opacity: 0; + transform: translate3d(-160px, -40px, 0) rotate(16deg); + animation-name: ccr-codex-meteor; + animation-timing-function: linear; + animation-iteration-count: infinite; + will-change: opacity, transform; + } + + @keyframes ccr-codex-starfield-slow { + from { background-position: 0 0; } + to { background-position: 180px 180px; } + } + + @keyframes ccr-codex-starfield-deep { + from { background-position: 0 0; } + to { background-position: -320px 320px; } + } + + @keyframes ccr-codex-nebula { + 0% { opacity: 0.42; transform: translate3d(-18px, 10px, 0) scale(1); } + 100% { opacity: 0.82; transform: translate3d(18px, -14px, 0) scale(1.08); } + } + + @keyframes ccr-codex-twinkle-star { + 0%, 100% { opacity: 0.18; transform: scale(0.65); } + 45% { opacity: 0.95; transform: scale(1.8); } + 70% { opacity: 0.36; transform: scale(1.1); } + } + + @keyframes ccr-codex-space-dust { + 0% { opacity: 0; transform: translate3d(-10px, 24px, 0) scale(0.7); } + 35% { opacity: 0.75; } + 100% { opacity: 0; transform: translate3d(34px, -44px, 0) scale(1.35); } + } + + @keyframes ccr-codex-meteor { + 0%, 48% { opacity: 0; transform: translate3d(-180px, -72px, 0) rotate(16deg) scaleX(0.75); } + 54% { opacity: 1; } + 72% { opacity: 0.85; } + 100% { opacity: 0; transform: translate3d(860px, 210px, 0) rotate(16deg) scaleX(1.25); } + } +`; + +function delay(milliseconds: number): Promise { + return new Promise((resolve) => window.setTimeout(resolve, milliseconds)); +} + +function formatDialogError(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + function ProviderAccountBalanceMetric({ compact = false, dimensions, diff --git a/src/renderer/pages/home/shared/i18n.tsx b/src/renderer/pages/home/shared/i18n.tsx index 681ff8d..8e85304 100644 --- a/src/renderer/pages/home/shared/i18n.tsx +++ b/src/renderer/pages/home/shared/i18n.tsx @@ -1374,6 +1374,7 @@ export const appCopy: Record = { "resets": "次", "All": "全部", "API endpoint": "API 地址", + "Active": "生效中", "Available": "可用", "CA certificate": "CA 证书", "Capability": "能力", @@ -1397,14 +1398,17 @@ export const appCopy: Record = { "Disabled": "已禁用", "Docs": "文档", "Edit description": "编辑描述", + "Effective": "生效", "Expand": "展开", "Expand models": "展开模型", + "Expired": "已过期", "Expires": "过期", "Filter": "筛选", "Filter agent": "筛选 Agent", "Filter request log model": "筛选请求日志模型", "Filter request log provider": "筛选请求日志供应商", "Filter request log status": "筛选请求日志状态", + "Full reset": "完整重置", "Invalid": "无效", "Manual install": "手动安装", "Manual install command": "手动安装命令", @@ -1466,7 +1470,13 @@ export const appCopy: Record = { "Remote provider manifest": "远程供应商 Manifest", "Refresh interval ms": "刷新间隔(毫秒)", "Response fields": "响应字段", - "Reset": "重置时间", + "Manual reset credit": "主动重置次数", + "Reset": "重置", + "Reset complete": "重置完成", + "Reset is unavailable.": "重置不可用。", + "Reset manual credit": "重置主动次数", + "Resetting": "正在重置", + "Used": "已使用", "Select at least one protocol.": "请至少选择一个协议。", "Select at least one usage response field.": "请至少选择一个用量响应字段。", "Showing first response fields only.": "仅显示前面的响应字段。", diff --git a/src/renderer/pages/home/shared/provider-accounts.ts b/src/renderer/pages/home/shared/provider-accounts.ts index aae1324..335952f 100644 --- a/src/renderer/pages/home/shared/provider-accounts.ts +++ b/src/renderer/pages/home/shared/provider-accounts.ts @@ -430,6 +430,64 @@ export function providerAccountMeterProgress(meter: ProviderAccountMeter): numbe return ratio === undefined ? undefined : Math.max(3, Math.round(ratio * 100)); } +export function providerAccountMeterValidityProgress(meter: ProviderAccountMeter, now = Date.now()): number | undefined { + const detail = providerAccountCurrentMeterDetail(meter, now); + if (!detail) { + return undefined; + } + const progress = providerAccountMeterDetailValidityProgress(detail, now); + return progress && progress > 0 ? progress : undefined; +} + +export function providerAccountCurrentMeterDetail(meter: ProviderAccountMeter, now = Date.now()): NonNullable[number] | undefined { + return (meter.details ?? []) + .filter((detail) => { + const effectiveAt = providerAccountDetailTimestamp(detail.effectiveAt); + const expiresAt = providerAccountDetailTimestamp(detail.expiresAt); + return effectiveAt !== undefined && expiresAt !== undefined && effectiveAt <= now && now < expiresAt; + }) + .sort((a, b) => (providerAccountDetailTimestamp(a.expiresAt) ?? Number.MAX_SAFE_INTEGER) - (providerAccountDetailTimestamp(b.expiresAt) ?? Number.MAX_SAFE_INTEGER))[0]; +} + +export function providerAccountMeterDetailValidityProgress(detail: NonNullable[number], now = Date.now()): number | undefined { + const effectiveAt = providerAccountDetailTimestamp(detail.effectiveAt); + const expiresAt = providerAccountDetailTimestamp(detail.expiresAt); + if (effectiveAt === undefined || expiresAt === undefined || expiresAt <= effectiveAt) { + return undefined; + } + if (now <= effectiveAt) { + return 100; + } + if (now >= expiresAt) { + return 0; + } + const ratio = (expiresAt - now) / (expiresAt - effectiveAt); + return Math.max(3, Math.round(Math.max(0, Math.min(1, ratio)) * 100)); +} + +export function formatProviderAccountDetailDate(value: string | undefined): string { + if (!value) { + return "-"; + } + const date = new Date(value); + if (Number.isNaN(date.getTime())) { + return value; + } + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + const hours = String(date.getHours()).padStart(2, "0"); + const minutes = String(date.getMinutes()).padStart(2, "0"); + return `${month}-${day} ${hours}:${minutes}`; +} + +function providerAccountDetailTimestamp(value: string | undefined): number | undefined { + if (!value) { + return undefined; + } + const timestamp = new Date(value).getTime(); + return Number.isFinite(timestamp) ? timestamp : undefined; +} + export function providerAccountBadgeVariant(status: ProviderAccountSnapshot["status"]): "danger" | "outline" | "success" | "warning" { if (status === "critical" || status === "error") { return "danger"; diff --git a/src/renderer/pages/tray/components/account-panel.tsx b/src/renderer/pages/tray/components/account-panel.tsx index 10ad8f6..3cf9f9e 100644 --- a/src/renderer/pages/tray/components/account-panel.tsx +++ b/src/renderer/pages/tray/components/account-panel.tsx @@ -1,6 +1,6 @@ import { accountMetersForDisplay, accountProgressClass, accountProgressColor, accountSnapshotLabel, compareAccountSnapshots, formatAccountMeterTitle, formatAccountMeterValue, - LoaderCircle, meterProgress, meterRemainingRatio, ProviderAccountMeter, ProviderAccountSnapshot, RefreshCw, TrayComponentVariants, + LoaderCircle, meterProgress, meterRemainingRatio, meterValidityProgress, ProviderAccountMeter, ProviderAccountSnapshot, RefreshCw, TrayComponentVariants, useTrayText } from "../shared"; import { RadialMetric } from "./widgets"; @@ -110,7 +110,7 @@ function AccountMeters({ return (
{meters.map((meter) => { - const progress = meterProgress(meter); + const progress = meterProgress(meter) ?? meterValidityProgress(meter); return (
@@ -132,7 +132,7 @@ function AccountMeters({ return (
{meters.map((meter) => { - const progress = meterProgress(meter); + const progress = meterProgress(meter) ?? meterValidityProgress(meter); return (
diff --git a/src/renderer/pages/tray/shared.tsx b/src/renderer/pages/tray/shared.tsx index abc3373..2a5be7e 100644 --- a/src/renderer/pages/tray/shared.tsx +++ b/src/renderer/pages/tray/shared.tsx @@ -686,6 +686,35 @@ export function meterProgress(meter: ProviderAccountMeter): number | undefined { return ratio === undefined ? undefined : Math.max(3, Math.round(ratio * 100)); } +export function meterValidityProgress(meter: ProviderAccountMeter, now = Date.now()): number | undefined { + const detail = currentMeterDetail(meter, now); + const effectiveAt = meterDetailTimestamp(detail?.effectiveAt); + const expiresAt = meterDetailTimestamp(detail?.expiresAt); + if (effectiveAt === undefined || expiresAt === undefined || expiresAt <= effectiveAt || now < effectiveAt || now >= expiresAt) { + return undefined; + } + const ratio = (expiresAt - now) / (expiresAt - effectiveAt); + return Math.max(3, Math.round(Math.max(0, Math.min(1, ratio)) * 100)); +} + +function currentMeterDetail(meter: ProviderAccountMeter, now = Date.now()): NonNullable[number] | undefined { + return (meter.details ?? []) + .filter((detail) => { + const effectiveAt = meterDetailTimestamp(detail.effectiveAt); + const expiresAt = meterDetailTimestamp(detail.expiresAt); + return effectiveAt !== undefined && expiresAt !== undefined && effectiveAt <= now && now < expiresAt; + }) + .sort((a, b) => (meterDetailTimestamp(a.expiresAt) ?? Number.MAX_SAFE_INTEGER) - (meterDetailTimestamp(b.expiresAt) ?? Number.MAX_SAFE_INTEGER))[0]; +} + +function meterDetailTimestamp(value: string | undefined): number | undefined { + if (!value) { + return undefined; + } + const timestamp = new Date(value).getTime(); + return Number.isFinite(timestamp) ? timestamp : undefined; +} + export function translateAccountMeterLabel(label: string, translate: (value: string) => string): string { return translate(label); } diff --git a/src/renderer/types/electron.d.ts b/src/renderer/types/electron.d.ts index c6fd5f4..6e7ea80 100644 --- a/src/renderer/types/electron.d.ts +++ b/src/renderer/types/electron.d.ts @@ -47,6 +47,8 @@ import type { ProfileOpenResult, ProfileRuntimeStatus, ProfileStopResult, + ProviderAccountResetRequest, + ProviderAccountResetResult, ProviderAccountSnapshotRequestOptions, ProviderAccountTestRequest, ProviderAccountTestResult, @@ -121,6 +123,7 @@ declare global { quitApp: () => Promise; revealProxyCertificate: () => Promise; renderHtmlPng?: (request: AppRenderHtmlPngRequest) => Promise; + resetCodexRateLimitCredit: (request: ProviderAccountResetRequest) => Promise; restartGateway: () => Promise; restartProxy: () => Promise; saveApiKeys: (apiKeys: ApiKeyConfig[]) => Promise; diff --git a/src/shared/app.ts b/src/shared/app.ts index a4f9e28..3fb5606 100644 --- a/src/shared/app.ts +++ b/src/shared/app.ts @@ -238,7 +238,18 @@ export type ProviderAccountMappedMeterConfig = { window?: ProviderAccountMeterWindow; }; +export type ProviderAccountMeterDetail = { + description?: string; + effectiveAt?: string; + expiresAt?: string; + id?: string; + label?: string; + redeemable?: boolean; + status?: string; +}; + export type ProviderAccountMeter = { + details?: ProviderAccountMeterDetail[]; id: string; kind: ProviderAccountMeterKind; label: string; @@ -366,6 +377,18 @@ export type ProviderAccountTestResult = { status?: ProviderAccountStatus; }; +export type ProviderAccountResetRequest = { + credentialId?: string; + creditId: string; + provider: string; +}; + +export type ProviderAccountResetResult = { + code?: string; + creditId: string; + ok: boolean; +}; + export type ProviderDeepLinkRequest = { error?: string; id: string; diff --git a/src/shared/ipc-channels.ts b/src/shared/ipc-channels.ts index 58f80c8..103b0b0 100644 --- a/src/shared/ipc-channels.ts +++ b/src/shared/ipc-channels.ts @@ -53,6 +53,7 @@ export const IPC_CHANNELS = { appRenderHtmlPng: "ccr:app:render-html-png", appRestartProxy: "ccr:app:restart-proxy", appRestartGateway: "ccr:app:restart-gateway", + appResetCodexRateLimitCredit: "ccr:app:reset-codex-rate-limit-credit", appSaveApiKeys: "ccr:app:save-api-keys", appClearProxyNetworkCaptures: "ccr:app:clear-proxy-network-captures", appStartGateway: "ccr:app:start-gateway", diff --git a/tests/main/local-agent-provider-codex.test.mjs b/tests/main/local-agent-provider-codex.test.mjs index 3e47a9d..e656f45 100644 --- a/tests/main/local-agent-provider-codex.test.mjs +++ b/tests/main/local-agent-provider-codex.test.mjs @@ -1,6 +1,13 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { codexProviderAccountConfig } from "../../src/main/local-agent-providers/codex.ts"; +import { + attachCodexRateLimitResetCreditDetails, + codexDefaultBaseUrl, + codexProviderAccountConfig, + codexRateLimitResetCreditDetails, + normalizeCodexProviderAccountConfig +} from "../../src/main/local-agent-providers/codex.ts"; +import { localAgentProviderApiKey } from "../../src/main/local-agent-providers/shared.ts"; test("Codex provider account config includes manual reset meter", () => { const config = codexProviderAccountConfig(); @@ -16,12 +23,20 @@ test("Codex provider account config includes manual reset meter", () => { assert.ok(Array.isArray(manualResetMeter.remaining)); assert.ok(manualResetMeter.remaining.includes("$.resetsAvailable")); assert.ok(manualResetMeter.remaining.includes("$.availableRateLimitResetCount")); + assert.ok(manualResetMeter.remaining.includes("$.rate_limit_reset_credits.available_count")); assert.ok(manualResetMeter.remaining.includes("$.rate_limit.manual_resets.remaining")); + assert.equal(manualResetMeter.remaining.at(-1), 0); assert.ok(Array.isArray(manualResetMeter.resetAt)); assert.ok(manualResetMeter.resetAt.includes("$.resetExpires")); assert.ok(manualResetMeter.resetAt.includes("$.expires_at")); assert.ok(manualResetMeter.resetAt.includes("$.rate_limit.manual_resets.expires_at")); assert.ok(manualResetMeter.resetAt.includes("$.manual_resets.reset_at")); + + const resetCreditsConnector = config.connectors.find((connector) => connector.type === "http-json" && connector.endpoint.endsWith("/wham/rate-limit-reset-credits")); + assert.ok(resetCreditsConnector); + const resetCreditsMeter = resetCreditsConnector.mapping.meters.find((meter) => meter.id === "codex_manual_resets"); + assert.ok(resetCreditsMeter); + assert.deepEqual(resetCreditsMeter.remaining, ["$.available_count", "$.rate_limit_reset_credits.available_count", 0]); }); test("Codex quota meters accept reset_at and resets_at usage shapes", () => { @@ -39,3 +54,153 @@ test("Codex quota meters accept reset_at and resets_at usage shapes", () => { assert.ok(secondaryQuota.resetAt.includes("$.rate_limit.secondary_window.resets_at")); assert.ok(secondaryQuota.resetAt.includes("$.rate_limits.secondary.resets_at")); }); + +test("Codex local provider account config upgrades persisted usage mapping", () => { + const oldAccount = JSON.parse(JSON.stringify(codexProviderAccountConfig())); + oldAccount.refreshIntervalMs = 45000; + const usageConnector = oldAccount.connectors.find((connector) => connector.type === "http-json" && connector.endpoint.endsWith("/wham/usage")); + const manualResetMeter = usageConnector.mapping.meters.find((meter) => meter.id === "codex_manual_resets"); + manualResetMeter.remaining = manualResetMeter.remaining.filter((path) => path !== "$.rate_limit_reset_credits.available_count"); + + const provider = normalizeCodexProviderAccountConfig({ + account: oldAccount, + api_base_url: codexDefaultBaseUrl, + api_key: localAgentProviderApiKey, + models: ["gpt-5-codex"], + name: "Codex API", + protocol: "openai_responses" + }); + + const upgradedUsageConnector = provider.account.connectors.find((connector) => connector.type === "http-json" && connector.endpoint.endsWith("/wham/usage")); + const upgradedResetCreditsConnector = provider.account.connectors.find((connector) => connector.type === "http-json" && connector.endpoint.endsWith("/wham/rate-limit-reset-credits")); + const upgradedManualResetMeter = upgradedUsageConnector.mapping.meters.find((meter) => meter.id === "codex_manual_resets"); + assert.equal(provider.account.refreshIntervalMs, 45000); + assert.ok(upgradedResetCreditsConnector); + assert.ok(upgradedManualResetMeter.remaining.includes("$.rate_limit_reset_credits.available_count")); +}); + +test("Codex reset credit details include available effective and expiry times", () => { + const details = codexRateLimitResetCreditDetails({ + rate_limit_reset_credits: { + credits: [ + { + effective_at: "2026-07-03T00:00:00Z", + expires_at: "2026-07-10T00:00:00Z", + id: "reset-1", + status: "available" + }, + { + effectiveAt: "2026-07-04T00:00:00Z", + expiresAt: "2026-07-11T00:00:00Z", + id: "reset-2", + status: "available" + }, + { + effective_at: "2026-06-01T00:00:00Z", + expires_at: "2026-06-08T00:00:00Z", + id: "reset-expired", + status: "expired" + } + ] + } + }); + + assert.deepEqual(details.map((detail) => detail.id), ["reset-1", "reset-2"]); + assert.equal(details[0].effectiveAt, "2026-07-03T00:00:00.000Z"); + assert.equal(details[0].expiresAt, "2026-07-10T00:00:00.000Z"); +}); + +test("Codex reset credit details support reset credits endpoint shape", () => { + const details = codexRateLimitResetCreditDetails({ + available_count: 2, + credits: [ + { + description: "Reset all Codex limits.", + expires_at: "2026-08-02T00:00:00Z", + id: "reset-root-1", + start_date: "2026-07-03T00:00:00Z", + status: "available", + title: "Usage reset" + }, + { + expires_at: "2026-07-20T00:00:00Z", + id: "reset-used", + start_date: "2026-06-20T00:00:00Z", + status: "used", + title: "Used reset" + } + ] + }); + + assert.equal(details.length, 1); + assert.equal(details[0].id, "reset-root-1"); + assert.equal(details[0].label, "Usage reset"); + assert.equal(details[0].description, "Reset all Codex limits."); + assert.equal(details[0].redeemable, true); + assert.equal(details[0].effectiveAt, "2026-07-03T00:00:00.000Z"); + assert.equal(details[0].expiresAt, "2026-08-02T00:00:00.000Z"); +}); + +test("Codex reset credit details attach to manual reset meter", () => { + const meters = attachCodexRateLimitResetCreditDetails([ + { + id: "codex_manual_resets", + kind: "requests", + label: "Manual resets", + remaining: 1, + source: "http-json", + unit: "resets", + window: "manual-reset" + } + ], { + rate_limit_reset_credits: { + credits: [ + { + effective_at: "2026-07-03T00:00:00Z", + expires_at: "2026-07-10T00:00:00Z", + id: "reset-1", + status: "available" + } + ] + } + }); + + assert.equal(meters[0].details.length, 1); + assert.equal(meters[0].details[0].id, "reset-1"); + assert.equal(meters[0].resetAt, "2026-07-10T00:00:00.000Z"); +}); + +test("Codex local provider account config keeps custom connectors", () => { + const account = { + connectors: [ + { + auth: "none", + endpoint: "https://example.com/account", + mapping: { + meters: [ + { + id: "custom", + kind: "balance", + label: "Custom", + remaining: "$.balance", + unit: "credits" + } + ] + }, + type: "http-json" + } + ], + enabled: true + }; + + const provider = normalizeCodexProviderAccountConfig({ + account, + api_base_url: codexDefaultBaseUrl, + api_key: localAgentProviderApiKey, + models: ["gpt-5-codex"], + name: "Codex API", + protocol: "openai_responses" + }); + + assert.equal(provider.account, account); +}); diff --git a/tests/renderer/overview-components.test.tsx b/tests/renderer/overview-components.test.tsx index 7a20916..5706c8e 100644 --- a/tests/renderer/overview-components.test.tsx +++ b/tests/renderer/overview-components.test.tsx @@ -3,6 +3,7 @@ import test from "node:test"; import * as React from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { OverviewView } from "../../src/renderer/pages/home/components/dashboard.tsx"; +import { providerAccountMeterDetailValidityProgress } from "../../src/renderer/pages/home/shared/provider-accounts.ts"; import type { OverviewWidgetConfig, ProviderAccountSnapshot } from "../../src/shared/app.ts"; import { accountSnapshots, installBrowserGlobals, usageStats } from "./fixtures.ts"; @@ -85,6 +86,7 @@ test("OverviewView renders the empty widget layout state", () => { test("OverviewView prioritizes Codex manual resets before folded balance meters", () => { const resetAt = new Date(Date.now() + 72 * 60 * 60 * 1000).toISOString(); + const resetEffectiveAt = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); const codexAccount: ProviderAccountSnapshot = { meters: [ { @@ -128,6 +130,15 @@ test("OverviewView prioritizes Codex manual resets before folded balance meters" id: "codex_manual_resets", kind: "requests", label: "Manual resets", + details: [ + { + description: "Reset all active Codex rate limits.", + effectiveAt: resetEffectiveAt, + expiresAt: resetAt, + id: "reset-1", + label: "Full reset" + } + ], remaining: 2, resetAt, unit: "resets", @@ -155,7 +166,81 @@ test("OverviewView prioritizes Codex manual resets before folded balance meters" assert.match(html, /Primary quota/); assert.match(html, /Secondary quota/); assert.match(html, /Manual resets/); + assert.match(html, /aria-expanded="false"/); + assert.match(html, /aria-label="Expand Manual resets/); + assert.doesNotMatch(html, /Effective/); + assert.doesNotMatch(html, /Expires/); + assert.doesNotMatch(html, /Full reset/); assert.match(html, /expires in/); assert.match(html, /2 resets/); assert.doesNotMatch(html, /Credit balance/); }); + +test("OverviewView does not render an outer progress bar for Codex manual resets", () => { + const resetAt = new Date(Date.now() + 72 * 60 * 60 * 1000).toISOString(); + const resetEffectiveAt = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); + const codexAccount: ProviderAccountSnapshot = { + meters: [ + { + id: "codex_manual_resets", + kind: "requests", + label: "Manual resets", + details: [ + { + effectiveAt: resetEffectiveAt, + expiresAt: resetAt, + id: "reset-1", + label: "Full reset" + } + ], + remaining: 2, + resetAt, + unit: "resets", + window: "manual-reset" + } + ], + provider: "Codex API", + source: "http-json", + status: "ok", + updatedAt: new Date().toISOString() + }; + + const html = renderToStaticMarkup( + undefined} + setUsageRange={() => undefined} + usageRange="30d" + usageStats={usageStats("30d")} + onWidgetsChange={() => undefined} + /> + ); + + assert.match(html, /Manual resets/); + assert.match(html, /aria-expanded="false"/); + assert.doesNotMatch(html, /style="width:[^"]*%"/); + assert.doesNotMatch(html, /Full reset/); +}); + +test("provider account reset credit detail progress uses each validity window", () => { + const effectiveAt = "2026-07-01T00:00:00.000Z"; + const expiresAt = "2026-07-11T00:00:00.000Z"; + + assert.equal(providerAccountMeterDetailValidityProgress({ + effectiveAt, + expiresAt + }, Date.parse("2026-07-06T00:00:00.000Z")), 50); + assert.equal(providerAccountMeterDetailValidityProgress({ + effectiveAt, + expiresAt + }, Date.parse("2026-06-30T00:00:00.000Z")), 100); + assert.equal(providerAccountMeterDetailValidityProgress({ + effectiveAt, + expiresAt + }, Date.parse("2026-07-12T00:00:00.000Z")), 0); + assert.equal(providerAccountMeterDetailValidityProgress({ + effectiveAt: expiresAt, + expiresAt + }, Date.parse("2026-07-06T00:00:00.000Z")), undefined); +}); diff --git a/tests/renderer/tray-components.test.tsx b/tests/renderer/tray-components.test.tsx index 3d94409..e52f864 100644 --- a/tests/renderer/tray-components.test.tsx +++ b/tests/renderer/tray-components.test.tsx @@ -146,6 +146,7 @@ test("AccountSummaryPanel covers empty and metered account states", () => { test("AccountSummaryPanel prioritizes Codex manual reset meter with expiration", () => { const resetAt = new Date(Date.now() + 72 * 60 * 60 * 1000).toISOString(); + const resetEffectiveAt = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); const html = renderToStaticMarkup( Date: Fri, 3 Jul 2026 15:16:14 +0800 Subject: [PATCH 15/62] Improve reset credit label localization --- .../pages/home/components/dashboard.tsx | 38 ++++++++++++++++++- src/renderer/pages/home/shared/i18n.tsx | 11 ++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/renderer/pages/home/components/dashboard.tsx b/src/renderer/pages/home/components/dashboard.tsx index a557a0e..e1e2cd9 100644 --- a/src/renderer/pages/home/components/dashboard.tsx +++ b/src/renderer/pages/home/components/dashboard.tsx @@ -2385,7 +2385,43 @@ function providerAccountMeterDetailLabel( index: number, t: (value: string) => string ): string { - return detail.label ? t(detail.label) : `#${index + 1}`; + return detail.label ? formatProviderAccountMeterDetailLabel(detail.label, t) : `#${index + 1}`; +} + +function formatProviderAccountMeterDetailLabel(value: string, t: (value: string) => string): string { + const trimmed = value.trim(); + const fullResetMatch = /^full reset(?:\s*\(([^)]*)\))?$/i.exec(trimmed); + if (fullResetMatch) { + const qualifier = fullResetMatch[1] ? formatCodexResetCreditQualifier(fullResetMatch[1], t) : ""; + if (!qualifier) { + return t("Full reset"); + } + return appTextLooksTranslated(t) + ? `${t("Full reset")}(${qualifier})` + : `${t("Full reset")} (${qualifier})`; + } + return t(trimmed); +} + +function formatCodexResetCreditQualifier(value: string, t: (value: string) => string): string { + return value + .replace(/\bweekly\b/gi, t("Weekly")) + .replace(/\bdaily\b/gi, t("Daily")) + .replace(/\bmonthly\b/gi, t("Monthly")) + .replace(/\byearly\b/gi, t("Yearly")) + .replace(/\bhours?\b/gi, t("hour")) + .replace(/\bhrs?\b/gi, t("hr")) + .replace(/\bminutes?\b/gi, t("minute")) + .replace(/\bmins?\b/gi, t("min")) + .replace(/\bdays?\b/gi, t("day")) + .replace(/\bweeks?\b/gi, t("week")) + .replace(/\bmonths?\b/gi, t("month")) + .replace(/\s*\+\s*/g, " + ") + .trim(); +} + +function appTextLooksTranslated(t: (value: string) => string): boolean { + return t("Manual reset credit") !== "Manual reset credit"; } function providerAccountMeterDetailStatusLabel(status: string | undefined, t: (value: string) => string): string { diff --git a/src/renderer/pages/home/shared/i18n.tsx b/src/renderer/pages/home/shared/i18n.tsx index 8e85304..b5e7dfc 100644 --- a/src/renderer/pages/home/shared/i18n.tsx +++ b/src/renderer/pages/home/shared/i18n.tsx @@ -1409,6 +1409,16 @@ export const appCopy: Record = { "Filter request log provider": "筛选请求日志供应商", "Filter request log status": "筛选请求日志状态", "Full reset": "完整重置", + "Daily": "每天", + "Monthly": "每月", + "Weekly": "每周", + "Yearly": "每年", + "hour": "小时", + "hr": "小时", + "min": "分钟", + "minute": "分钟", + "month": "个月", + "week": "周", "Invalid": "无效", "Manual install": "手动安装", "Manual install command": "手动安装命令", @@ -1476,6 +1486,7 @@ export const appCopy: Record = { "Reset is unavailable.": "重置不可用。", "Reset manual credit": "重置主动次数", "Resetting": "正在重置", + "Usage reset": "用量重置", "Used": "已使用", "Select at least one protocol.": "请至少选择一个协议。", "Select at least one usage response field.": "请至少选择一个用量响应字段。", From 24105304507e75a8fe359d9e93133e23a2f75d50 Mon Sep 17 00:00:00 2001 From: musistudio Date: Fri, 3 Jul 2026 15:35:07 +0800 Subject: [PATCH 16/62] Fix Claude app launch on macOS --- src/main/claude-app-cdp.ts | 7 +--- src/main/claude-app-launch.ts | 60 +++++++++++++++++++++++++-- tests/main/claude-app-cdp.test.mjs | 24 +++++++++++ tests/main/claude-app-launch.test.mjs | 54 ++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 tests/main/claude-app-cdp.test.mjs create mode 100644 tests/main/claude-app-launch.test.mjs diff --git a/src/main/claude-app-cdp.ts b/src/main/claude-app-cdp.ts index df2ca2f..9ced811 100644 --- a/src/main/claude-app-cdp.ts +++ b/src/main/claude-app-cdp.ts @@ -46,12 +46,9 @@ const claudeAppDesignCdpConnectTimeoutMs = 15_000; const claudeAppDesignCdpKeepAliveMs = 45_000; const claudeAppDesignCdpPollIntervalMs = 250; -export function shouldEnableClaudeAppDesignCdp(enabledByConfig = false): boolean { +export function shouldEnableClaudeAppDesignCdp(_enabledByConfig = false): boolean { const configured = process.env.CCR_CLAUDE_APP_DESIGN_CDP?.trim().toLowerCase(); - if (configured === "false" || configured === "0" || configured === "off") { - return false; - } - return enabledByConfig || configured === "true" || configured === "1" || configured === "on"; + return configured === "true" || configured === "1" || configured === "on"; } export async function reserveClaudeAppCdpPort(logger: ClaudeAppCdpLogger = console, enabledByConfig = false): Promise { diff --git a/src/main/claude-app-launch.ts b/src/main/claude-app-launch.ts index dc67f1b..f4f6ce9 100644 --- a/src/main/claude-app-launch.ts +++ b/src/main/claude-app-launch.ts @@ -19,6 +19,7 @@ export type ClaudeAppLaunchResult = { command: string; cdpPort?: number; claudeDesignProxy?: boolean; + pidIsLauncher?: boolean; pid?: number; userDataDir: string; }; @@ -52,8 +53,7 @@ export async function launchClaudeAppProfile(configDir: string, profile: Profile const shouldOpenDesign = shouldOpenClaudeAppDesign(config); const cdpPort = await reserveClaudeAppCdpPort(console, shouldOpenDesign); - const env: NodeJS.ProcessEnv = { - ...process.env, + const appEnv: Record = { ...profileEnv(profile), ...claudeCodeModelEnv(profile), ...(config ? botGatewayProfileEnv(config, profile, "app") : {}), @@ -64,11 +64,16 @@ export async function launchClaudeAppProfile(configDir: string, profile: Profile ELECTRON_ENABLE_LOGGING: "1", ...claudeCodeUtcTimezoneEnvOverride() }; + const env: NodeJS.ProcessEnv = { + ...process.env, + ...appEnv + }; delete env.ELECTRON_RUN_AS_NODE; const designUrl = claudeAppDesignUrl(config); const proxyUrl = claudeAppProxyUrl(config); - const child = spawn(lookup.executable, claudeElectronArgs(userDataDir, cdpPort, proxyUrl), { + const launch = claudeAppLaunchCommand(lookup.executable, userDataDir, cdpPort, proxyUrl, appEnv); + const child = spawn(launch.command, launch.args, { detached: true, env, stdio: "ignore" @@ -84,8 +89,9 @@ export async function launchClaudeAppProfile(configDir: string, profile: Profile return { child, claudeDesignProxy: Boolean(proxyUrl), - command: lookup.executable, + command: launch.command, ...(cdpPort ? { cdpPort } : {}), + ...(launch.pidIsLauncher ? { pidIsLauncher: launch.pidIsLauncher } : {}), pid: child.pid, userDataDir }; @@ -163,6 +169,52 @@ function claudeElectronArgs(userDataDir: string, cdpPort?: number, proxyUrl?: st ]; } +export function claudeAppLaunchCommand( + executable: string, + userDataDir: string, + cdpPort: number | undefined, + proxyUrl: string | undefined, + env: Record +): { args: string[]; command: string; pidIsLauncher?: boolean } { + const args = claudeElectronArgs(userDataDir, cdpPort, proxyUrl); + const appBundle = process.platform === "darwin" ? macAppBundleFromExecutable(executable) : undefined; + if (appBundle) { + return { + args: [ + "-W", + "-n", + ...macOpenEnvArgs(env), + appBundle, + "--args", + ...args + ], + command: "/usr/bin/open", + pidIsLauncher: true + }; + } + return { + args, + command: executable + }; +} + +function macAppBundleFromExecutable(executable: string): string | undefined { + const normalized = path.normalize(executable); + const marker = `${path.sep}Contents${path.sep}MacOS${path.sep}`; + const markerIndex = normalized.lastIndexOf(marker); + if (markerIndex <= 0) { + return undefined; + } + const appBundle = normalized.slice(0, markerIndex); + return appBundle.endsWith(".app") && isDirectory(appBundle) ? appBundle : undefined; +} + +function macOpenEnvArgs(env: Record): string[] { + return Object.entries(env) + .filter(([key, value]) => isEnvName(key) && typeof value === "string") + .flatMap(([key, value]) => ["--env", `${key}=${value}`]); +} + function claudeElectronUserDataDir(settingsDir: string, profile: ProfileConfig): string { return path.join( settingsDir, diff --git a/tests/main/claude-app-cdp.test.mjs b/tests/main/claude-app-cdp.test.mjs new file mode 100644 index 0000000..d61252f --- /dev/null +++ b/tests/main/claude-app-cdp.test.mjs @@ -0,0 +1,24 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { shouldEnableClaudeAppDesignCdp } from "../../src/main/claude-app-cdp.ts"; + +test("Claude App Design CDP is opt-in even when Claude Design is configured", (t) => { + const previous = process.env.CCR_CLAUDE_APP_DESIGN_CDP; + t.after(() => { + if (previous === undefined) { + delete process.env.CCR_CLAUDE_APP_DESIGN_CDP; + } else { + process.env.CCR_CLAUDE_APP_DESIGN_CDP = previous; + } + }); + + delete process.env.CCR_CLAUDE_APP_DESIGN_CDP; + assert.equal(shouldEnableClaudeAppDesignCdp(true), false); + assert.equal(shouldEnableClaudeAppDesignCdp(false), false); + + process.env.CCR_CLAUDE_APP_DESIGN_CDP = "true"; + assert.equal(shouldEnableClaudeAppDesignCdp(false), true); + + process.env.CCR_CLAUDE_APP_DESIGN_CDP = "off"; + assert.equal(shouldEnableClaudeAppDesignCdp(true), false); +}); diff --git a/tests/main/claude-app-launch.test.mjs b/tests/main/claude-app-launch.test.mjs new file mode 100644 index 0000000..8ad9ee6 --- /dev/null +++ b/tests/main/claude-app-launch.test.mjs @@ -0,0 +1,54 @@ +import assert from "node:assert/strict"; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { claudeAppLaunchCommand } from "../../src/main/claude-app-launch.ts"; + +test("claudeAppLaunchCommand opens macOS app bundles through LaunchServices", (t) => { + const tempDir = mkdtempForTest(); + t.after(() => rmSync(tempDir, { force: true, recursive: true })); + + const appBundle = path.join(tempDir, "Claude.app"); + const executable = path.join(appBundle, "Contents", "MacOS", "Claude"); + mkdirSync(path.dirname(executable), { recursive: true }); + writeFileSync(executable, ""); + + const launch = claudeAppLaunchCommand( + executable, + path.join(tempDir, "profile data"), + 49152, + "http://127.0.0.1:3456", + { + "BAD-NAME": "ignored", + CLAUDE_CONFIG_DIR: path.join(tempDir, "config"), + CCR_PROFILE_SURFACE: "app" + } + ); + + if (process.platform !== "darwin") { + assert.equal(launch.command, executable); + assert.equal(launch.pidIsLauncher, undefined); + return; + } + + assert.equal(launch.command, "/usr/bin/open"); + assert.equal(launch.pidIsLauncher, true); + assert.deepEqual(launch.args.slice(0, 2), ["-W", "-n"]); + assert.ok(launch.args.includes("--env")); + assert.ok(launch.args.includes(`CLAUDE_CONFIG_DIR=${path.join(tempDir, "config")}`)); + assert.ok(launch.args.includes("CCR_PROFILE_SURFACE=app")); + assert.equal(launch.args.some((arg) => arg.startsWith("BAD-NAME=")), false); + + const appIndex = launch.args.indexOf(appBundle); + assert.ok(appIndex > 0); + assert.equal(launch.args[appIndex + 1], "--args"); + assert.ok(launch.args.includes("--remote-debugging-port=49152")); + assert.ok(launch.args.includes("--remote-debugging-address=127.0.0.1")); + assert.ok(launch.args.includes("--proxy-server=http://127.0.0.1:3456")); + assert.ok(launch.args.includes(`--user-data-dir=${path.join(tempDir, "profile data")}`)); +}); + +function mkdtempForTest() { + return mkdtempSync(path.join(os.tmpdir(), "ccr-claude-app-launch-")); +} From 46fa7e21812e72d14007e6d8fd7322ba59718bc9 Mon Sep 17 00:00:00 2001 From: musi Date: Fri, 3 Jul 2026 21:29:10 +0800 Subject: [PATCH 17/62] Remove stale generated bin backups --- src/main/profile-launch-service.ts | 3 +- src/main/profile-service.ts | 94 +++++++++++++++++++------ tests/main/profile-service.test.mjs | 105 +++++++++++++++++++++++++++- 3 files changed, 177 insertions(+), 25 deletions(-) diff --git a/src/main/profile-launch-service.ts b/src/main/profile-launch-service.ts index 8aecd07..a42d5aa 100644 --- a/src/main/profile-launch-service.ts +++ b/src/main/profile-launch-service.ts @@ -12,7 +12,7 @@ import { codexCliMiddlewareRuntimeScript } from "./codex-cli-middleware-runtime" import { CONFIGDIR } from "./constants"; import { gatewayService } from "../server/gateway/service"; import { buildProfileLaunchPlan, findProfileForOpen, profileLaunchSpawnCommand, profileOpenCommand, resolveClaudeCodeSettingsFile, resolveProfileOpenSurface } from "./profile-launch-core"; -import { applyProfileConfig } from "./profile-service"; +import { applyProfileConfig, cleanupGeneratedBinBackups } from "./profile-service"; import { broadcastWindowsEnvironmentChanged, windowsSystemCommand } from "./windows-system"; const ccrPathBlockStart = "# >>> Claude Code Router CLI >>>"; @@ -1082,6 +1082,7 @@ function commandProfileRef(config: AppConfig, profile: ReturnType { + cleanupGeneratedBinBackups(); const appliedAt = new Date().toISOString(); const profiles = profileEntries(config); const result: ProfileApplyResult = { @@ -65,6 +66,7 @@ export async function applyProfileConfig(config: AppConfig): Promise { + const configDir = mkdtempSync(path.join(os.tmpdir(), "ccr-generated-bin-cleanup-")); + try { + const binDir = path.join(configDir, "bin"); + mkdirSync(binDir, { recursive: true }); + const deletedFiles = [ + "ccr-claude-code-api-key-default.ccr-backup-2026-01-01T00-00-00-000Z", + "ccr-claude-code-wrapper-default.ccr-original", + "ccr-codex-cli-stdio-default.ccr-original-missing", + "ccr-codex-cli-middleware.js.ccr-backup-2026-01-01T00-00-00-000Z" + ]; + const keptFiles = [ + "custom-tool.ccr-backup-2026-01-01T00-00-00-000Z", + "notes.txt" + ]; + for (const file of [...deletedFiles, ...keptFiles]) { + writeFileSync(path.join(binDir, file), "old"); + } + + assert.equal(cleanupGeneratedBinBackups(configDir), deletedFiles.length); + for (const file of deletedFiles) { + assert.equal(existsSync(path.join(binDir, file)), false); + } + for (const file of keptFiles) { + assert.equal(existsSync(path.join(binDir, file)), true); + } + } finally { + rmSync(configDir, { force: true, recursive: true }); + } +}); + +test("profile service overwrites generated bin files without creating backups", { skip: !process.env.CCR_INTERNAL_HOME_DIR }, async () => { + const profileId = "generated-bin-test"; + const commandExtension = process.platform === "win32" ? ".cmd" : ""; + const binDir = path.join(CONFIGDIR, "bin"); + mkdirSync(binDir, { recursive: true }); + const generatedFiles = [ + path.join(binDir, `ccr-claude-code-api-key-${profileId}${commandExtension}`), + path.join(binDir, `ccr-claude-code-wrapper-${profileId}${commandExtension}`), + path.join(binDir, "ccr-codex-cli-middleware.js") + ]; + for (const file of generatedFiles) { + writeFileSync(file, "old generated content\n"); + writeFileSync(`${file}.ccr-backup-2026-01-01T00-00-00-000Z`, "old backup\n"); + writeFileSync(`${file}.ccr-original`, "old original\n"); + } + + const config = createDefaultAppConfig({ + generatedConfigFile: path.join(CONFIGDIR, "gateway.config.json") + }); + config.Providers = [ + { + api_base_url: "https://example.test/v1", + api_key: "provider-key", + models: ["model"], + name: "Provider" + } + ]; + config.preferredProvider = "Provider"; + config.APIKEY = "ccr-profile-test"; + config.APIKEYS = [ + { + createdAt: "2026-01-01T00:00:00.000Z", + id: `profile:${profileId}`, + key: "ccr-profile-test", + name: "Profile: Generated Bin Test" + } + ]; + config.profile.profiles = [ + { + agent: "claude-code", + enabled: true, + env: {}, + id: profileId, + model: "Provider/model", + name: "Generated Bin Test", + scope: "ccr", + settingsFile: "~/.claude/settings.json", + smallFastModel: "", + surface: "auto" + } + ]; + + const result = await applyProfileConfig(config); + assert.equal(result.clients.length, 1); + assert.equal(result.clients[0].ok, true); + for (const file of generatedFiles) { + assert.notEqual(readFileSync(file, "utf8"), "old generated content\n"); + } + const backupEntries = readdirSync(binDir).filter((entry) => + ( + entry.startsWith(`ccr-claude-code-api-key-${profileId}`) || + entry.startsWith(`ccr-claude-code-wrapper-${profileId}`) || + entry.startsWith("ccr-codex-cli-middleware.js") + ) && entry.includes(".ccr-") + ); + assert.deepEqual(backupEntries, []); +}); test("profile service restores managed global Claude settings when only CCR-scoped Claude profiles are active", () => { const previousHome = process.env.HOME; From 7130e1e632c342b158489e1a5bce4f310405377c Mon Sep 17 00:00:00 2001 From: musistudio Date: Fri, 3 Jul 2026 22:53:10 +0800 Subject: [PATCH 18/62] Add TeamoRouter provider preset and docs --- README.md | 7 +++++++ README_zh.md | 7 +++++++ docs/public/provider-icons/teamorouter.png | Bin 0 -> 1776 bytes .../docs/en/configuration/provider-deeplink.md | 4 ++++ .../docs/zh/configuration/provider-deeplink.md | 4 ++++ docs/src/styles/global.css | 6 ++++++ src/main/presets/index.ts | 4 +++- src/main/presets/teamorouter/index.ts | 15 +++++++++++++++ .../assets/provider-icons/teamorouter.png | Bin 0 -> 1776 bytes src/renderer/pages/home/shared/options.ts | 2 ++ 10 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 docs/public/provider-icons/teamorouter.png create mode 100644 src/main/presets/teamorouter/index.ts create mode 100644 src/renderer/assets/provider-icons/teamorouter.png diff --git a/README.md b/README.md index 95847ab..3f5ce1e 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,13 @@ Codex support is powered by [musistudio/codexl](https://github.com/musistudio/co RunAPI + + + TeamoRouter icon +
+ TeamoRouter +
+ diff --git a/README_zh.md b/README_zh.md index 15bdb68..cd79161 100644 --- a/README_zh.md +++ b/README_zh.md @@ -168,6 +168,13 @@ CCR 可以完全通过桌面 UI 完成配置。首次使用建议按下面顺序 RunAPI + + + TeamoRouter 图标 +
+ TeamoRouter +
+ diff --git a/docs/public/provider-icons/teamorouter.png b/docs/public/provider-icons/teamorouter.png new file mode 100644 index 0000000000000000000000000000000000000000..0eb894bdabaf228f919cfa615bc77cffa6679af6 GIT binary patch literal 1776 zcmV5)u*<6B85^6cQ2=7#J855)u*;5)u*;5fKp*5)w~!GnN1V01b3fPE!Ei z#9E~wK!6bMaF8&ZO-uLy00wADL_t(|+U?xg>r8PJ0Pw^3UDxtJmPohkQEu6nQ1&cM zjpe~w_C$&(OQ|6x@j%vOO4bJ{DUwpk124*q_2T?5vVM*EZhrG!jyj)LW$t|M`JV4N z=id1NV~jDz7-Nhv#u#IaF~%5Uj4{R-V=REvllIRWHW(3$zQY$Bo>~P0R^A+o&5Dux zHbPYC%q1u_@-{@1&dgD%@d)Bro1<3a7KE4VMWZ3lAg~iTTBtvSkkW~0X+*$uksd;8 z=PM9MQpSJK7!N@tIsTKn?7E^~mGrs@Toq#7`(e~k#!h%EiF^Nh(Che zJ8dy|Q>tO7)xDjM>CbQ{7GBE0YM_l#;6-;-8EtnPy<~}0okz#prPy=e!E*I7z-+X# z2L%7S@C|g07WMZ~X(%KZX}P+iS_L4Fv|LX$=P&@QiL+eYP;G!%%+SlF%kSd>tnVU@ z54BjI0si4GUImAapjsdJrYB1frh zxf6>DKv|QKYOZ(UQ`Ow;@4F}}jdxbV^M?B*U$6YVknf?Zu9CVJ1?G^dPPEPcMjLzgIyI^n*gT~%GyuFTKq z^rUV`GsEb!-} z#73l;A?~4872+s6{hhLxDKnwzTSnyh@L#Arww7HMy^4$IWz`5hhov9#e6Vx)D zXZ-)c;Y0MOK1s8%=}P5on`S*}*79kMVv_&;o+Y)Z?kkEBuWx-WKr|gSdPmPUM`%t1 zkD;R@5(-g=p;Wi9S8)pydpjVNGLU} zSFs3)NGFR08?poFNOwLMhFI^vbUHA`7-Nhv#u#IaF~%5Uj4{R-V~jDzy!#4-BPs#K SiMXi%0000 RunAPIResponses / Chat Completions + + + TeamoRouterAnthropic / Chat / Responses +
## Embeddable Button Component diff --git a/docs/src/content/docs/zh/configuration/provider-deeplink.md b/docs/src/content/docs/zh/configuration/provider-deeplink.md index 12546d2..703ad8e 100644 --- a/docs/src/content/docs/zh/configuration/provider-deeplink.md +++ b/docs/src/content/docs/zh/configuration/provider-deeplink.md @@ -74,6 +74,10 @@ lead: 快速添加常见模型供应商,确认无误后即可保存,减少 RunAPIResponses / Chat Completions + + + TeamoRouterAnthropic / Chat / Responses +
## 嵌入式按钮组件 diff --git a/docs/src/styles/global.css b/docs/src/styles/global.css index 6bff293..a435ff4 100644 --- a/docs/src/styles/global.css +++ b/docs/src/styles/global.css @@ -1306,6 +1306,12 @@ h1 { --provider-brand-3: #f3f3f3; } +.doc-markdown a.provider-import-button.provider-teamorouter { + --provider-brand: #0c0c0f; + --provider-brand-2: #555b64; + --provider-brand-3: #f4f4f5; +} + .doc-markdown a.provider-import-button.provider-deepseek { --provider-brand: #173aa8; --provider-brand-2: #4e69ff; diff --git a/src/main/presets/index.ts b/src/main/presets/index.ts index 1afbb5f..cee4dea 100644 --- a/src/main/presets/index.ts +++ b/src/main/presets/index.ts @@ -9,6 +9,7 @@ import { openaiProviderPreset } from "./openai"; import { openRouterProviderPreset } from "./openrouter"; import { runApiProviderPreset } from "./runapi"; import { siliconFlowProviderPreset } from "./siliconflow"; +import { teamoRouterProviderPreset } from "./teamorouter"; import { zaiGlobalCodingProviderPreset } from "./zai-global-coding"; import { zaiGlobalGeneralProviderPreset } from "./zai-global-general"; import { zhipuCnCodingProviderPreset } from "./zhipu-cn-coding"; @@ -40,7 +41,8 @@ export const providerPresets: ProviderPreset[] = [ moonshotGlobalProviderPreset, bailianProviderPreset, siliconFlowProviderPreset, - runApiProviderPreset + runApiProviderPreset, + teamoRouterProviderPreset ]; export function getProviderPresets(): ProviderPreset[] { diff --git a/src/main/presets/teamorouter/index.ts b/src/main/presets/teamorouter/index.ts new file mode 100644 index 0000000..aa19e75 --- /dev/null +++ b/src/main/presets/teamorouter/index.ts @@ -0,0 +1,15 @@ +import { defaultProviderAccountConfig, type ProviderPreset } from "../../../shared/provider-presets"; + +export const teamoRouterProviderPreset: ProviderPreset = { + account: defaultProviderAccountConfig, + aliases: ["teamorouter", "teamo router", "teamo"], + endpoints: [ + { + baseUrl: "https://api.teamorouter.com", + protocols: ["anthropic_messages", "openai_chat_completions", "openai_responses"] + } + ], + id: "teamorouter", + name: "TeamoRouter", + websiteUrl: "https://teamorouter.com/" +}; diff --git a/src/renderer/assets/provider-icons/teamorouter.png b/src/renderer/assets/provider-icons/teamorouter.png new file mode 100644 index 0000000000000000000000000000000000000000..0eb894bdabaf228f919cfa615bc77cffa6679af6 GIT binary patch literal 1776 zcmV5)u*<6B85^6cQ2=7#J855)u*;5)u*;5fKp*5)w~!GnN1V01b3fPE!Ei z#9E~wK!6bMaF8&ZO-uLy00wADL_t(|+U?xg>r8PJ0Pw^3UDxtJmPohkQEu6nQ1&cM zjpe~w_C$&(OQ|6x@j%vOO4bJ{DUwpk124*q_2T?5vVM*EZhrG!jyj)LW$t|M`JV4N z=id1NV~jDz7-Nhv#u#IaF~%5Uj4{R-V=REvllIRWHW(3$zQY$Bo>~P0R^A+o&5Dux zHbPYC%q1u_@-{@1&dgD%@d)Bro1<3a7KE4VMWZ3lAg~iTTBtvSkkW~0X+*$uksd;8 z=PM9MQpSJK7!N@tIsTKn?7E^~mGrs@Toq#7`(e~k#!h%EiF^Nh(Che zJ8dy|Q>tO7)xDjM>CbQ{7GBE0YM_l#;6-;-8EtnPy<~}0okz#prPy=e!E*I7z-+X# z2L%7S@C|g07WMZ~X(%KZX}P+iS_L4Fv|LX$=P&@QiL+eYP;G!%%+SlF%kSd>tnVU@ z54BjI0si4GUImAapjsdJrYB1frh zxf6>DKv|QKYOZ(UQ`Ow;@4F}}jdxbV^M?B*U$6YVknf?Zu9CVJ1?G^dPPEPcMjLzgIyI^n*gT~%GyuFTKq z^rUV`GsEb!-} z#73l;A?~4872+s6{hhLxDKnwzTSnyh@L#Arww7HMy^4$IWz`5hhov9#e6Vx)D zXZ-)c;Y0MOK1s8%=}P5on`S*}*79kMVv_&;o+Y)Z?kkEBuWx-WKr|gSdPmPUM`%t1 zkD;R@5(-g=p;Wi9S8)pydpjVNGLU} zSFs3)NGFR08?poFNOwLMhFI^vbUHA`7-Nhv#u#IaF~%5Uj4{R-V~jDzy!#4-BPs#K SiMXi%0000 = { openrouter: openrouterProviderIconUrl, runapi: runapiProviderIconUrl, siliconflow: siliconflowProviderIconUrl, + teamorouter: teamorouterProviderIconUrl, "zai-global-coding": zaiGlobalCodingProviderIconUrl, "zai-global-general": zaiGlobalGeneralProviderIconUrl, "zhipu-cn-coding": zhipuCnCodingProviderIconUrl, From 398332251916e9d2622bdc9f2d4f5e5bdc72febd Mon Sep 17 00:00:00 2001 From: musistudio Date: Fri, 3 Jul 2026 22:57:08 +0800 Subject: [PATCH 19/62] feat: update ai-gateway version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c817649..4a85be9 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "rebuild:sqlite3": "electron-rebuild -f -w better-sqlite3" }, "dependencies": { - "@the-next-ai/ai-gateway": "^1.0.3", + "@the-next-ai/ai-gateway": "^1.0.4", "@the-next-ai/bot-gateway-sdk": "^0.1.0", "better-sqlite3": "^12.11.1", "electron-updater": "^6.8.9", From d41dae4e4a04aeb646642291592f473b535f3b89 Mon Sep 17 00:00:00 2001 From: musistudio Date: Fri, 3 Jul 2026 22:58:16 +0800 Subject: [PATCH 20/62] feat: update ai-gateway version --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 890dd6e..a63d39b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "3.0.6", "license": "MIT", "dependencies": { - "@the-next-ai/ai-gateway": "^1.0.3", + "@the-next-ai/ai-gateway": "^1.0.4", "@the-next-ai/bot-gateway-sdk": "^0.1.0", "better-sqlite3": "^12.11.1", "electron-updater": "^6.8.9", @@ -2066,9 +2066,9 @@ } }, "node_modules/@the-next-ai/ai-gateway": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@the-next-ai/ai-gateway/-/ai-gateway-1.0.3.tgz", - "integrity": "sha512-+cZtmhCB1UbxYpn0EPVHlr4qO6W/yBhMidF/JPYdeNG11uGbxSPY9CnS5rKF1v2B6mrgL65lUzL3VnMK4Iq2Ug==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@the-next-ai/ai-gateway/-/ai-gateway-1.0.4.tgz", + "integrity": "sha512-4SVznRNbm4+8acXk3VvinQO1Wv0NvkgrTe5bOijd5J6fE8GXRzwmfKAXM8pJMJJ/s6qLkJ7LJ4LqvuYs7rTABA==", "license": "MIT", "dependencies": { "diff": "^8.0.3", From 87a666df215b4e66368e3de253ec3f18f23a3d64 Mon Sep 17 00:00:00 2001 From: musi Date: Fri, 3 Jul 2026 23:04:05 +0800 Subject: [PATCH 21/62] chore: release 3.0.7 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a63d39b..dbcb224 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-code-router", - "version": "3.0.6", + "version": "3.0.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-code-router", - "version": "3.0.6", + "version": "3.0.7", "license": "MIT", "dependencies": { "@the-next-ai/ai-gateway": "^1.0.4", diff --git a/package.json b/package.json index 4a85be9..29a63fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-code-router", - "version": "3.0.6", + "version": "3.0.7", "license": "MIT", "description": "Local Claude Code Router gateway with CLI and web management UI.", "repository": { From ee44357a34c4ee0e7cf88e267aa0db459a1158bb Mon Sep 17 00:00:00 2001 From: jesieleo <90036937+jesieleo@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:52:03 +0800 Subject: [PATCH 22/62] fix(gateway): prevent three crashes from non-ASCII names, client disconnect, and immutable headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three independent crashes in src/server/gateway/service.ts, each surfacing as either a 502 or an Electron Uncaught Exception dialog. == 1. Non-ASCII provider/model names -> 502 ByteString error == When a provider name or model selector contains non-ASCII characters (CJK, emoji, accented Latin — e.g. "小米mimo/mimo-v2.5-pro-ultraspeed"), every routed request fails with: Unexpected status 502 Bad Gateway: Cannot convert argument to a ByteString because the character at index 0 has a value of 23567 which is greater than 255. Several observability headers derived their values from raw, unsanitized user-facing strings (x-ccr-logical-provider, x-ccr-routed- model, x-ccr-route-reason, the model-discovery/app-rewrite/codex-patch diagnostics, x-ccr-fallback-model). Node/undici enforces the HTTP ByteString contract (code point <= 255), so any non-ASCII char throws a TypeError that bubbles to the catch-all as 502. Fix: add sanitizeHeaderValue() that replaces characters outside the printable ASCII range with "-", and wrap all ten affected assignments. The core gateway (@the-next-ai/ai-gateway) does not read any x-ccr-* header (verified), so values stay observability-only. == 2. Client disconnect mid-stream -> Uncaught Exception == When a client closes the SSE connection mid-stream — normal during local tool execution (file creation) — the gateway emitted an Uncaught Exception that crashed the Electron main process: Uncaught Exception: Error: Client connection closed before response completed. Two gaps let the error escape: - responseBody.once("error", ...) consumes only the FIRST error, but a disconnect triggers several in quick succession (abort, destroy, EPIPE). Remaining errors re-throw as unhandled. - The destination ServerResponse had NO "error" listener, so write failures on a closed socket bubbled up directly. Fix: once("error") -> on("error") on the response body so every error is logged via writeStreamLog; add response.on("error") that marks the disconnect and aborts upstream. Only the spurious crash is removed — disconnects are still recorded and upstream still aborted. == 3. Codex apply_patch -> 502 "immutable" == Creating a file via Codex (apply_patch) fails with: Unexpected status 502 Bad Gateway: immutable Undici's fetch Response.headers can be immutable; mergeFallbackResponse Headers returns it as-is when no fallback occurred, and the codex patch bridge then calls responseHeaders.delete("content-length"), which throws TypeError: immutable. Fix: wrap the merged headers in new Headers(...) to guarantee a mutable copy regardless of upstream immutability or merge early-return. == Verification == - sanitizeHeaderValue: "小米mimo/mimo-v2.5-pro-ultraspeed" -> "mimo/mimo-v2.5-pro-ultraspeed" (ASCII-safe); real repro confirmed. - Disconnect crash: previously fired on every file-creating tool call; after fix, no crash, model continues normally. - immutable 502: previously fired on every apply_patch; after fix, files are created successfully. - ASCII-only configs unaffected (sanitization is a no-op; error handlers only affect the failure path). --- src/server/gateway/service.ts | 54 +++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/src/server/gateway/service.ts b/src/server/gateway/service.ts index 1680d7f..dd364d9 100644 --- a/src/server/gateway/service.ts +++ b/src/server/gateway/service.ts @@ -635,7 +635,7 @@ class GatewayService { const client = inferGatewayClient(apiKey, request.headers); const cursorCompatPreparation = prepareCursorOpenAICompatChatBody(this.config, client, method, path, requestBody); if (cursorCompatPreparation) { - headers["x-ccr-cursor-openai-compat"] = cursorCompatPreparation.diagnostic; + headers["x-ccr-cursor-openai-compat"] = sanitizeHeaderValue(cursorCompatPreparation.diagnostic); } let bodyToForward: Buffer | undefined = cursorCompatPreparation?.body ?? requestBody; let routeFallback = this.config.Router.fallback; @@ -643,12 +643,12 @@ class GatewayService { let codexApplyPatchBridgeActive = false; const claudeModelRewrite = prepareClaudeCodeDiscoveredModelRequest(this.config, request.headers, method, path, bodyToForward); if (claudeModelRewrite) { - headers["x-ccr-claude-model-discovery"] = claudeModelRewrite.diagnostic; + headers["x-ccr-claude-model-discovery"] = sanitizeHeaderValue(claudeModelRewrite.diagnostic); bodyToForward = claudeModelRewrite.body; } const claudeAppModelRewrite = prepareClaudeAppFallbackModelRequest(this.config, method, path, bodyToForward); if (claudeAppModelRewrite) { - headers["x-ccr-claude-app-model-rewrite"] = claudeAppModelRewrite.diagnostic; + headers["x-ccr-claude-app-model-rewrite"] = sanitizeHeaderValue(claudeAppModelRewrite.diagnostic); bodyToForward = claudeAppModelRewrite.body; routedModel = claudeAppModelRewrite.routedModel; } @@ -678,6 +678,16 @@ class GatewayService { upstreamAbortController.abort(new Error(clientDisconnectMessage)); onClientDisconnect?.(); }); + response.on("error", (error) => { + // Client-side write failures (EPIPE / ECONNRESET when the client closes + // mid-stream, common during tool execution) must not crash the main + // process as an Uncaught Exception. Swallow them here; the close handler + // above already records the disconnect via writeStreamLog. + if (!clientDisconnected) { + clientDisconnected = true; + upstreamAbortController.abort(new Error(clientDisconnectMessage)); + } + }); const writeRequestLog = ( statusCode: number, @@ -743,10 +753,10 @@ class GatewayService { }); const serialized = Buffer.from(`${JSON.stringify(routed.body)}\n`, "utf8"); headers["content-type"] = "application/json"; - headers["x-ccr-route-reason"] = routed.decision.reason; + headers["x-ccr-route-reason"] = sanitizeHeaderValue(routed.decision.reason); routeFallback = routed.decision.fallback ?? routeFallback; if (routed.decision.model) { - headers["x-ccr-routed-model"] = routed.decision.model; + headers["x-ccr-routed-model"] = sanitizeHeaderValue(routed.decision.model); routedModel = routed.decision.model; } bodyToForward = serialized; @@ -761,10 +771,10 @@ class GatewayService { }); const serialized = Buffer.from(`${JSON.stringify(routed.body)}\n`, "utf8"); headers["content-type"] = "application/json"; - headers["x-ccr-route-reason"] = routed.decision.reason; + headers["x-ccr-route-reason"] = sanitizeHeaderValue(routed.decision.reason); routeFallback = routed.decision.fallback ?? routeFallback; if (routed.decision.model) { - headers["x-ccr-routed-model"] = routed.decision.model; + headers["x-ccr-routed-model"] = sanitizeHeaderValue(routed.decision.model); routedModel = routed.decision.model; } bodyToForward = serialized; @@ -781,7 +791,7 @@ class GatewayService { if (codexApplyPatchBridgeRequest) { bodyToForward = codexApplyPatchBridgeRequest.body; codexApplyPatchBridgeActive = true; - headers["x-ccr-codex-patch-bridge"] = codexApplyPatchBridgeRequest.diagnostic; + headers["x-ccr-codex-patch-bridge"] = sanitizeHeaderValue(codexApplyPatchBridgeRequest.diagnostic); headers["content-type"] = "application/json"; } @@ -906,7 +916,13 @@ class GatewayService { bodyToForward = upstreamResult.attempt.body ?? bodyToForward; routedModel = upstreamResult.attempt.model ?? routedModel; const responseHeaders = rewriteCapabilityResponseHeaders( - mergeFallbackResponseHeaders(upstreamResponseHeaders(upstreamResult), upstreamResult), + // Copy into a mutable Headers instance: upstream fetch Response.headers + // can be immutable (TypeError: immutable on .delete/.set), and + // mergeFallbackResponseHeaders returns the original object as-is when + // no fallback occurred. Codex apply_patch / web-search paths call + // .delete("content-length") below, which would otherwise throw and + // surface as a 502. + new Headers(mergeFallbackResponseHeaders(upstreamResponseHeaders(upstreamResult), upstreamResult)), this.config ); const upstreamResponse = upstreamResult.response; @@ -995,7 +1011,7 @@ class GatewayService { writeStreamLog(); } }); - responseBody.once("error", (error) => { + responseBody.on("error", (error) => { streamDetectedError ??= sseErrorDetector.finish(); writeStreamLog(clientDisconnected ? clientDisconnectMessage : formatError(error)); }); @@ -5592,7 +5608,7 @@ function prepareUpstreamCredentialAttempt(input: { const headers: Record = { ...input.headers, "x-target-providers": selection.credentials.map((candidate) => candidate.internalName).join(","), - "x-ccr-logical-provider": target.provider.name, + "x-ccr-logical-provider": providerRuntimeId(target.provider), "x-ccr-provider-credential-chain": selection.credentials.map((candidate) => candidate.credentialId).join(",") }; delete headers["x-target-provider"]; @@ -6002,7 +6018,7 @@ function mergeFallbackResponseHeaders(headers: Headers, result: UpstreamFetchRes merged.set("x-ccr-fallback-delays-ms", formatFallbackDelays(result.failedAttempts)); } if (result.attempt.model) { - merged.set("x-ccr-fallback-model", result.attempt.model); + merged.set("x-ccr-fallback-model", sanitizeHeaderValue(result.attempt.model)); } } if (credentialIds.length) { @@ -6379,6 +6395,20 @@ function sanitizeProviderHeaderId(value: string | undefined): string | undefined return normalized || undefined; } +function sanitizeHeaderValue(value: unknown): string { + // HTTP header values must be ByteString (code point <= 255). Values derived + // from user-facing names — model selectors like "小米mimo/...", provider + // names, route reasons — can contain non-ASCII characters that crash Node's + // fetch/undici with "Cannot convert argument to a ByteString" (surfaced as + // 502). Normalize to ASCII while preserving case and printable punctuation. + const text = typeof value === "string" && value.trim() ? value : "unknown"; + const sanitized = text + .replace(/[^\x20-\x7E]+/g, "-") + .replace(/-{2,}/g, "-") + .replace(/^-+|-+$/g, ""); + return sanitized || "unknown"; +} + function providerCredentialInternalName( provider: GatewayProviderConfig, protocol: GatewayProviderProtocol, From daf745ac3f42184afd1a5f547892bba9bbfeae18 Mon Sep 17 00:00:00 2001 From: jesieleo <90036937+jesieleo@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:52:04 +0800 Subject: [PATCH 23/62] fix(codex-middleware): stop forcing sandbox_mode=workspace-write to avoid repeated COM+ setup failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When Codex runs through the ccr middleware, every command execution fails with three repeated Windows error dialogs: codex-windows-sandbox-setup.exe COM+ 注册表数据库检测到一个系统错误 (COM+ registry database system error) Root cause: the middleware's config/read handler hard-coded `sandbox_mode: "workspace-write"` in the virtual config returned to Codex (codex-cli-middleware-runtime.ts configRead). Unlike the `elevated` mode Codex reads from its own config.toml when run directly (or via tools that only swap config like cc-switch), `workspace-write` triggers `codex-windows-sandbox-setup.exe` setup refresh on every command. On systems where the COM+ catalog is unavailable (COMSysApp stopped or the catalog itself misbehaves — see openai/codex#29332), that refresh fails and surfaces as the repeated error dialogs. Empirically verified across the three legal sandbox_mode values: - read-only : does not trigger setup, but cannot write files - workspace-write : triggers setup refresh every command -> fails on affected systems (this bug) - danger-full-access : does not trigger setup, can write files, but has no isolation Forcing workspace-write therefore both overrides the user's own config.toml preference AND guarantees the failure on affected systems. Fix: omit sandbox_mode from the virtual config entirely, letting Codex read it from its own config.toml ([windows] sandbox / sandbox_mode) — matching the behavior of config-only tools like cc-switch. The user keeps full control: those who want isolation set workspace-write (and must ensure their COM+ works); those on affected systems set danger-full-access. This is a one-line removal plus an explanatory comment; configValues was already empty ({}), so Codex now falls back to its config file exactly as it does without any middleware. Refs: openai/codex#29332 --- src/main/codex-cli-middleware-runtime.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/codex-cli-middleware-runtime.ts b/src/main/codex-cli-middleware-runtime.ts index 2f7b30b..09f792b 100644 --- a/src/main/codex-cli-middleware-runtime.ts +++ b/src/main/codex-cli-middleware-runtime.ts @@ -2355,8 +2355,12 @@ function configRead(params, values) { model: agentEnv(runtimeAgent, "MODEL") || DEFAULT_MODEL, model_catalog_json: JSON.stringify(modelCatalogConfigValue()), model_provider: agentEnv(runtimeAgent, "MODEL_PROVIDER") || "claude-code", - approval_policy: "default", - sandbox_mode: "workspace-write" + approval_policy: "default" + // sandbox_mode intentionally omitted: let Codex read it from its own + // config.toml (e.g. [windows] sandbox) instead of forcing workspace-write. + // Forcing workspace-write triggers codex-windows-sandbox-setup.exe on every + // command, which fails on systems where the COM+ catalog is unavailable + // (see openai/codex#29332), surfacing as repeated error dialogs. } }; } From 528a8e0efaf54fd896af5dc2f1edd059eed2b85a Mon Sep 17 00:00:00 2001 From: musistudio Date: Sun, 5 Jul 2026 19:57:39 +0800 Subject: [PATCH 24/62] Refactor routing and provider integration --- .dockerignore | 17 + .gitignore | 6 +- Dockerfile | 54 + README.md | 2 +- README_zh.md | 2 +- build/build.mjs | 6 +- build/dev.mjs | 294 +++- build/docker-build.mjs | 37 + build/esbuild.config.mjs | 229 ++- build/test.mjs | 26 +- components.json | 2 +- docker-compose.yml | 23 + docker/README.md | 74 + docker/entrypoint.sh | 207 +++ docker/pm2.config.cjs | 35 + electron-builder.json | 2 +- package-lock.json | 1568 ++++++++++++++--- package.json | 29 +- packages/cli/LICENSE | 21 + packages/cli/README.md | 329 ++++ packages/cli/README_zh.md | 328 ++++ packages/cli/package.json | 48 + {src/main => packages/cli/src}/cli.ts | 227 ++- packages/cli/tsconfig.json | 7 + models.json => packages/core/models.json | 0 packages/core/package.json | 21 + .../core/src/agents/bot-gateway/env.ts | 4 +- .../bot-gateway/handoff-scan-service.ts | 4 +- .../agents/bot-gateway/qr-login-service.ts | 4 +- .../core/src/agents/claude-app/cdp.ts | 0 .../src/agents/claude-app/gateway-routes.ts | 4 +- .../src/agents/claude-app/gateway-service.ts | 12 +- .../core/src/agents/claude-app/launch.ts | 12 +- .../src/agents/claude-code/environment.ts | 0 .../core/src/agents/codex/app-launch.ts | 12 +- .../agents/codex/cli-middleware-runtime.ts | 0 .../core/src/agents/codex/model-catalog.ts | 6 +- .../agents/local-providers}/claude-code.ts | 4 +- .../core/src/agents/local-providers}/codex.ts | 6 +- .../src/agents/local-providers/service.ts | 12 +- .../src/agents/local-providers}/shared.ts | 2 +- .../core/src/agents/local-providers}/zcode.ts | 6 +- .../core/src/agents/zcode/profile-config.ts | 6 +- .../core/src/config}/api-key-store.ts | 6 +- .../core/src/config}/app-config-store.ts | 4 +- .../core/src/config}/config.ts | 16 +- .../core/src/config}/constants.ts | 12 +- .../core/src/config}/default-config.ts | 2 +- .../core/src/contracts}/app.ts | 0 .../core/src/contracts}/deep-link.ts | 4 +- .../core/src/contracts}/i18n.ts | 0 .../core/src/contracts}/ipc-channels.ts | 0 packages/core/src/entrypoints/server.ts | 129 ++ .../src}/gateway/claude-code-router-plugin.ts | 4 +- .../core/src}/gateway/model-catalog.ts | 2 +- .../src}/gateway/remote-control-service.ts | 0 .../core/src}/gateway/service.ts | 43 +- .../src}/mcp/browser-web-search-proxy-mcp.ts | 0 .../core/src}/mcp/fusion-tool-fallback-mcp.ts | 0 .../core/src}/mcp/fusion-vision-mcp.ts | 0 .../core/src}/mcp/network-capture-mcp.ts | 6 +- .../core/src}/mcp/tool-discovery.ts | 4 +- .../core/src/models/catalog-file.ts | 3 + .../core/src/models/pricing-service.ts | 2 +- .../src/observability}/request-log-store.ts | 10 +- .../src/platform}/windows-app-discovery.ts | 2 +- .../core/src/platform}/windows-system.ts | 0 .../core/src/plugins}/backend-service.ts | 2 +- .../core/src}/plugins/service.ts | 6 +- .../core/src/profiles/launch-core.ts | 6 +- .../core/src/profiles/launch-service.ts | 24 +- .../core/src/profiles/service.ts | 20 +- .../core/src/providers/account-service.ts | 18 +- .../core/src/providers/icons.ts | 8 +- .../core/src/providers/manifest-service.ts | 8 +- .../core/src/providers/model-catalog.ts | 8 +- .../src/providers}/presets/anthropic/index.ts | 2 +- .../src/providers}/presets/bailian/index.ts | 2 +- .../src/providers}/presets/deepseek/index.ts | 4 +- .../src/providers}/presets/gemini/index.ts | 2 +- .../core/src/providers}/presets/index.ts | 36 +- .../providers}/presets/kimi-coding/index.ts | 4 +- .../src/providers}/presets/mistral/index.ts | 4 +- .../src/providers}/presets/moonshot/index.ts | 4 +- .../src/providers}/presets/openai/index.ts | 2 +- .../providers}/presets/openrouter/index.ts | 4 +- .../src/providers}/presets/runapi/index.ts | 2 +- .../providers}/presets/siliconflow/index.ts | 4 +- .../providers}/presets/teamorouter/index.ts | 2 +- .../core/src/providers/presets/types.ts | 2 +- .../core/src/providers/presets/utils.ts | 10 +- .../presets/zai-global-coding/index.ts | 4 +- .../presets/zai-global-general/index.ts | 4 +- .../presets/zhipu-cn-coding/index.ts | 4 +- .../presets/zhipu-cn-general/index.ts | 4 +- .../core/src/providers/probe.ts | 8 +- .../core/src/providers/url.ts | 2 +- .../core/src}/proxy/certificates.ts | 2 +- .../core/src}/proxy/service.ts | 12 +- .../core/src/proxy}/system-proxy-fetch.ts | 6 +- .../core/src}/proxy/system-proxy.ts | 6 +- .../core/src/runtime}/app-paths.ts | 23 +- .../core/src/storage/migration.ts | 0 .../core/src/storage}/sqlite-native.ts | 0 .../core/src/usage/normalization.ts | 2 +- .../core/src/usage/store.ts | 64 +- .../core/src/web/management-server.ts | 56 +- packages/core/tsconfig.json | 7 + {assets => packages/electron/assets}/logo.png | Bin .../electron/assets}/tray-cyan.png | Bin .../electron/assets}/tray-orange.png | Bin .../electron/assets}/tray-violet.png | Bin {assets => packages/electron/assets}/tray.png | Bin packages/electron/package.json | 10 + .../electron/src}/main/app-menu.ts | 2 +- .../main/bot-gateway-qr-window-service.ts | 2 +- .../electron/src}/main/browser-preload.ts | 4 +- .../electron/src}/main/built-in-browser.ts | 10 +- .../electron/src}/main/deep-link.ts | 8 +- .../src}/main/electron-web-search-mcp.ts | 6 +- {src => packages/electron/src}/main/ipc.ts | 44 +- .../electron/src}/main/launch-at-login.ts | 2 +- .../electron/src}/main/main-app.ts | 12 +- {src => packages/electron/src}/main/main.ts | 31 +- .../electron/src}/main/preload.ts | 8 +- .../electron/src}/main/tray-controller.ts | 10 +- .../electron/src}/main/update-service.ts | 4 +- .../electron/src}/main/windows.ts | 2 +- packages/electron/tsconfig.json | 7 + packages/ui/package.json | 21 + .../src}/assets/agent-logos/claude-code.png | Bin .../ui/src}/assets/agent-logos/codex.png | Bin .../ui/src}/assets/agent-logos/zcode.png | Bin packages/ui/src/assets/logo.png | Bin 0 -> 1015415 bytes .../assets/onboarding/mascot-transition.svg | 0 .../src}/assets/provider-icons/anthropic.png | Bin .../ui/src}/assets/provider-icons/bailian.ico | Bin .../src}/assets/provider-icons/deepseek.ico | Bin .../ui/src}/assets/provider-icons/gemini.svg | 0 .../src}/assets/provider-icons/mistral.webp | Bin .../src}/assets/provider-icons/moonshot.ico | Bin .../ui/src}/assets/provider-icons/openai.png | Bin .../src}/assets/provider-icons/openrouter.ico | Bin .../ui/src}/assets/provider-icons/runapi.jpg | Bin .../assets/provider-icons/siliconflow.png | Bin .../assets/provider-icons/teamorouter.png | Bin .../provider-icons/zai-global-coding.svg | 0 .../provider-icons/zai-global-general.svg | 0 .../assets/provider-icons/zhipu-cn-coding.png | Bin .../provider-icons/zhipu-cn-general.png | Bin packages/ui/src/assets/tray-cyan.png | Bin 0 -> 90378 bytes packages/ui/src/assets/tray-orange.png | Bin 0 -> 96133 bytes packages/ui/src/assets/tray-violet.png | Bin 0 -> 104878 bytes .../ui/src}/components/ui/badge.tsx | 0 .../ui/src}/components/ui/button.tsx | 0 .../ui/src}/components/ui/card.tsx | 0 .../ui/src}/components/ui/checkbox.tsx | 0 .../ui/src}/components/ui/dialog.tsx | 0 .../ui/src}/components/ui/input.tsx | 0 .../ui/src}/components/ui/label.tsx | 0 .../ui/src}/components/ui/popover.tsx | 0 .../ui/src}/components/ui/select.tsx | 0 .../ui/src}/components/ui/switch.tsx | 0 .../ui/src}/components/ui/textarea.tsx | 0 .../ui/src}/lib/baseui-provider.tsx | 0 .../ui/src}/lib/usage-activity.ts | 2 +- .../renderer => packages/ui/src}/lib/utils.ts | 0 .../ui/src}/pages/browser/index.html | 0 .../ui/src}/pages/browser/main.tsx | 2 +- .../ui/src}/pages/home/App.tsx | 4 +- .../src}/pages/home/components/api-keys.tsx | 2 +- .../src}/pages/home/components/dashboard.tsx | 2 +- .../pages/home/components/dialog-stack.tsx | 2 +- .../src}/pages/home/components/extensions.tsx | 2 +- .../src}/pages/home/components/feedback.tsx | 2 +- .../ui/src}/pages/home/components/index.ts | 0 .../ui/src}/pages/home/components/layout.tsx | 2 +- .../pages/home/components/network-logs.tsx | 2 +- .../src}/pages/home/components/onboarding.tsx | 2 +- .../src}/pages/home/components/profiles.tsx | 2 +- .../src}/pages/home/components/providers.tsx | 6 +- .../ui/src}/pages/home/components/routing.tsx | 4 +- .../ui/src}/pages/home/components/server.tsx | 2 +- .../src}/pages/home/components/settings.tsx | 2 +- .../pages/home/components/share-cards.tsx | 2 +- .../ui/src}/pages/home/components/update.tsx | 2 +- .../pages/home/components/virtual-models.tsx | 2 +- .../ui/src}/pages/home/index.html | 0 .../ui/src}/pages/home/main.tsx | 0 .../ui/src}/pages/home/shared/api-keys.ts | 18 +- .../ui/src}/pages/home/shared/common.ts | 18 +- .../ui/src}/pages/home/shared/config.ts | 18 +- .../ui/src}/pages/home/shared/controls.tsx | 18 +- .../ui/src}/pages/home/shared/extensions.ts | 18 +- .../ui/src}/pages/home/shared/external.tsx | 18 +- .../ui/src}/pages/home/shared/fallbacks.ts | 4 +- .../ui/src}/pages/home/shared/i18n.tsx | 2 +- .../ui/src}/pages/home/shared/index.tsx | 2 +- .../ui/src}/pages/home/shared/logs.ts | 18 +- .../ui/src}/pages/home/shared/motion.tsx | 0 .../ui/src}/pages/home/shared/network.ts | 2 +- .../ui/src}/pages/home/shared/options.ts | 10 +- .../ui/src}/pages/home/shared/polling.ts | 0 .../ui/src}/pages/home/shared/profiles.ts | 18 +- .../pages/home/shared/provider-accounts.ts | 18 +- .../ui/src}/pages/home/shared/providers.ts | 18 +- .../ui/src}/pages/home/shared/routing.ts | 18 +- .../ui/src}/pages/home/shared/services.tsx | 18 +- .../ui/src}/pages/home/shared/types.ts | 18 +- .../ui/src}/pages/home/shared/usage.ts | 2 +- .../src}/pages/home/shared/virtual-models.ts | 18 +- .../ui/src}/pages/tray/TrayApp.tsx | 2 +- .../ui/src}/pages/tray/TrayDetailApp.tsx | 2 +- .../pages/tray/components/account-panel.tsx | 0 .../ui/src}/pages/tray/components/index.ts | 0 .../pages/tray/components/overview-panel.tsx | 0 .../pages/tray/components/source-grid.tsx | 0 .../pages/tray/components/status-strip.tsx | 0 .../pages/tray/components/usage-detail.tsx | 0 .../ui/src}/pages/tray/components/widgets.tsx | 0 .../ui/src}/pages/tray/index.html | 0 .../ui/src}/pages/tray/main.tsx | 0 .../ui/src}/pages/tray/shared.tsx | 14 +- .../ui/src}/styles/globals.css | 2 +- .../ui/src}/types/assets.d.ts | 0 .../ui/src}/types/electron.d.ts | 4 +- .../ui/src}/web-client-bridge.ts | 134 +- packages/ui/tsconfig.json | 7 + playwright.config.ts | 25 + scripts/generate-models-json.mjs | 2 +- tests/docker/docker-smoke.mjs | 267 +++ tests/e2e/cli-web.spec.ts | 230 +++ tests/main/app-paths.test.mjs | 59 + tests/main/bot-gateway-env.test.mjs | 2 +- tests/main/claude-app-cdp.test.mjs | 2 +- tests/main/claude-app-launch.test.mjs | 2 +- tests/main/claude-environment.test.mjs | 2 +- tests/main/codex-app-model-catalog.test.mjs | 2 +- .../codex-cli-middleware-runtime.test.mjs | 2 +- tests/main/codex-model-catalog.test.mjs | 2 +- tests/main/codex-patch-bridge.test.mjs | 2 +- tests/main/deep-link.test.mjs | 2 +- tests/main/gateway-virtual-models.test.mjs | 2 +- .../main/local-agent-provider-codex.test.mjs | 4 +- tests/main/model-catalog-file.test.mjs | 2 +- tests/main/profile-launch-core.test.mjs | 2 +- tests/main/profile-service.test.mjs | 6 +- tests/main/provider-preset-utils.test.mjs | 10 +- tests/main/provider-probe.test.mjs | 2 +- tests/main/provider-url.test.mjs | 2 +- tests/main/request-log-store.test.mjs | 2 +- tests/main/router-builtins.test.mjs | 4 +- tests/main/usage-normalization.test.mjs | 2 +- tests/main/usage-store.test.mjs | 34 +- tests/main/web-client-bridge-html.test.mjs | 13 + tests/renderer/components.test.tsx | 8 +- tests/renderer/fixtures.ts | 2 +- tests/renderer/overview-components.test.tsx | 6 +- tests/renderer/providers.test.ts | 4 +- tests/renderer/tray-components.test.tsx | 6 +- tests/renderer/usage-activity.test.mjs | 2 +- tests/renderer/usage-format.test.ts | 6 +- tests/renderer/virtual-models.test.ts | 4 +- tsconfig.json | 8 +- 264 files changed, 4717 insertions(+), 1008 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 build/docker-build.mjs create mode 100644 docker-compose.yml create mode 100644 docker/README.md create mode 100644 docker/entrypoint.sh create mode 100644 docker/pm2.config.cjs create mode 100644 packages/cli/LICENSE create mode 100644 packages/cli/README.md create mode 100644 packages/cli/README_zh.md create mode 100644 packages/cli/package.json rename {src/main => packages/cli/src}/cli.ts (72%) create mode 100644 packages/cli/tsconfig.json rename models.json => packages/core/models.json (100%) create mode 100644 packages/core/package.json rename src/main/bot-gateway-env.ts => packages/core/src/agents/bot-gateway/env.ts (98%) rename src/main/bot-handoff-scan-service.ts => packages/core/src/agents/bot-gateway/handoff-scan-service.ts (98%) rename src/main/bot-gateway-qr-login-service.ts => packages/core/src/agents/bot-gateway/qr-login-service.ts (99%) rename src/main/claude-app-cdp.ts => packages/core/src/agents/claude-app/cdp.ts (100%) rename src/shared/claude-app-gateway.ts => packages/core/src/agents/claude-app/gateway-routes.ts (98%) rename src/main/claude-app-gateway-service.ts => packages/core/src/agents/claude-app/gateway-service.ts (97%) rename src/main/claude-app-launch.ts => packages/core/src/agents/claude-app/launch.ts (96%) rename src/main/claude-environment.ts => packages/core/src/agents/claude-code/environment.ts (100%) rename src/main/codex-app-launch.ts => packages/core/src/agents/codex/app-launch.ts (98%) rename src/main/codex-cli-middleware-runtime.ts => packages/core/src/agents/codex/cli-middleware-runtime.ts (100%) rename src/main/codex-model-catalog.ts => packages/core/src/agents/codex/model-catalog.ts (99%) rename {src/main/local-agent-providers => packages/core/src/agents/local-providers}/claude-code.ts (98%) rename {src/main/local-agent-providers => packages/core/src/agents/local-providers}/codex.ts (99%) rename src/main/local-agent-provider-service.ts => packages/core/src/agents/local-providers/service.ts (66%) rename {src/main/local-agent-providers => packages/core/src/agents/local-providers}/shared.ts (99%) rename {src/main/local-agent-providers => packages/core/src/agents/local-providers}/zcode.ts (98%) rename src/main/zcode-profile-config.ts => packages/core/src/agents/zcode/profile-config.ts (98%) rename {src/main => packages/core/src/config}/api-key-store.ts (97%) rename {src/main => packages/core/src/config}/app-config-store.ts (98%) rename {src/main => packages/core/src/config}/config.ts (99%) rename {src/main => packages/core/src/config}/constants.ts (82%) rename {src/shared => packages/core/src/config}/default-config.ts (99%) rename {src/shared => packages/core/src/contracts}/app.ts (100%) rename {src/shared => packages/core/src/contracts}/deep-link.ts (99%) rename {src/shared => packages/core/src/contracts}/i18n.ts (100%) rename {src/shared => packages/core/src/contracts}/ipc-channels.ts (100%) create mode 100644 packages/core/src/entrypoints/server.ts rename {src/server => packages/core/src}/gateway/claude-code-router-plugin.ts (99%) rename {src/server => packages/core/src}/gateway/model-catalog.ts (99%) rename {src/server => packages/core/src}/gateway/remote-control-service.ts (100%) rename {src/server => packages/core/src}/gateway/service.ts (99%) rename {src/server => packages/core/src}/mcp/browser-web-search-proxy-mcp.ts (100%) rename {src/server => packages/core/src}/mcp/fusion-tool-fallback-mcp.ts (100%) rename {src/server => packages/core/src}/mcp/fusion-vision-mcp.ts (100%) rename {src/server => packages/core/src}/mcp/network-capture-mcp.ts (98%) rename {src/server => packages/core/src}/mcp/tool-discovery.ts (99%) rename src/main/model-catalog-file.ts => packages/core/src/models/catalog-file.ts (87%) rename src/main/model-pricing-service.ts => packages/core/src/models/pricing-service.ts (99%) rename {src/main => packages/core/src/observability}/request-log-store.ts (99%) rename {src/main => packages/core/src/platform}/windows-app-discovery.ts (99%) rename {src/main => packages/core/src/platform}/windows-system.ts (100%) rename {src/server => packages/core/src/plugins}/backend-service.ts (99%) rename {src/main => packages/core/src}/plugins/service.ts (99%) rename src/main/profile-launch-core.ts => packages/core/src/profiles/launch-core.ts (98%) rename src/main/profile-launch-service.ts => packages/core/src/profiles/launch-service.ts (98%) rename src/main/profile-service.ts => packages/core/src/profiles/service.ts (98%) rename src/main/provider-account-service.ts => packages/core/src/providers/account-service.ts (99%) rename src/main/provider-icons.ts => packages/core/src/providers/icons.ts (97%) rename src/main/provider-manifest-service.ts => packages/core/src/providers/manifest-service.ts (97%) rename src/main/provider-model-catalog.ts => packages/core/src/providers/model-catalog.ts (98%) rename {src/main => packages/core/src/providers}/presets/anthropic/index.ts (93%) rename {src/main => packages/core/src/providers}/presets/bailian/index.ts (92%) rename {src/main => packages/core/src/providers}/presets/deepseek/index.ts (90%) rename {src/main => packages/core/src/providers}/presets/gemini/index.ts (93%) rename {src/main => packages/core/src/providers}/presets/index.ts (59%) rename {src/main => packages/core/src/providers}/presets/kimi-coding/index.ts (87%) rename {src/main => packages/core/src/providers}/presets/mistral/index.ts (87%) rename {src/main => packages/core/src/providers}/presets/moonshot/index.ts (95%) rename {src/main => packages/core/src/providers}/presets/openai/index.ts (93%) rename {src/main => packages/core/src/providers}/presets/openrouter/index.ts (90%) rename {src/main => packages/core/src/providers}/presets/runapi/index.ts (91%) rename {src/main => packages/core/src/providers}/presets/siliconflow/index.ts (89%) rename {src/main => packages/core/src/providers}/presets/teamorouter/index.ts (92%) rename src/shared/provider-presets.ts => packages/core/src/providers/presets/types.ts (97%) rename src/shared/provider-preset-utils.ts => packages/core/src/providers/presets/utils.ts (96%) rename {src/main => packages/core/src/providers}/presets/zai-global-coding/index.ts (94%) rename {src/main => packages/core/src/providers}/presets/zai-global-general/index.ts (94%) rename {src/main => packages/core/src/providers}/presets/zhipu-cn-coding/index.ts (94%) rename {src/main => packages/core/src/providers}/presets/zhipu-cn-general/index.ts (94%) rename src/main/provider-probe.ts => packages/core/src/providers/probe.ts (99%) rename src/shared/provider-url.ts => packages/core/src/providers/url.ts (98%) rename {src/server => packages/core/src}/proxy/certificates.ts (99%) rename {src/server => packages/core/src}/proxy/service.ts (99%) rename {src/main => packages/core/src/proxy}/system-proxy-fetch.ts (97%) rename {src/server => packages/core/src}/proxy/system-proxy.ts (99%) rename {src/main => packages/core/src/runtime}/app-paths.ts (78%) rename src/main/storage-migration.ts => packages/core/src/storage/migration.ts (100%) rename {src/main => packages/core/src/storage}/sqlite-native.ts (100%) rename src/main/usage-normalization.ts => packages/core/src/usage/normalization.ts (97%) rename src/main/usage-store.ts => packages/core/src/usage/store.ts (93%) rename src/main/web-management-server.ts => packages/core/src/web/management-server.ts (95%) create mode 100644 packages/core/tsconfig.json rename {assets => packages/electron/assets}/logo.png (100%) rename {assets => packages/electron/assets}/tray-cyan.png (100%) rename {assets => packages/electron/assets}/tray-orange.png (100%) rename {assets => packages/electron/assets}/tray-violet.png (100%) rename {assets => packages/electron/assets}/tray.png (100%) create mode 100644 packages/electron/package.json rename {src => packages/electron/src}/main/app-menu.ts (98%) rename {src => packages/electron/src}/main/bot-gateway-qr-window-service.ts (98%) rename {src => packages/electron/src}/main/browser-preload.ts (91%) rename {src => packages/electron/src}/main/built-in-browser.ts (98%) rename {src => packages/electron/src}/main/deep-link.ts (88%) rename {src => packages/electron/src}/main/electron-web-search-mcp.ts (99%) rename {src => packages/electron/src}/main/ipc.ts (97%) rename {src => packages/electron/src}/main/launch-at-login.ts (92%) rename {src => packages/electron/src}/main/main-app.ts (95%) rename {src => packages/electron/src}/main/main.ts (72%) rename {src => packages/electron/src}/main/preload.ts (98%) rename {src => packages/electron/src}/main/tray-controller.ts (98%) rename {src => packages/electron/src}/main/update-service.ts (98%) rename {src => packages/electron/src}/main/windows.ts (99%) create mode 100644 packages/electron/tsconfig.json create mode 100644 packages/ui/package.json rename {src/renderer => packages/ui/src}/assets/agent-logos/claude-code.png (100%) rename {src/renderer => packages/ui/src}/assets/agent-logos/codex.png (100%) rename {src/renderer => packages/ui/src}/assets/agent-logos/zcode.png (100%) create mode 100644 packages/ui/src/assets/logo.png rename {src/renderer => packages/ui/src}/assets/onboarding/mascot-transition.svg (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/anthropic.png (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/bailian.ico (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/deepseek.ico (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/gemini.svg (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/mistral.webp (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/moonshot.ico (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/openai.png (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/openrouter.ico (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/runapi.jpg (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/siliconflow.png (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/teamorouter.png (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/zai-global-coding.svg (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/zai-global-general.svg (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/zhipu-cn-coding.png (100%) rename {src/renderer => packages/ui/src}/assets/provider-icons/zhipu-cn-general.png (100%) create mode 100644 packages/ui/src/assets/tray-cyan.png create mode 100644 packages/ui/src/assets/tray-orange.png create mode 100644 packages/ui/src/assets/tray-violet.png rename {src/renderer => packages/ui/src}/components/ui/badge.tsx (100%) rename {src/renderer => packages/ui/src}/components/ui/button.tsx (100%) rename {src/renderer => packages/ui/src}/components/ui/card.tsx (100%) rename {src/renderer => packages/ui/src}/components/ui/checkbox.tsx (100%) rename {src/renderer => packages/ui/src}/components/ui/dialog.tsx (100%) rename {src/renderer => packages/ui/src}/components/ui/input.tsx (100%) rename {src/renderer => packages/ui/src}/components/ui/label.tsx (100%) rename {src/renderer => packages/ui/src}/components/ui/popover.tsx (100%) rename {src/renderer => packages/ui/src}/components/ui/select.tsx (100%) rename {src/renderer => packages/ui/src}/components/ui/switch.tsx (100%) rename {src/renderer => packages/ui/src}/components/ui/textarea.tsx (100%) rename {src/renderer => packages/ui/src}/lib/baseui-provider.tsx (100%) rename {src/renderer => packages/ui/src}/lib/usage-activity.ts (99%) rename {src/renderer => packages/ui/src}/lib/utils.ts (100%) rename {src/renderer => packages/ui/src}/pages/browser/index.html (100%) rename {src/renderer => packages/ui/src}/pages/browser/main.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/App.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/api-keys.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/dashboard.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/dialog-stack.tsx (98%) rename {src/renderer => packages/ui/src}/pages/home/components/extensions.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/feedback.tsx (97%) rename {src/renderer => packages/ui/src}/pages/home/components/index.ts (100%) rename {src/renderer => packages/ui/src}/pages/home/components/layout.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/network-logs.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/onboarding.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/profiles.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/providers.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/routing.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/server.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/settings.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/share-cards.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/update.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/components/virtual-models.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/index.html (100%) rename {src/renderer => packages/ui/src}/pages/home/main.tsx (100%) rename {src/renderer => packages/ui/src}/pages/home/shared/api-keys.ts (97%) rename {src/renderer => packages/ui/src}/pages/home/shared/common.ts (98%) rename {src/renderer => packages/ui/src}/pages/home/shared/config.ts (96%) rename {src/renderer => packages/ui/src}/pages/home/shared/controls.tsx (98%) rename {src/renderer => packages/ui/src}/pages/home/shared/extensions.ts (98%) rename {src/renderer => packages/ui/src}/pages/home/shared/external.tsx (97%) rename {src/renderer => packages/ui/src}/pages/home/shared/fallbacks.ts (94%) rename {src/renderer => packages/ui/src}/pages/home/shared/i18n.tsx (99%) rename {src/renderer => packages/ui/src}/pages/home/shared/index.tsx (90%) rename {src/renderer => packages/ui/src}/pages/home/shared/logs.ts (99%) rename {src/renderer => packages/ui/src}/pages/home/shared/motion.tsx (100%) rename {src/renderer => packages/ui/src}/pages/home/shared/network.ts (98%) rename {src/renderer => packages/ui/src}/pages/home/shared/options.ts (98%) rename {src/renderer => packages/ui/src}/pages/home/shared/polling.ts (100%) rename {src/renderer => packages/ui/src}/pages/home/shared/profiles.ts (99%) rename {src/renderer => packages/ui/src}/pages/home/shared/provider-accounts.ts (97%) rename {src/renderer => packages/ui/src}/pages/home/shared/providers.ts (99%) rename {src/renderer => packages/ui/src}/pages/home/shared/routing.ts (98%) rename {src/renderer => packages/ui/src}/pages/home/shared/services.tsx (97%) rename {src/renderer => packages/ui/src}/pages/home/shared/types.ts (97%) rename {src/renderer => packages/ui/src}/pages/home/shared/usage.ts (99%) rename {src/renderer => packages/ui/src}/pages/home/shared/virtual-models.ts (99%) rename {src/renderer => packages/ui/src}/pages/tray/TrayApp.tsx (99%) rename {src/renderer => packages/ui/src}/pages/tray/TrayDetailApp.tsx (99%) rename {src/renderer => packages/ui/src}/pages/tray/components/account-panel.tsx (100%) rename {src/renderer => packages/ui/src}/pages/tray/components/index.ts (100%) rename {src/renderer => packages/ui/src}/pages/tray/components/overview-panel.tsx (100%) rename {src/renderer => packages/ui/src}/pages/tray/components/source-grid.tsx (100%) rename {src/renderer => packages/ui/src}/pages/tray/components/status-strip.tsx (100%) rename {src/renderer => packages/ui/src}/pages/tray/components/usage-detail.tsx (100%) rename {src/renderer => packages/ui/src}/pages/tray/components/widgets.tsx (100%) rename {src/renderer => packages/ui/src}/pages/tray/index.html (100%) rename {src/renderer => packages/ui/src}/pages/tray/main.tsx (100%) rename {src/renderer => packages/ui/src}/pages/tray/shared.tsx (98%) rename {src/renderer => packages/ui/src}/styles/globals.css (99%) rename {src/renderer => packages/ui/src}/types/assets.d.ts (100%) rename {src/renderer => packages/ui/src}/types/electron.d.ts (98%) rename {src/main => packages/ui/src}/web-client-bridge.ts (53%) create mode 100644 packages/ui/tsconfig.json create mode 100644 playwright.config.ts create mode 100644 tests/docker/docker-smoke.mjs create mode 100644 tests/e2e/cli-web.spec.ts create mode 100644 tests/main/app-paths.test.mjs create mode 100644 tests/main/web-client-bridge-html.test.mjs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..86b7318 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +.git +.DS_Store +.env +.env.* +node_modules +dist +packages/*/dist +release +release-local +logs +tmp +test-results +playwright-report +blob-report +docs/node_modules +docs/dist +docs/.astro diff --git a/.gitignore b/.gitignore index 291dafe..6efc5ae 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,8 @@ release tmp release-local logs -.opencat \ No newline at end of file +.opencat +test-results +playwright-report +blob-report +.tmp \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..147c7ce --- /dev/null +++ b/Dockerfile @@ -0,0 +1,54 @@ +ARG NODE_IMAGE=node:22-bookworm + +FROM ${NODE_IMAGE} AS build +WORKDIR /app + +COPY package.json package-lock.json ./ +COPY packages/cli/package.json packages/cli/package.json +COPY packages/core/package.json packages/core/package.json +COPY packages/electron/package.json packages/electron/package.json +COPY packages/ui/package.json packages/ui/package.json +RUN npm ci + +COPY . . +RUN npm run build:docker + +FROM ${NODE_IMAGE} AS runtime +ENV NODE_ENV=production \ + CCR_DATA_DIR=/data \ + CCR_WEB_HOST=127.0.0.1 \ + CCR_WEB_PORT=3459 \ + CCR_NGINX_PORT=8080 \ + CCR_GATEWAY_HOST=127.0.0.1 \ + CCR_GATEWAY_PORT=3456 \ + CCR_GATEWAY_CORE_PORT=3457 \ + CCR_PUBLIC_HOST=127.0.0.1 \ + CCR_PUBLIC_PORT=3458 + +WORKDIR /app + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates nginx \ + && rm -rf /var/lib/apt/lists/* \ + && rm -f /etc/nginx/sites-enabled/default /etc/nginx/conf.d/default.conf + +COPY package.json package-lock.json ./ +COPY packages/core/package.json packages/core/package.json +RUN npm ci --omit=dev --workspace=@claude-code-router/core --include-workspace-root=false \ + && npm cache clean --force + +COPY --from=build /app/packages/core/dist packages/core/dist +COPY --from=build /app/packages/ui/dist/renderer /usr/share/nginx/html +COPY docker/entrypoint.sh /usr/local/bin/ccr-docker-entrypoint +COPY docker/pm2.config.cjs docker/pm2.config.cjs + +RUN chmod +x /usr/local/bin/ccr-docker-entrypoint \ + && mkdir -p /data /run/nginx /var/lib/nginx /var/log/nginx + +VOLUME ["/data"] +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD node -e "fetch('http://127.0.0.1:' + (process.env.CCR_NGINX_PORT || '8080') + '/').then((r) => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))" + +ENTRYPOINT ["ccr-docker-entrypoint"] diff --git a/README.md b/README.md index 3f5ce1e..89ee179 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Claude Code Router Desktop

+

Claude Code Router

Chinese README diff --git a/README_zh.md b/README_zh.md index cd79161..dc0b436 100644 --- a/README_zh.md +++ b/README_zh.md @@ -1,4 +1,4 @@ -

Claude Code Router Desktop

+

Claude Code Router

English README diff --git a/build/build.mjs b/build/build.mjs index a07412b..ba43565 100644 --- a/build/build.mjs +++ b/build/build.mjs @@ -1,4 +1,4 @@ -import { buildBrowserRenderer, buildMain, buildRenderer, buildStyles, buildTrayRenderer, buildWebClientBridge, cleanDist, copyAppAssets, copyBrowserRendererHtml, copyMarketplacePlugins, copyModelCatalog, copyRendererHtml, copyTrayRendererHtml } from "./esbuild.config.mjs"; +import { buildBrowserRenderer, buildMain, buildRenderer, buildStyles, buildTrayRenderer, buildWebClientBridge, cleanDist, copyAppAssets, copyBrowserRendererHtml, copyMarketplacePlugins, copyModelCatalog, copyRendererHtml, copyTrayRendererHtml, syncUiRendererToRuntimeDists } from "./esbuild.config.mjs"; const mode = process.argv.includes("--dev") ? "development" : "production"; @@ -19,4 +19,6 @@ await Promise.all([ buildStyles({ minify: mode === "production" }) ]); -console.log(`Built Electron app assets in ${mode} mode.`); +syncUiRendererToRuntimeDists(); + +console.log(`Built monorepo package assets in ${mode} mode.`); diff --git a/build/dev.mjs b/build/dev.mjs index a7cf8fd..b91a341 100644 --- a/build/dev.mjs +++ b/build/dev.mjs @@ -5,12 +5,14 @@ import { spawn } from "node:child_process"; import { existsSync, readdirSync, readFileSync, statSync, watch } from "node:fs"; import path from "node:path"; import { - binPath, buildStyles, cleanDist, browserRendererHtmlInput, + cliSourceRoot, + coreSourceRoot, copyAppAssets, copyBrowserRendererHtml, + copyCliRuntimeToElectronDist, copyMarketplacePlugins, copyModelCatalog, copyRendererHtml, @@ -21,12 +23,12 @@ import { createRendererBuildOptions, createTrayRendererBuildOptions, createWebClientBridgeBuildOptions, - cssInput, - cssOutput, appAssetsInput, modelCatalogInput, projectRoot, + rendererRoot, rendererHtmlInput, + syncUiRendererToRuntimeDists, trayRendererHtmlInput, watchPlugin } from "./esbuild.config.mjs"; @@ -37,7 +39,12 @@ let pendingRestartReasons = []; const watchSignatures = new Map(); let shuttingDown = false; const restartDelayMs = 160; +const styleBuildDelayMs = 160; +const stylePollIntervalMs = 1000; const ignoredSignatureEntries = new Set([".DS_Store"]); +let styleBuildTimer = null; +let styleBuildInFlight = false; +let queuedStyleBuildReason = null; const ready = { browser: false, cli: false, @@ -46,6 +53,32 @@ const ready = { tray: false, webBridge: false }; +const devTarget = parseDevTarget(process.argv.slice(2)); +const enabled = { + cli: devTarget === "cli" || devTarget === "electron", + electron: devTarget === "electron", + ui: true +}; +const coreSharedSourceRoot = path.join(coreSourceRoot, "shared"); +const styleWatchRoots = [rendererRoot, coreSharedSourceRoot].filter((watchRoot) => existsSync(watchRoot)); +const activeReadyNames = new Set([ + ...(enabled.ui ? ["browser", "renderer", "tray", "webBridge"] : []), + ...(enabled.cli ? ["cli"] : []), + ...(enabled.electron ? ["main"] : []) +]); + +function parseDevTarget(args) { + const target = args[0] ?? "electron"; + if (target === "--help" || target === "-h") { + console.log("Usage: node build/dev.mjs [ui|cli|electron]"); + process.exit(0); + } + if (target === "ui" || target === "cli" || target === "electron") { + return target; + } + console.error(`Unknown dev target "${target}". Expected ui, cli, or electron.`); + process.exit(2); +} function logDev(message) { console.log(`[dev] ${new Date().toISOString()} ${message}`); @@ -57,6 +90,7 @@ function relativePath(file) { function readyState() { return Object.entries(ready) + .filter(([name]) => activeReadyNames.has(name)) .map(([name, value]) => `${name}:${value ? "ready" : "pending"}`) .join(" "); } @@ -163,7 +197,63 @@ function handleWatchedInput(label, watchedPath, eventType, filename, options, on } onChange(); - scheduleRestart(reason); + if (enabled.electron && options?.restart !== false) { + scheduleRestart(reason); + } +} + +function scheduleStyleBuild(reason) { + queuedStyleBuildReason = reason; + if (styleBuildTimer) { + clearTimeout(styleBuildTimer); + } + styleBuildTimer = setTimeout(() => { + styleBuildTimer = null; + void rebuildStyles(queuedStyleBuildReason ?? reason); + }, styleBuildDelayMs); +} + +async function rebuildStyles(reason) { + if (styleBuildInFlight) { + queuedStyleBuildReason = reason; + return; + } + + styleBuildInFlight = true; + queuedStyleBuildReason = null; + try { + logDev(`rebuilding styles: ${reason}`); + await buildStyles({ minify: false }); + syncUiRendererToRuntimeDists(); + if (enabled.electron) { + scheduleRestart(`styles rebuilt: ${reason}`); + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logDev(`style rebuild failed: ${message}`); + } finally { + styleBuildInFlight = false; + if (queuedStyleBuildReason) { + const queuedReason = queuedStyleBuildReason; + queuedStyleBuildReason = null; + scheduleStyleBuild(queuedReason); + } + } +} + +function pollStyleWatchRoots() { + for (const styleWatchRoot of styleWatchRoots) { + const label = `styles ${relativePath(styleWatchRoot)}`; + const signature = contentSignature(styleWatchRoot); + const previousSignature = watchSignatures.get(label); + if (previousSignature === signature.key) { + continue; + } + + watchSignatures.set(label, signature.key); + logDev(`watch event: ${label}; ${signature.summary}; content=changed`); + scheduleStyleBuild(label); + } } function markReady(name, reason = `${name} esbuild completed`) { @@ -171,7 +261,7 @@ function markReady(name, reason = `${name} esbuild completed`) { ready[name] = true; } logDev(`build ready: ${reason}; ${readyState()}`); - if (ready.browser && ready.cli && ready.main && ready.renderer && ready.tray && ready.webBridge) { + if (enabled.electron && Array.from(activeReadyNames).every((readyName) => ready[readyName])) { scheduleRestart(reason); } } @@ -221,114 +311,153 @@ function restartElectron() { }); } -logDev("starting dev build"); +logDev(`starting dev build target=${devTarget} ui=${enabled.ui ? "on" : "off"} cli=${enabled.cli ? "on" : "off"} electron=${enabled.electron ? "on" : "off"}`); cleanDist(); -copyAppAssets(); -copyMarketplacePlugins(); -copyModelCatalog(); +if (enabled.electron) { + copyAppAssets(); +} +if (enabled.cli || enabled.electron) { + copyMarketplacePlugins(); + copyModelCatalog(); +} copyBrowserRendererHtml(); copyRendererHtml(); copyTrayRendererHtml(); await buildStyles({ minify: false }); - -const tailwindProcess = spawn(binPath("tailwindcss"), ["-i", cssInput, "-o", cssOutput, "--watch"], { - cwd: projectRoot, - stdio: "inherit", - shell: process.platform === "win32" -}); -logDev(`Tailwind watcher started pid=${tailwindProcess.pid ?? "unknown"} input=${relativePath(cssInput)} output=${relativePath(cssOutput)}`); -tailwindProcess.on("exit", (code, signal) => { - logDev(`Tailwind watcher exited code=${code ?? "null"} signal=${signal ?? "null"}`); -}); +syncUiRendererToRuntimeDists(); rememberWatchSignature("home html", rendererHtmlInput); rememberWatchSignature("browser html", browserRendererHtmlInput); rememberWatchSignature("tray html", trayRendererHtmlInput); -rememberWatchSignature("app assets", appAssetsInput); -if (existsSync(modelCatalogInput)) { +for (const styleWatchRoot of styleWatchRoots) { + rememberWatchSignature(`styles ${relativePath(styleWatchRoot)}`, styleWatchRoot); +} +if (enabled.electron) { + rememberWatchSignature("app assets", appAssetsInput); +} +if ((enabled.cli || enabled.electron) && existsSync(modelCatalogInput)) { rememberWatchSignature("model catalog", modelCatalogInput); } const htmlWatcher = watch(rendererHtmlInput, { persistent: true }, (eventType, filename) => { - handleWatchedInput("home html", rendererHtmlInput, eventType, filename, undefined, copyRendererHtml); + handleWatchedInput("home html", rendererHtmlInput, eventType, filename, undefined, () => { + copyRendererHtml(); + syncUiRendererToRuntimeDists(); + }); }); const browserHtmlWatcher = watch(browserRendererHtmlInput, { persistent: true }, (eventType, filename) => { - handleWatchedInput("browser html", browserRendererHtmlInput, eventType, filename, undefined, copyBrowserRendererHtml); + handleWatchedInput("browser html", browserRendererHtmlInput, eventType, filename, undefined, () => { + copyBrowserRendererHtml(); + syncUiRendererToRuntimeDists(); + }); }); const trayHtmlWatcher = watch(trayRendererHtmlInput, { persistent: true }, (eventType, filename) => { - handleWatchedInput("tray html", trayRendererHtmlInput, eventType, filename, undefined, copyTrayRendererHtml); + handleWatchedInput("tray html", trayRendererHtmlInput, eventType, filename, undefined, () => { + copyTrayRendererHtml(); + syncUiRendererToRuntimeDists(); + }); }); -const appAssetsWatcher = watch(appAssetsInput, { persistent: true }, (eventType, filename) => { - handleWatchedInput("app assets", appAssetsInput, eventType, filename, { isDirectory: true }, copyAppAssets); -}); +const stylePoller = setInterval(pollStyleWatchRoots, stylePollIntervalMs); -const modelCatalogWatcher = existsSync(modelCatalogInput) +const appAssetsWatcher = enabled.electron + ? watch(appAssetsInput, { persistent: true }, (eventType, filename) => { + handleWatchedInput("app assets", appAssetsInput, eventType, filename, { isDirectory: true }, copyAppAssets); + }) + : { close: () => undefined }; + +const modelCatalogWatcher = (enabled.cli || enabled.electron) && existsSync(modelCatalogInput) ? watch(modelCatalogInput, { persistent: true }, (eventType, filename) => { handleWatchedInput("model catalog", modelCatalogInput, eventType, filename, undefined, copyModelCatalog); }) : { close: () => undefined }; -const mainContext = await esbuild.context( - createMainBuildOptions({ - mode: "development", - plugins: [watchPlugin("main", (name) => markReady(name))] - }) -); +const contexts = []; -const cliContext = await esbuild.context( - createCliBuildOptions({ - mode: "development", - plugins: [watchPlugin("cli", (name) => markReady(name))] - }) -); - -const rendererContext = await esbuild.context( - createRendererBuildOptions({ - mode: "development", - plugins: [ - watchPlugin("renderer", (name) => { - copyRendererHtml(); - markReady(name); +if (enabled.electron) { + contexts.push( + await esbuild.context( + createMainBuildOptions({ + mode: "development", + plugins: [watchPlugin("main", (name) => markReady(name))] }) - ] - }) -); + ) + ); +} -const trayRendererContext = await esbuild.context( - createTrayRendererBuildOptions({ - mode: "development", - plugins: [ - watchPlugin("tray", (name) => { - copyTrayRendererHtml(); - markReady(name); +if (enabled.cli) { + contexts.push( + await esbuild.context( + createCliBuildOptions({ + mode: "development", + plugins: [ + watchPlugin("cli", (name) => { + if (enabled.electron) { + copyCliRuntimeToElectronDist(); + } + markReady(name); + }) + ] }) - ] - }) -); + ) + ); +} -const browserRendererContext = await esbuild.context( - createBrowserRendererBuildOptions({ - mode: "development", - plugins: [ - watchPlugin("browser", (name) => { - copyBrowserRendererHtml(); - markReady(name); +if (enabled.ui) { + contexts.push( + await esbuild.context( + createRendererBuildOptions({ + mode: "development", + plugins: [ + watchPlugin("renderer", (name) => { + copyRendererHtml(); + syncUiRendererToRuntimeDists(); + markReady(name); + }) + ] }) - ] - }) -); + ), + await esbuild.context( + createTrayRendererBuildOptions({ + mode: "development", + plugins: [ + watchPlugin("tray", (name) => { + copyTrayRendererHtml(); + syncUiRendererToRuntimeDists(); + markReady(name); + }) + ] + }) + ), + await esbuild.context( + createBrowserRendererBuildOptions({ + mode: "development", + plugins: [ + watchPlugin("browser", (name) => { + copyBrowserRendererHtml(); + syncUiRendererToRuntimeDists(); + markReady(name); + }) + ] + }) + ), + await esbuild.context( + createWebClientBridgeBuildOptions({ + mode: "development", + plugins: [ + watchPlugin("webBridge", (name) => { + syncUiRendererToRuntimeDists(); + markReady(name); + }) + ] + }) + ) + ); +} -const webClientBridgeContext = await esbuild.context( - createWebClientBridgeBuildOptions({ - mode: "development", - plugins: [watchPlugin("webBridge", (name) => markReady(name))] - }) -); - -await Promise.all([mainContext.watch(), cliContext.watch(), rendererContext.watch(), trayRendererContext.watch(), browserRendererContext.watch(), webClientBridgeContext.watch()]); +await Promise.all(contexts.map((context) => context.watch())); logDev("watchers are active"); async function shutdown() { @@ -340,13 +469,16 @@ async function shutdown() { if (electronProcess) { electronProcess.kill(); } - tailwindProcess.kill(); + if (styleBuildTimer) { + clearTimeout(styleBuildTimer); + } htmlWatcher.close(); browserHtmlWatcher.close(); trayHtmlWatcher.close(); + clearInterval(stylePoller); appAssetsWatcher.close(); modelCatalogWatcher.close(); - await Promise.all([mainContext.dispose(), cliContext.dispose(), rendererContext.dispose(), trayRendererContext.dispose(), browserRendererContext.dispose(), webClientBridgeContext.dispose()]); + await Promise.all(contexts.map((context) => context.dispose())); process.exit(0); } diff --git a/build/docker-build.mjs b/build/docker-build.mjs new file mode 100644 index 0000000..75d4ed1 --- /dev/null +++ b/build/docker-build.mjs @@ -0,0 +1,37 @@ +import { + buildBrowserRenderer, + buildCoreServer, + buildRenderer, + buildStyles, + buildTrayRenderer, + buildWebClientBridge, + cleanDist, + copyBrowserRendererHtml, + copyMarketplacePlugins, + copyModelCatalog, + copyRendererHtml, + copyTrayRendererHtml, + syncUiRendererToRuntimeDists +} from "./esbuild.config.mjs"; + +const mode = process.argv.includes("--dev") ? "development" : "production"; + +cleanDist(); +copyMarketplacePlugins(); +copyModelCatalog(); +copyBrowserRendererHtml(); +copyRendererHtml(); +copyTrayRendererHtml(); + +await Promise.all([ + buildCoreServer({ mode }), + buildBrowserRenderer({ mode }), + buildRenderer({ mode }), + buildTrayRenderer({ mode }), + buildWebClientBridge({ mode }), + buildStyles({ minify: mode === "production" }) +]); + +syncUiRendererToRuntimeDists(); + +console.log(`Built Docker core server and UI assets in ${mode} mode.`); diff --git a/build/esbuild.config.mjs b/build/esbuild.config.mjs index bcf3a1a..86ad8b0 100644 --- a/build/esbuild.config.mjs +++ b/build/esbuild.config.mjs @@ -8,16 +8,43 @@ import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); export const projectRoot = path.resolve(__dirname, ".."); -export const distDir = path.join(projectRoot, "dist"); -export const mainOutDir = path.join(distDir, "main"); -export const rendererOutDir = path.join(distDir, "renderer"); -export const appAssetsDir = path.join(distDir, "assets"); +export const packagesRoot = path.join(projectRoot, "packages"); +export const cliRoot = path.join(packagesRoot, "cli"); +export const coreRoot = path.join(packagesRoot, "core"); +export const electronRoot = path.join(packagesRoot, "electron"); +export const uiRoot = path.join(packagesRoot, "ui"); +export const cliSourceRoot = path.join(cliRoot, "src"); +export const coreSourceRoot = path.join(coreRoot, "src"); +export const electronSourceRoot = path.join(electronRoot, "src"); +export const uiSourceRoot = path.join(uiRoot, "src"); +export const legacyDistDir = path.join(projectRoot, "dist"); +export const cliDistDir = path.join(cliRoot, "dist"); +export const coreDistDir = path.join(coreRoot, "dist"); +export const electronDistDir = path.join(electronRoot, "dist"); +export const uiDistDir = path.join(uiRoot, "dist"); +export const distDir = electronDistDir; +export const cliMainOutDir = path.join(cliDistDir, "main"); +export const coreMainOutDir = path.join(coreDistDir, "main"); +export const electronMainOutDir = path.join(electronDistDir, "main"); +export const mainOutDir = electronMainOutDir; +export const rendererOutDir = path.join(uiDistDir, "renderer"); +export const cliRendererOutDir = path.join(cliDistDir, "renderer"); +export const coreRendererOutDir = path.join(coreDistDir, "renderer"); +export const electronRendererOutDir = path.join(electronDistDir, "renderer"); +export const runtimeRendererOutDirs = [cliRendererOutDir, coreRendererOutDir, electronRendererOutDir]; +export const appAssetsDir = path.join(electronDistDir, "assets"); export const rendererAssetsDir = path.join(rendererOutDir, "assets"); -export const marketplacePluginsDir = path.join(distDir, "marketplace", "plugins"); -export const appAssetsInput = path.join(projectRoot, "assets"); -export const modelCatalogInput = path.join(projectRoot, "models.json"); -export const modelCatalogOutput = path.join(distDir, "models.json"); -export const rendererRoot = path.join(projectRoot, "src", "renderer"); +export const cliMarketplacePluginsDir = path.join(cliDistDir, "marketplace", "plugins"); +export const coreMarketplacePluginsDir = path.join(coreDistDir, "marketplace", "plugins"); +export const electronMarketplacePluginsDir = path.join(electronDistDir, "marketplace", "plugins"); +export const marketplacePluginsDir = electronMarketplacePluginsDir; +export const appAssetsInput = path.join(electronRoot, "assets"); +export const modelCatalogInput = path.join(coreRoot, "models.json"); +export const cliModelCatalogOutput = path.join(cliDistDir, "models.json"); +export const coreModelCatalogOutput = path.join(coreDistDir, "models.json"); +export const electronModelCatalogOutput = path.join(electronDistDir, "models.json"); +export const modelCatalogOutput = electronModelCatalogOutput; +export const rendererRoot = uiSourceRoot; export const rendererHtmlInput = path.join(rendererRoot, "pages", "home", "index.html"); export const rendererHtmlOutput = path.join(rendererOutDir, "pages", "home", "index.html"); export const browserRendererHtmlInput = path.join(rendererRoot, "pages", "browser", "index.html"); @@ -30,8 +57,10 @@ export const webClientBridgeOutput = path.join(rendererAssetsDir, "web-client-br const lightweightMcpBundleNames = ["browser-web-search-proxy-mcp.js", "fusion-vision-mcp.js", "fusion-tool-fallback-mcp.js"]; const lightweightMcpBundleMaxBytes = 128 * 1024; const forbiddenLightweightMcpInputs = [ - { prefix: "src/main/", reason: "main-process modules can pull in config, Electron, or native storage side effects" }, - { prefix: "src/renderer/", reason: "renderer modules do not belong in stdio MCP subprocesses" }, + { prefix: "packages/core/src/config/", reason: "config modules can pull in native storage side effects" }, + { prefix: "packages/core/src/storage/", reason: "native SQLite storage is not allowed in lightweight MCP subprocesses" }, + { prefix: "packages/electron/src/", reason: "Electron runtime modules are not allowed in lightweight MCP subprocesses" }, + { prefix: "packages/ui/src/", reason: "UI modules do not belong in stdio MCP subprocesses" }, { prefix: "node_modules/better-sqlite3/", reason: "native SQLite is not allowed in lightweight MCP subprocesses" }, { prefix: "node_modules/electron/", reason: "Electron runtime modules are not allowed in lightweight MCP subprocesses" } ]; @@ -45,15 +74,26 @@ const nodeExternals = [ ]; export function cleanDist() { - rmSync(distDir, { force: true, recursive: true }); + rmSync(legacyDistDir, { force: true, recursive: true }); + rmSync(cliDistDir, { force: true, recursive: true }); + rmSync(coreDistDir, { force: true, recursive: true }); + rmSync(electronDistDir, { force: true, recursive: true }); + rmSync(uiDistDir, { force: true, recursive: true }); ensureDist(); } export function ensureDist() { - mkdirSync(mainOutDir, { recursive: true }); + mkdirSync(cliMainOutDir, { recursive: true }); + mkdirSync(coreMainOutDir, { recursive: true }); + mkdirSync(electronMainOutDir, { recursive: true }); mkdirSync(appAssetsDir, { recursive: true }); - mkdirSync(marketplacePluginsDir, { recursive: true }); + mkdirSync(cliMarketplacePluginsDir, { recursive: true }); + mkdirSync(coreMarketplacePluginsDir, { recursive: true }); + mkdirSync(electronMarketplacePluginsDir, { recursive: true }); mkdirSync(rendererAssetsDir, { recursive: true }); + for (const outputDir of runtimeRendererOutDirs) { + mkdirSync(path.join(outputDir, "assets"), { recursive: true }); + } mkdirSync(path.dirname(rendererHtmlOutput), { recursive: true }); mkdirSync(path.dirname(browserRendererHtmlOutput), { recursive: true }); mkdirSync(path.dirname(trayRendererHtmlOutput), { recursive: true }); @@ -69,12 +109,16 @@ export function copyAppAssets() { export function copyModelCatalog() { ensureDist(); if (existsSync(modelCatalogInput)) { - cpSync(modelCatalogInput, modelCatalogOutput); + cpSync(modelCatalogInput, cliModelCatalogOutput); + cpSync(modelCatalogInput, coreModelCatalogOutput); + cpSync(modelCatalogInput, electronModelCatalogOutput); } } export function copyRendererHtml() { - copyRendererPageHtml(rendererHtmlInput, rendererHtmlOutput, "main.js"); + copyRendererPageHtml(rendererHtmlInput, rendererHtmlOutput, "main.js", { + beforeModuleScriptTags: [' '] + }); } export function copyTrayRendererHtml() { @@ -89,14 +133,25 @@ export function copyMarketplacePlugins() { ensureDist(); for (const filename of ["claude-design-plugin.cjs", "cursor-proxy-plugin.cjs"]) { const source = path.join(projectRoot, "examples", "plugins", filename); - const target = path.join(marketplacePluginsDir, filename); if (existsSync(source)) { - cpSync(source, target); + cpSync(source, path.join(cliMarketplacePluginsDir, filename)); + cpSync(source, path.join(coreMarketplacePluginsDir, filename)); + cpSync(source, path.join(electronMarketplacePluginsDir, filename)); } } } -function copyRendererPageHtml(input, output, scriptName) { +export function syncUiRendererToRuntimeDists() { + ensureDist(); + for (const outputDir of runtimeRendererOutDirs) { + rmSync(outputDir, { force: true, recursive: true }); + if (existsSync(rendererOutDir)) { + cpSync(rendererOutDir, outputDir, { recursive: true }); + } + } +} + +function copyRendererPageHtml(input, output, scriptName, options = {}) { ensureDist(); const source = readFileSync(input, "utf8"); const styleTag = ' '; @@ -105,6 +160,12 @@ function copyRendererPageHtml(input, output, scriptName) { ? source.replace(' ', scriptTag) : source.replace("", `${scriptTag}\n `); + for (const extraScriptTag of options.beforeModuleScriptTags ?? []) { + if (!hasScriptTag(html, extraScriptTag)) { + html = html.replace(scriptTag, `${extraScriptTag}\n${scriptTag}`); + } + } + if (!html.includes('href="../../assets/main.css"')) { html = html.replace("", `${styleTag}\n `); } @@ -112,18 +173,23 @@ function copyRendererPageHtml(input, output, scriptName) { writeFileSync(output, html, "utf8"); } +function hasScriptTag(html, scriptTag) { + const sourceMatch = scriptTag.match(/\bsrc="([^"]+)"/); + return sourceMatch ? html.includes(sourceMatch[1]) : html.includes(scriptTag); +} + export function createMainBuildOptions({ mode = "production", plugins = [] } = {}) { return { absWorkingDir: projectRoot, bundle: true, entryNames: "[name]", entryPoints: [ - path.join(projectRoot, "src", "main", "main.ts"), - path.join(projectRoot, "src", "main", "browser-preload.ts"), - path.join(projectRoot, "src", "server", "mcp", "browser-web-search-proxy-mcp.ts"), - path.join(projectRoot, "src", "server", "mcp", "fusion-vision-mcp.ts"), - path.join(projectRoot, "src", "server", "mcp", "fusion-tool-fallback-mcp.ts"), - path.join(projectRoot, "src", "main", "preload.ts") + path.join(electronSourceRoot, "main", "main.ts"), + path.join(electronSourceRoot, "main", "browser-preload.ts"), + path.join(coreSourceRoot, "mcp", "browser-web-search-proxy-mcp.ts"), + path.join(coreSourceRoot, "mcp", "fusion-vision-mcp.ts"), + path.join(coreSourceRoot, "mcp", "fusion-tool-fallback-mcp.ts"), + path.join(electronSourceRoot, "main", "preload.ts") ], external: nodeExternals, format: "cjs", @@ -131,9 +197,9 @@ export function createMainBuildOptions({ mode = "production", plugins = [] } = { logLevel: "info", metafile: true, minify: mode === "production", - outdir: mainOutDir, + outdir: electronMainOutDir, platform: "node", - plugins, + plugins: [packageAliasPlugin(), ...plugins], sourcemap: mode !== "production", target: "node22" }; @@ -144,15 +210,42 @@ export function createCliBuildOptions({ mode = "production", plugins = [] } = {} absWorkingDir: projectRoot, bundle: true, entryNames: "[name]", - entryPoints: [path.join(projectRoot, "src", "main", "cli.ts")], + entryPoints: [ + path.join(cliSourceRoot, "cli.ts"), + path.join(coreSourceRoot, "mcp", "fusion-vision-mcp.ts"), + path.join(coreSourceRoot, "mcp", "fusion-tool-fallback-mcp.ts") + ], external: nodeExternals.filter((moduleName) => moduleName !== "electron"), format: "cjs", legalComments: "none", logLevel: "info", minify: mode === "production", - outdir: mainOutDir, + outdir: cliMainOutDir, platform: "node", - plugins: [forbidCliElectronPlugin(), ...plugins], + plugins: [forbidCliElectronPlugin(), packageAliasPlugin(), ...plugins], + sourcemap: mode !== "production", + target: "node22" + }; +} + +export function createCoreServerBuildOptions({ mode = "production", plugins = [] } = {}) { + return { + absWorkingDir: projectRoot, + bundle: true, + entryNames: "[name]", + entryPoints: [ + path.join(coreSourceRoot, "entrypoints", "server.ts"), + path.join(coreSourceRoot, "mcp", "fusion-vision-mcp.ts"), + path.join(coreSourceRoot, "mcp", "fusion-tool-fallback-mcp.ts") + ], + external: nodeExternals.filter((moduleName) => moduleName !== "electron"), + format: "cjs", + legalComments: "none", + logLevel: "info", + minify: mode === "production", + outdir: coreMainOutDir, + platform: "node", + plugins: [forbidCliElectronPlugin(), packageAliasPlugin(), ...plugins], sourcemap: mode !== "production", target: "node22" }; @@ -183,7 +276,7 @@ export function createRendererBuildOptions({ mode = "production", plugins = [] } minify: mode === "production", outfile: path.join(rendererAssetsDir, "main.js"), platform: "browser", - plugins: [rendererAliasPlugin(), ...plugins], + plugins: [rendererAliasPlugin(), packageAliasPlugin(), ...plugins], publicPath: "../../assets", sourcemap: mode !== "production", target: "chrome120" @@ -210,14 +303,14 @@ export function createWebClientBridgeBuildOptions({ mode = "production", plugins return { absWorkingDir: projectRoot, bundle: true, - entryPoints: [path.join(projectRoot, "src", "main", "web-client-bridge.ts")], + entryPoints: [path.join(uiSourceRoot, "web-client-bridge.ts")], format: "iife", legalComments: "none", logLevel: "info", minify: mode === "production", outfile: webClientBridgeOutput, platform: "browser", - plugins, + plugins: [packageAliasPlugin(), ...plugins], sourcemap: mode !== "production", target: "chrome120" }; @@ -239,11 +332,21 @@ export function watchPlugin(name, onEnd) { export async function buildMain(options = {}) { const [mainBuildResult] = await Promise.all([ esbuild.build(createMainBuildOptions(options)), - esbuild.build(createCliBuildOptions(options)) + buildCoreServer(options), + buildCli(options) ]); + copyCliRuntimeToElectronDist(); validateLightweightMcpBundles(mainBuildResult.metafile); } +export async function buildCli(options = {}) { + await esbuild.build(createCliBuildOptions(options)); +} + +export async function buildCoreServer(options = {}) { + await esbuild.build(createCoreServerBuildOptions(options)); +} + export async function buildRenderer(options = {}) { await esbuild.build(createRendererBuildOptions(options)); } @@ -260,6 +363,14 @@ export async function buildWebClientBridge(options = {}) { await esbuild.build(createWebClientBridgeBuildOptions(options)); } +export function copyCliRuntimeToElectronDist() { + ensureDist(); + const cliRuntime = path.join(cliMainOutDir, "cli.js"); + if (existsSync(cliRuntime)) { + cpSync(cliRuntime, path.join(electronMainOutDir, "cli.js")); + } +} + export async function buildStyles({ minify = false } = {}) { ensureDist(); const args = ["-i", cssInput, "-o", cssOutput]; @@ -305,6 +416,26 @@ function rendererAliasPlugin() { }; } +function packageAliasPlugin() { + return { + name: "ccr-package-alias", + setup(build) { + build.onResolve({ filter: /^@ccr\/cli\// }, (args) => { + return { path: resolvePackageImport(cliSourceRoot, args.path.slice("@ccr/cli/".length)) }; + }); + build.onResolve({ filter: /^@ccr\/core\// }, (args) => { + return { path: resolvePackageImport(coreSourceRoot, args.path.slice("@ccr/core/".length)) }; + }); + build.onResolve({ filter: /^@ccr\/electron\// }, (args) => { + return { path: resolvePackageImport(electronSourceRoot, args.path.slice("@ccr/electron/".length)) }; + }); + build.onResolve({ filter: /^@ccr\/ui\// }, (args) => { + return { path: resolvePackageImport(uiSourceRoot, args.path.slice("@ccr/ui/".length)) }; + }); + } + }; +} + function forbidCliElectronPlugin() { return { name: "forbid-cli-electron", @@ -371,19 +502,23 @@ function normalizeBuildPath(value) { } function resolveRendererImport(importPath) { - const basePath = path.resolve(rendererRoot, importPath); + return resolvePackageImport(rendererRoot, importPath); +} + +function resolvePackageImport(rootDir, importPath) { + const packageBasePath = path.resolve(rootDir, importPath); const candidates = [ - basePath, - `${basePath}.tsx`, - `${basePath}.ts`, - `${basePath}.jsx`, - `${basePath}.js`, - `${basePath}.json`, - `${basePath}.css`, - path.join(basePath, "index.tsx"), - path.join(basePath, "index.ts"), - path.join(basePath, "index.jsx"), - path.join(basePath, "index.js") + packageBasePath, + `${packageBasePath}.tsx`, + `${packageBasePath}.ts`, + `${packageBasePath}.jsx`, + `${packageBasePath}.js`, + `${packageBasePath}.json`, + `${packageBasePath}.css`, + path.join(packageBasePath, "index.tsx"), + path.join(packageBasePath, "index.ts"), + path.join(packageBasePath, "index.jsx"), + path.join(packageBasePath, "index.js") ]; for (const candidate of candidates) { @@ -392,5 +527,5 @@ function resolveRendererImport(importPath) { } } - return basePath; + return packageBasePath; } diff --git a/build/test.mjs b/build/test.mjs index 8319a30..55c04d9 100644 --- a/build/test.mjs +++ b/build/test.mjs @@ -6,7 +6,9 @@ import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const projectRoot = path.resolve(__dirname, ".."); const testsOutDir = path.join(projectRoot, "dist", "tests"); -const rendererRoot = path.join(projectRoot, "src", "renderer"); +const rendererRoot = path.join(projectRoot, "packages", "ui", "src"); +const cliSourceRoot = path.join(projectRoot, "packages", "cli", "src"); +const coreSourceRoot = path.join(projectRoot, "packages", "core", "src"); const testSuites = [ { name: "main", testDir: path.join(projectRoot, "tests", "main") }, { name: "renderer", testDir: path.join(projectRoot, "tests", "renderer") } @@ -53,7 +55,7 @@ for (const suite of selectedSuites) { logLevel: "info", outdir: path.join(testsOutDir, suite.name), platform: "node", - plugins: [rendererAliasPlugin()], + plugins: [rendererAliasPlugin(), packageAliasPlugin()], target: "node22" }); } @@ -87,8 +89,26 @@ function rendererAliasPlugin() { }; } +function packageAliasPlugin() { + return { + name: "test-package-alias", + setup(build) { + build.onResolve({ filter: /^@ccr\/cli\// }, (args) => { + return { path: resolvePackageImport(cliSourceRoot, args.path.slice("@ccr/cli/".length)) }; + }); + build.onResolve({ filter: /^@ccr\/core\// }, (args) => { + return { path: resolvePackageImport(coreSourceRoot, args.path.slice("@ccr/core/".length)) }; + }); + } + }; +} + function resolveRendererImport(importPath) { - const basePath = path.resolve(rendererRoot, importPath); + return resolvePackageImport(rendererRoot, importPath); +} + +function resolvePackageImport(rootDir, importPath) { + const basePath = path.resolve(rootDir, importPath); const candidates = [ basePath, `${basePath}.tsx`, diff --git a/components.json b/components.json index e6f7108..a1acbd1 100644 --- a/components.json +++ b/components.json @@ -5,7 +5,7 @@ "tsx": true, "tailwind": { "config": "", - "css": "src/renderer/styles/globals.css", + "css": "packages/ui/src/styles/globals.css", "baseColor": "neutral", "cssVariables": true, "prefix": "" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8a8e7a8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +services: + ccr: + build: + context: . + dockerfile: Dockerfile + image: claude-code-router:local + environment: + CCR_WEB_HOST: 127.0.0.1 + CCR_WEB_PORT: 3459 + CCR_NGINX_PORT: 8080 + CCR_GATEWAY_HOST: 127.0.0.1 + CCR_GATEWAY_PORT: 3456 + CCR_GATEWAY_CORE_PORT: 3457 + CCR_PUBLIC_HOST: 127.0.0.1 + CCR_PUBLIC_PORT: 3458 + ports: + - "3458:8080" + volumes: + - ccr-data:/data + restart: unless-stopped + +volumes: + ccr-data: diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..0901836 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,74 @@ +# Docker deployment + +This image runs the core server package with PM2 and serves the built UI package +through Nginx. Nginx is the only published entrypoint: it serves the UI, proxies +management API calls to the internal core server, and proxies gateway API calls +to the internal gateway listener. + +## Build and run + +```sh +docker compose up --build +``` + +Then open: + +- Web UI: +- Gateway endpoint: + +The container stores config and SQLite databases under `/data`, backed by the +`ccr-data` volume in `docker-compose.yml`. + +On a fresh data volume, the Web UI starts immediately. The gateway port is +available through the same Nginx port, but the gateway only starts after at +least one provider and model are configured. + +## Image scripts + +```sh +npm run docker:build +npm run docker:run +``` + +## Smoke test + +```sh +npm run test:docker +``` + +The smoke test builds the image, starts an isolated temporary container with a +special-character `CCR_WEB_AUTH_TOKEN`, verifies that only the Nginx port is +published, checks UI and RPC authentication, confirms legacy Docker config is +migrated to the public Nginx router endpoint, and removes its temporary +container and volume. Set `CCR_DOCKER_TEST_SKIP_BUILD=1` to reuse an already +built image. + +The Dockerfile defaults to `node:22-bookworm` for reliable native SQLite +installation. To use a different Node base image: + +```sh +docker build --build-arg NODE_IMAGE=node:22-bookworm-slim -t claude-code-router:local . +``` + +## Environment + +| Variable | Default | Description | +| --- | --- | --- | +| `CCR_WEB_HOST` | `127.0.0.1` | Internal core server bind host. | +| `CCR_WEB_PORT` | `3459` | Internal core server port. | +| `CCR_NGINX_PORT` | `8080` | Nginx port that serves UI and proxies management/gateway requests. | +| `CCR_WEB_AUTH_TOKEN` | generated | Shared management UI token used by Nginx redirects and the core server. | +| `CCR_GATEWAY_HOST` | `127.0.0.1` | Internal gateway bind host. | +| `CCR_GATEWAY_PORT` | `3456` | Internal gateway port proxied by Nginx. | +| `CCR_GATEWAY_CORE_PORT` | `3457` | Internal core gateway port used for first-run config. | +| `CCR_PUBLIC_HOST` | `127.0.0.1` | Host used for the first-run public router endpoint. | +| `CCR_PUBLIC_PORT` | `3458` | Port used for the first-run public router endpoint. | +| `CCR_PUBLIC_BASE_URL` | `http://127.0.0.1:3458` | Full public router endpoint override. | +| `CCR_DATA_DIR` | `/data` | Container data root. | +| `CCR_NO_GATEWAY` | `0` | Set to `1` to run only the Web UI management service. | +| `CCR_DOCKER_INIT_CONFIG` | `1` | Set to `0` to disable first-run `config.json` bootstrap. | +| `CCR_DOCKER_SYNC_PUBLIC_ENDPOINT` | `1` | Sync existing Docker config to the Nginx public router endpoint on startup. | + +The first-run bootstrap writes a minimal legacy `config.json` only when neither +`config.json` nor `config.sqlite` exists in the mounted data directory. Once the +UI saves settings into SQLite, existing persisted configuration takes priority. diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..c4b8983 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,207 @@ +#!/bin/sh +set -eu + +CCR_DATA_DIR="${CCR_DATA_DIR:-/data}" +CCR_WEB_HOST="${CCR_WEB_HOST:-127.0.0.1}" +CCR_WEB_PORT="${CCR_WEB_PORT:-3459}" +CCR_NGINX_PORT="${CCR_NGINX_PORT:-8080}" +CCR_GATEWAY_HOST="${CCR_GATEWAY_HOST:-127.0.0.1}" +CCR_GATEWAY_PORT="${CCR_GATEWAY_PORT:-3456}" +CCR_GATEWAY_CORE_PORT="${CCR_GATEWAY_CORE_PORT:-3457}" +CCR_PUBLIC_HOST="${CCR_PUBLIC_HOST:-127.0.0.1}" +CCR_PUBLIC_PORT="${CCR_PUBLIC_PORT:-3458}" +CCR_PUBLIC_BASE_URL="${CCR_PUBLIC_BASE_URL:-http://${CCR_PUBLIC_HOST}:${CCR_PUBLIC_PORT}}" +CCR_NO_GATEWAY="${CCR_NO_GATEWAY:-0}" + +if [ -z "${CCR_WEB_AUTH_TOKEN:-}" ]; then + CCR_WEB_AUTH_TOKEN="$(node -e "process.stdout.write(require('node:crypto').randomBytes(32).toString('base64url'))")" +fi +CCR_WEB_AUTH_TOKEN_QUERY="$(node -e "process.stdout.write(encodeURIComponent(process.argv[1] || ''))" "${CCR_WEB_AUTH_TOKEN}")" + +export HOME="${CCR_DATA_DIR}" +export CCR_DATA_DIR +export CCR_GATEWAY_CORE_PORT +export CCR_GATEWAY_HOST +export CCR_GATEWAY_PORT +export CCR_NGINX_PORT +export CCR_NO_GATEWAY +export CCR_PUBLIC_BASE_URL +export CCR_PUBLIC_HOST +export CCR_PUBLIC_PORT +export CCR_WEB_AUTH_TOKEN +export CCR_WEB_AUTH_TOKEN_QUERY +export CCR_WEB_HOST +export CCR_WEB_PORT + +CONFIG_DIR="${HOME}/.claude-code-router" +CONFIG_FILE="${CONFIG_DIR}/config.json" +APP_CONFIG_DB_FILE="${CONFIG_DIR}/config.sqlite" + +mkdir -p "${CONFIG_DIR}" "${CONFIG_DIR}/app-data" /run/nginx /var/lib/nginx /var/log/nginx + +if [ "${CCR_DOCKER_INIT_CONFIG:-1}" != "0" ] && [ ! -f "${CONFIG_FILE}" ] && [ ! -f "${APP_CONFIG_DB_FILE}" ]; then + node - <<'NODE' +const fs = require("node:fs"); +const path = require("node:path"); + +const configDir = path.join(process.env.HOME, ".claude-code-router"); +const configFile = path.join(configDir, "config.json"); +const gatewayHost = process.env.CCR_GATEWAY_HOST || "0.0.0.0"; +const gatewayPort = Number(process.env.CCR_GATEWAY_PORT || "3456"); +const gatewayCorePort = Number(process.env.CCR_GATEWAY_CORE_PORT || "3457"); +const publicBaseUrl = (process.env.CCR_PUBLIC_BASE_URL || `http://127.0.0.1:${process.env.CCR_PUBLIC_PORT || "3458"}`).replace(/\/+$/, ""); + +fs.mkdirSync(configDir, { recursive: true, mode: 0o700 }); +fs.writeFileSync(configFile, `${JSON.stringify({ + HOST: gatewayHost, + PORT: gatewayPort, + gateway: { + coreHost: "127.0.0.1", + corePort: gatewayCorePort, + enabled: true, + host: gatewayHost, + port: gatewayPort + }, + routerEndpoint: publicBaseUrl +}, null, 2)}\n`, { mode: 0o600 }); +NODE +fi + +if [ "${CCR_DOCKER_SYNC_PUBLIC_ENDPOINT:-1}" != "0" ]; then + node - <<'NODE' +const fs = require("node:fs"); +const path = require("node:path"); + +const configDir = path.join(process.env.HOME, ".claude-code-router"); +const configFile = path.join(configDir, "config.json"); +const appConfigDbFile = path.join(configDir, "config.sqlite"); +const gatewayHost = process.env.CCR_GATEWAY_HOST || "127.0.0.1"; +const gatewayPort = Number(process.env.CCR_GATEWAY_PORT || "3456"); +const gatewayCorePort = Number(process.env.CCR_GATEWAY_CORE_PORT || "3457"); +const publicBaseUrl = (process.env.CCR_PUBLIC_BASE_URL || `http://127.0.0.1:${process.env.CCR_PUBLIC_PORT || "3458"}`).replace(/\/+$/, ""); + +function syncConfig(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return value; + } + value.HOST = gatewayHost; + value.PORT = gatewayPort; + value.gateway = { + ...(value.gateway && typeof value.gateway === "object" && !Array.isArray(value.gateway) ? value.gateway : {}), + coreHost: "127.0.0.1", + corePort: gatewayCorePort, + enabled: true, + host: gatewayHost, + port: gatewayPort + }; + value.routerEndpoint = publicBaseUrl; + return value; +} + +function syncJsonFile() { + if (!fs.existsSync(configFile)) { + return; + } + const parsed = JSON.parse(fs.readFileSync(configFile, "utf8")); + fs.writeFileSync(configFile, `${JSON.stringify(syncConfig(parsed), null, 2)}\n`, { mode: 0o600 }); +} + +function syncSqliteConfig() { + if (!fs.existsSync(appConfigDbFile)) { + return; + } + let Database; + try { + Database = require("better-sqlite3"); + } catch { + return; + } + const db = new Database(appConfigDbFile); + try { + const row = db.prepare("select value_json from app_config where key = ?").get("default"); + if (!row?.value_json) { + return; + } + const parsed = JSON.parse(row.value_json); + db.prepare("update app_config set value_json = ?, updated_at = ? where key = ?") + .run(JSON.stringify(syncConfig(parsed)), new Date().toISOString(), "default"); + } finally { + db.close(); + } +} + +syncJsonFile(); +syncSqliteConfig(); +NODE +fi + +cat > /etc/nginx/conf.d/default.conf <=6.9.0" } }, + "node_modules/@claude-code-router/core": { + "resolved": "packages/core", + "link": true + }, + "node_modules/@claude-code-router/electron": { + "resolved": "packages/electron", + "link": true + }, + "node_modules/@claude-code-router/ui": { + "resolved": "packages/ui", + "link": true + }, "node_modules/@date-io/core": { "version": "2.17.0", "resolved": "https://registry.npmjs.org/@date-io/core/-/core-2.17.0.tgz", "integrity": "sha512-+EQE8xZhRM/hsY0CDTVyayMDDY5ihc4MqXCrPxooKw19yAzUIC6uUqsZeaOFNL9YKTNxYKrJP5DFgE8o5xRCOw==", - "dev": true, "license": "MIT" }, "node_modules/@date-io/date-fns": { "version": "2.17.0", "resolved": "https://registry.npmjs.org/@date-io/date-fns/-/date-fns-2.17.0.tgz", "integrity": "sha512-L0hWZ/mTpy3Gx/xXJ5tq5CzHo0L7ry6KEO9/w/JWiFWFLZgiNVo3ex92gOl3zmzjHqY/3Ev+5sehAr8UnGLEng==", - "dev": true, "license": "MIT", "dependencies": { "@date-io/core": "^2.17.0" @@ -88,7 +98,6 @@ "version": "2.17.0", "resolved": "https://registry.npmjs.org/@date-io/moment/-/moment-2.17.0.tgz", "integrity": "sha512-e4nb4CDZU4k0WRVhz1Wvl7d+hFsedObSauDHKtZwU9kt7gdYEAzKgnrSCTHsEaXrDumdrkCYTeZ0Tmyk7uV4tw==", - "dev": true, "license": "MIT", "dependencies": { "@date-io/core": "^2.17.0" @@ -106,7 +115,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -119,7 +127,6 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", - "dev": true, "license": "MIT", "dependencies": { "@dnd-kit/accessibility": "^3.1.1", @@ -135,7 +142,6 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", - "dev": true, "license": "MIT", "dependencies": { "@dnd-kit/utilities": "^3.2.2", @@ -150,7 +156,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -1170,7 +1175,6 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", "integrity": "sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==", - "dev": true, "license": "ISC", "dependencies": { "get-stream": "^6.0.1", @@ -1184,14 +1188,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@mapbox/geojson-types/-/geojson-types-1.0.2.tgz", "integrity": "sha512-e9EBqHHv3EORHrSfbR9DqecPNn+AmuAoQxV6aL8Xu30bJMJR1o8PZLZzpk1Wq7/NfCbuhmakHTPYRhoqLsXRnw==", - "dev": true, "license": "ISC" }, "node_modules/@mapbox/jsonlint-lines-primitives": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -1200,7 +1202,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz", "integrity": "sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg==", - "dev": true, "license": "BSD-3-Clause", "peerDependencies": { "mapbox-gl": ">=0.32.1 <2.0.0" @@ -1210,28 +1211,24 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==", - "dev": true, "license": "ISC" }, "node_modules/@mapbox/tiny-sdf": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-1.2.5.tgz", "integrity": "sha512-cD8A/zJlm6fdJOk6DqPUV8mcpyJkRz2x2R+/fYcWDYG3oWbG7/L7Yl/WqQ1VZCjnL9OTIMAn6c+BC5Eru4sQEw==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/@mapbox/unitbezier": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz", "integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==", - "dev": true, "license": "BSD-2-Clause" }, "node_modules/@mapbox/vector-tile": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "@mapbox/point-geometry": "~0.1.0" @@ -1241,7 +1238,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", - "dev": true, "license": "ISC", "engines": { "node": ">=6.0.0" @@ -1251,13 +1247,16 @@ "version": "3.6.3", "resolved": "https://registry.npmjs.org/@math.gl/web-mercator/-/web-mercator-3.6.3.tgz", "integrity": "sha512-UVrkSOs02YLehKaehrxhAejYMurehIHPfFQvPFZmdJHglHOU4V2cCUApTVEwOksvCp161ypEqVp+9H6mGhTTcw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.0", "gl-matrix": "^3.4.0" } }, + "node_modules/@musistudio/claude-code-router": { + "resolved": "packages/cli", + "link": true + }, "node_modules/@noble/hashes": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", @@ -1638,11 +1637,259 @@ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", "license": "MIT" }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@pm2/agent": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@pm2/agent/-/agent-2.1.1.tgz", + "integrity": "sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==", + "license": "AGPL-3.0", + "dependencies": { + "async": "~3.2.0", + "chalk": "~3.0.0", + "dayjs": "~1.8.24", + "debug": "~4.3.1", + "eventemitter2": "~5.0.1", + "fast-json-patch": "^3.1.0", + "fclone": "~1.0.11", + "pm2-axon": "~4.0.1", + "pm2-axon-rpc": "~0.7.0", + "proxy-agent": "~6.4.0", + "semver": "~7.5.0", + "ws": "~7.5.10" + } + }, + "node_modules/@pm2/agent/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@pm2/agent/node_modules/dayjs": { + "version": "1.8.36", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz", + "integrity": "sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==", + "license": "MIT" + }, + "node_modules/@pm2/agent/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@pm2/agent/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/agent/node_modules/ws": { + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", + "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@pm2/blessed": { + "version": "0.1.81", + "resolved": "https://registry.npmjs.org/@pm2/blessed/-/blessed-0.1.81.tgz", + "integrity": "sha512-ZcNHqQjMuNRcQ7Z1zJbFIQZO/BDKV3KbiTckWdfbUaYhj7uNmUwb+FbdDWSCkvxNr9dBJQwvV17o6QBkAvgO0g==", + "license": "MIT", + "bin": { + "blessed": "bin/tput.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@pm2/io": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@pm2/io/-/io-6.1.0.tgz", + "integrity": "sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ==", + "license": "Apache-2", + "dependencies": { + "async": "~2.6.1", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "require-in-the-middle": "^5.0.0", + "semver": "~7.5.4", + "shimmer": "^1.2.0", + "signal-exit": "^3.0.3", + "tslib": "1.9.3" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@pm2/io/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/@pm2/io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@pm2/io/node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "license": "MIT" + }, + "node_modules/@pm2/io/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@pm2/io/node_modules/tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "license": "Apache-2.0" + }, + "node_modules/@pm2/js-api": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@pm2/js-api/-/js-api-0.8.1.tgz", + "integrity": "sha512-n9tDOz1ojyDOs05XthEXrLFVQYbbh2oAN19UakLPyEZDrUyEq05h8wIZU8+dNXBQY/KeFlWMLVA76nnX52ofRg==", + "license": "Apache-2", + "dependencies": { + "async": "^2.6.3", + "debug": "~4.3.1", + "eventemitter2": "^6.3.1", + "extrareqp2": "^1.0.0", + "ws": "^8.21.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@pm2/js-api/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/@pm2/js-api/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@pm2/js-api/node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "license": "MIT" + }, + "node_modules/@pm2/pm2-version-check": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@pm2/pm2-version-check/-/pm2-version-check-1.0.4.tgz", + "integrity": "sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1" + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.12.0.tgz", "integrity": "sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==", - "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", @@ -1669,7 +1916,6 @@ "version": "11.1.8", "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.8.tgz", "integrity": "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==", - "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -1680,7 +1926,6 @@ "version": "2.6.5-forked.0", "resolved": "https://registry.npmjs.org/@rtsao/csstype/-/csstype-2.6.5-forked.0.tgz", "integrity": "sha512-0HwnY8uPWcCloTgdbbaJG3MbDUfNf6yKWZfCKxFv9yj2Sbp4mSKaIjC7Cr/5L4hMxvrrk85CU3wlAg7EtBBJ1Q==", - "dev": true, "license": "MIT" }, "node_modules/@sindresorhus/is": { @@ -1700,14 +1945,12 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, "license": "MIT" }, "node_modules/@standard-schema/utils": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", - "dev": true, "license": "MIT" }, "node_modules/@szmarczak/http-timer": { @@ -2096,6 +2339,12 @@ "node": ">=22.0.0" } }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, "node_modules/@types/better-sqlite3": { "version": "7.6.13", "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", @@ -2123,28 +2372,24 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "dev": true, "license": "MIT" }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "dev": true, "license": "MIT" }, "node_modules/@types/d3-ease": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "dev": true, "license": "MIT" }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "dev": true, "license": "MIT", "dependencies": { "@types/d3-color": "*" @@ -2154,14 +2399,12 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "dev": true, "license": "MIT" }, "node_modules/@types/d3-scale": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", - "dev": true, "license": "MIT", "dependencies": { "@types/d3-time": "*" @@ -2171,7 +2414,6 @@ "version": "3.1.8", "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", - "dev": true, "license": "MIT", "dependencies": { "@types/d3-path": "*" @@ -2181,14 +2423,12 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "dev": true, "license": "MIT" }, "node_modules/@types/d3-timer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "dev": true, "license": "MIT" }, "node_modules/@types/debug": { @@ -2215,7 +2455,6 @@ "version": "2.0.46", "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", - "dev": true, "license": "MIT" }, "node_modules/@types/http-cache-semantics": { @@ -2266,14 +2505,14 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.31", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -2304,7 +2543,6 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", - "dev": true, "license": "MIT" }, "node_modules/@xmldom/xmldom": { @@ -2337,7 +2575,6 @@ "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14" @@ -2376,6 +2613,30 @@ } } }, + "node_modules/amp": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/amp/-/amp-0.3.1.tgz", + "integrity": "sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==", + "license": "MIT" + }, + "node_modules/amp-message": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/amp-message/-/amp-message-0.1.2.tgz", + "integrity": "sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==", + "license": "MIT", + "dependencies": { + "amp": "0.3.1" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2390,7 +2651,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -2402,6 +2662,28 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "4.0.0-node10", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.0.0-node10.tgz", + "integrity": "sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/app-builder-lib": { "version": "26.15.3", "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", @@ -2586,11 +2868,22 @@ "node": ">=12.0.0" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, "license": "MIT" }, "node_modules/async-exit-hook": { @@ -2633,7 +2926,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.3.tgz", "integrity": "sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==", - "dev": true, "license": "MIT", "dependencies": { "core-js": "^2.5.0" @@ -2673,7 +2965,6 @@ "version": "4.12.1", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==", - "dev": true, "license": "MPL-2.0", "engines": { "node": ">=4" @@ -2712,7 +3003,6 @@ "version": "16.1.1", "resolved": "https://registry.npmjs.org/baseui/-/baseui-16.1.1.tgz", "integrity": "sha512-pux441GCtmYrI8NE1SYqwsDnBad47msc7T/6p9JhHYrqYKo2y52YAO+7oSoFqIdaJxoY9r443U14hDkF68p56w==", - "dev": true, "license": "MIT", "dependencies": { "@date-io/date-fns": "^2.13.1", @@ -2759,21 +3049,18 @@ "version": "2.6.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.11.tgz", "integrity": "sha512-l8YyEC9NBkSm783PFTvh0FmJy7s5pFKrDp49ZL7zBGX3fWkO+N4EEyan1qqp8cwPLDcD0OSdyY6hAMoxp34JFw==", - "dev": true, "license": "MIT" }, "node_modules/baseui/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, "license": "MIT" }, "node_modules/baseui/node_modules/react-uid": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/react-uid/-/react-uid-2.3.0.tgz", "integrity": "sha512-tsPZ77GR0pISGYmpCLHAbZTabKXZ7zBniKPVqVMMfnXFyo39zq5g/psIlD5vLTKkjQEhWOO8JhqcHnxkwNu6eA==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^1.10.0" @@ -2795,7 +3082,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz", "integrity": "sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg==", - "dev": true, "license": "MIT", "engines": { "node": ">8.0.0" @@ -2809,7 +3095,6 @@ "version": "1.8.5", "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.5.tgz", "integrity": "sha512-HeTwlNa37AFa8MDZFZOKcNEkuF2YflA0hpGPiTT9vR7OawEt+GZbfM6wqkBahD3D3pUjIabQYzsnY/BSJbgq6Q==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", @@ -2827,9 +3112,17 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, "license": "0BSD" }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/better-sqlite3": { "version": "12.11.1", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz", @@ -2844,6 +3137,18 @@ "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -2885,6 +3190,12 @@ "dev": true, "license": "MIT" }, + "node_modules/bodec": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bodec/-/bodec-0.1.0.tgz", + "integrity": "sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==", + "license": "MIT" + }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -2910,7 +3221,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2947,7 +3257,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, "license": "MIT" }, "node_modules/builder-util": { @@ -3062,7 +3371,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/card-validator/-/card-validator-6.2.0.tgz", "integrity": "sha512-1vYv45JaE9NmixZr4dl6ykzbFKv7imI9L7MQDs235b/a7EGbG8rrEsipeHtVvscLSUbl3RX6UP5gyOe0Y2FlHA==", - "dev": true, "license": "MIT", "dependencies": { "credit-card-type": "^8.0.0" @@ -3085,6 +3393,36 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/charm": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", + "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", + "license": "MIT/X11" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -3118,6 +3456,30 @@ "node": ">=8" } }, + "node_modules/cli-tableau": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cli-tableau/-/cli-tableau-2.0.1.tgz", + "integrity": "sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==", + "dependencies": { + "chalk": "3.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/cli-tableau/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -3150,7 +3512,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3160,7 +3521,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3173,7 +3533,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -3193,7 +3552,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 10" @@ -3234,7 +3592,6 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true, "hasInstallScript": true, "license": "MIT" }, @@ -3249,7 +3606,12 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/credit-card-type/-/credit-card-type-8.3.0.tgz", "integrity": "sha512-czfZUpQ7W9CDxZL4yFLb1kFtM/q2lTOY975hL2aO+DC8+GRNDVSXVCHXhVFZPxiUKmQCZbFP8vIhxx5TBQaThw==", - "dev": true, + "license": "MIT" + }, + "node_modules/croner": { + "version": "4.1.97", + "resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz", + "integrity": "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==", "license": "MIT" }, "node_modules/cross-dirname": { @@ -3303,7 +3665,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz", "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==", - "dev": true, "license": "MIT", "dependencies": { "hyphenate-style-name": "^1.0.2", @@ -3314,21 +3675,24 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==", - "dev": true, "license": "MIT" }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "license": "MIT" + }, + "node_modules/culvert": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/culvert/-/culvert-0.1.2.tgz", + "integrity": "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==", "license": "MIT" }, "node_modules/d3": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", - "dev": true, "license": "ISC", "dependencies": { "d3-array": "3", @@ -3370,7 +3734,6 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "dev": true, "license": "ISC", "dependencies": { "internmap": "1 - 2" @@ -3383,7 +3746,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3393,7 +3755,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "dev": true, "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -3410,7 +3771,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "dev": true, "license": "ISC", "dependencies": { "d3-path": "1 - 3" @@ -3423,7 +3783,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3433,7 +3792,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "dev": true, "license": "ISC", "dependencies": { "d3-array": "^3.2.0" @@ -3446,7 +3804,6 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "dev": true, "license": "ISC", "dependencies": { "delaunator": "5" @@ -3459,7 +3816,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3469,7 +3825,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "dev": true, "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -3483,7 +3838,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "dev": true, "license": "ISC", "dependencies": { "commander": "7", @@ -3509,7 +3863,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=12" @@ -3519,7 +3872,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "dev": true, "license": "ISC", "dependencies": { "d3-dsv": "1 - 3" @@ -3532,7 +3884,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "dev": true, "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -3547,7 +3898,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3557,7 +3907,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "dev": true, "license": "ISC", "dependencies": { "d3-array": "2.5.0 - 3" @@ -3570,7 +3919,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3580,7 +3928,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "dev": true, "license": "ISC", "dependencies": { "d3-color": "1 - 3" @@ -3593,7 +3940,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3603,7 +3949,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3613,7 +3958,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3623,7 +3967,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3633,7 +3976,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "dev": true, "license": "ISC", "dependencies": { "d3-array": "2.10.0 - 3", @@ -3650,7 +3992,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "dev": true, "license": "ISC", "dependencies": { "d3-color": "1 - 3", @@ -3664,7 +4005,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3674,7 +4014,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "dev": true, "license": "ISC", "dependencies": { "d3-path": "^3.1.0" @@ -3687,7 +4026,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "dev": true, "license": "ISC", "dependencies": { "d3-array": "2 - 3" @@ -3700,7 +4038,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "dev": true, "license": "ISC", "dependencies": { "d3-time": "1 - 3" @@ -3713,7 +4050,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -3723,7 +4059,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "dev": true, "license": "ISC", "dependencies": { "d3-color": "1 - 3", @@ -3743,7 +4078,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "dev": true, "license": "ISC", "dependencies": { "d3-dispatch": "1 - 3", @@ -3756,11 +4090,19 @@ "node": ">=12" } }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.21.0" @@ -3777,12 +4119,17 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.3.8.tgz", "integrity": "sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==", - "dev": true, "license": "MIT", "peerDependencies": { "date-fns": ">=2.0.0" } }, + "node_modules/dayjs": { + "version": "1.11.15", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.15.tgz", + "integrity": "sha512-MC+DfnSWiM9APs7fpiurHGCoeIx0Gdl6QZBy+5lu8MbYKN5FZEXqOgrundfibdfhGZ15o9hzmZ2xJjZnbvgKXQ==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -3804,7 +4151,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", - "dev": true, "license": "MIT" }, "node_modules/decompress-response": { @@ -3891,11 +4237,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/delaunator": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", - "dev": true, "license": "ISC", "dependencies": { "robust-predicates": "^3.0.2" @@ -3945,7 +4304,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "dev": true, "license": "MIT" }, "node_modules/diff": { @@ -4016,7 +4374,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.7", @@ -4081,7 +4438,6 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", - "dev": true, "license": "ISC" }, "node_modules/ejs": { @@ -4311,6 +4667,18 @@ "node": ">=10.13.0" } }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/env-paths": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", @@ -4345,7 +4713,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4384,7 +4751,6 @@ "version": "1.47.1", "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.1.tgz", "integrity": "sha512-5RAqEwf4P4E17p+W75KLOWw/nOvKZzSQpxM32IpI2KZLaVonjTrZ0Ai5ghMaVI9eKC2p8eoQgcBdkEDgzFk6+Q==", - "dev": true, "license": "MIT", "workspaces": [ "docs", @@ -4455,9 +4821,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=10" }, @@ -4465,11 +4829,68 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", + "integrity": "sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==", + "license": "MIT" + }, "node_modules/eventemitter3": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, "license": "MIT" }, "node_modules/expand-template": { @@ -4488,6 +4909,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/extrareqp2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/extrareqp2/-/extrareqp2-1.0.0.tgz", + "integrity": "sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, "node_modules/fast-decode-uri-component": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", @@ -4500,6 +4930,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "license": "MIT" + }, "node_modules/fast-json-stringify": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.4.0.tgz", @@ -4591,11 +5027,16 @@ "reusify": "^1.0.4" } }, + "node_modules/fclone": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", + "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", + "license": "MIT" + }, "node_modules/file-selector": { "version": "0.1.19", "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.19.tgz", "integrity": "sha512-kCWw3+Aai8Uox+5tHCNgMFaUdgidxvMnLWO6fM5sZ0hA2wlHP5/DHGF0ECe84BiB95qdJbKNEJhWKVDvMN+JDQ==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.1" @@ -4654,7 +5095,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -4681,7 +5121,6 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-1.3.6.tgz", "integrity": "sha512-Ik/6OCk9RQQ0T5Xw+hKNLWrjSMtv51dD4GRmJjbD5a58TIEpI5a5iXagKVl3Z5UuyslMCA8Xwnu76jQob62Yhg==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.3" @@ -4690,6 +5129,26 @@ "node": ">=10" } }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/form-data": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", @@ -4711,7 +5170,6 @@ "version": "12.40.0", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.40.0.tgz", "integrity": "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==", - "dev": true, "license": "MIT", "dependencies": { "motion-dom": "^12.40.0", @@ -4762,11 +5220,24 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4776,7 +5247,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==", - "dev": true, "license": "ISC" }, "node_modules/get-caller-file": { @@ -4832,7 +5302,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -4841,6 +5310,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/git-node-fs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/git-node-fs/-/git-node-fs-1.0.0.tgz", + "integrity": "sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==", + "license": "MIT" + }, + "node_modules/git-sha1": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/git-sha1/-/git-sha1-0.1.2.tgz", + "integrity": "sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==", + "license": "MIT" + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -4851,7 +5346,6 @@ "version": "3.4.4", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", - "dev": true, "license": "MIT" }, "node_modules/glob": { @@ -4871,6 +5365,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/global-agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", @@ -4957,14 +5463,12 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/grid-index/-/grid-index-1.1.0.tgz", "integrity": "sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==", - "dev": true, "license": "ISC" }, "node_modules/hammerjs": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", "integrity": "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.0" @@ -4974,7 +5478,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5027,7 +5530,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5060,7 +5562,6 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.0", @@ -5088,7 +5589,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -5102,14 +5602,12 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -5142,7 +5640,6 @@ "version": "10.2.0", "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", - "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -5177,7 +5674,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-5.1.2.tgz", "integrity": "sha512-PYUF+94gDfhy+LsQxM0g3d6Hge4l1pAqOSOiZuHWzMvQEGsbRQ/ck2WioLqrY2ZkHyPgVUXxn+hrkF7D6QUGbA==", - "dev": true, "license": "MIT", "dependencies": { "css-in-js-utils": "^2.0.0" @@ -5187,7 +5683,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -5197,12 +5692,20 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" } }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", @@ -5212,11 +5715,37 @@ "node": ">= 10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5236,7 +5765,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -5249,7 +5777,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -5289,7 +5816,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5323,11 +5849,22 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-git": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/js-git/-/js-git-0.7.8.tgz", + "integrity": "sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==", + "license": "MIT", + "dependencies": { + "bodec": "^0.1.0", + "culvert": "^0.1.2", + "git-sha1": "^0.1.2", + "pako": "^0.2.5" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -5388,7 +5925,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, "license": "ISC", "optional": true }, @@ -5421,14 +5957,12 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", - "dev": true, "license": "MIT" }, "node_modules/kdbush": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==", - "dev": true, "license": "ISC" }, "node_modules/keyv": { @@ -5759,7 +6293,6 @@ "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "dev": true, "license": "MIT" }, "node_modules/lodash.escaperegexp": { @@ -5779,7 +6312,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -5802,7 +6334,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -5815,7 +6346,6 @@ "version": "1.20.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.20.0.tgz", "integrity": "sha512-jhXLeC/7m0/tjL1nzMdKk6x256zWA6AtbhTVreHOiKPoeX2d6MK4FbyIQPpVq0E6iPWBisyy1TW+pEge/uMEuQ==", - "dev": true, "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -5835,7 +6365,6 @@ "version": "1.13.3", "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.13.3.tgz", "integrity": "sha512-p8lJFEiqmEQlyv+DQxFAOG/XPWN0Wp7j/Psq93Zywz7qt9CcUKFYDBOoOEKzqe6gudHVJY8/Bhqw6VDpX2lSBg==", - "dev": true, "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@mapbox/geojson-rewind": "^0.5.2", @@ -5893,7 +6422,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.0.tgz", "integrity": "sha512-7g0+ejkOaI9w5x6LvQwmj68kUj6rxROywPSCqmclG/HBacmFnZqhVscQ8kovkn9FBCNJmOz6SY42+jnvZzDWdw==", - "dev": true, "license": "MIT" }, "node_modules/micromatch": { @@ -6006,7 +6534,6 @@ "version": "2.7.3", "resolved": "https://registry.npmjs.org/mjolnir.js/-/mjolnir.js-2.7.3.tgz", "integrity": "sha512-Z5z/+FzZqOSO3juSVKV3zcm4R2eAlWwlKMcqHmyFEJAaLILNcDKnIbnb4/kbcGyIuhtdWrzu8WOIR7uM6I34aw==", - "dev": true, "license": "MIT", "dependencies": { "@types/hammerjs": "^2.0.41", @@ -6041,14 +6568,18 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/mockdate/-/mockdate-2.0.5.tgz", "integrity": "sha512-ST0PnThzWKcgSLyc+ugLVql45PvESt3Ul/wrdV/OPc/6Pr8dbLAIJsN1cIp41FLzbN+srVTNIRn+5Cju0nyV6A==", - "dev": true, + "license": "MIT" + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", "license": "MIT" }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "dev": true, "license": "MIT", "engines": { "node": "*" @@ -6058,7 +6589,6 @@ "version": "12.40.0", "resolved": "https://registry.npmjs.org/motion/-/motion-12.40.0.tgz", "integrity": "sha512-yjrHUrBFW6kQvjJwRsoiPSAhC5tRwRqNGJWmiJ4CrGnbKp0V88AdzkhBmDoqIsIPfarOe0Uddd37Xq43/gIocA==", - "dev": true, "license": "MIT", "dependencies": { "framer-motion": "^12.40.0", @@ -6085,7 +6615,6 @@ "version": "12.40.0", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz", "integrity": "sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==", - "dev": true, "license": "MIT", "dependencies": { "motion-utils": "^12.39.0" @@ -6095,7 +6624,6 @@ "version": "12.39.0", "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", - "dev": true, "license": "MIT" }, "node_modules/mri": { @@ -6118,15 +6646,67 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==", - "dev": true, "license": "MIT" }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "license": "ISC" + }, "node_modules/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "license": "MIT" }, + "node_modules/needle": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", + "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/node-abi": { "version": "4.31.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.31.0.tgz", @@ -6260,6 +6840,15 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", @@ -6277,7 +6866,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6356,6 +6944,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -6376,6 +7002,12 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, "node_modules/path-scurry": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", @@ -6405,7 +7037,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.3.0.tgz", "integrity": "sha512-XDF38WCH3z5OV/OVa8GKUNtLAyneuzbCisx7QUCF8Q6Nutx0WnJrQe5O+kOtBlLfRNUws98Y58Lblp+NJG5T4Q==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "ieee754": "^1.1.12", @@ -6441,7 +7072,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -6450,6 +7080,38 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidusage": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz", + "integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pidusage/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/pino": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", @@ -6518,6 +7180,38 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -6533,11 +7227,204 @@ "node": ">=10.4.0" } }, + "node_modules/pm2": { + "version": "6.0.14", + "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.14.tgz", + "integrity": "sha512-wX1FiFkzuT2H/UUEA8QNXDAA9MMHDsK/3UHj6Dkd5U7kxyigKDA5gyDw78ycTQZAuGCLWyUX5FiXEuVQWafukA==", + "license": "AGPL-3.0", + "dependencies": { + "@pm2/agent": "~2.1.1", + "@pm2/blessed": "0.1.81", + "@pm2/io": "~6.1.0", + "@pm2/js-api": "~0.8.0", + "@pm2/pm2-version-check": "^1.0.4", + "ansis": "4.0.0-node10", + "async": "3.2.6", + "chokidar": "3.6.0", + "cli-tableau": "2.0.1", + "commander": "2.15.1", + "croner": "4.1.97", + "dayjs": "1.11.15", + "debug": "4.4.3", + "enquirer": "2.3.6", + "eventemitter2": "5.0.1", + "fclone": "1.0.11", + "js-yaml": "4.1.1", + "mkdirp": "1.0.4", + "needle": "2.4.0", + "pidusage": "3.0.2", + "pm2-axon": "~4.0.1", + "pm2-axon-rpc": "~0.7.1", + "pm2-deploy": "~1.0.2", + "pm2-multimeter": "^0.1.2", + "promptly": "2.2.0", + "semver": "7.7.2", + "source-map-support": "0.5.21", + "sprintf-js": "1.1.2", + "vizion": "~2.2.1" + }, + "bin": { + "pm2": "bin/pm2", + "pm2-dev": "bin/pm2-dev", + "pm2-docker": "bin/pm2-docker", + "pm2-runtime": "bin/pm2-runtime" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "pm2-sysmonit": "^1.2.8" + } + }, + "node_modules/pm2-axon": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pm2-axon/-/pm2-axon-4.0.1.tgz", + "integrity": "sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==", + "license": "MIT", + "dependencies": { + "amp": "~0.3.1", + "amp-message": "~0.1.1", + "debug": "^4.3.1", + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=5" + } + }, + "node_modules/pm2-axon-rpc": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/pm2-axon-rpc/-/pm2-axon-rpc-0.7.1.tgz", + "integrity": "sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1" + }, + "engines": { + "node": ">=5" + } + }, + "node_modules/pm2-deploy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pm2-deploy/-/pm2-deploy-1.0.2.tgz", + "integrity": "sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==", + "license": "MIT", + "dependencies": { + "run-series": "^1.1.8", + "tv4": "^1.3.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pm2-multimeter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz", + "integrity": "sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==", + "license": "MIT/X11", + "dependencies": { + "charm": "~0.1.1" + } + }, + "node_modules/pm2-sysmonit": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/pm2-sysmonit/-/pm2-sysmonit-1.2.8.tgz", + "integrity": "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==", + "license": "Apache", + "optional": true, + "dependencies": { + "async": "^3.2.0", + "debug": "^4.3.1", + "pidusage": "^2.0.21", + "systeminformation": "^5.7", + "tx2": "~1.0.4" + } + }, + "node_modules/pm2-sysmonit/node_modules/pidusage": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-2.0.21.tgz", + "integrity": "sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pm2-sysmonit/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/pm2/node_modules/commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "license": "MIT" + }, + "node_modules/pm2/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/pm2/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pm2/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pm2/node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "license": "BSD-3-Clause" + }, "node_modules/polished": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.17.8" @@ -6551,7 +7438,6 @@ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -6592,7 +7478,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==", - "dev": true, "license": "ISC" }, "node_modules/prebuild-install": { @@ -6700,11 +7585,19 @@ "node": ">=10" } }, + "node_modules/promptly": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/promptly/-/promptly-2.2.0.tgz", + "integrity": "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==", + "license": "MIT", + "dependencies": { + "read": "^1.0.4" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -6716,7 +7609,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", - "dev": true, "license": "MIT", "dependencies": { "react-is": "^16.3.2", @@ -6730,14 +7622,12 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, "license": "MIT" }, "node_modules/proper-lockfile": { @@ -6756,7 +7646,40 @@ "version": "3.6.1", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==", - "dev": true, + "license": "MIT" + }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, "node_modules/pump": { @@ -6812,7 +7735,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", - "dev": true, "license": "ISC" }, "node_modules/rc": { @@ -6834,7 +7756,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -6847,7 +7768,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.8.tgz", "integrity": "sha512-ma2FePH0z3px2+WOu6h+YycZcEvFmmxIlAb62cF52bG86eMySciO/EQZeQMXd07kPCYB0a1dWDT5J+KE9mCDUw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.13" @@ -6860,7 +7780,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -6874,7 +7793,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-9.0.0.tgz", "integrity": "sha512-wZ2o9B2qkdE3RumWhfyZT9swgJYJPeU5qHEcMU8weYpmLex1eeWX0CC32/Y0VutB+BBi2D+iePV/YZIiB4kZGw==", - "dev": true, "license": "MIT", "dependencies": { "attr-accept": "^1.1.3", @@ -6893,7 +7811,6 @@ "version": "2.13.7", "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.13.7.tgz", "integrity": "sha512-20lpZHEQrXPb+pp1tzd4ULL6DyO5D2KnR0G69tTDdydrmNhU7pdFmbQUYVyHUgp+xN29IuFR0PVuhOmvaZL9Og==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", @@ -6917,7 +7834,6 @@ "version": "7.79.0", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.79.0.tgz", "integrity": "sha512-mhYp/MTmXvzYX6AJcJVko0rktoIhhmRnEouObj4wF5i/tCttgJvnp1+9wRkpITZjDTqpo4IOSJqu0dBlPlV/Lw==", - "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" @@ -6934,7 +7850,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-input-mask/-/react-input-mask-2.0.4.tgz", "integrity": "sha512-1hwzMr/aO9tXfiroiVCx5EtKohKwLk/NT8QlJXHQ4N+yJJFyUuMT+zfTpLBwX/lK3PkuMlievIffncpMZ3HGRQ==", - "dev": true, "license": "MIT", "dependencies": { "invariant": "^2.2.4", @@ -6949,7 +7864,6 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", - "dev": true, "license": "MIT", "peer": true }, @@ -6957,14 +7871,12 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", - "dev": true, "license": "MIT" }, "node_modules/react-map-gl": { "version": "5.2.13", "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-5.2.13.tgz", "integrity": "sha512-yYqpZJADB7o5epiYpkcUED5MWevFnOsVn9mgKfizxjsenLMpVNgNgb/x0e9DS4VZtpOLWW2ZXYBB4BJZJlZmTQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", @@ -6986,7 +7898,6 @@ "version": "1.0.26", "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.26.tgz", "integrity": "sha512-CblNyiNVw2o+hsa5/49NH2ogGxZ+t+3aweRvNSq7TVjDIlwk7ir4lencEg5HxHeSzwNarSkNkiu0qJSOXtxm5A==", - "dev": true, "license": "MIT", "peerDependencies": { "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0", @@ -6997,7 +7908,6 @@ "version": "3.4.1", "resolved": "https://registry.npmjs.org/react-movable/-/react-movable-3.4.1.tgz", "integrity": "sha512-VTrBKjRWV4yVUDGj5dIZIhf07fyoFDXzIV6xs3KNNHpgPbkPT7FNu/Vbe11ZjHmT48uOykCmRMpS66DrzRbZZg==", - "dev": true, "license": "MIT", "peerDependencies": { "react": "*", @@ -7008,7 +7918,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/react-multi-ref/-/react-multi-ref-1.0.2.tgz", "integrity": "sha512-6oS5yzrZ4UrdMHbF6QAnnaoIe9h8R+Xv4m8uJWVK8/Q4RCc6RTT0XJ/LZ7llVgFcVbnDHeUAcVIhtRgFyzjJpA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.24.4" @@ -7018,7 +7927,6 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/react-range/-/react-range-1.10.0.tgz", "integrity": "sha512-kDo0LiBUHIQIP8menx0UoxTnHr7UXBYpIYl/DR9jCaO1o29VwvCLpkP/qOTNQz5hkJadPg1uEM07XJcJ1XGoKw==", - "dev": true, "license": "MIT", "peerDependencies": { "react": "*", @@ -7029,7 +7937,6 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", - "dev": true, "license": "MIT", "dependencies": { "@types/use-sync-external-store": "^0.0.6", @@ -7053,7 +7960,6 @@ "version": "9.22.6", "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.6.tgz", "integrity": "sha512-U5j7KuUQt3AaMatlMJ0UJddqSiX+Km0YJxSqbAzIiGw5EmNz0khMyqP2hzgu4+QUtm+QPIrxzUX4raJxmVJnHg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.7.2", @@ -7072,12 +7978,23 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/read-binary-file-arch": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", @@ -7107,6 +8024,18 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/real-require": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", @@ -7120,7 +8049,6 @@ "version": "3.8.1", "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==", - "dev": true, "license": "MIT", "workspaces": [ "www" @@ -7151,14 +8079,12 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "dev": true, "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", - "dev": true, "license": "MIT", "peerDependencies": { "redux": "^5.0.0" @@ -7183,6 +8109,20 @@ "node": ">=0.10.0" } }, + "node_modules/require-in-the-middle": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz", + "integrity": "sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/resedit": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", @@ -7205,16 +8145,35 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", - "dev": true, "license": "MIT" }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", - "dev": true, "license": "MIT" }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -7226,7 +8185,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", - "dev": true, "license": "MIT", "dependencies": { "protocol-buffers-schema": "^3.3.1" @@ -7375,14 +8333,32 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", - "dev": true, "license": "Unlicense" }, + "node_modules/run-series": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.9.tgz", + "integrity": "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/rw": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/safe-buffer": { @@ -7426,7 +8402,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, "license": "MIT" }, "node_modules/sanitize-filename": { @@ -7452,7 +8427,6 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -7540,11 +8514,16 @@ "node": ">=8" } }, + "node_modules/shimmer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", + "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", + "license": "BSD-2-Clause" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, "license": "ISC" }, "node_modules/simple-concat": { @@ -7605,6 +8584,44 @@ "node": ">=10" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/sonic-boom": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", @@ -7618,7 +8635,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -7638,7 +8654,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -7722,7 +8737,6 @@ "version": "1.6.2", "resolved": "https://registry.npmjs.org/styletron-engine-atomic/-/styletron-engine-atomic-1.6.2.tgz", "integrity": "sha512-vWSIxpCkYbVD3xhnxYwnJDJePD8p8pJCu7g1zFxdQIM4z34Eo1zj3LgXJI6yeNMrJ7PkE56SYK6ZOo3hpTOBzA==", - "dev": true, "license": "MIT", "dependencies": { "inline-style-prefixer": "^5.1.0", @@ -7733,7 +8747,6 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/styletron-react/-/styletron-react-6.1.1.tgz", "integrity": "sha512-K04BwKZTrdRG/wR5BaFG8z0bFu1jkT2HAp0UP5ZeMAKW6Ix8J3yuROWLoLUMZafaRRQ9LjiLpIl65u75L7YZow==", - "dev": true, "license": "MIT", "dependencies": { "prop-types": "^15.6.0", @@ -7747,7 +8760,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/styletron-standard/-/styletron-standard-3.1.0.tgz", "integrity": "sha512-Cr2q0IFsag6OaIeD/LBNRuCxNTPa/WtTbKP1X3o50mDudN8FGwmD5h1sMJ/Bu5+mO/2NfrNAv9V9zUXn6lXXMA==", - "dev": true, "license": "MIT", "dependencies": { "@rtsao/csstype": "2.6.5-forked.0", @@ -7772,7 +8784,6 @@ "version": "7.1.5", "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==", - "dev": true, "license": "ISC", "dependencies": { "kdbush": "^3.0.0" @@ -7782,7 +8793,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -7791,11 +8801,49 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/systeminformation": { + "version": "5.31.12", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.12.tgz", + "integrity": "sha512-qnTtO5wHrKeKE/MvQ6iIt6XAV+5fgt/kPIQf27DYgjVQQuHUfWkV4Gu6k04ZpEzAMuyQ3ZsovY7Ivhp+E9JyWw==", + "license": "MIT", + "optional": true, + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, "node_modules/tailwind-merge": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -7966,7 +9014,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true, "license": "MIT" }, "node_modules/tiny-typed-emitter": { @@ -8027,7 +9074,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==", - "dev": true, "license": "ISC" }, "node_modules/tmp": { @@ -8054,7 +9100,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -8086,7 +9131,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, "license": "0BSD" }, "node_modules/tunnel-agent": { @@ -8101,6 +9145,34 @@ "node": "*" } }, + "node_modules/tv4": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", + "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", + "license": [ + { + "type": "Public Domain", + "url": "http://geraintluff.github.io/tv4/LICENSE.txt" + }, + { + "type": "MIT", + "url": "http://jsonary.com/LICENSE.txt" + } + ], + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/tx2": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz", + "integrity": "sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==", + "license": "MIT", + "optional": true, + "dependencies": { + "json-stringify-safe": "^5.0.1" + } + }, "node_modules/type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", @@ -8187,7 +9259,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -8209,7 +9280,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "dev": true, "license": "MIT", "dependencies": { "detect-node-es": "^1.1.0", @@ -8232,7 +9302,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "dev": true, "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -8255,7 +9324,6 @@ "version": "37.3.6", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", - "dev": true, "license": "MIT AND ISC", "dependencies": { "@types/d3-array": "^3.0.3", @@ -8279,17 +9347,39 @@ "resolved": "https://registry.npmjs.org/viewport-mercator-project/-/viewport-mercator-project-7.0.4.tgz", "integrity": "sha512-0jzpL6pIMocCKWg1C3mqi/N4UPgZC3FzwghEm1H+XsUo8hNZAyJc3QR7YqC816ibOR8aWT5pCsV+gCu8/BMJgg==", "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, "license": "MIT", "dependencies": { "@math.gl/web-mercator": "^3.5.5" } }, + "node_modules/vizion": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vizion/-/vizion-2.2.1.tgz", + "integrity": "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==", + "license": "Apache-2.0", + "dependencies": { + "async": "^2.6.3", + "git-node-fs": "^1.0.0", + "ini": "^1.3.5", + "js-git": "^0.7.8" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/vizion/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, "node_modules/vt-pbf": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", - "dev": true, "license": "MIT", "dependencies": { "@mapbox/point-geometry": "0.1.0", @@ -8301,7 +9391,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" @@ -8406,7 +9495,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, "license": "ISC" }, "node_modules/yargs": { @@ -8450,6 +9538,68 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "packages/cli": { + "name": "@musistudio/claude-code-router", + "version": "3.0.7", + "license": "MIT", + "dependencies": { + "@the-next-ai/ai-gateway": "^1.0.4", + "@the-next-ai/bot-gateway-sdk": "^0.1.0", + "better-sqlite3": "^12.11.1", + "node-forge": "^1.4.0", + "undici": "^7.27.2" + }, + "bin": { + "ccr": "dist/main/cli.js" + }, + "engines": { + "node": ">=22" + } + }, + "packages/core": { + "name": "@claude-code-router/core", + "version": "3.0.7", + "dependencies": { + "@the-next-ai/ai-gateway": "^1.0.4", + "@the-next-ai/bot-gateway-sdk": "^0.1.0", + "better-sqlite3": "^12.11.1", + "node-forge": "^1.4.0", + "pm2": "^6.0.13", + "undici": "^7.27.2" + }, + "bin": { + "ccr-core-server": "dist/main/server.js" + }, + "engines": { + "node": ">=22" + } + }, + "packages/electron": { + "name": "@claude-code-router/electron", + "version": "3.0.7", + "dependencies": { + "electron-updater": "^6.8.9" + } + }, + "packages/ui": { + "name": "@claude-code-router/ui", + "version": "3.0.7", + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "baseui": "^16.1.1", + "clsx": "^2.1.1", + "lucide-react": "^1.17.0", + "motion": "^12.40.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "recharts": "^3.8.1", + "styletron-engine-atomic": "^1.6.2", + "styletron-react": "^6.1.1", + "tailwind-merge": "^3.6.0" + } } } } diff --git a/package.json b/package.json index 29a63fe..aa9c7e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { - "name": "claude-code-router", + "name": "claude-code-router-monorepo", "version": "3.0.7", + "private": true, "license": "MIT", "description": "Local Claude Code Router gateway with CLI and web management UI.", "repository": { @@ -18,15 +19,9 @@ "gateway", "router" ], - "main": "dist/main/main.js", - "bin": { - "ccr": "dist/main/cli.js" - }, - "files": [ - "dist", - "LICENSE", - "README.md", - "README_zh.md" + "main": "packages/electron/dist/main/main.js", + "workspaces": [ + "packages/*" ], "engines": { "node": ">=22" @@ -35,9 +30,13 @@ "access": "public" }, "scripts": { - "dev": "node build/dev.mjs", - "build": "npm run build:assets && electron-builder", + "dev": "npm run dev:cli", + "dev:ui": "node build/dev.mjs ui", + "dev:cli": "node build/dev.mjs cli", + "dev:electron": "node build/dev.mjs electron", + "build": "node build/build.mjs && electron-builder", "build:assets": "node build/build.mjs", + "build:docker": "node build/docker-build.mjs", "build:app:mac": "npm run build:app:mac:local", "build:app:mac:local": "npm run build:assets && electron-builder --config build/electron-builder.local.cjs --mac --publish never", "build:app:mac:release": "node build/macos-release-preflight.mjs && npm run build:assets && electron-builder --mac --publish never", @@ -45,7 +44,12 @@ "prepack": "npm run build:assets", "prepublishOnly": "npm run typecheck", "preview": "npm run build:assets && electron .", + "docker:build": "docker build -t claude-code-router:local .", + "docker:run": "docker run --rm -p 3458:8080 -v ccr-data:/data claude-code-router:local", "test": "node build/test.mjs && node build/run-tests.mjs", + "test:docker": "node tests/docker/docker-smoke.mjs", + "test:e2e": "npm run build:assets && playwright test", + "test:e2e:install": "playwright install chromium", "test:main": "node build/test.mjs main && node build/run-tests.mjs main", "test:renderer": "node build/test.mjs renderer && node build/run-tests.mjs renderer", "typecheck": "tsc --noEmit", @@ -63,6 +67,7 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "@playwright/test": "^1.61.1", "@tailwindcss/cli": "^4.3.0", "@types/better-sqlite3": "^7.6.13", "@types/node": "^22.10.2", diff --git a/packages/cli/LICENSE b/packages/cli/LICENSE new file mode 100644 index 0000000..ea7e8ac --- /dev/null +++ b/packages/cli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 musistudio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 0000000..3f5ce1e --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,329 @@ +

Claude Code Router Desktop

+ +

+ Chinese README + Discord + X + License + Documentation +

+ +

+ Claude Code Router Desktop screenshot +

+ +Claude Code Router Desktop is a local gateway and desktop control panel for routing agent requests from Claude Code, Codex, ZCode, and compatible clients to the model provider you actually want to use. + +## Why Use CCR + +- Use one local endpoint for multiple agent tools instead of configuring every client separately. +- Route requests with default routing, conditional rules, fallback targets, and request rewrites instead of editing client configuration by hand. +- Mix providers without changing your workflow. CCR supports OpenAI-compatible APIs, Anthropic Messages, Gemini Generate Content, OpenRouter, DeepSeek, SiliconFlow, Moonshot, Kimi Code, Mistral, Z.AI, Bailian, and custom providers. +- Control cost and reliability with fallback routing, API key rotation, usage statistics, and request logs. + +## Features + +- **Overview dashboard**: inspect system status, usage widgets, account balances, model distribution, and share cards. +- **Provider management**: add provider presets or custom endpoints, probe protocol support, test model connectivity, manage credentials, and monitor supported account balances where available. +- **Routing rules**: configure default routing, conditional and model-prefix rules, fallback handling, and request rewrites. +- **Agent Config**: configure Claude Code, Codex, and ZCode launch entries, models, scopes, and multi-instance app profiles. +- **Gateway compatibility**: translate supported client requests through the local CCR model gateway. +- **Proxy mode**: capture supported API traffic through a local proxy with optional system proxy integration and network capture. +- **Fusion models**: combine a base model with vision, web search, or MCP tools into a reusable selectable model. + +## Documentation + +Read the full documentation at [ccrdesk.top](https://ccrdesk.top/). + +## Download And Install + +1. Open the [GitHub Releases page](https://github.com/musistudio/claude-code-router/releases). +2. Download the package for your platform: + - macOS Apple Silicon: `Claude-Code-Router_-mac-Apple-Silicon-arm64.dmg` or `.zip` + - macOS Intel: `Claude-Code-Router_-mac-Intel-x64.dmg` or `.zip` + - Windows: `Claude Code Router_.exe` + - Linux: `Claude Code Router_.AppImage` +3. Install and launch **Claude Code Router**. +4. On first launch, CCR creates its local configuration database: + - macOS/Linux: `~/.claude-code-router/config.sqlite` + - Windows: `%APPDATA%\Claude Code Router\config.sqlite` + +CCR stores runtime configuration in SQLite. A legacy `config.json` is read only once for migration when no SQLite config exists. + +After the service is started from the **Server** page, CCR listens on `http://localhost:8080` by default. The **Server** page controls the gateway `Host`, `Port`, proxy mode, system proxy, network capture, and CA certificate status. + +## Quick Start + +CCR can be configured entirely from the desktop UI. Use this setup order for a clean first run. + +### 1. Add a provider + +Open **Providers**, click **Add Provider**, then choose a built-in preset or **Other / custom API endpoint**. Fill in the provider name, base URL, protocol, API key, and model list. Run protocol probing and model connectivity checks when available, then save the provider. + +### 2. Configure routing + +Open **Routing** to add conditional rules, configure request rewrites, and set fallback behavior. + +Use **Add Routing Rule** for request conditions, model-prefix routing, or rule-level fallback targets. + +### 3. Start the gateway + +Open **Server** and click **Start**. After the page shows Running, CCR listens on `http://localhost:8080`. Enable **Auto start** if you want CCR to start the local gateway whenever the desktop app opens. + +### 4. Connect your agent tool + +Open **Agent Config** and choose the client you want to use. Configure Claude Code, Codex, or ZCode, select the target model and effect scope, then apply the config. For app entries, use the **Open Agent** action to open the target app through CCR. + +### 5. Monitor and adjust + +Use **Settings → Logs & Observability** to enable request logs and agent observability. Use **Logs** to confirm `request model`, `resolved provider`, `resolved model`, status, tokens, latency, and errors; use the tray window for quick token and account status. + +## Acknowledgements + +Codex support is powered by [musistudio/codexl](https://github.com/musistudio/codexl). + +## Support & Sponsoring + +
+ +

If you find this project helpful, please consider sponsoring its development. Your support is greatly appreciated.

+ + + + + + +
+ + Support on Ko-fi + +
+ One-time support via Ko-fi +
+ + Sponsor with PayPal + +
+ International sponsorship +
+ + + + + + +
+ Alipay +
+ Alipay QR code +
+ WeChat Pay +
+ WeChat Pay QR code +
+ +
+ +### Our Sponsors + +
+ +

A huge thank you to all our sponsors for their generous support.

+ + + + + + + + + + + + +
+ + Zhipu icon +
+ Z智谱 +
+
+ + AIHubmix icon +
+ AIHubmix +
+
+ + BurnCloud icon +
+ BurnCloud +
+
+ + 302.AI icon +
+ 302.AI +
+
+ + RunAPI icon +
+ RunAPI +
+
+ + TeamoRouter icon +
+ TeamoRouter +
+
+ +

Community Sponsors

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@Simon Leischnig@duanshuaimin@vrgitadmin@*o@ceilwoo@*说
@*更@K*g@R*R@bobleer@*苗@*划
@Clarence-pan@carter003@S*r@*晖@*敏@Z*z
@*然@cluic@*苗@PromptExpert@*应@yusnake
@*飞@董*@*汀@*涯@*:-)@**磊
@*琢@*成@Z*o@*琨@congzhangzh@*_
@Z*m@*鑫@c*y@*昕@witsice@b*g
@*亿@*辉@JACK@*光@W*l@kesku
@biguncle@二吉吉@a*g@*林@*咸@*明
@S*y@f*o@*智@F*t@r*c@qierkang
@*军@snrise-z@*王@greatheart1000@*王@zcutlip
@Peng-YM@*更@*.@F*t@*政@*铭
@*叶@七*o@*青@**晨@*远@*霄
@**吉@**飞@**驰@x*g@**东@*落
@哆*k@*涛@苗大@*呢@d*u@crizcraig
s*s*火*勤**锟*涛**明
*知*语*瓜
+ +If your name is masked, please contact me via my homepage email to update it with your GitHub username. + +
+ +## License + +This project is licensed under the [MIT License](LICENSE). diff --git a/packages/cli/README_zh.md b/packages/cli/README_zh.md new file mode 100644 index 0000000..cd79161 --- /dev/null +++ b/packages/cli/README_zh.md @@ -0,0 +1,328 @@ +

Claude Code Router Desktop

+ +

+ English README + Discord + X + License + 文档 +

+ +

+ Claude Code Router Desktop 项目截图 +

+ +Claude Code Router Desktop 是一个本地网关和桌面控制台,用来把 Claude Code、Codex、ZCode 以及兼容客户端的 Agent 请求路由到你真正想使用的模型服务。 + +## 为什么使用 CCR + +- 用一个本地入口连接多个 Agent 工具,不需要在每个客户端里重复配置 Provider。 +- 在不改变工作流的情况下混用不同 Provider。CCR 支持 OpenAI 兼容 API、Anthropic Messages、Gemini Generate Content、OpenRouter、DeepSeek、SiliconFlow、Moonshot、Kimi Code、Mistral、Z.AI、百炼以及自定义 Provider。 +- 通过 fallback 路由、API Key 轮换、用量统计和请求日志来控制成本和可靠性。 + +## 功能和特性 + +- **概览仪表盘**:查看系统状态、用量组件、账号余额、模型分布和分享卡片。 +- **Provider 管理**:添加预设或自定义端点,探测协议支持,检测模型连通性,管理凭据,并在可用时查看账号余额。 +- **路由规则**:配置条件路由、模型前缀规则、失败降级和请求改写。 +- **Agent配置**:为 Claude Code、Codex 和 ZCode 配置启动入口、模型、作用范围和多开 App 配置。 +- **网关兼容层**:通过本地 CCR 模型网关转换支持的客户端请求。 +- **代理模式**:通过本地代理捕获支持的 API 流量,可选系统代理和网络捕获。 +- **Fusion 组合模型**:把基础模型与视觉、联网搜索或 MCP 工具组合成新的可选模型。 + +## 文档 + +完整文档见 [ccrdesk.top](https://ccrdesk.top/)。 + +## 下载和安装 + +1. 打开 [GitHub Releases 页面](https://github.com/musistudio/claude-code-router/releases)。 +2. 按系统下载对应安装包: + - macOS Apple 芯片:`Claude-Code-Router_-mac-Apple-Silicon-arm64.dmg` 或 `.zip` + - macOS Intel 芯片:`Claude-Code-Router_-mac-Intel-x64.dmg` 或 `.zip` + - Windows:`Claude Code Router_.exe` + - Linux:`Claude Code Router_.AppImage` +3. 安装并启动 **Claude Code Router**。 +4. 首次启动后,CCR 会创建本地配置数据库: + - macOS/Linux:`~/.claude-code-router/config.sqlite` + - Windows:`%APPDATA%\Claude Code Router\config.sqlite` + +CCR 的运行配置存储在 SQLite 中。旧版 `config.json` 只会在没有 SQLite 配置时作为迁移来源读取一次。 + +从 **服务** 页面启动后,CCR 默认监听 `http://localhost:8080`。**服务** 页面负责配置网关 `Host`、`Port`、代理模式、系统代理、网络捕获和 CA 证书状态。 + +## 快速开始 + +CCR 可以完全通过桌面 UI 完成配置。首次使用建议按下面顺序操作。 + +### 1. 添加 Provider + +打开 **供应商**,点击 **添加供应商**,选择内置预设或 **其他 / 自定义 API 端点**。按表单填写 Provider 名称、基础 URL、协议、API Key 和模型列表。可用时先运行协议探测和模型连通性检查,然后保存 Provider。 + +### 2. 设置路由 + +打开 **路由**,添加条件规则,配置请求改写和失败降级。 + +如果需要更细粒度控制,使用 **添加路由规则** 添加模型前缀、请求条件或规则级失败降级目标。 + +### 3. 启动网关 + +打开 **服务**,点击 **启动**。页面显示运行中后,CCR 会在本机监听 `http://localhost:8080`。如果希望每次打开桌面应用时自动启动网关,可以启用自动启动。 + +### 4. 连接 Agent 工具 + +打开 **Agent配置**,选择要使用的客户端。配置 Claude Code、Codex 或 ZCode,选择目标模型和作用范围,然后应用配置。对于 App 入口,可以使用 **打开 Agent** 操作通过 CCR 打开目标应用。 + +### 5. 日常查看和调整 + +到 **设置 → 日志与观测** 打开请求日志和 Agent 观测。使用 **日志** 确认 `request model`、`resolved provider`、`resolved model`、状态码、tokens、耗时和错误;使用托盘窗口快速查看 Token 和账号状态。 + +## 致谢 + +对 Codex 的支持来自于 [musistudio/codexl](https://github.com/musistudio/codexl) 这个项目。 + +## 支持与赞助 + +
+ +

如果你觉得这个项目有帮助,欢迎赞助项目开发。非常感谢你的支持。

+ + + + + + +
+ + 通过 Ko-fi 赞助 + +
+ 通过 Ko-fi 单次赞助 +
+ + 通过 PayPal 赞助 + +
+ 国际赞助通道 +
+ + + + + + +
+ 支付宝 +
+ 支付宝收款码 +
+ 微信支付 +
+ 微信支付收款码 +
+ +
+ +### 我们的赞助商 + +
+ +

非常感谢所有赞助商的慷慨支持。

+ + + + + + + + + + + + +
+ + 智谱图标 +
+ Z智谱 +
+
+ + AIHubmix 图标 +
+ AIHubmix +
+
+ + BurnCloud 图标 +
+ BurnCloud +
+
+ + 302.AI 图标 +
+ 302.AI +
+
+ + RunAPI 图标 +
+ RunAPI +
+
+ + TeamoRouter 图标 +
+ TeamoRouter +
+
+ +

社区赞助者

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@Simon Leischnig@duanshuaimin@vrgitadmin@*o@ceilwoo@*说
@*更@K*g@R*R@bobleer@*苗@*划
@Clarence-pan@carter003@S*r@*晖@*敏@Z*z
@*然@cluic@*苗@PromptExpert@*应@yusnake
@*飞@董*@*汀@*涯@*:-)@**磊
@*琢@*成@Z*o@*琨@congzhangzh@*_
@Z*m@*鑫@c*y@*昕@witsice@b*g
@*亿@*辉@JACK@*光@W*l@kesku
@biguncle@二吉吉@a*g@*林@*咸@*明
@S*y@f*o@*智@F*t@r*c@qierkang
@*军@snrise-z@*王@greatheart1000@*王@zcutlip
@Peng-YM@*更@*.@F*t@*政@*铭
@*叶@七*o@*青@**晨@*远@*霄
@**吉@**飞@**驰@x*g@**东@*落
@哆*k@*涛@苗大@*呢@d*u@crizcraig
s*s*火*勤**锟*涛**明
*知*语*瓜
+ +如果你的名字被打码,请通过我的主页邮箱联系我更新为 GitHub 用户名。 + +
+ +## 许可证 + +本项目基于 [MIT License](LICENSE) 发布。 diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000..9f9eb2c --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,48 @@ +{ + "name": "@musistudio/claude-code-router", + "version": "3.0.7", + "license": "MIT", + "description": "Local Claude Code Router gateway with CLI and web management UI.", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/musistudio/claude-code-router.git" + }, + "bugs": { + "url": "https://github.com/musistudio/claude-code-router/issues" + }, + "homepage": "https://github.com/musistudio/claude-code-router#readme", + "keywords": [ + "claude-code", + "codex", + "llm", + "gateway", + "router" + ], + "main": "dist/main/cli.js", + "bin": { + "ccr": "dist/main/cli.js" + }, + "files": [ + "dist", + "LICENSE", + "README.md", + "README_zh.md" + ], + "engines": { + "node": ">=22" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "prepack": "npm --prefix ../.. run build:assets", + "prepublishOnly": "npm --prefix ../.. run typecheck" + }, + "dependencies": { + "@the-next-ai/ai-gateway": "^1.0.4", + "@the-next-ai/bot-gateway-sdk": "^0.1.0", + "better-sqlite3": "^12.11.1", + "node-forge": "^1.4.0", + "undici": "^7.27.2" + } +} diff --git a/src/main/cli.ts b/packages/cli/src/cli.ts similarity index 72% rename from src/main/cli.ts rename to packages/cli/src/cli.ts index 8964d4f..8a957de 100644 --- a/src/main/cli.ts +++ b/packages/cli/src/cli.ts @@ -1,18 +1,19 @@ #!/usr/bin/env node import { spawn, spawnSync } from "node:child_process"; +import { randomBytes } from "node:crypto"; import { accessSync, constants as fsConstants, existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs"; import path from "node:path"; -import { assertAvailableGatewayModels, type ProfileConfig, type ProfileOpenSurface } from "../shared/app"; -import { botGatewayProfileEnv } from "./bot-gateway-env"; -import { applyClaudeAppGatewayConfig } from "./claude-app-gateway-service"; -import { launchClaudeAppProfile, resolveClaudeAppProfileUserDataDir } from "./claude-app-launch"; -import { launchCodexAppProfile, launchZcodeAppProfile } from "./codex-app-launch"; -import { loadAppConfig } from "./config"; -import { CONFIGDIR } from "./constants"; -import { applyProfileConfig, applyProfileRuntimeConfig } from "./profile-service"; -import { ensureProfileGateway } from "./profile-launch-service"; -import { buildProfileLaunchPlan, defaultProfileOpenSurface, findProfileForOpen, profileLaunchSpawnCommand, resolveProfileOpenSurface } from "./profile-launch-core"; -import { startWebManagementServer } from "./web-management-server"; +import { botGatewayProfileEnv } from "@ccr/core/agents/bot-gateway/env"; +import { applyClaudeAppGatewayConfig } from "@ccr/core/agents/claude-app/gateway-service"; +import { launchClaudeAppProfile, resolveClaudeAppProfileUserDataDir } from "@ccr/core/agents/claude-app/launch"; +import { launchCodexAppProfile, launchZcodeAppProfile } from "@ccr/core/agents/codex/app-launch"; +import { loadAppConfig } from "@ccr/core/config/config"; +import { CONFIGDIR } from "@ccr/core/config/constants"; +import { applyProfileConfig, applyProfileRuntimeConfig } from "@ccr/core/profiles/service"; +import { ensureProfileGateway } from "@ccr/core/profiles/launch-service"; +import { buildProfileLaunchPlan, defaultProfileOpenSurface, findProfileForOpen, profileLaunchSpawnCommand, resolveProfileOpenSurface } from "@ccr/core/profiles/launch-core"; +import { openSystemExternal, startWebManagementServer } from "@ccr/core/web/management-server"; +import { assertAvailableGatewayModels, type ProfileConfig, type ProfileOpenSurface } from "@ccr/core/contracts/app"; type ProfileCliOptions = { agentArgs: string[]; @@ -23,7 +24,7 @@ type ProfileCliOptions = { }; type WebCliOptions = { - command: "start" | "web"; + command: "start" | "ui" | "web"; daemonChild: boolean; help: boolean; host?: string; @@ -42,14 +43,19 @@ type CliOptions = ProfileCliOptions | StopCliOptions | WebCliOptions; type ServiceState = { host?: string; pid: number; + serviceToken?: string; startedAt: string; startGateway: boolean; url: string; }; const serviceStateFileName = "service.json"; +const serviceInstanceTokenEnv = "CCR_SERVICE_INSTANCE_TOKEN"; +const serviceRpcTimeoutMs = 2_000; const serviceStartTimeoutMs = 30_000; const serviceStopTimeoutMs = 10_000; +const webAuthHeader = "x-ccr-web-auth"; +const webAuthQueryParam = "ccr_web_token"; async function main(): Promise { const delegatedExitCode = delegateManagedDesktopCliToExternalCli(); @@ -67,6 +73,14 @@ async function main(): Promise { await startService(options); return; } + if (options.command === "ui") { + if (options.help) { + printUiHelp(0); + return; + } + await openManagementUi(options); + return; + } if (options.command === "stop") { if (options.help) { printStopHelp(0); @@ -175,6 +189,9 @@ function parseArgs(args: string[]): CliOptions { if (args[0] === "start") { return parseWebArgs(args.slice(1), "start"); } + if (args[0] === "ui") { + return parseWebArgs(args.slice(1), "ui", true); + } if (args[0] === "stop") { return parseStopArgs(args.slice(1)); } @@ -244,12 +261,12 @@ function parseStopArgs(args: string[]): StopCliOptions { return options; } -function parseWebArgs(args: string[], command: WebCliOptions["command"]): WebCliOptions { +function parseWebArgs(args: string[], command: WebCliOptions["command"], defaultOpen = false): WebCliOptions { const options: WebCliOptions = { command, daemonChild: false, help: false, - open: false, + open: defaultOpen, startGateway: true }; for (let index = 0; index < args.length; index += 1) { @@ -303,24 +320,31 @@ function parseWebArgs(args: string[], command: WebCliOptions["command"]): WebCli async function startService(options: WebCliOptions): Promise { const current = readServiceState(); - if (current && isProcessRunning(current.pid)) { + const currentVerification = current ? await verifyServiceState(current) : undefined; + if (current && currentVerification?.ok) { process.stdout.write(`CCR service is already running at ${current.url} (pid ${current.pid}).\n`); + if (options.open) { + await openManagementUrl(current.url); + } return; } - clearServiceState(); + if (current) { + clearServiceState(current.pid); + } + const serviceToken = generateServiceToken(); const childArgs = [ currentCliScript(), "serve", "--daemon-child", ...(options.host ? ["--host", options.host] : []), ...(options.port ? ["--port", String(options.port)] : []), - ...(options.open ? ["--open"] : ["--no-open"]), + "--no-open", ...(options.startGateway ? [] : ["--no-gateway"]) ]; const child = spawn(process.execPath, childArgs, { detached: true, - env: serviceChildEnv(), + env: serviceChildEnv(serviceToken), stdio: "ignore", windowsHide: true }); @@ -335,10 +359,31 @@ async function startService(options: WebCliOptions): Promise { throw new Error(`CCR service did not report ready within ${serviceStartTimeoutMs}ms.`); } process.stdout.write(`CCR service started at ${state.url} (pid ${state.pid}).\n`); + if (options.open) { + await openManagementUrl(state.url); + } } -function serviceChildEnv(): NodeJS.ProcessEnv { +async function openManagementUi(options: WebCliOptions): Promise { + await startService({ + ...options, + command: "start" + }); +} + +async function openManagementUrl(url: string): Promise { + try { + await openSystemExternal(url); + process.stdout.write(`Opened CCR management UI at ${url}\n`); + } catch (error) { + process.stderr.write(`Failed to open browser: ${formatError(error)}\n`); + process.stdout.write(`CCR management UI is available at ${url}\n`); + } +} + +function serviceChildEnv(serviceToken: string): NodeJS.ProcessEnv { const env = { ...process.env }; + env[serviceInstanceTokenEnv] = serviceToken; if (process.versions.electron) { env.ELECTRON_RUN_AS_NODE = "1"; } else { @@ -355,9 +400,11 @@ async function runWebServer(options: WebCliOptions): Promise { startGateway: options.startGateway }); if (options.daemonChild) { + const serviceToken = process.env[serviceInstanceTokenEnv]?.trim() || undefined; writeServiceState({ host: options.host, pid: process.pid, + ...(serviceToken ? { serviceToken } : {}), startedAt: new Date().toISOString(), startGateway: options.startGateway, url: runtime.url @@ -389,15 +436,19 @@ async function stopService(): Promise { process.stdout.write("CCR service is not running.\n"); return; } - if (!isProcessRunning(state.pid)) { + const verification = await verifyServiceState(state); + if (!verification.ok) { clearServiceState(state.pid); process.stdout.write("CCR service is not running.\n"); return; } - process.kill(state.pid, "SIGTERM"); - const stopped = await waitForProcessExit(state.pid, serviceStopTimeoutMs); - if (!stopped && isProcessRunning(state.pid)) { - throw new Error(`CCR service pid ${state.pid} did not stop within ${serviceStopTimeoutMs}ms.`); + + await callServiceRpc(state, "quitApp"); + const stopped = verification.trustedPid + ? await waitForProcessExit(state.pid, serviceStopTimeoutMs) + : await waitForServiceUnavailable(state, serviceStopTimeoutMs); + if (!stopped) { + throw new Error(`CCR service did not stop within ${serviceStopTimeoutMs}ms.`); } clearServiceState(state.pid); process.stdout.write("CCR service stopped.\n"); @@ -407,11 +458,13 @@ function printHelp(exitCode: number): void { const output = [ "Usage:", " ccr start [--host ] [--port ] [--open] [--no-gateway]", + " ccr ui [--host ] [--port ] [--no-gateway]", " ccr stop", " ccr [cli|app] [-- ]", "", "Examples:", " ccr start", + " ccr ui", " ccr stop", " ccr Codex", " ccr default-codex -- --model gpt-5-codex", @@ -432,7 +485,31 @@ function printStartHelp(exitCode: number): void { " --port Management server port. Defaults to 3458.", " --open Open the management page in the default browser.", " --no-open Do not open the management page.", - " --no-gateway Start only the web management server." + " --no-gateway Start only the web management server.", + "", + "Environment:", + " CCR_WEB_AUTH_TOKEN Use this token for management UI and RPC authentication." + ].join("\n"); + const stream = exitCode === 0 ? process.stdout : process.stderr; + stream.write(`${output}\n`); + process.exitCode = exitCode; +} + +function printUiHelp(exitCode: number): void { + const output = [ + "Usage:", + " ccr ui [--host ] [--port ] [--no-gateway]", + "", + "Starts the background CCR service if needed and opens the management UI in the default browser.", + "", + "Options:", + " --host Management server host. Defaults to 127.0.0.1.", + " --port Management server port. Defaults to 3458.", + " --no-open Start or find the service and print the management URL without opening a browser.", + " --no-gateway Start only the web management server when the service is not already running.", + "", + "Environment:", + " CCR_WEB_AUTH_TOKEN Use this token for management UI and RPC authentication." ].join("\n"); const stream = exitCode === 0 ? process.stdout : process.stderr; stream.write(`${output}\n`); @@ -460,7 +537,10 @@ function printWebHelp(exitCode: number): void { " --host Management server host. Defaults to 127.0.0.1.", " --port Management server port. Defaults to 3458.", " --open Open the management page in the default browser.", - " --no-gateway Start only the web management server." + " --no-gateway Start only the web management server.", + "", + "Environment:", + " CCR_WEB_AUTH_TOKEN Use this token for management UI and RPC authentication." ].join("\n"); const stream = exitCode === 0 ? process.stdout : process.stderr; stream.write(`${output}\n`); @@ -481,6 +561,7 @@ function readServiceState(): ServiceState | undefined { return { host: parsed.host, pid, + serviceToken: typeof parsed.serviceToken === "string" && parsed.serviceToken.trim() ? parsed.serviceToken.trim() : undefined, startedAt: parsed.startedAt || "", startGateway: parsed.startGateway !== false, url: parsed.url @@ -608,7 +689,7 @@ async function waitForServiceState(pid: number | undefined, timeoutMs: number): const startedAt = Date.now(); while (Date.now() - startedAt < timeoutMs) { const state = readServiceState(); - if (state && (!pid || state.pid === pid) && isProcessRunning(state.pid)) { + if (state && (!pid || state.pid === pid) && (await verifyServiceState(state)).ok) { return state; } await delay(150); @@ -640,6 +721,98 @@ function isProcessRunning(pid: number | undefined): boolean { } } +type ServiceStateVerification = + | { ok: true; trustedPid: boolean } + | { ok: false }; + +type ServiceIdentity = { + pid?: unknown; + serviceTokenConfigured?: unknown; + serviceTokenMatches?: unknown; +}; + +async function verifyServiceState(state: ServiceState): Promise { + if (!isProcessRunning(state.pid)) { + return { ok: false }; + } + + if (state.serviceToken) { + const identity = await callServiceRpc(state, "getServiceIdentity", [state.serviceToken]).catch(() => undefined); + if (identity?.serviceTokenMatches === true && Number(identity.pid) === state.pid) { + return { ok: true, trustedPid: true }; + } + return { ok: false }; + } + + const appInfo = await callServiceRpc<{ name?: unknown }>(state, "getAppInfo").catch(() => undefined); + return appInfo?.name === "Claude Code Router" + ? { ok: true, trustedPid: false } + : { ok: false }; +} + +async function waitForServiceUnavailable(state: ServiceState, timeoutMs: number): Promise { + const startedAt = Date.now(); + while (Date.now() - startedAt < timeoutMs) { + const appInfo = await callServiceRpc<{ name?: unknown }>(state, "getAppInfo").catch(() => undefined); + if (appInfo?.name !== "Claude Code Router") { + return true; + } + await delay(150); + } + const appInfo = await callServiceRpc<{ name?: unknown }>(state, "getAppInfo").catch(() => undefined); + return appInfo?.name !== "Claude Code Router"; +} + +async function callServiceRpc(state: ServiceState, method: string, args: unknown[] = []): Promise { + const endpoint = serviceRpcEndpoint(state.url); + const authToken = serviceAuthToken(state.url); + if (!endpoint || !authToken) { + throw new Error("CCR service state does not include a usable management URL."); + } + + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), serviceRpcTimeoutMs); + try { + const response = await fetch(endpoint, { + body: JSON.stringify({ args, method }), + headers: { + "content-type": "application/json", + [webAuthHeader]: authToken + }, + method: "POST", + signal: controller.signal + }); + const payload = await response.json().catch(() => undefined) as { ok?: boolean; value?: T } | undefined; + if (!response.ok || !payload?.ok) { + throw new Error(`CCR service RPC ${method} failed with HTTP ${response.status}`); + } + return payload.value as T; + } finally { + clearTimeout(timer); + } +} + +function serviceRpcEndpoint(url: string): string | undefined { + try { + const parsed = new URL(url); + return `${parsed.origin}/api/ccr/rpc`; + } catch { + return undefined; + } +} + +function serviceAuthToken(url: string): string { + try { + return new URL(url).searchParams.get(webAuthQueryParam)?.trim() ?? ""; + } catch { + return ""; + } +} + +function generateServiceToken(): string { + return randomBytes(32).toString("base64url"); +} + function delay(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 0000000..1f70006 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src/**/*.ts", "src/**/*.d.ts"] +} diff --git a/models.json b/packages/core/models.json similarity index 100% rename from models.json rename to packages/core/models.json diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..a063ef1 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,21 @@ +{ + "name": "@claude-code-router/core", + "version": "3.0.7", + "private": true, + "description": "Claude Code Router core gateway, routing, provider, and storage services.", + "main": "dist/main/server.js", + "bin": { + "ccr-core-server": "dist/main/server.js" + }, + "engines": { + "node": ">=22" + }, + "dependencies": { + "@the-next-ai/ai-gateway": "^1.0.4", + "@the-next-ai/bot-gateway-sdk": "^0.1.0", + "better-sqlite3": "^12.11.1", + "node-forge": "^1.4.0", + "pm2": "^6.0.13", + "undici": "^7.27.2" + } +} diff --git a/src/main/bot-gateway-env.ts b/packages/core/src/agents/bot-gateway/env.ts similarity index 98% rename from src/main/bot-gateway-env.ts rename to packages/core/src/agents/bot-gateway/env.ts index 524945b..afc2041 100644 --- a/src/main/bot-gateway-env.ts +++ b/packages/core/src/agents/bot-gateway/env.ts @@ -1,8 +1,8 @@ import os from "node:os"; import { createRequire } from "node:module"; import path from "node:path"; -import type { AppConfig, BotGatewayRuntimeConfig, ProfileConfig, ProfileOpenSurface } from "../shared/app"; -import { CONFIGDIR } from "./constants"; +import type { AppConfig, BotGatewayRuntimeConfig, ProfileConfig, ProfileOpenSurface } from "@ccr/core/contracts/app"; +import { CONFIGDIR } from "@ccr/core/config/constants"; const requireFromHere = createRequire(__filename); diff --git a/src/main/bot-handoff-scan-service.ts b/packages/core/src/agents/bot-gateway/handoff-scan-service.ts similarity index 98% rename from src/main/bot-handoff-scan-service.ts rename to packages/core/src/agents/bot-gateway/handoff-scan-service.ts index 8522eb0..59712eb 100644 --- a/src/main/bot-handoff-scan-service.ts +++ b/packages/core/src/agents/bot-gateway/handoff-scan-service.ts @@ -1,7 +1,7 @@ import { execFile } from "node:child_process"; import { promisify } from "node:util"; -import type { BotHandoffScanTarget } from "../shared/app"; -import { windowsSystemCommand } from "./windows-system"; +import type { BotHandoffScanTarget } from "@ccr/core/contracts/app"; +import { windowsSystemCommand } from "@ccr/core/platform/windows-system"; const execFileAsync = promisify(execFile); diff --git a/src/main/bot-gateway-qr-login-service.ts b/packages/core/src/agents/bot-gateway/qr-login-service.ts similarity index 99% rename from src/main/bot-gateway-qr-login-service.ts rename to packages/core/src/agents/bot-gateway/qr-login-service.ts index b44edca..4f60b45 100644 --- a/src/main/bot-gateway-qr-login-service.ts +++ b/packages/core/src/agents/bot-gateway/qr-login-service.ts @@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import { CONFIGDIR } from "./constants"; +import { CONFIGDIR } from "@ccr/core/config/constants"; import type { BotGatewayQrLoginCancelRequest, BotGatewayQrLoginCancelResult, @@ -11,7 +11,7 @@ import type { BotGatewayQrLoginWaitRequest, BotGatewayQrLoginWaitResult, BotGatewayRuntimeConfig -} from "../shared/app"; +} from "@ccr/core/contracts/app"; type BotGatewayClientWithRequest = { close?: () => Promise | void; diff --git a/src/main/claude-app-cdp.ts b/packages/core/src/agents/claude-app/cdp.ts similarity index 100% rename from src/main/claude-app-cdp.ts rename to packages/core/src/agents/claude-app/cdp.ts diff --git a/src/shared/claude-app-gateway.ts b/packages/core/src/agents/claude-app/gateway-routes.ts similarity index 98% rename from src/shared/claude-app-gateway.ts rename to packages/core/src/agents/claude-app/gateway-routes.ts index c4127f7..3ccda7e 100644 --- a/src/shared/claude-app-gateway.ts +++ b/packages/core/src/agents/claude-app/gateway-routes.ts @@ -1,5 +1,5 @@ -import type { AppConfig } from "./app"; -import { normalizeProfileScopeValue } from "./app"; +import type { AppConfig } from "@ccr/core/contracts/app"; +import { normalizeProfileScopeValue } from "@ccr/core/contracts/app"; export const CLAUDE_APP_FALLBACK_MODEL = "claude-sonnet-4-5"; export const CLAUDE_APP_ONE_MILLION_CONTEXT_SUFFIX = "[1m]"; diff --git a/src/main/claude-app-gateway-service.ts b/packages/core/src/agents/claude-app/gateway-service.ts similarity index 97% rename from src/main/claude-app-gateway-service.ts rename to packages/core/src/agents/claude-app/gateway-service.ts index 89d1e35..58c7eff 100644 --- a/src/main/claude-app-gateway-service.ts +++ b/packages/core/src/agents/claude-app/gateway-service.ts @@ -2,16 +2,16 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } import os from "node:os"; import path from "node:path"; import { createHash, randomBytes, randomUUID } from "node:crypto"; -import { resolveRuntimeAppPath } from "./app-paths"; -import { saveAppConfig } from "./config"; -import { CONFIGDIR } from "./constants"; +import { resolveRuntimeAppPath } from "@ccr/core/runtime/app-paths"; +import { saveAppConfig } from "@ccr/core/config/config"; +import { CONFIGDIR } from "@ccr/core/config/constants"; import { buildClaudeAppGatewayInferenceModels, type ClaudeAppGatewayInferenceModel, type ClaudeAppGatewayModelRouteOptions -} from "../shared/claude-app-gateway"; -import { NO_AVAILABLE_GATEWAY_MODELS_MESSAGE, hasAvailableGatewayModels, type ApiKeyConfig, type AppConfig, type ClaudeAppGatewayApplyResult } from "../shared/app"; -import { findModelCatalogEntry } from "../server/gateway/model-catalog"; +} from "@ccr/core/agents/claude-app/gateway-routes"; +import { NO_AVAILABLE_GATEWAY_MODELS_MESSAGE, hasAvailableGatewayModels, type ApiKeyConfig, type AppConfig, type ClaudeAppGatewayApplyResult } from "@ccr/core/contracts/app"; +import { findModelCatalogEntry } from "@ccr/core/gateway/model-catalog"; const CLAUDE_APP_CONFIG_ID = "8f69f2f1-3275-4ad8-9317-4aa7e972f311"; const CLAUDE_APP_CONFIG_NAME = "Claude Code Router"; diff --git a/src/main/claude-app-launch.ts b/packages/core/src/agents/claude-app/launch.ts similarity index 96% rename from src/main/claude-app-launch.ts rename to packages/core/src/agents/claude-app/launch.ts index f4f6ce9..cdfa57d 100644 --- a/src/main/claude-app-launch.ts +++ b/packages/core/src/agents/claude-app/launch.ts @@ -2,12 +2,12 @@ import { spawn, type ChildProcess } from "node:child_process"; import { mkdirSync, readdirSync, readFileSync, statSync } from "node:fs"; import os from "node:os"; import path from "node:path"; -import type { AppConfig, ProfileConfig } from "../shared/app"; -import { botGatewayProfileEnv } from "./bot-gateway-env"; -import { prepareClaudeAppCdpUserDataDir, reserveClaudeAppCdpPort, scheduleClaudeAppDesignCdp } from "./claude-app-cdp"; -import { claudeCodeUtcTimezoneEnvOverride } from "./claude-environment"; -import { resolveClaudeCodeSettingsFile } from "./profile-launch-core"; -import { normalizeWindowsDesktopAppCandidate, windowsDesktopAppCandidates } from "./windows-app-discovery"; +import type { AppConfig, ProfileConfig } from "@ccr/core/contracts/app"; +import { botGatewayProfileEnv } from "@ccr/core/agents/bot-gateway/env"; +import { prepareClaudeAppCdpUserDataDir, reserveClaudeAppCdpPort, scheduleClaudeAppDesignCdp } from "@ccr/core/agents/claude-app/cdp"; +import { claudeCodeUtcTimezoneEnvOverride } from "@ccr/core/agents/claude-code/environment"; +import { resolveClaudeCodeSettingsFile } from "@ccr/core/profiles/launch-core"; +import { normalizeWindowsDesktopAppCandidate, windowsDesktopAppCandidates } from "@ccr/core/platform/windows-app-discovery"; type ClaudeAppLookupResult = { checked: string[]; diff --git a/src/main/claude-environment.ts b/packages/core/src/agents/claude-code/environment.ts similarity index 100% rename from src/main/claude-environment.ts rename to packages/core/src/agents/claude-code/environment.ts diff --git a/src/main/codex-app-launch.ts b/packages/core/src/agents/codex/app-launch.ts similarity index 98% rename from src/main/codex-app-launch.ts rename to packages/core/src/agents/codex/app-launch.ts index 2a70b69..566770d 100644 --- a/src/main/codex-app-launch.ts +++ b/packages/core/src/agents/codex/app-launch.ts @@ -2,12 +2,12 @@ import { spawn, type ChildProcess } from "node:child_process"; import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; -import type { AppConfig, ProfileConfig } from "../shared/app"; -import { botGatewayProfileEnv } from "./bot-gateway-env"; -import { codexModelCatalogJson } from "./codex-model-catalog"; -import { buildProfileLaunchPlan, resolveCodexConfigFile } from "./profile-launch-core"; -import { normalizeWindowsDesktopAppCandidate, windowsDesktopAppCandidates } from "./windows-app-discovery"; -import { writeZcodeGatewayConfig, zcodeHomeFromConfigFile } from "./zcode-profile-config"; +import type { AppConfig, ProfileConfig } from "@ccr/core/contracts/app"; +import { botGatewayProfileEnv } from "@ccr/core/agents/bot-gateway/env"; +import { codexModelCatalogJson } from "@ccr/core/agents/codex/model-catalog"; +import { buildProfileLaunchPlan, resolveCodexConfigFile } from "@ccr/core/profiles/launch-core"; +import { normalizeWindowsDesktopAppCandidate, windowsDesktopAppCandidates } from "@ccr/core/platform/windows-app-discovery"; +import { writeZcodeGatewayConfig, zcodeHomeFromConfigFile } from "@ccr/core/agents/zcode/profile-config"; type CodexAppLookupResult = { checked: string[]; diff --git a/src/main/codex-cli-middleware-runtime.ts b/packages/core/src/agents/codex/cli-middleware-runtime.ts similarity index 100% rename from src/main/codex-cli-middleware-runtime.ts rename to packages/core/src/agents/codex/cli-middleware-runtime.ts diff --git a/src/main/codex-model-catalog.ts b/packages/core/src/agents/codex/model-catalog.ts similarity index 99% rename from src/main/codex-model-catalog.ts rename to packages/core/src/agents/codex/model-catalog.ts index 07ba04d..dd51f76 100644 --- a/src/main/codex-model-catalog.ts +++ b/packages/core/src/agents/codex/model-catalog.ts @@ -1,11 +1,11 @@ -import { BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME } from "../shared/app"; -import type { AppConfig, GatewayProviderConfig, GatewayProviderProtocol, VirtualModelProfileConfig } from "../shared/app"; +import { BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME } from "@ccr/core/contracts/app"; +import type { AppConfig, GatewayProviderConfig, GatewayProviderProtocol, VirtualModelProfileConfig } from "@ccr/core/contracts/app"; import { findModelCatalogEntry, modelCatalogMaxInputTokens, readCatalogCapability, type ModelCatalogEntry -} from "../server/gateway/model-catalog"; +} from "@ccr/core/gateway/model-catalog"; const fusionModelProviderName = "Fusion"; const codexDefaultContextWindow = 128_000; diff --git a/src/main/local-agent-providers/claude-code.ts b/packages/core/src/agents/local-providers/claude-code.ts similarity index 98% rename from src/main/local-agent-providers/claude-code.ts rename to packages/core/src/agents/local-providers/claude-code.ts index ae1ad43..37c05ff 100644 --- a/src/main/local-agent-providers/claude-code.ts +++ b/packages/core/src/agents/local-providers/claude-code.ts @@ -5,7 +5,7 @@ import type { LocalAgentProviderImportResult, ProviderAccountConfig, ProviderAccountMappingConfig -} from "../../shared/app"; +} from "@ccr/core/contracts/app"; import { bearerAuthPlugin, findOauthTokenSet, @@ -16,7 +16,7 @@ import { uniqueProviderName, uniqueStrings, type OAuthTokenSet -} from "./shared"; +} from "@ccr/core/agents/local-providers/shared"; const claudeDefaultModels = ["claude-sonnet-4-20250514"]; diff --git a/src/main/local-agent-providers/codex.ts b/packages/core/src/agents/local-providers/codex.ts similarity index 99% rename from src/main/local-agent-providers/codex.ts rename to packages/core/src/agents/local-providers/codex.ts index eb50bbc..2a16a11 100644 --- a/src/main/local-agent-providers/codex.ts +++ b/packages/core/src/agents/local-providers/codex.ts @@ -9,8 +9,8 @@ import type { ProviderAccountMappingConfig, ProviderAccountMeter, ProviderAccountMeterDetail -} from "../../shared/app"; -import { normalizeProviderBaseUrl } from "../../shared/provider-url"; +} from "@ccr/core/contracts/app"; +import { normalizeProviderBaseUrl } from "@ccr/core/providers/url"; import { isRecord, localAgentProviderApiKey, @@ -26,7 +26,7 @@ import { uniqueProviderName, uniqueStrings, type OAuthTokenSet -} from "./shared"; +} from "@ccr/core/agents/local-providers/shared"; export const codexDefaultBaseUrl = "https://chatgpt.com/backend-api/codex"; diff --git a/src/main/local-agent-provider-service.ts b/packages/core/src/agents/local-providers/service.ts similarity index 66% rename from src/main/local-agent-provider-service.ts rename to packages/core/src/agents/local-providers/service.ts index 8ef2326..d86415f 100644 --- a/src/main/local-agent-provider-service.ts +++ b/packages/core/src/agents/local-providers/service.ts @@ -2,13 +2,13 @@ import type { LocalAgentProviderCandidate, LocalAgentProviderImportRequest, LocalAgentProviderImportResult -} from "../shared/app"; -import { claudeCodeCandidate, importClaudeCodeProvider } from "./local-agent-providers/claude-code"; -import { codexCandidate, importCodexProvider } from "./local-agent-providers/codex"; -import { importZcodeProvider, zcodeCandidate } from "./local-agent-providers/zcode"; +} from "@ccr/core/contracts/app"; +import { claudeCodeCandidate, importClaudeCodeProvider } from "@ccr/core/agents/local-providers/claude-code"; +import { codexCandidate, importCodexProvider } from "@ccr/core/agents/local-providers/codex"; +import { importZcodeProvider, zcodeCandidate } from "@ccr/core/agents/local-providers/zcode"; -export { codexDefaultBaseUrl, readCodexAuth } from "./local-agent-providers/codex"; -export { localAgentProviderApiKey, type OAuthTokenSet } from "./local-agent-providers/shared"; +export { codexDefaultBaseUrl, readCodexAuth } from "@ccr/core/agents/local-providers/codex"; +export { localAgentProviderApiKey, type OAuthTokenSet } from "@ccr/core/agents/local-providers/shared"; export function getLocalAgentProviderCandidates(): LocalAgentProviderCandidate[] { return [ diff --git a/src/main/local-agent-providers/shared.ts b/packages/core/src/agents/local-providers/shared.ts similarity index 99% rename from src/main/local-agent-providers/shared.ts rename to packages/core/src/agents/local-providers/shared.ts index 34f952e..f70c238 100644 --- a/src/main/local-agent-providers/shared.ts +++ b/packages/core/src/agents/local-providers/shared.ts @@ -5,7 +5,7 @@ import type { LocalAgentProviderKind, ProviderAccountConfig, ProviderDeepLinkPayload -} from "../../shared/app"; +} from "@ccr/core/contracts/app"; export type OAuthTokenSet = { accountId?: string; diff --git a/src/main/local-agent-providers/zcode.ts b/packages/core/src/agents/local-providers/zcode.ts similarity index 98% rename from src/main/local-agent-providers/zcode.ts rename to packages/core/src/agents/local-providers/zcode.ts index 247f754..f32f3c0 100644 --- a/src/main/local-agent-providers/zcode.ts +++ b/packages/core/src/agents/local-providers/zcode.ts @@ -4,8 +4,8 @@ import type { LocalAgentProviderCandidate, LocalAgentProviderImportResult, ProviderAccountConfig -} from "../../shared/app"; -import { findProviderPresetByBaseUrl } from "../presets"; +} from "@ccr/core/contracts/app"; +import { findProviderPresetByBaseUrl } from "@ccr/core/providers/presets/index"; import { apiKeyAuthPlugin, cloneProviderAccountConfig, @@ -21,7 +21,7 @@ import { uniqueProviderName, uniqueStrings, type ApiTokenSet -} from "./shared"; +} from "@ccr/core/agents/local-providers/shared"; type ZcodeConfiguredProvider = { apiKey: string; diff --git a/src/main/zcode-profile-config.ts b/packages/core/src/agents/zcode/profile-config.ts similarity index 98% rename from src/main/zcode-profile-config.ts rename to packages/core/src/agents/zcode/profile-config.ts index 9e62c65..c8ebb35 100644 --- a/src/main/zcode-profile-config.ts +++ b/packages/core/src/agents/zcode/profile-config.ts @@ -1,9 +1,9 @@ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; -import type { AppConfig, ProfileConfig } from "../shared/app"; -import { normalizeRouteSelector } from "../server/gateway/claude-code-router-plugin"; -import { buildCodexModelCatalogIds } from "./codex-model-catalog"; +import type { AppConfig, ProfileConfig } from "@ccr/core/contracts/app"; +import { normalizeRouteSelector } from "@ccr/core/gateway/claude-code-router-plugin"; +import { buildCodexModelCatalogIds } from "@ccr/core/agents/codex/model-catalog"; export type ZcodeProfileConfigWriteResult = { backupFile?: string; diff --git a/src/main/api-key-store.ts b/packages/core/src/config/api-key-store.ts similarity index 97% rename from src/main/api-key-store.ts rename to packages/core/src/config/api-key-store.ts index d60bf0b..76fca9c 100644 --- a/src/main/api-key-store.ts +++ b/packages/core/src/config/api-key-store.ts @@ -1,8 +1,8 @@ import { chmodSync, existsSync, mkdirSync } from "node:fs"; import { dirname } from "node:path"; -import { API_KEYS_DB_FILE, LEGACY_API_KEYS_DB_FILES } from "./constants"; -import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "./sqlite-native"; -import type { ApiKeyConfig, ApiKeyLimitConfig } from "../shared/app"; +import { API_KEYS_DB_FILE, LEGACY_API_KEYS_DB_FILES } from "@ccr/core/config/constants"; +import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "@ccr/core/storage/sqlite-native"; +import type { ApiKeyConfig, ApiKeyLimitConfig } from "@ccr/core/contracts/app"; type SqlDatabase = BetterSqliteDatabase; type SqlValue = bigint | Buffer | number | string | null; diff --git a/src/main/app-config-store.ts b/packages/core/src/config/app-config-store.ts similarity index 98% rename from src/main/app-config-store.ts rename to packages/core/src/config/app-config-store.ts index 837b9cc..13e1209 100644 --- a/src/main/app-config-store.ts +++ b/packages/core/src/config/app-config-store.ts @@ -1,7 +1,7 @@ import { chmodSync, existsSync, mkdirSync } from "node:fs"; import { dirname } from "node:path"; -import { APP_CONFIG_DB_FILE, LEGACY_APP_CONFIG_DB_FILES } from "./constants"; -import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "./sqlite-native"; +import { APP_CONFIG_DB_FILE, LEGACY_APP_CONFIG_DB_FILES } from "@ccr/core/config/constants"; +import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "@ccr/core/storage/sqlite-native"; type SqlDatabase = BetterSqliteDatabase; type SqlValue = bigint | Buffer | number | string | null; diff --git a/src/main/config.ts b/packages/core/src/config/config.ts similarity index 99% rename from src/main/config.ts rename to packages/core/src/config/config.ts index ef2702a..82666fd 100644 --- a/src/main/config.ts +++ b/packages/core/src/config/config.ts @@ -1,12 +1,12 @@ import { createHash, randomBytes } from "node:crypto"; import { existsSync, readFileSync } from "node:fs"; -import { loadPersistedAppConfig, replacePersistedAppConfig } from "./app-config-store"; -import { loadPersistedApiKeys, replacePersistedApiKeys } from "./api-key-store"; -import { CONFIG_FILE, GATEWAY_CONFIG_FILE, LEGACY_CONFIG_FILE, LEGACY_WINDOWS_CONFIG_FILE } from "./constants"; -import { normalizeCodexProviderAccountConfig } from "./local-agent-providers/codex"; -import { CLAUDE_CODE_DEFAULT_ENV, CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV, DEFAULT_OVERVIEW_WIDGETS, DEFAULT_TRAY_COMPONENT_VARIANTS, DEFAULT_TRAY_WIDGETS, DEFAULT_TRAY_WINDOW_MODULES, OVERVIEW_WIDGET_SIZE_VALUES, ROUTER_FALLBACK_MAX_RETRY_COUNT, TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS, enforceSingleEnabledGlobalProfilePerAgent } from "../shared/app"; -import { createDefaultAppConfig } from "../shared/default-config"; -import { findProviderPresetByBaseUrl, providerApiKeySafetyIssue, providerEndpointCanReceiveProviderApiKey } from "./presets"; +import { loadPersistedAppConfig, replacePersistedAppConfig } from "@ccr/core/config/app-config-store"; +import { loadPersistedApiKeys, replacePersistedApiKeys } from "@ccr/core/config/api-key-store"; +import { CONFIG_FILE, GATEWAY_CONFIG_FILE, LEGACY_CONFIG_FILE, LEGACY_WINDOWS_CONFIG_FILE } from "@ccr/core/config/constants"; +import { normalizeCodexProviderAccountConfig } from "@ccr/core/agents/local-providers/codex"; +import { CLAUDE_CODE_DEFAULT_ENV, CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV, DEFAULT_OVERVIEW_WIDGETS, DEFAULT_TRAY_COMPONENT_VARIANTS, DEFAULT_TRAY_WIDGETS, DEFAULT_TRAY_WINDOW_MODULES, OVERVIEW_WIDGET_SIZE_VALUES, ROUTER_FALLBACK_MAX_RETRY_COUNT, TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS, enforceSingleEnabledGlobalProfilePerAgent } from "@ccr/core/contracts/app"; +import { createDefaultAppConfig } from "@ccr/core/config/default-config"; +import { findProviderPresetByBaseUrl, providerApiKeySafetyIssue, providerEndpointCanReceiveProviderApiKey } from "@ccr/core/providers/presets/index"; import type { AppConfig, ApiKeyConfig, @@ -54,7 +54,7 @@ import type { TrayWidgetType, TrayWidgetVariant, TrayWindowModuleId -} from "../shared/app"; +} from "@ccr/core/contracts/app"; type LoadedProfileConfig = Partial> & { claudeCode?: Partial; diff --git a/src/main/constants.ts b/packages/core/src/config/constants.ts similarity index 82% rename from src/main/constants.ts rename to packages/core/src/config/constants.ts index d97409b..b283061 100644 --- a/src/main/constants.ts +++ b/packages/core/src/config/constants.ts @@ -1,20 +1,18 @@ import path from "node:path"; -import { APP_NAME, APP_STORAGE_NAME, LEGACY_CONFIGDIR, resolveRuntimeAppPath } from "./app-paths"; -import { copyMissingDirectoryContents } from "./storage-migration"; +import { APP_NAME, APP_STORAGE_NAME, LEGACY_CONFIGDIR, resolveRuntimeAppPath, resolveRuntimeConfigDir, resolveRuntimeDataDir } from "@ccr/core/runtime/app-paths"; +import { copyMissingDirectoryContents } from "@ccr/core/storage/migration"; -export { IPC_CHANNELS } from "../shared/ipc-channels"; +export { IPC_CHANNELS } from "@ccr/core/contracts/ipc-channels"; export const LEGACY_CONFIG_FILE = path.join(LEGACY_CONFIGDIR, "config.json"); export { APP_NAME, APP_STORAGE_NAME, LEGACY_CONFIGDIR }; -export const CONFIGDIR = process.platform === "win32" - ? path.join(resolveRuntimeAppPath("appData"), APP_STORAGE_NAME) - : LEGACY_CONFIGDIR; +export const CONFIGDIR = resolveRuntimeConfigDir(); export const LEGACY_WINDOWS_CONFIGDIR = path.join(resolveRuntimeAppPath("appData"), APP_NAME); export const LEGACY_WINDOWS_CONFIG_FILE = path.join(LEGACY_WINDOWS_CONFIGDIR, "config.json"); export const CONFIG_FILE = path.join(CONFIGDIR, "config.json"); export const ONBOARDING_FINISHED_FILE = path.join(CONFIGDIR, ".onboard_finished"); -export const DATADIR = resolveRuntimeAppPath("userData"); +export const DATADIR = resolveRuntimeDataDir(); export const APP_CONFIG_DB_FILE = path.join(CONFIGDIR, "config.sqlite"); export const API_KEYS_DB_FILE = path.join(DATADIR, "api-keys.sqlite"); export const LEGACY_APP_CONFIG_DB_FILES = process.platform === "win32" ? [path.join(LEGACY_WINDOWS_CONFIGDIR, "config.sqlite")] : []; diff --git a/src/shared/default-config.ts b/packages/core/src/config/default-config.ts similarity index 99% rename from src/shared/default-config.ts rename to packages/core/src/config/default-config.ts index c7953cd..61b81d8 100644 --- a/src/shared/default-config.ts +++ b/packages/core/src/config/default-config.ts @@ -6,7 +6,7 @@ import { DEFAULT_TRAY_WINDOW_MODULES, type AppConfig, type ProxyRouteTarget -} from "./app"; +} from "@ccr/core/contracts/app"; export const DEFAULT_PROXY_TARGETS: ProxyRouteTarget[] = [ { host: "api.anthropic.com", paths: ["/v1/messages", "/v1/messages/count_tokens"] }, diff --git a/src/shared/app.ts b/packages/core/src/contracts/app.ts similarity index 100% rename from src/shared/app.ts rename to packages/core/src/contracts/app.ts diff --git a/src/shared/deep-link.ts b/packages/core/src/contracts/deep-link.ts similarity index 99% rename from src/shared/deep-link.ts rename to packages/core/src/contracts/deep-link.ts index 5b9999e..b8bb98a 100644 --- a/src/shared/deep-link.ts +++ b/packages/core/src/contracts/deep-link.ts @@ -6,8 +6,8 @@ import type { ProviderDeepLinkPayload, ProviderDeepLinkRequest, ProviderManifestDeepLinkPayload -} from "./app"; -import { providerUrlWithDefaultScheme } from "./provider-url"; +} from "@ccr/core/contracts/app"; +import { providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; export const appDeepLinkProtocol = "ccr"; export const providerDeepLinkHost = "provider"; diff --git a/src/shared/i18n.ts b/packages/core/src/contracts/i18n.ts similarity index 100% rename from src/shared/i18n.ts rename to packages/core/src/contracts/i18n.ts diff --git a/src/shared/ipc-channels.ts b/packages/core/src/contracts/ipc-channels.ts similarity index 100% rename from src/shared/ipc-channels.ts rename to packages/core/src/contracts/ipc-channels.ts diff --git a/packages/core/src/entrypoints/server.ts b/packages/core/src/entrypoints/server.ts new file mode 100644 index 0000000..2059d8a --- /dev/null +++ b/packages/core/src/entrypoints/server.ts @@ -0,0 +1,129 @@ +#!/usr/bin/env node +import { startWebManagementServer } from "@ccr/core/web/management-server"; + +type CoreServerOptions = { + help: boolean; + host?: string; + open: boolean; + port?: number; + startGateway: boolean; +}; + +export async function runCoreServer(args = process.argv.slice(2)): Promise { + const options = parseCoreServerArgs(args); + if (options.help) { + printHelp(); + return; + } + + const runtime = await startWebManagementServer({ + host: options.host, + open: options.open, + port: options.port, + startGateway: options.startGateway + }); + process.stdout.write(`CCR core server is running at ${runtime.url}\n`); + + let closing = false; + const shutdown = (signal: NodeJS.Signals) => { + if (closing) { + return; + } + closing = true; + void runtime.close().finally(() => { + process.exit(signal === "SIGINT" ? 130 : 143); + }); + }; + process.once("SIGINT", shutdown); + process.once("SIGTERM", shutdown); + await new Promise(() => undefined); +} + +function parseCoreServerArgs(args: string[]): CoreServerOptions { + const options: CoreServerOptions = { + help: false, + open: false, + startGateway: true + }; + + for (let index = 0; index < args.length; index += 1) { + const arg = args[index]; + if (arg === "--help" || arg === "-h") { + options.help = true; + continue; + } + if (arg === "--open") { + options.open = true; + continue; + } + if (arg === "--no-open") { + options.open = false; + continue; + } + if (arg === "--gateway") { + options.startGateway = true; + continue; + } + if (arg === "--no-gateway") { + options.startGateway = false; + continue; + } + if (arg === "--host") { + index += 1; + options.host = requiredArg(args[index], "--host"); + continue; + } + if (arg.startsWith("--host=")) { + options.host = requiredArg(arg.slice("--host=".length), "--host"); + continue; + } + if (arg === "--port") { + index += 1; + options.port = parsePort(requiredArg(args[index], "--port")); + continue; + } + if (arg.startsWith("--port=")) { + options.port = parsePort(requiredArg(arg.slice("--port=".length), "--port")); + continue; + } + throw new Error(`Unknown core server option: ${arg}`); + } + + return options; +} + +function printHelp(): void { + process.stdout.write([ + "Usage:", + " ccr-core-server [--host ] [--port ] [--no-gateway]", + "", + "Options:", + " --host Management server host. Defaults to CCR_WEB_HOST or 127.0.0.1.", + " --port Management server port. Defaults to CCR_WEB_PORT or 3458.", + " --no-gateway Start only the web management server.", + "", + "Environment:", + " CCR_WEB_AUTH_TOKEN Use this token for management UI and RPC authentication." + ].join("\n") + "\n"); +} + +function requiredArg(value: string | undefined, flag: string): string { + if (!value) { + throw new Error(`Missing value for ${flag}`); + } + return value; +} + +function parsePort(value: string): number { + const port = Number(value); + if (!Number.isInteger(port) || port <= 0 || port > 65535) { + throw new Error(`Invalid port: ${value}`); + } + return port; +} + +runCoreServer().catch((error) => { + const message = error instanceof Error ? error.message : String(error); + process.stderr.write(`${message}\n`); + process.exitCode = 1; +}); diff --git a/src/server/gateway/claude-code-router-plugin.ts b/packages/core/src/gateway/claude-code-router-plugin.ts similarity index 99% rename from src/server/gateway/claude-code-router-plugin.ts rename to packages/core/src/gateway/claude-code-router-plugin.ts index 18d6d51..765d9cd 100644 --- a/src/server/gateway/claude-code-router-plugin.ts +++ b/packages/core/src/gateway/claude-code-router-plugin.ts @@ -2,8 +2,8 @@ import { createRequire } from "node:module"; import { EventEmitter } from "node:events"; import os from "node:os"; import path from "node:path"; -import { availableGatewayModelIds, type AppConfig, type RouterBuiltInAgentRuleId, type RouterConfig, type RouterFallbackConfig, type RouterRule, type RouterRuleCondition, type RouterRuleRewrite } from "../../shared/app"; -import { CONFIGDIR } from "../../main/constants"; +import { availableGatewayModelIds, type AppConfig, type RouterBuiltInAgentRuleId, type RouterConfig, type RouterFallbackConfig, type RouterRule, type RouterRuleCondition, type RouterRuleRewrite } from "@ccr/core/contracts/app"; +import { CONFIGDIR } from "@ccr/core/config/constants"; type HeaderValue = string | string[] | undefined; diff --git a/src/server/gateway/model-catalog.ts b/packages/core/src/gateway/model-catalog.ts similarity index 99% rename from src/server/gateway/model-catalog.ts rename to packages/core/src/gateway/model-catalog.ts index 5220e95..b32e2e6 100644 --- a/src/server/gateway/model-catalog.ts +++ b/packages/core/src/gateway/model-catalog.ts @@ -1,4 +1,4 @@ -import { loadModelCatalogPayload } from "../../main/model-catalog-file"; +import { loadModelCatalogPayload } from "@ccr/core/models/catalog-file"; const claudeCodeDefaultContextTokens = 200_000; let modelCatalogIndex: ModelCatalogIndex | undefined; diff --git a/src/server/gateway/remote-control-service.ts b/packages/core/src/gateway/remote-control-service.ts similarity index 100% rename from src/server/gateway/remote-control-service.ts rename to packages/core/src/gateway/remote-control-service.ts diff --git a/src/server/gateway/service.ts b/packages/core/src/gateway/service.ts similarity index 99% rename from src/server/gateway/service.ts rename to packages/core/src/gateway/service.ts index 1680d7f..9ef07fb 100644 --- a/src/server/gateway/service.ts +++ b/packages/core/src/gateway/service.ts @@ -22,35 +22,35 @@ import type { VirtualModelFusionVisionConfig, VirtualModelFusionWebSearchConfig, VirtualModelFusionWebSearchProvider -} from "../../shared/app"; +} from "@ccr/core/contracts/app"; import { CLAUDE_APP_FALLBACK_MODEL, buildClaudeAppGatewayModelRoutes, inferClaudeAppGatewayTargetModel, resolveClaudeAppGatewayRouteModel, type ClaudeAppGatewayModelRouteOptions -} from "../../shared/claude-app-gateway"; +} from "@ccr/core/agents/claude-app/gateway-routes"; import { BUILTIN_FUSION_VISION_TOOL_NAME, BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME, NO_AVAILABLE_GATEWAY_MODELS_MESSAGE, ROUTER_FALLBACK_MAX_RETRY_COUNT, hasAvailableGatewayModels -} from "../../shared/app"; -import { findProviderPresetByBaseUrl, providerApiKeySafetyIssue } from "../../main/presets"; -import { normalizeProviderBaseUrl as normalizeProviderBaseUrlInput } from "../../shared/provider-url"; -import { backendService } from "../backend-service"; -import { RAW_TRACE_SPOOL_DIR } from "../../main/constants"; -import { loadPersistedApiKeys } from "../../main/api-key-store"; -import { codexDefaultBaseUrl, readCodexAuth } from "../../main/local-agent-provider-service"; -import { fetchWithSystemProxy, getSystemProxyUrlForProtocol } from "../../main/system-proxy-fetch"; -import { handleNetworkCaptureMcpRequest, isNetworkCaptureMcpPath } from "../mcp/network-capture-mcp"; -import { pluginService } from "../../main/plugins/service"; -import { proxyService } from "../proxy/service"; -import { createSseErrorDetector, recordGatewayRequestLog, updateGatewayRequestLogFromRawTrace, type RequestLogRawTraceUpdateInput } from "../../main/request-log-store"; -import { recordGatewayUsageCapture } from "../../main/usage-store"; -import { ClaudeCodeRouterPlugin, normalizeRouteSelector } from "./claude-code-router-plugin"; -import { ccrRemoteControlPathPrefix, ccrRemoteControlService } from "./remote-control-service"; +} from "@ccr/core/contracts/app"; +import { findProviderPresetByBaseUrl, providerApiKeySafetyIssue } from "@ccr/core/providers/presets/index"; +import { normalizeProviderBaseUrl as normalizeProviderBaseUrlInput } from "@ccr/core/providers/url"; +import { backendService } from "@ccr/core/plugins/backend-service"; +import { RAW_TRACE_SPOOL_DIR } from "@ccr/core/config/constants"; +import { loadPersistedApiKeys } from "@ccr/core/config/api-key-store"; +import { codexDefaultBaseUrl, readCodexAuth } from "@ccr/core/agents/local-providers/service"; +import { fetchWithSystemProxy, getSystemProxyUrlForProtocol } from "@ccr/core/proxy/system-proxy-fetch"; +import { handleNetworkCaptureMcpRequest, isNetworkCaptureMcpPath } from "@ccr/core/mcp/network-capture-mcp"; +import { pluginService } from "@ccr/core/plugins/service"; +import { proxyService } from "@ccr/core/proxy/service"; +import { createSseErrorDetector, recordGatewayRequestLog, updateGatewayRequestLogFromRawTrace, type RequestLogRawTraceUpdateInput } from "@ccr/core/observability/request-log-store"; +import { recordGatewayUsageCapture } from "@ccr/core/usage/store"; +import { ClaudeCodeRouterPlugin, normalizeRouteSelector } from "@ccr/core/gateway/claude-code-router-plugin"; +import { ccrRemoteControlPathPrefix, ccrRemoteControlService } from "@ccr/core/gateway/remote-control-service"; import { claudeCodeEffectiveMaxInputTokens, findModelCatalogEntry, @@ -59,7 +59,7 @@ import { readCatalogCapability, type ModelCatalogCapabilities, type ModelCatalogEntry -} from "./model-catalog"; +} from "@ccr/core/gateway/model-catalog"; type CoreGatewayProvider = { apikey?: string; @@ -388,11 +388,12 @@ class GatewayService { const runtimeId = randomUUID(); const upstreamProxyUrl = proxyService.getUpstreamProxyUrl("https") ?? await getSystemProxyUrlForProtocol("https"); this.child = spawnGatewayProcess(config, upstreamProxyUrl, runtimeId, this.coreAuthToken); + const managedChild = this.child; writeManagedCoreGatewayMarker(config, this.child, runtimeId); this.child.stdout?.on("data", (chunk) => console.info(`[gateway] ${chunk.toString().trimEnd()}`)); this.child.stderr?.on("data", (chunk) => console.warn(`[gateway] ${chunk.toString().trimEnd()}`)); this.child.on("exit", (code, signal) => { - void this.handleCoreGatewayExit(code, signal); + void this.handleCoreGatewayExit(managedChild, code, signal); }); } @@ -495,8 +496,8 @@ class GatewayService { }); } - private async handleCoreGatewayExit(code: number | null, signal: NodeJS.Signals | null): Promise { - if (this.status.state === "stopped") { + private async handleCoreGatewayExit(child: ChildProcess, code: number | null, signal: NodeJS.Signals | null): Promise { + if (this.child !== child || this.status.state === "stopped") { return; } removeManagedCoreGatewayMarker(this.config); diff --git a/src/server/mcp/browser-web-search-proxy-mcp.ts b/packages/core/src/mcp/browser-web-search-proxy-mcp.ts similarity index 100% rename from src/server/mcp/browser-web-search-proxy-mcp.ts rename to packages/core/src/mcp/browser-web-search-proxy-mcp.ts diff --git a/src/server/mcp/fusion-tool-fallback-mcp.ts b/packages/core/src/mcp/fusion-tool-fallback-mcp.ts similarity index 100% rename from src/server/mcp/fusion-tool-fallback-mcp.ts rename to packages/core/src/mcp/fusion-tool-fallback-mcp.ts diff --git a/src/server/mcp/fusion-vision-mcp.ts b/packages/core/src/mcp/fusion-vision-mcp.ts similarity index 100% rename from src/server/mcp/fusion-vision-mcp.ts rename to packages/core/src/mcp/fusion-vision-mcp.ts diff --git a/src/server/mcp/network-capture-mcp.ts b/packages/core/src/mcp/network-capture-mcp.ts similarity index 98% rename from src/server/mcp/network-capture-mcp.ts rename to packages/core/src/mcp/network-capture-mcp.ts index 2c6e0b7..15dd6dc 100644 --- a/src/server/mcp/network-capture-mcp.ts +++ b/packages/core/src/mcp/network-capture-mcp.ts @@ -1,7 +1,7 @@ -import packageJson from "../../../package.json"; +import packageJson from "../../package.json"; import type { IncomingMessage, ServerResponse } from "node:http"; -import type { ProxyNetworkExchange } from "../../shared/app"; -import { proxyService } from "../proxy/service"; +import type { ProxyNetworkExchange } from "@ccr/core/contracts/app"; +import { proxyService } from "@ccr/core/proxy/service"; type JsonPrimitive = boolean | null | number | string; type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }; diff --git a/src/server/mcp/tool-discovery.ts b/packages/core/src/mcp/tool-discovery.ts similarity index 99% rename from src/server/mcp/tool-discovery.ts rename to packages/core/src/mcp/tool-discovery.ts index 61f0276..efe2b41 100644 --- a/src/server/mcp/tool-discovery.ts +++ b/packages/core/src/mcp/tool-discovery.ts @@ -1,11 +1,11 @@ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process"; -import { fetchWithSystemProxy } from "../../main/system-proxy-fetch"; +import { fetchWithSystemProxy } from "@ccr/core/proxy/system-proxy-fetch"; import type { GatewayMcpRemoteServerConfig, GatewayMcpServerConfig, GatewayMcpStdioServerConfig, GatewayMcpToolInfo -} from "../../shared/app"; +} from "@ccr/core/contracts/app"; type JsonRpcMessage = { error?: unknown; diff --git a/src/main/model-catalog-file.ts b/packages/core/src/models/catalog-file.ts similarity index 87% rename from src/main/model-catalog-file.ts rename to packages/core/src/models/catalog-file.ts index 68b5c86..08c09e1 100644 --- a/src/main/model-catalog-file.ts +++ b/packages/core/src/models/catalog-file.ts @@ -24,8 +24,11 @@ export function modelCatalogPathCandidates(): string[] { process.env.CCR_MODEL_CATALOG_PATH?.trim() || "", process.env.CCR_MODELS_JSON_PATH?.trim() || "", pathResolve(process.cwd(), "models.json"), + pathResolve(process.cwd(), "packages", "core", "models.json"), + pathResolve(process.cwd(), "packages", "cli", "models.json"), pathResolve(__dirname, "..", "models.json"), pathResolve(__dirname, "..", "assets", "models.json"), + pathResolve(__dirname, "..", "..", "models.json"), pathResolve(__dirname, "..", "..", "..", "models.json") ]); } diff --git a/src/main/model-pricing-service.ts b/packages/core/src/models/pricing-service.ts similarity index 99% rename from src/main/model-pricing-service.ts rename to packages/core/src/models/pricing-service.ts index 92c074d..e581d21 100644 --- a/src/main/model-pricing-service.ts +++ b/packages/core/src/models/pricing-service.ts @@ -1,4 +1,4 @@ -import { fetchWithSystemProxy } from "./system-proxy-fetch"; +import { fetchWithSystemProxy } from "@ccr/core/proxy/system-proxy-fetch"; type ModelPricingSource = "litellm" | "models.dev" | "openrouter"; diff --git a/src/main/request-log-store.ts b/packages/core/src/observability/request-log-store.ts similarity index 99% rename from src/main/request-log-store.ts rename to packages/core/src/observability/request-log-store.ts index 25c735f..edd289e 100644 --- a/src/main/request-log-store.ts +++ b/packages/core/src/observability/request-log-store.ts @@ -1,10 +1,10 @@ import { mkdirSync } from "node:fs"; import { dirname } from "node:path"; import { StringDecoder } from "node:string_decoder"; -import { REQUEST_LOGS_DB_FILE } from "./constants"; -import { estimateUsageCostUsd } from "./model-pricing-service"; -import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "./sqlite-native"; -import { normalizeUsageInputTokens } from "./usage-normalization"; +import { REQUEST_LOGS_DB_FILE } from "@ccr/core/config/constants"; +import { estimateUsageCostUsd } from "@ccr/core/models/pricing-service"; +import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "@ccr/core/storage/sqlite-native"; +import { normalizeUsageInputTokens } from "@ccr/core/usage/normalization"; import type { AgentAnalysisAgentRow, AgentAnalysisFilter, @@ -38,7 +38,7 @@ import type { RequestLogRetryAttempt, RequestLogStatusFilter, UsageStatsRange -} from "../shared/app"; +} from "@ccr/core/contracts/app"; type SqlDatabase = BetterSqliteDatabase; type SqlValue = bigint | Buffer | number | string | null; diff --git a/src/main/windows-app-discovery.ts b/packages/core/src/platform/windows-app-discovery.ts similarity index 99% rename from src/main/windows-app-discovery.ts rename to packages/core/src/platform/windows-app-discovery.ts index 1737231..827e6f7 100644 --- a/src/main/windows-app-discovery.ts +++ b/packages/core/src/platform/windows-app-discovery.ts @@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process"; import { readdirSync, statSync } from "node:fs"; import os from "node:os"; import path from "node:path"; -import { windowsSystemCommand } from "./windows-system"; +import { windowsSystemCommand } from "@ccr/core/platform/windows-system"; export type WindowsDesktopAppDiscoveryOptions = { appDirs: string[]; diff --git a/src/main/windows-system.ts b/packages/core/src/platform/windows-system.ts similarity index 100% rename from src/main/windows-system.ts rename to packages/core/src/platform/windows-system.ts diff --git a/src/server/backend-service.ts b/packages/core/src/plugins/backend-service.ts similarity index 99% rename from src/server/backend-service.ts rename to packages/core/src/plugins/backend-service.ts index 09c142b..84abbf2 100644 --- a/src/server/backend-service.ts +++ b/packages/core/src/plugins/backend-service.ts @@ -1,7 +1,7 @@ import { copyFileSync, existsSync, mkdirSync, rmSync } from "node:fs"; import http, { type IncomingMessage, type Server, type ServerResponse } from "node:http"; import path from "node:path"; -import { createBetterSqliteDatabase, type BetterSqliteDatabase, type BetterSqliteStatement } from "../main/sqlite-native"; +import { createBetterSqliteDatabase, type BetterSqliteDatabase, type BetterSqliteStatement } from "@ccr/core/storage/sqlite-native"; type MaybePromise = T | Promise; export type SqliteValue = bigint | Buffer | number | string | Uint8Array | null; diff --git a/src/main/plugins/service.ts b/packages/core/src/plugins/service.ts similarity index 99% rename from src/main/plugins/service.ts rename to packages/core/src/plugins/service.ts index 83dfcdc..ae9ebd9 100644 --- a/src/main/plugins/service.ts +++ b/packages/core/src/plugins/service.ts @@ -14,9 +14,9 @@ import type { ProviderAccountMeter, ProviderAccountPluginConnectorConfig, ProviderAccountSnapshot -} from "../../shared/app"; -import { backendService, type RegisteredHttpBackend, type SqliteStore, type SqliteStoreOptions } from "../../server/backend-service"; -import { CONFIGDIR, DATADIR } from "../constants"; +} from "@ccr/core/contracts/app"; +import { backendService, type RegisteredHttpBackend, type SqliteStore, type SqliteStoreOptions } from "@ccr/core/plugins/backend-service"; +import { CONFIGDIR, DATADIR } from "@ccr/core/config/constants"; type MaybePromise = T | Promise; type PluginLogger = { diff --git a/src/main/profile-launch-core.ts b/packages/core/src/profiles/launch-core.ts similarity index 98% rename from src/main/profile-launch-core.ts rename to packages/core/src/profiles/launch-core.ts index ea5bea2..ab187fd 100644 --- a/src/main/profile-launch-core.ts +++ b/packages/core/src/profiles/launch-core.ts @@ -1,7 +1,7 @@ import path from "node:path"; -import type { AppConfig, ProfileConfig, ProfileOpenSurface } from "../shared/app"; -import { claudeCodeUtcTimezoneEnvOverride } from "./claude-environment"; -import { resolveZcodeConfigFile } from "./zcode-profile-config"; +import type { AppConfig, ProfileConfig, ProfileOpenSurface } from "@ccr/core/contracts/app"; +import { claudeCodeUtcTimezoneEnvOverride } from "@ccr/core/agents/claude-code/environment"; +import { resolveZcodeConfigFile } from "@ccr/core/agents/zcode/profile-config"; export type ProfileLaunchPlan = { args: string[]; diff --git a/src/main/profile-launch-service.ts b/packages/core/src/profiles/launch-service.ts similarity index 98% rename from src/main/profile-launch-service.ts rename to packages/core/src/profiles/launch-service.ts index a42d5aa..66bec32 100644 --- a/src/main/profile-launch-service.ts +++ b/packages/core/src/profiles/launch-service.ts @@ -2,18 +2,18 @@ import { spawn, spawnSync, type ChildProcess } from "node:child_process"; import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; -import { assertAvailableGatewayModels, type AppConfig, type ProfileOpenCommandResult, type ProfileOpenRequest, type ProfileOpenResult, type ProfileRuntimeEntry, type ProfileRuntimeStatus, type ProfileStopResult } from "../shared/app"; -import { botGatewayProfileEnv } from "./bot-gateway-env"; -import { applyClaudeAppGatewayConfig, readClaudeAppGatewayApiKeyCandidates } from "./claude-app-gateway-service"; -import { launchClaudeAppProfile, resolveClaudeAppProfileUserDataDir } from "./claude-app-launch"; -import { claudeCodeUtcTimezoneEnvOverride } from "./claude-environment"; -import { launchCodexAppProfile, launchZcodeAppProfile, refreshCodexCompatibleAppProfileFiles } from "./codex-app-launch"; -import { codexCliMiddlewareRuntimeScript } from "./codex-cli-middleware-runtime"; -import { CONFIGDIR } from "./constants"; -import { gatewayService } from "../server/gateway/service"; -import { buildProfileLaunchPlan, findProfileForOpen, profileLaunchSpawnCommand, profileOpenCommand, resolveClaudeCodeSettingsFile, resolveProfileOpenSurface } from "./profile-launch-core"; -import { applyProfileConfig, cleanupGeneratedBinBackups } from "./profile-service"; -import { broadcastWindowsEnvironmentChanged, windowsSystemCommand } from "./windows-system"; +import { assertAvailableGatewayModels, type AppConfig, type ProfileOpenCommandResult, type ProfileOpenRequest, type ProfileOpenResult, type ProfileRuntimeEntry, type ProfileRuntimeStatus, type ProfileStopResult } from "@ccr/core/contracts/app"; +import { botGatewayProfileEnv } from "@ccr/core/agents/bot-gateway/env"; +import { applyClaudeAppGatewayConfig, readClaudeAppGatewayApiKeyCandidates } from "@ccr/core/agents/claude-app/gateway-service"; +import { launchClaudeAppProfile, resolveClaudeAppProfileUserDataDir } from "@ccr/core/agents/claude-app/launch"; +import { claudeCodeUtcTimezoneEnvOverride } from "@ccr/core/agents/claude-code/environment"; +import { launchCodexAppProfile, launchZcodeAppProfile, refreshCodexCompatibleAppProfileFiles } from "@ccr/core/agents/codex/app-launch"; +import { codexCliMiddlewareRuntimeScript } from "@ccr/core/agents/codex/cli-middleware-runtime"; +import { CONFIGDIR } from "@ccr/core/config/constants"; +import { gatewayService } from "@ccr/core/gateway/service"; +import { buildProfileLaunchPlan, findProfileForOpen, profileLaunchSpawnCommand, profileOpenCommand, resolveClaudeCodeSettingsFile, resolveProfileOpenSurface } from "@ccr/core/profiles/launch-core"; +import { applyProfileConfig, cleanupGeneratedBinBackups } from "@ccr/core/profiles/service"; +import { broadcastWindowsEnvironmentChanged, windowsSystemCommand } from "@ccr/core/platform/windows-system"; const ccrPathBlockStart = "# >>> Claude Code Router CLI >>>"; const ccrPathBlockEnd = "# <<< Claude Code Router CLI <<<"; diff --git a/src/main/profile-service.ts b/packages/core/src/profiles/service.ts similarity index 98% rename from src/main/profile-service.ts rename to packages/core/src/profiles/service.ts index 1f877ee..c4366d1 100644 --- a/src/main/profile-service.ts +++ b/packages/core/src/profiles/service.ts @@ -2,16 +2,16 @@ import { randomBytes } from "node:crypto"; import { chmodSync, copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; -import { CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV, NO_AVAILABLE_GATEWAY_MODELS_MESSAGE, enforceSingleEnabledGlobalProfilePerAgent, hasAvailableGatewayModels, type ApiKeyConfig, type AppConfig, type ProfileApplyResult, type ProfileClientApplyStatus, type ProfileClientKind, type ProfileConfig } from "../shared/app"; -import { replacePersistedApiKeys } from "./api-key-store"; -import { botGatewayProfileEnv } from "./bot-gateway-env"; -import { claudeCodeUtcTimezoneEnvOverride } from "./claude-environment"; -import { writeCodexCompatibleAppModelCatalog } from "./codex-app-launch"; -import { codexCliMiddlewareRuntimeScript } from "./codex-cli-middleware-runtime"; -import { codexModelCatalogJson } from "./codex-model-catalog"; -import { CONFIGDIR } from "./constants"; -import { resolveZcodeConfigFile, writeZcodeGatewayConfig, zcodeHomeFromConfigFile } from "./zcode-profile-config"; -import { normalizeRouteSelector } from "../server/gateway/claude-code-router-plugin"; +import { CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY_ENV, NO_AVAILABLE_GATEWAY_MODELS_MESSAGE, enforceSingleEnabledGlobalProfilePerAgent, hasAvailableGatewayModels, type ApiKeyConfig, type AppConfig, type ProfileApplyResult, type ProfileClientApplyStatus, type ProfileClientKind, type ProfileConfig } from "@ccr/core/contracts/app"; +import { replacePersistedApiKeys } from "@ccr/core/config/api-key-store"; +import { botGatewayProfileEnv } from "@ccr/core/agents/bot-gateway/env"; +import { claudeCodeUtcTimezoneEnvOverride } from "@ccr/core/agents/claude-code/environment"; +import { writeCodexCompatibleAppModelCatalog } from "@ccr/core/agents/codex/app-launch"; +import { codexCliMiddlewareRuntimeScript } from "@ccr/core/agents/codex/cli-middleware-runtime"; +import { codexModelCatalogJson } from "@ccr/core/agents/codex/model-catalog"; +import { CONFIGDIR } from "@ccr/core/config/constants"; +import { resolveZcodeConfigFile, writeZcodeGatewayConfig, zcodeHomeFromConfigFile } from "@ccr/core/agents/zcode/profile-config"; +import { normalizeRouteSelector } from "@ccr/core/gateway/claude-code-router-plugin"; const managedRootStart = "# BEGIN CCR managed profile"; const managedRootEnd = "# END CCR managed profile"; diff --git a/src/main/provider-account-service.ts b/packages/core/src/providers/account-service.ts similarity index 99% rename from src/main/provider-account-service.ts rename to packages/core/src/providers/account-service.ts index 094d36b..31df114 100644 --- a/src/main/provider-account-service.ts +++ b/packages/core/src/providers/account-service.ts @@ -1,12 +1,12 @@ import { createHash, randomUUID } from "node:crypto"; -import { loadAppConfig } from "./config"; -import { attachCodexRateLimitResetCreditDetails } from "./local-agent-providers/codex"; -import { localAgentProviderApiKey, readCodexAuth } from "./local-agent-provider-service"; -import { pluginService } from "./plugins/service"; -import { getUsageTotalsSince } from "./usage-store"; -import { findProviderPresetByBaseUrl, providerEndpointCanReceiveProviderApiKey } from "./presets"; -import { fetchWithSystemProxy } from "./system-proxy-fetch"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../shared/provider-url"; +import { loadAppConfig } from "@ccr/core/config/config"; +import { attachCodexRateLimitResetCreditDetails } from "@ccr/core/agents/local-providers/codex"; +import { localAgentProviderApiKey, readCodexAuth } from "@ccr/core/agents/local-providers/service"; +import { pluginService } from "@ccr/core/plugins/service"; +import { getUsageTotalsSince } from "@ccr/core/usage/store"; +import { findProviderPresetByBaseUrl, providerEndpointCanReceiveProviderApiKey } from "@ccr/core/providers/presets/index"; +import { fetchWithSystemProxy } from "@ccr/core/proxy/system-proxy-fetch"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import type { AppConfig, GatewayProviderConfig, @@ -36,7 +36,7 @@ import type { ProviderAccountStandardConnectorConfig, ProviderCredentialConfig, ProviderAccountStatus -} from "../shared/app"; +} from "@ccr/core/contracts/app"; type CacheEntry = { expiresAt: number; diff --git a/src/main/provider-icons.ts b/packages/core/src/providers/icons.ts similarity index 97% rename from src/main/provider-icons.ts rename to packages/core/src/providers/icons.ts index c522032..78f2c17 100644 --- a/src/main/provider-icons.ts +++ b/packages/core/src/providers/icons.ts @@ -2,10 +2,10 @@ import { createHash } from "node:crypto"; import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import { PROVIDER_ICON_CACHE_DIR } from "./constants"; -import { fetchWithSystemProxy } from "./system-proxy-fetch"; -import type { ProviderIconDetectionRequest, ProviderIconDetectionResult } from "../shared/app"; -import { compactProviderUrl, providerUrlWithDefaultScheme } from "../shared/provider-url"; +import { PROVIDER_ICON_CACHE_DIR } from "@ccr/core/config/constants"; +import { fetchWithSystemProxy } from "@ccr/core/proxy/system-proxy-fetch"; +import type { ProviderIconDetectionRequest, ProviderIconDetectionResult } from "@ccr/core/contracts/app"; +import { compactProviderUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; const faviconServiceUrl = "https://t0.gstatic.com/faviconV2"; const maxProviderIconBytes = 1024 * 1024; diff --git a/src/main/provider-manifest-service.ts b/packages/core/src/providers/manifest-service.ts similarity index 97% rename from src/main/provider-manifest-service.ts rename to packages/core/src/providers/manifest-service.ts index f287364..40bdf89 100644 --- a/src/main/provider-manifest-service.ts +++ b/packages/core/src/providers/manifest-service.ts @@ -1,9 +1,9 @@ import { lookup } from "node:dns/promises"; import https from "node:https"; import net from "node:net"; -import { parseProviderManifestPayload } from "../shared/deep-link"; -import { findProviderPresetByBaseUrl, providerEndpointCanReceiveProviderApiKey, providerIdentitySafetyIssue } from "./presets"; -import { providerUrlWithDefaultScheme } from "../shared/provider-url"; +import { parseProviderManifestPayload } from "@ccr/core/contracts/deep-link"; +import { findProviderPresetByBaseUrl, providerEndpointCanReceiveProviderApiKey, providerIdentitySafetyIssue } from "@ccr/core/providers/presets/index"; +import { providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import type { GatewayProviderConfig, ProviderAccountConnectorConfig, @@ -12,7 +12,7 @@ import type { ProviderDeepLinkPayload, ProviderManifestFetchRequest, ProviderManifestFetchResult -} from "../shared/app"; +} from "@ccr/core/contracts/app"; type SafeAddress = { address: string; diff --git a/src/main/provider-model-catalog.ts b/packages/core/src/providers/model-catalog.ts similarity index 98% rename from src/main/provider-model-catalog.ts rename to packages/core/src/providers/model-catalog.ts index 87519c6..ef57208 100644 --- a/src/main/provider-model-catalog.ts +++ b/packages/core/src/providers/model-catalog.ts @@ -1,7 +1,7 @@ -import type { ProviderCatalogModelsRequest, ProviderCatalogModelsResult } from "../shared/app"; -import { providerUrlWithDefaultScheme } from "../shared/provider-url"; -import { loadModelCatalogPayload } from "./model-catalog-file"; -import { findProviderPreset, findProviderPresetByBaseUrl } from "./presets"; +import type { ProviderCatalogModelsRequest, ProviderCatalogModelsResult } from "@ccr/core/contracts/app"; +import { providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; +import { loadModelCatalogPayload } from "@ccr/core/models/catalog-file"; +import { findProviderPreset, findProviderPresetByBaseUrl } from "@ccr/core/providers/presets/index"; type CatalogProviderEntry = { apiUrls: string[]; diff --git a/src/main/presets/anthropic/index.ts b/packages/core/src/providers/presets/anthropic/index.ts similarity index 93% rename from src/main/presets/anthropic/index.ts rename to packages/core/src/providers/presets/anthropic/index.ts index 7cdc93b..929a717 100644 --- a/src/main/presets/anthropic/index.ts +++ b/packages/core/src/providers/presets/anthropic/index.ts @@ -1,4 +1,4 @@ -import { defaultProviderAccountConfig, type ProviderPreset } from "../../../shared/provider-presets"; +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; export const anthropicProviderPreset: ProviderPreset = { account: defaultProviderAccountConfig, diff --git a/src/main/presets/bailian/index.ts b/packages/core/src/providers/presets/bailian/index.ts similarity index 92% rename from src/main/presets/bailian/index.ts rename to packages/core/src/providers/presets/bailian/index.ts index f52da99..5a21269 100644 --- a/src/main/presets/bailian/index.ts +++ b/packages/core/src/providers/presets/bailian/index.ts @@ -1,4 +1,4 @@ -import { defaultProviderAccountConfig, type ProviderPreset } from "../../../shared/provider-presets"; +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; export const bailianProviderPreset: ProviderPreset = { account: defaultProviderAccountConfig, diff --git a/src/main/presets/deepseek/index.ts b/packages/core/src/providers/presets/deepseek/index.ts similarity index 90% rename from src/main/presets/deepseek/index.ts rename to packages/core/src/providers/presets/deepseek/index.ts index ee87dc6..ee51cd3 100644 --- a/src/main/presets/deepseek/index.ts +++ b/packages/core/src/providers/presets/deepseek/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const deepSeekProviderAccountConfig: ProviderAccountConfig = { connectors: [ diff --git a/src/main/presets/gemini/index.ts b/packages/core/src/providers/presets/gemini/index.ts similarity index 93% rename from src/main/presets/gemini/index.ts rename to packages/core/src/providers/presets/gemini/index.ts index 4d92ad5..7e0fec7 100644 --- a/src/main/presets/gemini/index.ts +++ b/packages/core/src/providers/presets/gemini/index.ts @@ -1,4 +1,4 @@ -import { defaultProviderAccountConfig, type ProviderPreset } from "../../../shared/provider-presets"; +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; export const geminiProviderPreset: ProviderPreset = { account: defaultProviderAccountConfig, diff --git a/src/main/presets/index.ts b/packages/core/src/providers/presets/index.ts similarity index 59% rename from src/main/presets/index.ts rename to packages/core/src/providers/presets/index.ts index cee4dea..760c2b1 100644 --- a/src/main/presets/index.ts +++ b/packages/core/src/providers/presets/index.ts @@ -1,19 +1,19 @@ -import { anthropicProviderPreset } from "./anthropic"; -import { bailianProviderPreset } from "./bailian"; -import { deepSeekProviderPreset } from "./deepseek"; -import { geminiProviderPreset } from "./gemini"; -import { kimiCodingProviderPreset } from "./kimi-coding"; -import { mistralProviderPreset } from "./mistral"; -import { moonshotChinaProviderPreset, moonshotGlobalProviderPreset } from "./moonshot"; -import { openaiProviderPreset } from "./openai"; -import { openRouterProviderPreset } from "./openrouter"; -import { runApiProviderPreset } from "./runapi"; -import { siliconFlowProviderPreset } from "./siliconflow"; -import { teamoRouterProviderPreset } from "./teamorouter"; -import { zaiGlobalCodingProviderPreset } from "./zai-global-coding"; -import { zaiGlobalGeneralProviderPreset } from "./zai-global-general"; -import { zhipuCnCodingProviderPreset } from "./zhipu-cn-coding"; -import { zhipuCnGeneralProviderPreset } from "./zhipu-cn-general"; +import { anthropicProviderPreset } from "@ccr/core/providers/presets/anthropic/index"; +import { bailianProviderPreset } from "@ccr/core/providers/presets/bailian/index"; +import { deepSeekProviderPreset } from "@ccr/core/providers/presets/deepseek/index"; +import { geminiProviderPreset } from "@ccr/core/providers/presets/gemini/index"; +import { kimiCodingProviderPreset } from "@ccr/core/providers/presets/kimi-coding/index"; +import { mistralProviderPreset } from "@ccr/core/providers/presets/mistral/index"; +import { moonshotChinaProviderPreset, moonshotGlobalProviderPreset } from "@ccr/core/providers/presets/moonshot/index"; +import { openaiProviderPreset } from "@ccr/core/providers/presets/openai/index"; +import { openRouterProviderPreset } from "@ccr/core/providers/presets/openrouter/index"; +import { runApiProviderPreset } from "@ccr/core/providers/presets/runapi/index"; +import { siliconFlowProviderPreset } from "@ccr/core/providers/presets/siliconflow/index"; +import { teamoRouterProviderPreset } from "@ccr/core/providers/presets/teamorouter/index"; +import { zaiGlobalCodingProviderPreset } from "@ccr/core/providers/presets/zai-global-coding/index"; +import { zaiGlobalGeneralProviderPreset } from "@ccr/core/providers/presets/zai-global-general/index"; +import { zhipuCnCodingProviderPreset } from "@ccr/core/providers/presets/zhipu-cn-coding/index"; +import { zhipuCnGeneralProviderPreset } from "@ccr/core/providers/presets/zhipu-cn-general/index"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -22,8 +22,8 @@ import { providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList, providerPresetMatchesBaseUrl -} from "../../shared/provider-preset-utils"; -import type { ProviderIdentitySafetyIssue, ProviderPreset } from "../../shared/provider-presets"; +} from "@ccr/core/providers/presets/utils"; +import type { ProviderIdentitySafetyIssue, ProviderPreset } from "@ccr/core/providers/presets/types"; export const providerPresets: ProviderPreset[] = [ openaiProviderPreset, diff --git a/src/main/presets/kimi-coding/index.ts b/packages/core/src/providers/presets/kimi-coding/index.ts similarity index 87% rename from src/main/presets/kimi-coding/index.ts rename to packages/core/src/providers/presets/kimi-coding/index.ts index 6848c28..924fd10 100644 --- a/src/main/presets/kimi-coding/index.ts +++ b/packages/core/src/providers/presets/kimi-coding/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const kimiCodingProviderAccountConfig: ProviderAccountConfig = { connectors: [ diff --git a/src/main/presets/mistral/index.ts b/packages/core/src/providers/presets/mistral/index.ts similarity index 87% rename from src/main/presets/mistral/index.ts rename to packages/core/src/providers/presets/mistral/index.ts index ff351f4..e12bc2b 100644 --- a/src/main/presets/mistral/index.ts +++ b/packages/core/src/providers/presets/mistral/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const mistralProviderAccountConfig: ProviderAccountConfig = { connectors: [ diff --git a/src/main/presets/moonshot/index.ts b/packages/core/src/providers/presets/moonshot/index.ts similarity index 95% rename from src/main/presets/moonshot/index.ts rename to packages/core/src/providers/presets/moonshot/index.ts index d6887f0..b4ea02d 100644 --- a/src/main/presets/moonshot/index.ts +++ b/packages/core/src/providers/presets/moonshot/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const moonshotGlobalProviderAccountConfig: ProviderAccountConfig = { connectors: [ diff --git a/src/main/presets/openai/index.ts b/packages/core/src/providers/presets/openai/index.ts similarity index 93% rename from src/main/presets/openai/index.ts rename to packages/core/src/providers/presets/openai/index.ts index e2d5cf9..a4ea97f 100644 --- a/src/main/presets/openai/index.ts +++ b/packages/core/src/providers/presets/openai/index.ts @@ -1,4 +1,4 @@ -import { defaultProviderAccountConfig, type ProviderPreset } from "../../../shared/provider-presets"; +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; export const openaiProviderPreset: ProviderPreset = { account: defaultProviderAccountConfig, diff --git a/src/main/presets/openrouter/index.ts b/packages/core/src/providers/presets/openrouter/index.ts similarity index 90% rename from src/main/presets/openrouter/index.ts rename to packages/core/src/providers/presets/openrouter/index.ts index 86d56da..7570322 100644 --- a/src/main/presets/openrouter/index.ts +++ b/packages/core/src/providers/presets/openrouter/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const openRouterProviderAccountConfig: ProviderAccountConfig = { connectors: [ diff --git a/src/main/presets/runapi/index.ts b/packages/core/src/providers/presets/runapi/index.ts similarity index 91% rename from src/main/presets/runapi/index.ts rename to packages/core/src/providers/presets/runapi/index.ts index 1f19acb..5f982ab 100644 --- a/src/main/presets/runapi/index.ts +++ b/packages/core/src/providers/presets/runapi/index.ts @@ -1,4 +1,4 @@ -import { defaultProviderAccountConfig, type ProviderPreset } from "../../../shared/provider-presets"; +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; export const runApiProviderPreset: ProviderPreset = { account: defaultProviderAccountConfig, diff --git a/src/main/presets/siliconflow/index.ts b/packages/core/src/providers/presets/siliconflow/index.ts similarity index 89% rename from src/main/presets/siliconflow/index.ts rename to packages/core/src/providers/presets/siliconflow/index.ts index 737c338..a4af567 100644 --- a/src/main/presets/siliconflow/index.ts +++ b/packages/core/src/providers/presets/siliconflow/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const siliconFlowProviderAccountConfig: ProviderAccountConfig = { connectors: [ diff --git a/src/main/presets/teamorouter/index.ts b/packages/core/src/providers/presets/teamorouter/index.ts similarity index 92% rename from src/main/presets/teamorouter/index.ts rename to packages/core/src/providers/presets/teamorouter/index.ts index aa19e75..e7909fa 100644 --- a/src/main/presets/teamorouter/index.ts +++ b/packages/core/src/providers/presets/teamorouter/index.ts @@ -1,4 +1,4 @@ -import { defaultProviderAccountConfig, type ProviderPreset } from "../../../shared/provider-presets"; +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; export const teamoRouterProviderPreset: ProviderPreset = { account: defaultProviderAccountConfig, diff --git a/src/shared/provider-presets.ts b/packages/core/src/providers/presets/types.ts similarity index 97% rename from src/shared/provider-presets.ts rename to packages/core/src/providers/presets/types.ts index e004fff..c4811ff 100644 --- a/src/shared/provider-presets.ts +++ b/packages/core/src/providers/presets/types.ts @@ -1,4 +1,4 @@ -import type { GatewayProviderProtocol, ProviderAccountConfig } from "./app"; +import type { GatewayProviderProtocol, ProviderAccountConfig } from "@ccr/core/contracts/app"; export type ProviderPresetEndpoint = { baseUrl: string; diff --git a/src/shared/provider-preset-utils.ts b/packages/core/src/providers/presets/utils.ts similarity index 96% rename from src/shared/provider-preset-utils.ts rename to packages/core/src/providers/presets/utils.ts index 7bdb9b2..362e19d 100644 --- a/src/shared/provider-preset-utils.ts +++ b/packages/core/src/providers/presets/utils.ts @@ -3,8 +3,8 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "./provider-presets"; -import { providerUrlWithDefaultScheme } from "./provider-url"; +} from "@ccr/core/providers/presets/types"; +import { providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; export function findProviderPresetInList( presets: ProviderPreset[], @@ -188,7 +188,11 @@ function providerEndpointMatchesBaseUrl(endpointBaseUrl: string, baseUrl: string const endpointPath = normalizeProviderPresetPath(endpoint.pathname); const candidatePath = normalizeProviderPresetPath(candidate.pathname); - return endpointPath === "/" || candidatePath === "/" || candidatePath === endpointPath || candidatePath.startsWith(`${endpointPath}/`); + return endpointPath === "/" || + candidatePath === "/" || + candidatePath === endpointPath || + candidatePath.startsWith(`${endpointPath}/`) || + endpointPath.startsWith(`${candidatePath}/`); } function providerEndpointHost(baseUrl: string): string | undefined { diff --git a/src/main/presets/zai-global-coding/index.ts b/packages/core/src/providers/presets/zai-global-coding/index.ts similarity index 94% rename from src/main/presets/zai-global-coding/index.ts rename to packages/core/src/providers/presets/zai-global-coding/index.ts index d757fcc..24c0719 100644 --- a/src/main/presets/zai-global-coding/index.ts +++ b/packages/core/src/providers/presets/zai-global-coding/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const zaiQuotaMapping: ProviderAccountMappingConfig = { meters: [ diff --git a/src/main/presets/zai-global-general/index.ts b/packages/core/src/providers/presets/zai-global-general/index.ts similarity index 94% rename from src/main/presets/zai-global-general/index.ts rename to packages/core/src/providers/presets/zai-global-general/index.ts index 44612fd..377b564 100644 --- a/src/main/presets/zai-global-general/index.ts +++ b/packages/core/src/providers/presets/zai-global-general/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const zaiQuotaMapping: ProviderAccountMappingConfig = { meters: [ diff --git a/src/main/presets/zhipu-cn-coding/index.ts b/packages/core/src/providers/presets/zhipu-cn-coding/index.ts similarity index 94% rename from src/main/presets/zhipu-cn-coding/index.ts rename to packages/core/src/providers/presets/zhipu-cn-coding/index.ts index a28dda1..04540d2 100644 --- a/src/main/presets/zhipu-cn-coding/index.ts +++ b/packages/core/src/providers/presets/zhipu-cn-coding/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const zhipuQuotaMapping: ProviderAccountMappingConfig = { meters: [ diff --git a/src/main/presets/zhipu-cn-general/index.ts b/packages/core/src/providers/presets/zhipu-cn-general/index.ts similarity index 94% rename from src/main/presets/zhipu-cn-general/index.ts rename to packages/core/src/providers/presets/zhipu-cn-general/index.ts index c0c0e52..2db2f5d 100644 --- a/src/main/presets/zhipu-cn-general/index.ts +++ b/packages/core/src/providers/presets/zhipu-cn-general/index.ts @@ -1,5 +1,5 @@ -import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "../../../shared/app"; -import type { ProviderPreset } from "../../../shared/provider-presets"; +import type { ProviderAccountConfig, ProviderAccountMappingConfig } from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; const zhipuQuotaMapping: ProviderAccountMappingConfig = { meters: [ diff --git a/src/main/provider-probe.ts b/packages/core/src/providers/probe.ts similarity index 99% rename from src/main/provider-probe.ts rename to packages/core/src/providers/probe.ts index aaf6d1f..e5b24d4 100644 --- a/src/main/provider-probe.ts +++ b/packages/core/src/providers/probe.ts @@ -10,15 +10,15 @@ import type { GatewayProviderProbeRequest, GatewayProviderProbeResult, GatewayProviderProtocol -} from "../shared/app"; -import { providerApiKeySafetyIssue } from "./presets"; -import { fetchWithSystemProxy } from "./system-proxy-fetch"; +} from "@ccr/core/contracts/app"; +import { providerApiKeySafetyIssue } from "@ccr/core/providers/presets/index"; +import { fetchWithSystemProxy } from "@ccr/core/proxy/system-proxy-fetch"; import { compactProviderUrl, parseProviderBaseUrl, providerBaseUrlForProtocol, type ParsedProviderBaseUrl -} from "../shared/provider-url"; +} from "@ccr/core/providers/url"; type ModelSource = NonNullable; diff --git a/src/shared/provider-url.ts b/packages/core/src/providers/url.ts similarity index 98% rename from src/shared/provider-url.ts rename to packages/core/src/providers/url.ts index b105f2b..afb4c3a 100644 --- a/src/shared/provider-url.ts +++ b/packages/core/src/providers/url.ts @@ -1,4 +1,4 @@ -import type { GatewayProviderProtocol } from "./app"; +import type { GatewayProviderProtocol } from "@ccr/core/contracts/app"; export type ParsedProviderBaseUrl = { anthropicBaseUrl: string; diff --git a/src/server/proxy/certificates.ts b/packages/core/src/proxy/certificates.ts similarity index 99% rename from src/server/proxy/certificates.ts rename to packages/core/src/proxy/certificates.ts index ea5ae91..57bb1d6 100644 --- a/src/server/proxy/certificates.ts +++ b/packages/core/src/proxy/certificates.ts @@ -4,7 +4,7 @@ import net from "node:net"; import os from "node:os"; import path from "node:path"; import forge from "node-forge"; -import { CERTDIR, PROXY_CA_CERT_DER_FILE, PROXY_CA_CERT_FILE, PROXY_CA_KEY_FILE } from "../../main/constants"; +import { CERTDIR, PROXY_CA_CERT_DER_FILE, PROXY_CA_CERT_FILE, PROXY_CA_KEY_FILE } from "@ccr/core/config/constants"; const pki = forge.pki; const certificateDirectoryMode = 0o700; diff --git a/src/server/proxy/service.ts b/packages/core/src/proxy/service.ts similarity index 99% rename from src/server/proxy/service.ts rename to packages/core/src/proxy/service.ts index 4db90ff..8c8cfa0 100644 --- a/src/server/proxy/service.ts +++ b/packages/core/src/proxy/service.ts @@ -21,10 +21,10 @@ import type { ProxyNetworkSnapshot, ProxyRouteTarget, ProxyStatus -} from "../../shared/app"; -import { PROXY_CA_CERT_FILE } from "../../main/constants"; -import { windowsSystemCommand } from "../../main/windows-system"; -import { pluginService, type GatewayPluginProxyRouteMatch } from "../../main/plugins/service"; +} from "@ccr/core/contracts/app"; +import { PROXY_CA_CERT_FILE } from "@ccr/core/config/constants"; +import { windowsSystemCommand } from "@ccr/core/platform/windows-system"; +import { pluginService, type GatewayPluginProxyRouteMatch } from "@ccr/core/plugins/service"; import { createCertificateForHost, ensureProxyCertificateAuthority, @@ -36,8 +36,8 @@ import { readProxyCertificateFingerprintSha256, readProxyCertificateAuthority, type CertificateAuthority -} from "./certificates"; -import { formatUpstreamProxy, readCurrentSystemUpstreamProxy, systemProxyManager, type UpstreamProxyConfig, type UpstreamProxyServer } from "./system-proxy"; +} from "@ccr/core/proxy/certificates"; +import { formatUpstreamProxy, readCurrentSystemUpstreamProxy, systemProxyManager, type UpstreamProxyConfig, type UpstreamProxyServer } from "@ccr/core/proxy/system-proxy"; type MitmServer = { host: string; diff --git a/src/main/system-proxy-fetch.ts b/packages/core/src/proxy/system-proxy-fetch.ts similarity index 97% rename from src/main/system-proxy-fetch.ts rename to packages/core/src/proxy/system-proxy-fetch.ts index fd6d941..f1e10bc 100644 --- a/src/main/system-proxy-fetch.ts +++ b/packages/core/src/proxy/system-proxy-fetch.ts @@ -1,7 +1,7 @@ import { ProxyAgent, type Dispatcher } from "undici"; -import { loadAppConfig } from "./config"; -import { readCurrentSystemUpstreamProxy, systemProxyManager, type UpstreamProxyConfig, type UpstreamProxyServer } from "../server/proxy/system-proxy"; -import type { AppConfig } from "../shared/app"; +import { loadAppConfig } from "@ccr/core/config/config"; +import { readCurrentSystemUpstreamProxy, systemProxyManager, type UpstreamProxyConfig, type UpstreamProxyServer } from "@ccr/core/proxy/system-proxy"; +import type { AppConfig } from "@ccr/core/contracts/app"; type FetchInitWithDispatcher = RequestInit & { dispatcher?: Dispatcher; diff --git a/src/server/proxy/system-proxy.ts b/packages/core/src/proxy/system-proxy.ts similarity index 99% rename from src/server/proxy/system-proxy.ts rename to packages/core/src/proxy/system-proxy.ts index 90d91eb..c17f7e8 100644 --- a/src/server/proxy/system-proxy.ts +++ b/packages/core/src/proxy/system-proxy.ts @@ -1,9 +1,9 @@ import { execFile } from "node:child_process"; import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import path from "node:path"; -import type { ProxySystemStatus } from "../../shared/app"; -import { DATADIR } from "../../main/constants"; -import { windowsSystemCommand } from "../../main/windows-system"; +import type { ProxySystemStatus } from "@ccr/core/contracts/app"; +import { DATADIR } from "@ccr/core/config/constants"; +import { windowsSystemCommand } from "@ccr/core/platform/windows-system"; export type UpstreamProxyServer = { host: string; diff --git a/src/main/app-paths.ts b/packages/core/src/runtime/app-paths.ts similarity index 78% rename from src/main/app-paths.ts rename to packages/core/src/runtime/app-paths.ts index 5307b13..874977f 100644 --- a/src/main/app-paths.ts +++ b/packages/core/src/runtime/app-paths.ts @@ -33,6 +33,24 @@ export function resolveRuntimeAppPath(name: RuntimePathName): string { return fallbackUserDataDir(); } +export function resolveRuntimeConfigDir(): string { + if (process.platform === "win32") { + return path.join(resolveRuntimeAppPath("appData"), APP_STORAGE_NAME); + } + return path.join(resolveRuntimeAppPath("home"), `.${APP_STORAGE_NAME}`); +} + +export function resolveRuntimeDataDir(): string { + const configured = readConfiguredPath("userData"); + if (configured) { + return configured; + } + if (process.platform === "win32") { + return resolveRuntimeConfigDir(); + } + return path.join(resolveRuntimeConfigDir(), "app-data"); +} + function readConfiguredPath(name: RuntimePathName): string | undefined { const key = name === "home" ? homeDirEnv @@ -59,8 +77,5 @@ function fallbackAppDataDir(): string { } function fallbackUserDataDir(): string { - if (process.platform === "win32") { - return path.join(fallbackAppDataDir(), APP_STORAGE_NAME); - } - return path.join(LEGACY_CONFIGDIR, "app-data"); + return resolveRuntimeDataDir(); } diff --git a/src/main/storage-migration.ts b/packages/core/src/storage/migration.ts similarity index 100% rename from src/main/storage-migration.ts rename to packages/core/src/storage/migration.ts diff --git a/src/main/sqlite-native.ts b/packages/core/src/storage/sqlite-native.ts similarity index 100% rename from src/main/sqlite-native.ts rename to packages/core/src/storage/sqlite-native.ts diff --git a/src/main/usage-normalization.ts b/packages/core/src/usage/normalization.ts similarity index 97% rename from src/main/usage-normalization.ts rename to packages/core/src/usage/normalization.ts index a3dfa0b..e2355b8 100644 --- a/src/main/usage-normalization.ts +++ b/packages/core/src/usage/normalization.ts @@ -1,4 +1,4 @@ -import type { GatewayProviderProtocol } from "../shared/app"; +import type { GatewayProviderProtocol } from "@ccr/core/contracts/app"; export type UsageTokenAccounting = { cacheReadTokens?: number; diff --git a/src/main/usage-store.ts b/packages/core/src/usage/store.ts similarity index 93% rename from src/main/usage-store.ts rename to packages/core/src/usage/store.ts index 45cd1f9..d35c73c 100644 --- a/src/main/usage-store.ts +++ b/packages/core/src/usage/store.ts @@ -1,10 +1,10 @@ import { mkdirSync } from "node:fs"; import { EventEmitter } from "node:events"; import { dirname } from "node:path"; -import { USAGE_DB_FILE } from "./constants"; -import { estimateUsageCostUsd } from "./model-pricing-service"; -import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "./sqlite-native"; -import { normalizeUsageInputTokens } from "./usage-normalization"; +import { USAGE_DB_FILE } from "@ccr/core/config/constants"; +import { estimateUsageCostUsd } from "@ccr/core/models/pricing-service"; +import { createBetterSqliteDatabase, type BetterSqliteDatabase } from "@ccr/core/storage/sqlite-native"; +import { normalizeUsageInputTokens } from "@ccr/core/usage/normalization"; import type { GatewayProviderProtocol, UsageComparisonRow, @@ -13,7 +13,7 @@ import type { UsageStatsRange, UsageStatsSnapshot, UsageTotals -} from "../shared/app"; +} from "@ccr/core/contracts/app"; type SqlDatabase = BetterSqliteDatabase; type SqlValue = bigint | Buffer | number | string | null; @@ -90,6 +90,7 @@ type UsageSnapshot = UsageNumbers & { }; const usageEvents = new EventEmitter(); +const usageStatsRanges = new Set(["today", "24h", "7d", "30d"]); const emptyTotals: UsageTotals = { avgDurationMs: 0, cacheRatio: 0, @@ -175,10 +176,11 @@ export class UsageStore { usageEvents.emit("recorded"); } - async getStats(range: UsageStatsRange = "7d", filter: UsageStatsFilter = {}): Promise { + async getStats(range: UsageStatsRange | null | undefined = "7d", filter: UsageStatsFilter | null | undefined = {}): Promise { const database = await this.getDatabase(); const now = new Date(); - const since = getRangeSince(range, now); + const normalizedRange = normalizeUsageRange(range); + const since = getRangeSince(normalizedRange, now); const query = buildUsageWhereClause(since, filter); return { @@ -186,14 +188,14 @@ export class UsageStore { generatedAt: now.toISOString(), models: readModelRows(database, query), providerModels: readProviderModelRows(database, query), - range, + range: normalizedRange, recentRequests: readRecentRequestRows(database, query), - series: readUsageSeries(database, range, now, query), + series: readUsageSeries(database, normalizedRange, now, query), totals: readUsageTotals(database, query) }; } - async getTotalsSince(since: Date, filter: UsageStatsFilter = {}, options: UsageStatsQueryOptions = {}): Promise { + async getTotalsSince(since: Date, filter: UsageStatsFilter | null | undefined = {}, options: UsageStatsQueryOptions | null | undefined = {}): Promise { const database = await this.getDatabase(); return readUsageTotals(database, buildUsageWhereClause(since, filter, options)); } @@ -284,16 +286,16 @@ function ensureUsageSchema(database: SqlDatabase): void { database.exec("CREATE INDEX IF NOT EXISTS usage_events_credential_created_at_idx ON usage_events(credential_id, created_at)"); } -export async function getUsageStats(range?: UsageStatsRange, filter?: UsageStatsFilter): Promise { +export async function getUsageStats(range?: UsageStatsRange | null, filter?: UsageStatsFilter | null): Promise { try { return await usageStore.getStats(range, filter); } catch (error) { console.warn(`[usage] Failed to read usage stats: ${formatError(error)}`); - return emptySnapshot(range ?? "7d"); + return emptySnapshot(normalizeUsageRange(range)); } } -export async function getTodayUsageTotals(filter?: UsageStatsFilter, options?: UsageStatsQueryOptions): Promise { +export async function getTodayUsageTotals(filter?: UsageStatsFilter | null, options?: UsageStatsQueryOptions | null): Promise { try { return await usageStore.getTotalsSince(floorDay(new Date()), filter, options); } catch (error) { @@ -302,7 +304,7 @@ export async function getTodayUsageTotals(filter?: UsageStatsFilter, options?: U } } -export async function getUsageTotalsSince(since: Date, filter?: UsageStatsFilter, options?: UsageStatsQueryOptions): Promise { +export async function getUsageTotalsSince(since: Date, filter?: UsageStatsFilter | null, options?: UsageStatsQueryOptions | null): Promise { try { return await usageStore.getTotalsSince(since, filter, options); } catch (error) { @@ -346,19 +348,21 @@ export async function recordGatewayUsageCapture(input: UsageCaptureInput): Promi function buildUsageWhereClause( since: Date, - filter: UsageStatsFilter, - options: UsageStatsQueryOptions = {} + filter: UsageStatsFilter | null | undefined, + options: UsageStatsQueryOptions | null | undefined = {} ): UsageWhereClause { + const normalizedFilter = normalizeUsageFilter(filter); + const normalizedOptions = normalizeUsageQueryOptions(options); const where = ["created_at >= ?"]; const params: SqlValue[] = [since.toISOString()]; - const credential = normalizeFilterValue(filter.credential); - const provider = normalizeFilterValue(filter.provider); - const model = normalizeFilterValue(filter.model); + const credential = normalizeFilterValue(normalizedFilter.credential); + const provider = normalizeFilterValue(normalizedFilter.provider); + const model = normalizeFilterValue(normalizedFilter.model); if (provider) { where.push("provider = ?"); params.push(provider); - } else if (!options.includeProxy && filter.includeProxy !== true) { + } else if (!normalizedOptions.includeProxy && normalizedFilter.includeProxy !== true) { where.push("provider <> ?"); params.push("proxy"); } @@ -377,6 +381,26 @@ function buildUsageWhereClause( }; } +function normalizeUsageRange(range: UsageStatsRange | null | undefined): UsageStatsRange { + return range && usageStatsRanges.has(range) ? range : "7d"; +} + +function normalizeUsageFilter(filter: UsageStatsFilter | null | undefined): UsageStatsFilter { + if (!isRecord(filter)) { + return {}; + } + return { + credential: typeof filter.credential === "string" ? filter.credential : undefined, + includeProxy: filter.includeProxy === true, + model: typeof filter.model === "string" ? filter.model : undefined, + provider: typeof filter.provider === "string" ? filter.provider : undefined + }; +} + +function normalizeUsageQueryOptions(options: UsageStatsQueryOptions | null | undefined): UsageStatsQueryOptions { + return isRecord(options) && options.includeProxy === true ? { includeProxy: true } : {}; +} + function configureSqliteDatabase(database: SqlDatabase): void { database.pragma("journal_mode = WAL"); database.pragma("synchronous = NORMAL"); diff --git a/src/main/web-management-server.ts b/packages/core/src/web/management-server.ts similarity index 95% rename from src/main/web-management-server.ts rename to packages/core/src/web/management-server.ts index 6b31ff3..f5ff82d 100644 --- a/src/main/web-management-server.ts +++ b/packages/core/src/web/management-server.ts @@ -7,27 +7,27 @@ import os from "node:os"; import path from "node:path"; import { pathToFileURL } from "node:url"; import packageJson from "../../package.json"; -import { loadPersistedAppSetting, replacePersistedAppSetting } from "./app-config-store"; -import { scanBotHandoffBluetoothTargets, scanBotHandoffWifiTargets } from "./bot-handoff-scan-service"; -import { cancelBotGatewayQrLogin, startBotGatewayQrLogin, waitBotGatewayQrLogin } from "./bot-gateway-qr-login-service"; -import { syncClaudeAppGatewayConfig, restoreClaudeAppGatewayConfig } from "./claude-app-gateway-service"; -import { loadAppConfig, saveApiKeysConfig, saveAppConfig } from "./config"; -import { API_KEYS_DB_FILE, APP_CONFIG_DB_FILE, APP_NAME, CONFIGDIR, CONFIG_FILE, DATADIR, GATEWAY_CONFIG_FILE, LEGACY_CONFIG_FILE, ONBOARDING_FINISHED_FILE, PROXY_CA_CERT_FILE, REQUEST_LOGS_DB_FILE, USAGE_DB_FILE } from "./constants"; -import { detectProviderIcon } from "./provider-icons"; -import { fetchProviderManifest } from "./provider-manifest-service"; -import { getLocalAgentProviderCandidates, importLocalAgentProvider } from "./local-agent-provider-service"; -import { getProviderCatalogModels } from "./provider-model-catalog"; -import { getProviderPresets } from "./presets"; -import { checkGatewayProviderConnectivity, probeGatewayProvider, probeGatewayProviderCandidates } from "./provider-probe"; -import { applyProfileConfig } from "./profile-service"; -import { getProfileOpenCommand, getProfileRuntimeStatus, openProfileFromCcr, stopProfileFromCcr } from "./profile-launch-service"; -import { ensureProxyCertificateAuthority } from "../server/proxy/certificates"; -import { proxyService } from "../server/proxy/service"; -import { listMcpServerTools } from "../server/mcp/tool-discovery"; -import { getAgentAnalysis, getAgentTracePayload, getRequestLogDetail, getRequestLogs } from "./request-log-store"; -import { getUsageStats } from "./usage-store"; -import { gatewayService } from "../server/gateway/service"; -import { getProviderAccountSnapshots, invalidateProviderAccountSnapshotCache, resetCodexRateLimitCredit, testProviderAccountConnector } from "./provider-account-service"; +import { loadPersistedAppSetting, replacePersistedAppSetting } from "@ccr/core/config/app-config-store"; +import { scanBotHandoffBluetoothTargets, scanBotHandoffWifiTargets } from "@ccr/core/agents/bot-gateway/handoff-scan-service"; +import { cancelBotGatewayQrLogin, startBotGatewayQrLogin, waitBotGatewayQrLogin } from "@ccr/core/agents/bot-gateway/qr-login-service"; +import { syncClaudeAppGatewayConfig, restoreClaudeAppGatewayConfig } from "@ccr/core/agents/claude-app/gateway-service"; +import { loadAppConfig, saveApiKeysConfig, saveAppConfig } from "@ccr/core/config/config"; +import { API_KEYS_DB_FILE, APP_CONFIG_DB_FILE, APP_NAME, CONFIGDIR, CONFIG_FILE, DATADIR, GATEWAY_CONFIG_FILE, LEGACY_CONFIG_FILE, ONBOARDING_FINISHED_FILE, PROXY_CA_CERT_FILE, REQUEST_LOGS_DB_FILE, USAGE_DB_FILE } from "@ccr/core/config/constants"; +import { detectProviderIcon } from "@ccr/core/providers/icons"; +import { fetchProviderManifest } from "@ccr/core/providers/manifest-service"; +import { getLocalAgentProviderCandidates, importLocalAgentProvider } from "@ccr/core/agents/local-providers/service"; +import { getProviderCatalogModels } from "@ccr/core/providers/model-catalog"; +import { getProviderPresets } from "@ccr/core/providers/presets/index"; +import { checkGatewayProviderConnectivity, probeGatewayProvider, probeGatewayProviderCandidates } from "@ccr/core/providers/probe"; +import { applyProfileConfig } from "@ccr/core/profiles/service"; +import { getProfileOpenCommand, getProfileRuntimeStatus, openProfileFromCcr, stopProfileFromCcr } from "@ccr/core/profiles/launch-service"; +import { ensureProxyCertificateAuthority } from "@ccr/core/proxy/certificates"; +import { proxyService } from "@ccr/core/proxy/service"; +import { listMcpServerTools } from "@ccr/core/mcp/tool-discovery"; +import { getAgentAnalysis, getAgentTracePayload, getRequestLogDetail, getRequestLogs } from "@ccr/core/observability/request-log-store"; +import { getUsageStats } from "@ccr/core/usage/store"; +import { gatewayService } from "@ccr/core/gateway/service"; +import { getProviderAccountSnapshots, invalidateProviderAccountSnapshotCache, resetCodexRateLimitCredit, testProviderAccountConnector } from "@ccr/core/providers/account-service"; import type { AgentAnalysisFilter, AgentAnalysisTracePayloadRequest, @@ -62,9 +62,10 @@ import type { RequestLogListFilter, UsageStatsFilter, UsageStatsRange -} from "../shared/app"; +} from "@ccr/core/contracts/app"; export type WebManagementServerOptions = { + authToken?: string; host?: string; open?: boolean; port?: number; @@ -124,7 +125,7 @@ const pluginMarketplace: PluginMarketplaceEntry[] = [ export async function startWebManagementServer(options: WebManagementServerOptions = {}): Promise { const host = options.host?.trim() || readEnvString("CCR_WEB_HOST") || defaultWebHost; const requestedPort = options.port ?? readEnvPort("CCR_WEB_PORT") ?? defaultWebPort; - const authToken = randomBytes(32).toString("base64url"); + const authToken = options.authToken?.trim() || readEnvString("CCR_WEB_AUTH_TOKEN") || randomBytes(32).toString("base64url"); let security: WebManagementSecurityContext | undefined; const server = createServer((request, response) => { if (!security) { @@ -276,6 +277,13 @@ const rpcHandlers: Record = { getAppInfo: () => getCliAppInfo(), getConfig: () => loadAppConfig(), getGatewayStatus: () => gatewayService.getStatus(), + getServiceIdentity: (serviceToken) => ({ + pid: process.pid, + serviceTokenConfigured: Boolean(process.env.CCR_SERVICE_INSTANCE_TOKEN?.trim()), + serviceTokenMatches: typeof serviceToken === "string" && + Boolean(serviceToken.trim()) && + serviceToken === process.env.CCR_SERVICE_INSTANCE_TOKEN + }), getLocalAgentProviderCandidates: () => getLocalAgentProviderCandidates(), getOnboardingFinished: async () => Boolean(readString(await loadPersistedAppSetting(onboardingFinishedAtSettingKey)) || existsSync(ONBOARDING_FINISHED_FILE)), getPluginMarketplace: () => pluginMarketplace, @@ -1043,7 +1051,7 @@ async function revealFile(file: string): Promise { await openSystemExternal(pathToFileURL(path.dirname(file)).toString()); } -function openSystemExternal(target: string): Promise { +export function openSystemExternal(target: string): Promise { if (!target || target === "about:blank") { return Promise.resolve(); } diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..1f70006 --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src/**/*.ts", "src/**/*.d.ts"] +} diff --git a/assets/logo.png b/packages/electron/assets/logo.png similarity index 100% rename from assets/logo.png rename to packages/electron/assets/logo.png diff --git a/assets/tray-cyan.png b/packages/electron/assets/tray-cyan.png similarity index 100% rename from assets/tray-cyan.png rename to packages/electron/assets/tray-cyan.png diff --git a/assets/tray-orange.png b/packages/electron/assets/tray-orange.png similarity index 100% rename from assets/tray-orange.png rename to packages/electron/assets/tray-orange.png diff --git a/assets/tray-violet.png b/packages/electron/assets/tray-violet.png similarity index 100% rename from assets/tray-violet.png rename to packages/electron/assets/tray-violet.png diff --git a/assets/tray.png b/packages/electron/assets/tray.png similarity index 100% rename from assets/tray.png rename to packages/electron/assets/tray.png diff --git a/packages/electron/package.json b/packages/electron/package.json new file mode 100644 index 0000000..08551a0 --- /dev/null +++ b/packages/electron/package.json @@ -0,0 +1,10 @@ +{ + "name": "@claude-code-router/electron", + "version": "3.0.7", + "private": true, + "description": "Claude Code Router Electron desktop shell.", + "main": "dist/main/main.js", + "dependencies": { + "electron-updater": "^6.8.9" + } +} diff --git a/src/main/app-menu.ts b/packages/electron/src/main/app-menu.ts similarity index 98% rename from src/main/app-menu.ts rename to packages/electron/src/main/app-menu.ts index de2f219..b601dbc 100644 --- a/src/main/app-menu.ts +++ b/packages/electron/src/main/app-menu.ts @@ -1,5 +1,5 @@ import { app, dialog, Menu, type BrowserWindow, type MenuItemConstructorOptions } from "electron"; -import { APP_NAME, IPC_CHANNELS } from "./constants"; +import { APP_NAME, IPC_CHANNELS } from "@ccr/core/config/constants"; import windowsManager from "./windows"; export function setupApplicationMenu(): void { diff --git a/src/main/bot-gateway-qr-window-service.ts b/packages/electron/src/main/bot-gateway-qr-window-service.ts similarity index 98% rename from src/main/bot-gateway-qr-window-service.ts rename to packages/electron/src/main/bot-gateway-qr-window-service.ts index d020130..a65d490 100644 --- a/src/main/bot-gateway-qr-window-service.ts +++ b/packages/electron/src/main/bot-gateway-qr-window-service.ts @@ -4,7 +4,7 @@ import type { BotGatewayQrWindowCloseResult, BotGatewayQrWindowOpenRequest, BotGatewayQrWindowOpenResult -} from "../shared/app"; +} from "@ccr/core/contracts/app"; const qrWindows = new Map(); diff --git a/src/main/browser-preload.ts b/packages/electron/src/main/browser-preload.ts similarity index 91% rename from src/main/browser-preload.ts rename to packages/electron/src/main/browser-preload.ts index f479d88..2e1930f 100644 --- a/src/main/browser-preload.ts +++ b/packages/electron/src/main/browser-preload.ts @@ -1,6 +1,6 @@ import { contextBridge, ipcRenderer, type IpcRendererEvent } from "electron"; -import { IPC_CHANNELS } from "../shared/ipc-channels"; -import type { BuiltInBrowserState } from "../shared/app"; +import { IPC_CHANNELS } from "@ccr/core/contracts/ipc-channels"; +import type { BuiltInBrowserState } from "@ccr/core/contracts/app"; contextBridge.exposeInMainWorld("ccrBrowser", { back: (tabId?: string) => ipcRenderer.invoke(IPC_CHANNELS.browserBack, tabId) as Promise, diff --git a/src/main/built-in-browser.ts b/packages/electron/src/main/built-in-browser.ts similarity index 98% rename from src/main/built-in-browser.ts rename to packages/electron/src/main/built-in-browser.ts index d845d5a..a8ae1b0 100644 --- a/src/main/built-in-browser.ts +++ b/packages/electron/src/main/built-in-browser.ts @@ -14,11 +14,11 @@ import { } from "electron"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import type { AppConfig, BuiltInBrowserState, BuiltInBrowserTabState, GatewayPluginAppConfig, InstalledBrowserApp } from "../shared/app"; -import { IPC_CHANNELS } from "../shared/ipc-channels"; -import { APP_NAME } from "./constants"; -import { pluginService } from "./plugins/service"; -import { proxyService } from "../server/proxy/service"; +import type { AppConfig, BuiltInBrowserState, BuiltInBrowserTabState, GatewayPluginAppConfig, InstalledBrowserApp } from "@ccr/core/contracts/app"; +import { IPC_CHANNELS } from "@ccr/core/contracts/ipc-channels"; +import { APP_NAME } from "@ccr/core/config/constants"; +import { pluginService } from "@ccr/core/plugins/service"; +import { proxyService } from "@ccr/core/proxy/service"; type BrowserTab = BuiltInBrowserTabState & { view: WebContentsView; diff --git a/src/main/deep-link.ts b/packages/electron/src/main/deep-link.ts similarity index 88% rename from src/main/deep-link.ts rename to packages/electron/src/main/deep-link.ts index 9304f50..6d6f1cf 100644 --- a/src/main/deep-link.ts +++ b/packages/electron/src/main/deep-link.ts @@ -1,9 +1,9 @@ import { app } from "electron"; import path from "node:path"; -import { appDeepLinkProtocol, createProviderDeepLinkRequest as createSharedProviderDeepLinkRequest, isAppDeepLinkUrl } from "../shared/deep-link"; -import type { ProviderDeepLinkRequest } from "../shared/app"; -import { IPC_CHANNELS } from "./constants"; -import { providerIdentitySafetyIssue } from "./presets"; +import { appDeepLinkProtocol, createProviderDeepLinkRequest as createSharedProviderDeepLinkRequest, isAppDeepLinkUrl } from "@ccr/core/contracts/deep-link"; +import type { ProviderDeepLinkRequest } from "@ccr/core/contracts/app"; +import { IPC_CHANNELS } from "@ccr/core/config/constants"; +import { providerIdentitySafetyIssue } from "@ccr/core/providers/presets/index"; import windowsManager from "./windows"; class DeepLinkService { diff --git a/src/main/electron-web-search-mcp.ts b/packages/electron/src/main/electron-web-search-mcp.ts similarity index 99% rename from src/main/electron-web-search-mcp.ts rename to packages/electron/src/main/electron-web-search-mcp.ts index 8fa90cd..0badf3b 100644 --- a/src/main/electron-web-search-mcp.ts +++ b/packages/electron/src/main/electron-web-search-mcp.ts @@ -1,13 +1,13 @@ import { BrowserWindow, WebContentsView, app, session, type WebContents } from "electron"; import type { IncomingMessage, ServerResponse } from "node:http"; import { join as pathJoin } from "node:path"; -import { backendService } from "../server/backend-service"; -import type { GatewayMcpServerConfig } from "../shared/app"; +import { backendService } from "@ccr/core/plugins/backend-service"; +import type { GatewayMcpServerConfig } from "@ccr/core/contracts/app"; import type { BrowserWebSearchMcpIntegration, BrowserWebSearchMcpRegistration, BrowserWebSearchProtocolRecord -} from "../server/gateway/service"; +} from "@ccr/core/gateway/service"; type JsonPrimitive = boolean | null | number | string; type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }; diff --git a/src/main/ipc.ts b/packages/electron/src/main/ipc.ts similarity index 97% rename from src/main/ipc.ts rename to packages/electron/src/main/ipc.ts index 12cf675..ce6a657 100644 --- a/src/main/ipc.ts +++ b/packages/electron/src/main/ipc.ts @@ -4,35 +4,35 @@ import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync import net from "node:net"; import path from "node:path"; import { deflateSync, inflateSync } from "node:zlib"; -import { loadPersistedAppSetting, replacePersistedAppSetting } from "./app-config-store"; +import { loadPersistedAppSetting, replacePersistedAppSetting } from "@ccr/core/config/app-config-store"; import { builtInBrowserService } from "./built-in-browser"; -import { scanBotHandoffBluetoothTargets, scanBotHandoffWifiTargets } from "./bot-handoff-scan-service"; -import { cancelBotGatewayQrLogin, startBotGatewayQrLogin, waitBotGatewayQrLogin } from "./bot-gateway-qr-login-service"; +import { scanBotHandoffBluetoothTargets, scanBotHandoffWifiTargets } from "@ccr/core/agents/bot-gateway/handoff-scan-service"; +import { cancelBotGatewayQrLogin, startBotGatewayQrLogin, waitBotGatewayQrLogin } from "@ccr/core/agents/bot-gateway/qr-login-service"; import { closeBotGatewayQrWindow, openBotGatewayQrWindow } from "./bot-gateway-qr-window-service"; -import { syncClaudeAppGatewayConfig } from "./claude-app-gateway-service"; -import { loadAppConfig, saveApiKeysConfig, saveAppConfig } from "./config"; -import { API_KEYS_DB_FILE, APP_CONFIG_DB_FILE, APP_NAME, CONFIGDIR, CONFIG_FILE, DATADIR, GATEWAY_CONFIG_FILE, IPC_CHANNELS, LEGACY_CONFIG_FILE, ONBOARDING_FINISHED_FILE, PROXY_CA_CERT_FILE, REQUEST_LOGS_DB_FILE, USAGE_DB_FILE } from "./constants"; +import { syncClaudeAppGatewayConfig } from "@ccr/core/agents/claude-app/gateway-service"; +import { loadAppConfig, saveApiKeysConfig, saveAppConfig } from "@ccr/core/config/config"; +import { API_KEYS_DB_FILE, APP_CONFIG_DB_FILE, APP_NAME, CONFIGDIR, CONFIG_FILE, DATADIR, GATEWAY_CONFIG_FILE, IPC_CHANNELS, LEGACY_CONFIG_FILE, ONBOARDING_FINISHED_FILE, PROXY_CA_CERT_FILE, REQUEST_LOGS_DB_FILE, USAGE_DB_FILE } from "@ccr/core/config/constants"; import { deepLinkService } from "./deep-link"; -import { gatewayService } from "../server/gateway/service"; -import { getProviderAccountSnapshots, invalidateProviderAccountSnapshotCache, resetCodexRateLimitCredit, testProviderAccountConnector } from "./provider-account-service"; -import { detectProviderIcon } from "./provider-icons"; -import { fetchProviderManifest } from "./provider-manifest-service"; -import { getLocalAgentProviderCandidates, importLocalAgentProvider } from "./local-agent-provider-service"; +import { gatewayService } from "@ccr/core/gateway/service"; +import { getProviderAccountSnapshots, invalidateProviderAccountSnapshotCache, resetCodexRateLimitCredit, testProviderAccountConnector } from "@ccr/core/providers/account-service"; +import { detectProviderIcon } from "@ccr/core/providers/icons"; +import { fetchProviderManifest } from "@ccr/core/providers/manifest-service"; +import { getLocalAgentProviderCandidates, importLocalAgentProvider } from "@ccr/core/agents/local-providers/service"; import { isLaunchAtLoginSupported, syncLaunchAtLogin } from "./launch-at-login"; -import { getProviderCatalogModels } from "./provider-model-catalog"; -import { getProviderPresets } from "./presets"; -import { checkGatewayProviderConnectivity, probeGatewayProvider, probeGatewayProviderCandidates } from "./provider-probe"; -import { applyProfileConfig } from "./profile-service"; -import { getProfileOpenCommand, getProfileRuntimeStatus, openProfileFromCcr, stopProfileFromCcr } from "./profile-launch-service"; -import { ensureProxyCertificateAuthority } from "../server/proxy/certificates"; -import { proxyService } from "../server/proxy/service"; -import { listMcpServerTools } from "../server/mcp/tool-discovery"; -import { getAgentAnalysis, getAgentTracePayload, getRequestLogDetail, getRequestLogs } from "./request-log-store"; +import { getProviderCatalogModels } from "@ccr/core/providers/model-catalog"; +import { getProviderPresets } from "@ccr/core/providers/presets/index"; +import { checkGatewayProviderConnectivity, probeGatewayProvider, probeGatewayProviderCandidates } from "@ccr/core/providers/probe"; +import { applyProfileConfig } from "@ccr/core/profiles/service"; +import { getProfileOpenCommand, getProfileRuntimeStatus, openProfileFromCcr, stopProfileFromCcr } from "@ccr/core/profiles/launch-service"; +import { ensureProxyCertificateAuthority } from "@ccr/core/proxy/certificates"; +import { proxyService } from "@ccr/core/proxy/service"; +import { listMcpServerTools } from "@ccr/core/mcp/tool-discovery"; +import { getAgentAnalysis, getAgentTracePayload, getRequestLogDetail, getRequestLogs } from "@ccr/core/observability/request-log-store"; import trayController from "./tray-controller"; import { appUpdateService } from "./update-service"; -import { getUsageStats } from "./usage-store"; +import { getUsageStats } from "@ccr/core/usage/store"; import windowsManager from "./windows"; -import type { AgentAnalysisFilter, AgentAnalysisTracePayloadRequest, ApiKeyConfig, AppCaptureElementPngRequest, AppCaptureElementPngResult, AppConfig, AppDataExportResult, AppImageExportTargetRequest, AppImageExportTargetResult, AppInfo, AppRenderHtmlPngRequest, AppRenderHtmlPngResult, AppSaveConfigOptions, BotGatewayQrLoginCancelRequest, BotGatewayQrLoginStartRequest, BotGatewayQrLoginWaitRequest, BotGatewayQrWindowCloseRequest, BotGatewayQrWindowOpenRequest, GatewayPluginAppConfig, GatewayProviderConnectivityCheckRequest, GatewayProviderProbeCandidatesRequest, GatewayProviderProbeRequest, GatewayStatus, LocalAgentProviderImportRequest, PluginDependency, PluginDirectorySelection, PluginMarketplaceEntry, ProfileApplyResult, ProfileOpenRequest, ProviderAccountResetRequest, ProviderAccountSnapshotRequestOptions, ProviderAccountTestRequest, ProviderCatalogModelsRequest, ProviderIconDetectionRequest, ProviderManifestFetchRequest, RequestLogListFilter, UsageStatsFilter, UsageStatsRange } from "../shared/app"; +import type { AgentAnalysisFilter, AgentAnalysisTracePayloadRequest, ApiKeyConfig, AppCaptureElementPngRequest, AppCaptureElementPngResult, AppConfig, AppDataExportResult, AppImageExportTargetRequest, AppImageExportTargetResult, AppInfo, AppRenderHtmlPngRequest, AppRenderHtmlPngResult, AppSaveConfigOptions, BotGatewayQrLoginCancelRequest, BotGatewayQrLoginStartRequest, BotGatewayQrLoginWaitRequest, BotGatewayQrWindowCloseRequest, BotGatewayQrWindowOpenRequest, GatewayPluginAppConfig, GatewayProviderConnectivityCheckRequest, GatewayProviderProbeCandidatesRequest, GatewayProviderProbeRequest, GatewayStatus, LocalAgentProviderImportRequest, PluginDependency, PluginDirectorySelection, PluginMarketplaceEntry, ProfileApplyResult, ProfileOpenRequest, ProviderAccountResetRequest, ProviderAccountSnapshotRequestOptions, ProviderAccountTestRequest, ProviderCatalogModelsRequest, ProviderIconDetectionRequest, ProviderManifestFetchRequest, RequestLogListFilter, UsageStatsFilter, UsageStatsRange } from "@ccr/core/contracts/app"; const pluginMarketplace: PluginMarketplaceEntry[] = [ { diff --git a/src/main/launch-at-login.ts b/packages/electron/src/main/launch-at-login.ts similarity index 92% rename from src/main/launch-at-login.ts rename to packages/electron/src/main/launch-at-login.ts index f79ea65..731cb73 100644 --- a/src/main/launch-at-login.ts +++ b/packages/electron/src/main/launch-at-login.ts @@ -1,5 +1,5 @@ import { app } from "electron"; -import type { AppConfig } from "../shared/app"; +import type { AppConfig } from "@ccr/core/contracts/app"; export function isLaunchAtLoginSupported(platform = process.platform): boolean { return platform === "darwin" || platform === "win32"; diff --git a/src/main/main-app.ts b/packages/electron/src/main/main-app.ts similarity index 95% rename from src/main/main-app.ts rename to packages/electron/src/main/main-app.ts index eb7f7f0..f71892a 100644 --- a/src/main/main-app.ts +++ b/packages/electron/src/main/main-app.ts @@ -1,14 +1,14 @@ import { app, BrowserWindow, dialog, shell } from "electron"; import { setupApplicationMenu } from "./app-menu"; -import { loadAppConfig } from "./config"; -import { restoreClaudeAppGatewayConfig, syncClaudeAppGatewayConfig } from "./claude-app-gateway-service"; +import { loadAppConfig } from "@ccr/core/config/config"; +import { restoreClaudeAppGatewayConfig, syncClaudeAppGatewayConfig } from "@ccr/core/agents/claude-app/gateway-service"; import { deepLinkService } from "./deep-link"; -import { gatewayService } from "../server/gateway/service"; +import { gatewayService } from "@ccr/core/gateway/service"; import "./ipc"; -import { applyProfileConfig } from "./profile-service"; -import { ensureCcrCliLauncher } from "./profile-launch-service"; +import { applyProfileConfig } from "@ccr/core/profiles/service"; +import { ensureCcrCliLauncher } from "@ccr/core/profiles/launch-service"; import { syncLaunchAtLogin } from "./launch-at-login"; -import { proxyService } from "../server/proxy/service"; +import { proxyService } from "@ccr/core/proxy/service"; import trayController from "./tray-controller"; import { appUpdateService } from "./update-service"; import { browserWebSearchMcpService } from "./electron-web-search-mcp"; diff --git a/src/main/main.ts b/packages/electron/src/main/main.ts similarity index 72% rename from src/main/main.ts rename to packages/electron/src/main/main.ts index 17baae6..8873d24 100644 --- a/src/main/main.ts +++ b/packages/electron/src/main/main.ts @@ -1,13 +1,18 @@ import { app, dialog } from "electron"; -import path from "node:path"; -import { APP_STORAGE_NAME, setRuntimeAppPaths } from "./app-paths"; -import { copyMissingDirectoryContents, sameFilesystemPath } from "./storage-migration"; +import { mkdirSync } from "node:fs"; +import { resolveRuntimeDataDir, setRuntimeAppPaths } from "@ccr/core/runtime/app-paths"; +import { copyMissingDirectoryContents, sameFilesystemPath } from "@ccr/core/storage/migration"; const appDataPath = app.getPath("appData"); -const userDataPath = configureRuntimeUserDataPath(appDataPath); +const homePath = app.getPath("home"); setRuntimeAppPaths({ appData: appDataPath, - home: app.getPath("home"), + home: homePath +}); +const userDataPath = configureRuntimeUserDataPath(app.getPath("userData")); +setRuntimeAppPaths({ + appData: appDataPath, + home: homePath, userData: userDataPath }); @@ -44,19 +49,15 @@ function reportFatalStartupError(error: unknown): void { app.exit(1); } -function configureRuntimeUserDataPath(appDataPath: string): string { - const currentUserDataPath = app.getPath("userData"); - if (process.platform !== "win32") { +function configureRuntimeUserDataPath(currentUserDataPath: string): string { + const sharedUserDataPath = resolveRuntimeDataDir(); + mkdirSync(sharedUserDataPath, { recursive: true }); + if (sameFilesystemPath(currentUserDataPath, sharedUserDataPath)) { return currentUserDataPath; } - const storageUserDataPath = path.join(appDataPath, APP_STORAGE_NAME); - if (sameFilesystemPath(currentUserDataPath, storageUserDataPath)) { - return currentUserDataPath; - } - - copyMissingDirectoryContents(currentUserDataPath, storageUserDataPath, "Windows app data directory"); - app.setPath("userData", storageUserDataPath); + copyMissingDirectoryContents(currentUserDataPath, sharedUserDataPath, "Electron app data directory"); + app.setPath("userData", sharedUserDataPath); return app.getPath("userData"); } diff --git a/src/main/preload.ts b/packages/electron/src/main/preload.ts similarity index 98% rename from src/main/preload.ts rename to packages/electron/src/main/preload.ts index 4f45200..89e10f7 100644 --- a/src/main/preload.ts +++ b/packages/electron/src/main/preload.ts @@ -1,6 +1,6 @@ import { contextBridge, ipcRenderer } from "electron"; -import { browserErrorI18nLanguage, formatLocalizedErrorMessage } from "../shared/i18n"; -import { IPC_CHANNELS } from "../shared/ipc-channels"; +import { browserErrorI18nLanguage, formatLocalizedErrorMessage } from "@ccr/core/contracts/i18n"; +import { IPC_CHANNELS } from "@ccr/core/contracts/ipc-channels"; import type { AgentAnalysisFilter, AgentAnalysisSnapshot, @@ -73,8 +73,8 @@ import type { UsageStatsFilter, UsageStatsRange, UsageStatsSnapshot -} from "../shared/app"; -import type { ProviderPreset } from "../shared/provider-presets"; +} from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; function invoke(channel: string, ...args: unknown[]): Promise { return ipcRenderer.invoke(channel, ...args).catch((error) => { diff --git a/src/main/tray-controller.ts b/packages/electron/src/main/tray-controller.ts similarity index 98% rename from src/main/tray-controller.ts rename to packages/electron/src/main/tray-controller.ts index 7379dd0..b3d287c 100644 --- a/src/main/tray-controller.ts +++ b/packages/electron/src/main/tray-controller.ts @@ -2,12 +2,12 @@ import { BrowserWindow, Menu, Tray, app, nativeImage, screen } from "electron"; import path from "node:path"; import { pathToFileURL } from "node:url"; import { deflateSync } from "node:zlib"; -import { loadAppConfig } from "./config"; -import { APP_NAME } from "./constants"; -import { getProviderAccountSnapshots } from "./provider-account-service"; -import { getTodayUsageTotals, onUsageRecorded } from "./usage-store"; +import { loadAppConfig } from "@ccr/core/config/config"; +import { APP_NAME } from "@ccr/core/config/constants"; +import { getProviderAccountSnapshots } from "@ccr/core/providers/account-service"; +import { getTodayUsageTotals, onUsageRecorded } from "@ccr/core/usage/store"; import windowsManager from "./windows"; -import type { AppConfig, ProviderAccountMeter, TrayBalanceProgressConfig, TrayIconPreference } from "../shared/app"; +import type { AppConfig, ProviderAccountMeter, TrayBalanceProgressConfig, TrayIconPreference } from "@ccr/core/contracts/app"; const popoverMenuWidth = 420; const popoverPreferredHeight = 740; diff --git a/src/main/update-service.ts b/packages/electron/src/main/update-service.ts similarity index 98% rename from src/main/update-service.ts rename to packages/electron/src/main/update-service.ts index 290ba6a..a507e60 100644 --- a/src/main/update-service.ts +++ b/packages/electron/src/main/update-service.ts @@ -1,8 +1,8 @@ import { app } from "electron"; import { autoUpdater, type ProgressInfo, type UpdateDownloadedEvent, type UpdateInfo } from "electron-updater"; -import { IPC_CHANNELS } from "./constants"; +import { IPC_CHANNELS } from "@ccr/core/config/constants"; import windowsManager from "./windows"; -import type { AppUpdateStatus } from "../shared/app"; +import type { AppUpdateStatus } from "@ccr/core/contracts/app"; type InstallPreparation = () => Promise; type UpdateCheckOptions = { diff --git a/src/main/windows.ts b/packages/electron/src/main/windows.ts similarity index 99% rename from src/main/windows.ts rename to packages/electron/src/main/windows.ts index 634ec51..81f7070 100644 --- a/src/main/windows.ts +++ b/packages/electron/src/main/windows.ts @@ -2,7 +2,7 @@ import { app, BrowserWindow, screen } from "electron"; import { existsSync } from "node:fs"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import { APP_NAME, IPC_CHANNELS, ONBOARDING_FINISHED_FILE } from "./constants"; +import { APP_NAME, IPC_CHANNELS, ONBOARDING_FINISHED_FILE } from "@ccr/core/config/constants"; type WindowName = "main" | string; type WindowBounds = { height: number; width: number; x?: number; y?: number }; diff --git a/packages/electron/tsconfig.json b/packages/electron/tsconfig.json new file mode 100644 index 0000000..1f70006 --- /dev/null +++ b/packages/electron/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src/**/*.ts", "src/**/*.d.ts"] +} diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..46df2e8 --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,21 @@ +{ + "name": "@claude-code-router/ui", + "version": "3.0.7", + "private": true, + "description": "Claude Code Router web management UI.", + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "baseui": "^16.1.1", + "clsx": "^2.1.1", + "lucide-react": "^1.17.0", + "motion": "^12.40.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "recharts": "^3.8.1", + "styletron-engine-atomic": "^1.6.2", + "styletron-react": "^6.1.1", + "tailwind-merge": "^3.6.0" + } +} diff --git a/src/renderer/assets/agent-logos/claude-code.png b/packages/ui/src/assets/agent-logos/claude-code.png similarity index 100% rename from src/renderer/assets/agent-logos/claude-code.png rename to packages/ui/src/assets/agent-logos/claude-code.png diff --git a/src/renderer/assets/agent-logos/codex.png b/packages/ui/src/assets/agent-logos/codex.png similarity index 100% rename from src/renderer/assets/agent-logos/codex.png rename to packages/ui/src/assets/agent-logos/codex.png diff --git a/src/renderer/assets/agent-logos/zcode.png b/packages/ui/src/assets/agent-logos/zcode.png similarity index 100% rename from src/renderer/assets/agent-logos/zcode.png rename to packages/ui/src/assets/agent-logos/zcode.png diff --git a/packages/ui/src/assets/logo.png b/packages/ui/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..76801d10af2a07417d061774f83295a07cd7fff7 GIT binary patch literal 1015415 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

+;D80OeLFX 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

y-3{Vj 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%0Au}0x5%=n42KW#QrK|?YCJJeCwwm+Q6e7O}7RJa1 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

md3(CM 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%0D|Jv{E#Mx6M5SgIa)$a$@12vGzpoD{fHc=vKM(& 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%0AhLOd217LU*bHV0X8%g+P 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%0A;}r{ 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%0A$4mF><8n|+pZ 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�c1Mw9GPNaVS&*%)7n4p9+lFBSU-*UN|Mrfj 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

!&3H(`Ic**(Sw? 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

ro8 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%0D99t&5~ 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 LNkvXXu0mjf;`%+S literal 0 HcmV?d00001 diff --git a/src/renderer/assets/onboarding/mascot-transition.svg b/packages/ui/src/assets/onboarding/mascot-transition.svg similarity index 100% rename from src/renderer/assets/onboarding/mascot-transition.svg rename to packages/ui/src/assets/onboarding/mascot-transition.svg diff --git a/src/renderer/assets/provider-icons/anthropic.png b/packages/ui/src/assets/provider-icons/anthropic.png similarity index 100% rename from src/renderer/assets/provider-icons/anthropic.png rename to packages/ui/src/assets/provider-icons/anthropic.png diff --git a/src/renderer/assets/provider-icons/bailian.ico b/packages/ui/src/assets/provider-icons/bailian.ico similarity index 100% rename from src/renderer/assets/provider-icons/bailian.ico rename to packages/ui/src/assets/provider-icons/bailian.ico diff --git a/src/renderer/assets/provider-icons/deepseek.ico b/packages/ui/src/assets/provider-icons/deepseek.ico similarity index 100% rename from src/renderer/assets/provider-icons/deepseek.ico rename to packages/ui/src/assets/provider-icons/deepseek.ico diff --git a/src/renderer/assets/provider-icons/gemini.svg b/packages/ui/src/assets/provider-icons/gemini.svg similarity index 100% rename from src/renderer/assets/provider-icons/gemini.svg rename to packages/ui/src/assets/provider-icons/gemini.svg diff --git a/src/renderer/assets/provider-icons/mistral.webp b/packages/ui/src/assets/provider-icons/mistral.webp similarity index 100% rename from src/renderer/assets/provider-icons/mistral.webp rename to packages/ui/src/assets/provider-icons/mistral.webp diff --git a/src/renderer/assets/provider-icons/moonshot.ico b/packages/ui/src/assets/provider-icons/moonshot.ico similarity index 100% rename from src/renderer/assets/provider-icons/moonshot.ico rename to packages/ui/src/assets/provider-icons/moonshot.ico diff --git a/src/renderer/assets/provider-icons/openai.png b/packages/ui/src/assets/provider-icons/openai.png similarity index 100% rename from src/renderer/assets/provider-icons/openai.png rename to packages/ui/src/assets/provider-icons/openai.png diff --git a/src/renderer/assets/provider-icons/openrouter.ico b/packages/ui/src/assets/provider-icons/openrouter.ico similarity index 100% rename from src/renderer/assets/provider-icons/openrouter.ico rename to packages/ui/src/assets/provider-icons/openrouter.ico diff --git a/src/renderer/assets/provider-icons/runapi.jpg b/packages/ui/src/assets/provider-icons/runapi.jpg similarity index 100% rename from src/renderer/assets/provider-icons/runapi.jpg rename to packages/ui/src/assets/provider-icons/runapi.jpg diff --git a/src/renderer/assets/provider-icons/siliconflow.png b/packages/ui/src/assets/provider-icons/siliconflow.png similarity index 100% rename from src/renderer/assets/provider-icons/siliconflow.png rename to packages/ui/src/assets/provider-icons/siliconflow.png diff --git a/src/renderer/assets/provider-icons/teamorouter.png b/packages/ui/src/assets/provider-icons/teamorouter.png similarity index 100% rename from src/renderer/assets/provider-icons/teamorouter.png rename to packages/ui/src/assets/provider-icons/teamorouter.png diff --git a/src/renderer/assets/provider-icons/zai-global-coding.svg b/packages/ui/src/assets/provider-icons/zai-global-coding.svg similarity index 100% rename from src/renderer/assets/provider-icons/zai-global-coding.svg rename to packages/ui/src/assets/provider-icons/zai-global-coding.svg diff --git a/src/renderer/assets/provider-icons/zai-global-general.svg b/packages/ui/src/assets/provider-icons/zai-global-general.svg similarity index 100% rename from src/renderer/assets/provider-icons/zai-global-general.svg rename to packages/ui/src/assets/provider-icons/zai-global-general.svg diff --git a/src/renderer/assets/provider-icons/zhipu-cn-coding.png b/packages/ui/src/assets/provider-icons/zhipu-cn-coding.png similarity index 100% rename from src/renderer/assets/provider-icons/zhipu-cn-coding.png rename to packages/ui/src/assets/provider-icons/zhipu-cn-coding.png diff --git a/src/renderer/assets/provider-icons/zhipu-cn-general.png b/packages/ui/src/assets/provider-icons/zhipu-cn-general.png similarity index 100% rename from src/renderer/assets/provider-icons/zhipu-cn-general.png rename to packages/ui/src/assets/provider-icons/zhipu-cn-general.png diff --git a/packages/ui/src/assets/tray-cyan.png b/packages/ui/src/assets/tray-cyan.png new file mode 100644 index 0000000000000000000000000000000000000000..b259c8ad417b3f34b35ae486448b19a30f9e6c1c GIT binary patch literal 90378 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ð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

F1$GrDXuYbK5||EDc$v6vIC^YKc&m0O%!3c* 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 z zNk<14p<0>p1BPPIp=lN0sJ>GLjY}gSB|XU%8sNj871f$8k+0zEV}z z4;6<-d{x>{;N9Sh*)Fl-rQ#G)pDuU`--@s9GF07ic&@AQK5gFXIyYZl6PsM^`*U`a zqJKlxS@s+UtdZ_w%yaI3djj@*-bM4U+iW-p_YV`PWms#Obec^4B=Q^za0vk1QMI2w zEQ>8Xj}3R#7})d>y7_~T&$T%qSxZxq)-vxWF9XPd+p_lk-lkgWAHR=swUzV3Z{aQoPuvJ3$|h|Hhpr2W9JHm>j= zr^ULmU97R}Doxwm2FvHFy*wjolkFiRIB)-+rH?A$ssj+PPJv9dEhaAP#`Ij5BhQqI z)Ojnj`cU7M_tZTJiS4}AUKa%?#UC4j9epXeSp)B z(dr1z^^!1Xq1JfRU-@}4ZSAnzKNj4y*0aj`SE)`CgMF0sh18_n|Mj4~;7K9tVSN{a zvM%fOk#6lyznw|r3yxh$nb|hnx3tDFxZ~0e&`A!!gITp85xG%gQYxkFS2)@T#m1SQhP!00a zUk_I`Ddf<_WQ7d*)4_5IX&T)u;qWi&JhpT+_UZc|(C=}yT4Vfj0bLu~GCJ#WUaqpL ztj@uoh2o!vFggvF*-EpAiW9(c<|LWfl~aGEC_b3i8=MYj>Sgzs5!6jXNmVN4A{l3b zuP$X)^qSDcZ%5y`ClL}tD;{|whff#*Z0lHO5_FnR02)hwtSMlL)d_TNd90Jgd*1I< ztbZOpDdn4N`#Tpfmj=0()ml4lWM^83?b?0%ed`mrj`sOH*Xd&JVPyP;^{v&VYE2e& z2)N!`M?PtTJR*ZtfW}z4QW>4w?7~i4RTaqW9TEZH=^uU3{wJ~L#c9UPH)mMKzcg=; z7NT#cqPjq?C*Jq$0;djD620-_|I^8QMY+t?d$)H1bshoje~Nxo1Fk(h273c84J%Z= z=T7W79+I(YAjfQAZDQRcMsO3B zmMk@%f185;&epAQ2~z8;at`yGpLZLF^gdMRV~zRWXX;;fICeSgICtH|&DHqcw`q0l zd3e-74i2khMjwznMf|KR*9)2^FMLAQSsrtptvpWKa$;XD!iHbhJdI_~X~ht+#Cz#u zqFa*R&5o|*6nAa$eN61FzB|`lkXsfG|6zTt+?!2-D&OloUrxw=xMLL4svCHu!9na> z8igQvfS)I!w)%-vKI?HRiQ}ppHq^c^3GKH}eo9``)wV_esV{)sB_Qi5GmtFlSME~& z-O(JAdR;iB2*Gy#nGeWVhc#k;oJP&o&m#@hKP|821N^TK#CCci75B9PzggBhj{5_y zy6tP8!K!^sI#YW{`++YHvT=*q6>gW%dl2qS`Z82ek<=8B82($M?Z5t3Z2g(3e&(}i z#~Jf65L_MI5zr#C_7LUqCn*i`G$P$~mwI=S_uS^$GuioZCwLS<7~kcsDti5M`Krk0 zwtv#^0d({MjJaQfNttWNwH231cJ#Dj!{awNZu$=Nw7jx08)^5HD`1@WPcY?$22Czpc0wO9A%$`q&WJbH5r4uT`8|epKFhCx-RDDY9LKNY9Qcs-@SvGmaKrAQ-it(#iM8N zb9l`uM;jx+AFga(Qvj-}qujj3?JB$Lu!N`jB-WVxNBlWZFtXHsmO<>n^0sqtwK?zE z3xc-J;}qi;jWG}VEE~Oc!(|9g(;fgn-#wkrFC4`Q7`XHwZ<49 z>EEwL%XkVF!yrkMq>af_rQv93MLoXn6h*S#%GETwR**7baGH>c@;d`2b5*uVTbHvq zG;|dC*sswr=6qk^+YfT+r-Ul)ewcw(tu^+YrXm9NFUM|T2Qls@aTxg+Lh{C=8CvX{ z5SZedjwzn4^MiSqu-*RevQdGLAb5Sj;^Xn1lwZN&1!k$7=Koj z``AH*BzPa(WIvHAO-DnYF7rG4ssC;4-0FFTfe#&(X?Wv#m0qwTf*Gi^eQK*|_!ahP zlSiyc1czI9a8&nheF^pzB^yN*rs@`bIEsD?a?Y1~55HAo$Gy&?>WaE^N#CfV*ecF( zK|`zrGrnxjvqO5#RT3V*$GaQ5xK(t-aWh4fPN)wsK(p0pPCv0XH`p{_*uFu2k^JD> z18{iYv~5yIj3LS%ImJus7gcS>Pr$z3k#Ck4VKittC$*O#NzM@II4R+*fI2Bash8Lx z&286t#XZM?+)#6tlKhNBck;~g?xDW!Z+Q$6KV>b?G1V&U03f@>NeVI50)~nt7gIFU**G#Qwg!Dd?@goj z)4dWNmJ6_bP&llM_B%Ctn1+9}CtO@TcrpkN$P6p;t3thcSoSN=f*VpETXH zAYqPHri37`QHm$;^WCZ*5OPQQtRcFF^a9?<_CNXDK1v&)aIgx}tRJ7Q8{8&tkli&- z*-o0)+}?_wKmw-m?#HkUy)6E&l!!XTKb%r~I+lXXmQm8CBJ_K-2S2|Wx?gD+&J1}k zZ~pG2*#+0HaThQ~@fY}`zzuG(zlP7?5)TtWY(^5@63YJ**6jc-j2F|L+*1rwZ$^xq zkHj5zf#e!V+D@T0XCB0K= z{#w7&*tg%wkRHxVRR>@v1oPN%*DyYMQ&2jV;=3>)nRk}>&wguuz26Txa@~bPy3k3C z;XjN0gqUdb4v&^PndnV7idPzU7IbG zaABite7dP*h;J9HYz!i+cJh%F<;wPkkquO&AVg+5&{uWcrlO7RYWNe4=xv%2wk36+2KE;?DQ{Q9yu1r?XgfkQz z0iM_Qz~J^ckH|OpWvl)LQY{4wvRCM-1!{XK30lq>k7lZMyR~bx6|FbQK=`a6C<9n; z$eMAkw491;btA#iZlQb{vWn0S;fF=b*cF~VOckTSrnNh*ht{i@73tW%+R8CeP8gNl zwl#pDt1T!1Nkb?UwS}q*xttqMvpK}3(v@gL;+(>9?^9mvd#K=jdQ4F5OHN)w*Gv6# zN!nJiHROt}o1?xgT?-`@=u7-r&%>tmyT?Sm_InNNbClcQQFhtIdR>**kQ4jfJkKeF zJ)|;@7Ulx%jJ(UUm*}JI_zM46kVF2hDtO>Yt;lMzE=W7tZzucop*OZ0shwinlN z$B69Sbz2}5fk@Ob_{m#%;~Pws)sweO&Kv4ShU%yBZlMc#tw`t?`Kx(Rp0Q5I=_`~Q zOef=R5-x=rk0z$dp74Z?!wqoNb*dp4p-BWRPChKU7`BEo6{p+VVB-a%ts^r=a1Muh zA2r?-hS7C)x!csHiqja9YI5X!n1^1vOOM=cqZi58u@i`NC8*J zJE)~UFdn-IH`&IYZ-N73gF-y&rF6=XoU9LiQ*Gr~N*B1#s+z85!q ziHrXoDPso+Ah#2lL552C)YWtxi(dWOA0imBPr`7^2~_g95gO5Y49hO^)>JQuXK614 zACW1jc#ig~U?aH*71w3Wlf=Zb2j+KBPjio*8r!co9v(R?=d6b^RJjae!!v#<)E+jt z7s0!6UeM{re~1WwG4AV29-dVhw?v!#MD%p(pe-DfkKa>6Q#B1x;y-4|AHb>DUrJor zm(deF;Z>$h)rLiw_OrjrfXo_!SU)9jefXObeS#cx|Wxc3MFB3}p({p^u^@EUq>GdoI~0x# zkOY#2ziZ0fk8!D`LSorbup+I>Z=^l7YE2}ZN`92$JjMCzxLuh8q#dPeYl?@rlz{>&&kvlTv#&Y3KhVXRXnxgsK zo|YNY{VRfJvfpW6wYBlxy?Mm~?aMqqb2zldS?3PJ zpSehMvFeBhaGl?)GyJy5xG_3d174Sz1e86s(?$> zs-pjbeD0!k_u(FTcNv6KP`egbTGvI@Isx5&sb z90CCj|0GEa!Am$WaHqiXQ$KTCg2BuUFd+7DlgPp)AS_3L?}!NjUM}23GIm#Usrdj6 zg=_Ja@Nzi0%I$QF8AZUWjGF z&jEN>2eSPvjzr0UF^^G=nzmSJnQOp9z`GsmgxG26QhN+)>as+_5Ie$RP7A0Z+#{%r z#qc(V__X<1?rEvw1zI%c1OLQ6G{*gROf(C&c~|j{y(0)Z#gR-#nMIIl7JS)K$qTRu zhL~qOn9iuN2yQV8Hh5R^K7U8hJmc9c#sN!TFGvM_=eUt%-jaWkv{A5D?pHUdezx>I z?gd^~I(4#L-|-q0;BmpxQ$CiypklBvTSx_l;H723YTydMi`*T1gqFUlFK|YVm6esr lJx=!8SbI$i%ci3xn#hVoOCn?^xJCC%`#69b?Zg?3hf9?AHZyX%7m|toRzu0)K zFdZ;k+NPU5kbNou^7-=R0#)Pn=|+e%72a+m8rrn!G9ZQ{S&C-pTZl%ZZ|8;C`lVk=`yI=uFgmK-@#J zJkzqiMjrFO>x6bD1U=v&XQDOY9`Z>1Wv3e&7A6GID-nRqqpM$CU1x^V_EoXb2c-Z0 zyU>4b6}RDj#z)Y*N5ZDz@t+OWh@&sakCFHr0bfV|?-qepZbWNDP9c13vrGBMQKc!6O=KmBrqn)|DTJ*x}hUh=`!HeHb9!c3=;hEAK z&-~9;YyL!1U|c=Hp7ghle@lvAictJI|B+|-za(A$eYHP*X8-H|81t{~zX7jqJ>o}r z2L1b1O9;q>fY1HO3#Nazawby5Txk{B*1op=KiA>4iU8uA3G2@rZn*tpXuMChX&LAP z9%7H8xyAm`pchNbcV(+(EpDOXe_7!3UzZ+6NM+9b%L4U(U8M>h?z{5)f5YUV&y}yN z&F2tZ|EO@~tBp*&2}}-L6&(I0!RTKJ(7&S#ME)%S?n;6qm{8?E+Tz`+_G`Z@q^8iJeY2`d%z%z|$`1F$d`8lnf6n`#1-&M8S7@6&-+?IC`f zwB|L4w20DS=r=lO=<#i*A2t`1vx709JBEOp>NhHD9!jV$=gR~ZL(lQChc%2Eu<|5=_~6%MGB);2oqR}z8#kAhiztQq ztyGNw{oBsae&utRS`x>yiGS^YK! zn2J4D?L}L4k21@1WZqNq`@f6HXC05G6%U}TujRx)WOF0-I&4X_hf~m|S@2Jd4_MGC zT7riz!f|d_zy9d69MieK!Im$DNbpH? zpS2tvck%#F<4DAmEmvsGo#Q{aeQ-%{f2*-<6pzP)Qf|?m7z{b4$C2j%QuhbAOC~+4 z*nFN65wp|;bKbRuFD_h&BP|gP7xi-n@6+3}B(5M&n7@0b?~OztRntp04};TBITzw< zNS{-a5AfLqF?4geGp4hw!v8zo9-q0L_Kok+2iOCv1Xe@A?Wigko2O1@wuUzq4mDqz z^GTh}RmTjDknSNj8N7O~YSO0)S^h6(d_5#CvsJ|lDpMfZ^Di_@`-`VsAj00$f3W)s z%}!*l@U+#1Dd!)I!{;6~zboX^Zu7N5{sUZ23mVJbSKw~~?~ig`BX$U)J^w+vMHtb& z8eC2y)-it1_-~$|zk(t6mD{~{)`)NIlKXCbMe$eEeKn`7=aJrdf4F=BK93lZ`fvqC z*JJ{B&3#P>%5ZxH9+?C_^7@T(_iue2Bj$U{Y9=%D`DJ>k`Ssstt{nm>@xZ~1c8B#I5(_bP z=b;K)?q@A45J&Wy_GM@5><;t72kJrkTJpwq4U2s> zTpwrv=<_~neyLH{^8)Pfgsko2G$e>gdf=guv?CgPI2HmpnKeeACpjcg;rhgQZ|%Aa zak{nMh>U1|;EVkyc zi*T~8@i`uQjf0ji>e^<%!iZv3Wij^w*&8ndfUq?|r%?04G-vy!8kjTTOldqb(&U8v zki;3QhBIhJz?_b*77gS7I>9`x`W$cq8 zJ;;|o{GAVD51c>j=8Fd|&adO^t0lwe6nA|1ot`MO0da!n{tAsbJDz`w2r*;7rJ-Q^#j}&hhA9#E1_1OX1AU+=U_xNA28@SylZ%unYJG9SrNiAQa3yN~ zqW3fkEL4ABzBlM`BYMq)@RQASkUh4nqh_e3w`T3ZuU=B$I|a~Tm@pqflUHi5Z&^e$ zf(=RAfG7Z+wlaa&Y*JPf0R2!7;Pv_mRRKPtB*~O4`1Tb9WwOE%$-d19_~Neb<^%#Q z(7u$ht|s8ZN3;f|sef$tGk9+OpiBzCu1{v~``U*WJs>Sx4`hgRpBTc}QNqhc3_OT7 z;jXSJhE)U3YI?+utO96Hv%FZP;h1_J`asMWyX%a2ApY6LJE^Hkrcwym1Ll9Ncv^=7 zuI7xi!3vu_seFH(?loXSj2*qEHN^|!$E2uMkcDkls3v!^GtpYt=iI;M2cs$59l%@E znf&IF&zH_)jtk;R{rgI7H8Ll59Zr+RxQ|a`P+-?yGnp7g>BCK6PWtdQ$I;?7s>eMq zmt$L>DZVkrW6~Y7ZW;eu&D)T+Nl-5Id^!d(MY=^bg&Jmn!Y|Q&`)@9qVa1&I5Lu7shVAiD$a0J+C8Sy#4l%q?k!*RCLB5w1x=?K5whw2!uCHXV& zjFwbf7F+enxR1yg>xdLcY&rPMDN5kvzEoh#%m_XZyNi~(T#oRz=0iz<03R2+AnQGK zms=1Z!cjeag2~Z0ADhuJCB=rYyA->q+2QuvT2(x?FI_Z&b=2*^a2*dFyiOfP8e&{F z;IK~QnH$?QvntC!)u$PJe1)A34ea$$}i}71tB|Y_7+%<&rFJ#+dI&r9V4|B-O3rVh|00fRsJePmu&C(RUg4)!# zjXCSVyRHZbRbN<}bXeMEJGJxfgkfrEK`?)+KM=MlTW1{A(Kst=hSvNdEM}tk*83Rf z(V?;bhBKS<5&Uo)9YR|3b+FWadnv{zwhh+=*jNYR{utLdH_W98Cq=ZMJG85zS4|z} zeG-`H16m|!UjX>=s4VBa=ih|$HmCDv{)I^9mqg^oU~{|mfy=V!HVxJw=uL6w-=GmQ z|II8gzH|b>%27BpC0qVA9FWwvyLSuH3e(oO>{J98K+*ILljZ5vj%`aq&{F3!U=h%( zj}-M4cc%COd*bLn6J~ob)%?{p(}bwUdR{Y#JVwXw>N~*O&ybf55+DVIyOcIaRT30$@zy1Nb0n7abCm~zV10l zPZI$j7J*$>k!-lPm!PU7wu^I(F=#>&`32tTs4@)z>KgFSj{CAlU&b0Cc9ap= z>tEk``KkV!pWl!SdLzRHG^)7c2ta(}1|ofMNBxc%C=k&Fx=1EnXtc%g97XN<#q?}G zcy<{LA6+?sSNqh>;RAeembEd(YYkF|>2*AZk3EgoSYX3F_8#v9nwB>((?#s!tTQX5 zU^c^I2QYrr;+C__W~XC=43zQQob}U@NK$813GxRrV%~iO`;yJ?h__C~SWFPE25>ha zayX)QDzC#gbv}N-zgqh*VUTw0&?6v+)V=w?Ehd{fds2XJuOnOHJ=MK#^1Y38e8?sh z_>gUGMghTSBndlxz>8lw6(Ltm-YJThwQD*MW7dBDHTV>@Om*=Ik3=)(Sxhr*plu{Q z0F3T+L_ymnqO{Cw|d7n2$wOc?*hdhPCJSRoTScNZS`fjQ1wh z;^3!eO!#UuSmI2^G(il9A%Sq?WK3;e0j?TDH7}wy8mIW?4?Zo!c;wxsU4o$s@RbU| zsnsi7Ji>75r z3V3l&v)`MDFI0f)UR2Y|!$4dS^gIQ}nu!odmBjG}+II^`WF(i?s=P@$@{V~j}jTU-6F)Nz;%WYpErq_ z4=H~c`Gd0YqJmwP;&soDf&T7w+}u7}T*GPWnEVD`A=q%^k~KmKS`Cd5T{Bz#I0Lb* zv)cF4*!5q!C_Z&Dw<=Y|H~GXSNAIG|EKLX;#-_iE;e6*o7)p@X_O3_GJODK1g&etB zjnS-rP9pln;;c~XnYc8>&pUdJ=9dPkU(wrBRXlrymE1NpvGqnmNG#}WWxTZ)UD5#O z`;KETme49Ps}QqBRNr)~C$h@y;wubzR>MEGW`_M;+G~EJJpK4QV%5ty2DBj(6py{_ zlu>&Ujl0YhGOz5SqBg${R6NK&r?+ighn11y(w*(k4Oi`dpJkxpz%3M*J0jCw&qeko z7LD1E<4~7VD}R)^qyKNm%K{e=N(?;axy1j$e^V7)b8=2^O33duwYg@7vBwJ#li7kY z1RY7G#rO_Win1Z{leX{IwWy+4(u~%U-5Q7^%LNoMFY$V%?UArLUSfm%mKV`~rg5X2 zIcA83(sJK^7tywpmykJ3AicB|k{f(B$fR+BHJ`(@zyNM*8z9U@8o%%OpePs%D~Dn> zJlYI_pn2mHA-G=?Zp{Z;i~2qfZ9Q4X{~?G6FlD}Mb}TtGoE&a1Gu19- zv4>7Iv}IyQIuXk+w@mKZn5}tg0vol>m`B+k;_$ zH_!vHijWpe5^lHw>eCaG;WJl$*@Oh5c5!dkNHdY+u(lJb%tk~4=y=a1#`}!vIIG0( z4lhm}AE$d4Pn$rLnKNy9Jt|GEcyg3~-Po-3YuhSSZR-(dTLVhN{CsGb8wlTKYC{gt zJ4nM99VbC8NIzqsC)&a9B1I^oT%s)tGQaSLo41yZZNw01>vNy))lKSP2BPg5Ir_uC zQ0qqcj|o>wPeQrED3>uqe>o7`l#;|_DZ0%i>tdM+mWNzV3EkNhrt5b}79E)KV*S8w z^xB2Kz;f8!%$9#>(7HlyhfzJ7*ZFqh*m`gmN#h9ZYhifEVjfQ#VTG>0ZVVlbqFawO ztufOp*NnvNqoc^DP?@(6AXqE6 z?z9Q0sA)Jq#LP;1*z6+Jp>-!BXU(A9`G!O6vaxx~c{#uD<}3_57L$(6Fh?Pf8kbQ8 z=@$i{LrNxd)DFy%HNkv@B`E+SA)dV)u}krgnme2mxwUHr9wOaORqK>{$A5cQB+GsH>svGuMmLbw<>&En+YB%X_2I_%pzrod zH{Woh+UrG4zpK>f8S+B;TyGAiJYgc7y^!?&%Um=2lOGW`LCvVGB!Ddv-y)`@X@KULP9lBa#~zNEXY@*~AVBF#!sd3V&=Th# zeL_|T<62q~nrp>u;^gYw{coS}-AgwUqsO`LU!ELBdgeALedx%qAmX)aHR$8Th%=F-jkHFJ+;6|;wj z62>PW{(5P~KbyCCb>Ta-1+owC`My}8KXop?v3#Uvtv2wq$yVYiuVuH1sJ!Em^5(e3#JZLn9VjJmsGZDvZ zGH1u;prZy5CJri&+bWYf#&)8Ng!u@4(1QE$^=a%|J5Zh1p1u@~Rme%UzUDJ`0w}&D3X6?jm$9_;b$S@CKr*}P`pgw= z;0Z{%%P`;k(`%d_+5^?{_XqKVkKt?a{s0tr8v2##^S(cs{EWmUpArHrR*J$V1gH}= zklbEu?S+k%UJJ3azY@8HHnjgFgV(sA15j~V-gq-s7;1E|;UbxLL+aW1 zUd1yt-Ffz~8flXFw9|yuM|+us3^kM|aOw}+2lMp49u^VsTZOI0u`SyD>^(XHK0m2f zz9H%-TqV8u(qVL9PdF(Z^gL1D0h?5SEv{-qGid;DF=mMQG_XnD*E%U0^N3q;!~uK+ zUvhyU7peMd5rb-2`h(2*;~zlOVpRD>r|m~trCG115Ug`=FLUz^d!_hWxJ=_7X0sSV1BS|8dh8#AT2?r;qD7UK!Y4SLI4hWzM z45PTK7`&28cDHbjeDz1)OUbc;x3Vho5KpNJP39~M20{hljr(~cnUReREVJ@Gb<6I@ zVMM<_((#5G3~~_M)&DJHOdOG{9`}(wmC1=ZmG8B)hkD{-YzgKSjt7_q70-#NsE`Eehyuo+?SY4|?YOBjsrTb?+c&oK( zVCJTq9`h48de3eaw5`uZaJOPY(jZN6J`kh_)t!?B_CE?N3{3>r(>Xz_=)ey5tJqr0X6)5DxF%YKmRh?Df z`sPA{Ot)l*e{eHNa(%1@mauus49QrfP1*JuvXn1#xR)G6bABvsbF2GL0W2^_l17uU zA=^{DN{~v1)}kx^RzHA9o(JvDZb|<>bf)`c80otTsL9jz_^`dVNXbVMadg(=s9Jzh zR|6f{T2;)w_?U>Q>~+g!m)eW#v21_pz>*d#j@!&Am|M0$Apv+4geYMFW##E_JqD1d#88 zUt9QYo^)%Jkv05^*~s!_m^5%|3LD2Gj zIyO+iyY4$nsIcM|1RZZb0iT6&EvK5&jT=->xkKFEyLZ4x8i)|_R**Jyr*;ui;f68X;)^8!+jx@zAVFCH=k4dj)xn?5P|K6lGb z=N)71tu4_sm!|^Z47Tj4vL!8=EaCk6OUGZ#$l|F(sdR>qqjY2BOHH`@JU z8Ld_+CsHV*_zPcj=K9O2o!XH>|db6S;0K=cRIJDAll41$Qn{Z*>deke%k9 zR^lzrzwuRXJRczILX!|Kvc~ie7XUGs5LJ1Q_~OmZ>+IST2+D{w$W19Wbtr>c#6!4J z+!`$?NfBp0nG@2bD|7L=;EUFW?#D$Gjj8d1rzyUV3`<;YJlyiNJaX3d|HPTkRmAR0 z7EyH~lP2Gus@KUF34FRwP%NBM;V0VED_ow#VWjSLU!aPqS&H* zX{`X5}L_Xgb2_k=L5=sMTd-`8vJ3%?YxRHBgYY!5-O_`#24gqwR;@l0k(YTsbn52EjC_Y zC$O6v+jP!|Xz~3$;CR3dHko{N@=9~fzT#bqBt_W7i^sMTU5|^jR1T}mIGuhLm!VEm z8$?O^%RNiAX(8U9qEf$`yq}Wg9QRP$9**U{c`M~h&g9e~4_gJZ$SW2V(J9o}kD?+D zS-XuUE^qr`d#d$mxBRbMZ7iWPA@?5CYC{yJL^5@G7h2^cOL|7^dmeVxW}QT4d~5KD zTDNeHygz_^oH)06{OFj+0Man469aJmd?S?st~8V$Gk6rHVWg zla$xMQQSGdIyjjGP5g{)Y zf*;@7Cs}smpG9NnvFIsK>xDWFKa1|}^zfH!IHbM7X8sEm`GGR{Ue}zlS4sVH_RE{Z z(AD_M*>OGpe6|nmIS^Fz%e1?+y?TIeDNpm4lqjB<hF_25v6ZZcl zP@d8(Lv-Q3zNU+Q%e&ogi9e@O!>%n@508-=MW?24zukD5meS|`;jsYZMf=XzP&|sI za?#z4A+Jc^66jF2EOW|N&}?LPel87r0h9g|q9Q0iIm(nfw*A~E=N=bE@&;A*FaSXj zR^sr~d@#S5Sdz=L;hlCn9LzV@k?h1-EA=2U2ugjk*{Sx zcXA3pu!WsNSAwYIMbJbn-&&RS?s4vU*NYfx*UVLPHKYLJgYw#$y{Ns@TIfyD?_x7S zqu3d;_S-vk;x)BgUjc6=gDlS{)H;KX@9AjJ;Vc9(WNR*Isxf?vw!O;r^!sg*Xc48v z(JT(O*dNTi@kH6yst3~Bk2q{HCB79Fy}Pw5ktb>KwJx?Bc55KoX-ANUC^vCxYiQ(Q9%evn>*qs9wTwbRI=7vK6UY{B46rSmRDov6v z*p?!S*zh3U#E`U372Qvuc&m~DtU6(Y`(mfsK)XCKUPT8ROTZ-6;ULL#=KB z&vv&a?ft>N^qz$~_{|%e71%nY%OLXnOi-S2?@q&*G=pr40Y<;HBrA`UnvdSdL8Z&l zLG^g33)9e){fTec4Ty zwldi%@S7&@wp=kA+1+kZ&9&hDig#6(t1|=KfKRv+swdT-@1omSmmfG$flg(^l;8VA-1(rN zS5Q4uAB1yL_fHgS#gU|cg0Q&U>7 z3W%K1NjqJ<3!+KC~R} zuzvK~n=a3h)A`Q_;`}_&WdnUM328{++2kSAxT_@_i{*t zZ126>wBRJu^F8IMStqVw*{VRX1TNo!o(v>Q=LLKi&7^uu6aMu#E!6K~3W({(;Tj4q zJNPrP((BktM9yDM)5sQoN?@Da!!Gp&er)QdQrCQ3Cx)A4flHv`F_F#}K(*8umX71k z9{xd^p9l0wMU*PW15fo>UpKdN0U;1>?DE3n(KrvUZ(^bJAMbi_d`iqW6K3E#)w=ca zZZ73FrMtp)xdi#_rtiN$kBbVYE@JUx%;1=RE-beEK-l!h>Op$5FS~_VcFyt7d%8l- zLiU%3 zyQ*YKS41SA*+d~n;QT&xPA*$B>cy^^(`clanWrwL5KpK@1vNp_Kj_$g8RVaQ1qnQc8xy2m!~T}+tcikxMrWIa!X zVJTrK&A}N;5wbxvv8je^coJ$tPn6|_9%?8<;BooS+}nfqX|yz+8G5QwYN)OLSbayV zOG|CWcaJdo#4s<|I>l%8#*UxPr?LJ?ap9xT?qrVp&*6rlc0O^ul7MH$FZu2*p z_%`>>9c8f%?yqrVVkrrVeFNn}x=S=>!buv?KV7!apINluZQg3sz93+YFu3)SSk%<= z^@{vp-Zcip$EX0M$v6T%)C9ra z-o~ef^=%D%WYPL_C}vj-1F~8JW;EI&hEZM5{CX@rp7DS=?%nS*0A$=z`ks2rnkQQ0 znLK%)wmKDeALJaq7S-n83Q~_(kM0-$YR7zS<@N`kTY<8>MqA!~NLJR2m-h{5#t$Q@ zK0>&-WQ-mf{qYG`;MI1!pUzSBVwCp7+PiM$Urxh><(l@*KbK$0)iT`oA7dA|9W|O) zMJ`>Nuej^G8^-kN)uMNEYO#G)?|R*?%)6<0`|2fU8Gf#?7}hr0UGLtZ{(Oe(Z&_>2 zRB8i5RYhsGeC=FwizQ80<@7y1zUNxHC{+2L`*hY`@E3AOBQn%YdY6pxb5mSqAoT zpGR++W3pUu+qw`LDbpj>rHF&)ZTb7%w36X2_)`iip1M?f{Dby5r?tVQQ*Z3=9tmL2 zF5AmZ7XjG-<2t24_eg;SmA`lc^pG^KKcD4*?$Dc5Zv`&ba{AsEcqWYxKD$SB-~XF8 z^<(+G9(UGclWCpvaI(E~P$tk`e}^a4wP26zvO;Zqe~5rk2Gx^fWHz^js>lo$ZTPFd zxL4~88LLUt>!lmTF=##&Z5`768K$b2`f-*kJ(j@$i{GIvH9ssQsP{7i{Yzr?g6b0` zRbBhxE<%U2mz34aB0_a>BD_X!m5AhZ@51vtlvH)R)bupLdib(XclhocAJ>XN$6%NO z0q4772Hp}Ax3TpBj|FVH0OQkApN^M8OXt4a?Cd-dbf1U(ZGA6~R|U$^ez@O`xS^gT z&N@W~AVvMpV3`^GpiX=S33G21|0RY4qB2V1p16;fE2X?L&wAT!W9s({W9~jJ0+{}I zgL2XYE6{u{ct65@OAuzbbdKad-2nA<(jEvf%FBXQ9|YKbfH9J6oAf@-SV^#l{31?J zSET55coxLqbtBV6?@7Cm`zPJHObwGC;vz*iXVjQa#E#k=-IPJ`PQfJ?yPv_q^NYCWZ!JN;|ZOwbKT(^P_j{9r`+5{7ui&JF8eGsqdFlNL>06nw&f~c zF4H-$m5>k z)MAZkYMI(s@qI`#^MSzt4jW^G2LT}lpJX`H(x2OjGO`;|&|Krb6?(UiX6sKFg{zha z&C=LJ1l^ZT8j{cRJVv*jPY3IXh1gW+YD2de|;w>IwlnB2PNPqe9=@HB45T&B&@CQmORo{JIXTMW>BoLs{!o@bJq6&OrZJL~O z&i=mnU2*-x(x}%W`sJ!S{lbq@h;#>QgJV-UKNCc7bOdfdVB9yo}+P$vE~4N7&oP>AoA*e)n@aMD~D$+l#)zd zs6Ota!@0H@@^=*2>i{HmQU7$(OcB85T4pt)$@b|G;)p;0d@3?i*Tzup-!YX0T9)x1 zuvDcg)1Z}h1c?2CKLb!7cD#Lj6!?PVd+rl`LLxnv4l%J@U&^DsBHlbMmxr|DR!9z0 zjYQ>&$3SEP9DVq`#CP++gm>x9Ff_fD$A$Fen3_nuag^$<+}c=%W62V=*H%iF*1uN| z-|j`FzN#O{kv%dg;z;{_-OqXK26@^OGVPdRX_g2fs_n5Y!Tb}$5}HlB*35!tm3W?v zID=AIL7}yLawk(Yi4^tRW^oonS9DiM7rTq?$ejYBu>L0xtg{WmwQlE$b2|Ng``TGJ z=q;*?k?yIE;_zOT`{ekWAw|LK9pMG}r_B?yZ2bvK(jtAYGJcz-Ka$n@!5E)-|MmQv zpvb&>9_QOyIe0!akI_g8P-qmlmwS|u?g{H}FPLBfzS@5l= zSTQlE&T3iK)1`SI&f|16yhb!KE{@(>dITX%0~RHEO~G&Rz=>5VcaXabI+#zxJI8Ry~mj&EpD1Tq(Ke@mDbHI=n%=c&Fe8%PAm46c`%Xin@>6=)FwxDyz%PQXa zFoMMYWL$NvT1pz$uFv`J6D@a5?4oZlSKibN`uKpu=z0Bcb9zXhvM%@AVTw}e>KjLd^WTnzJ2gJ`SP1$LvM@Tx!e&HMT z%ty}6sM_7Qt#b&lq6cgH~=sqr_-e)xbr(49#FN3`rUFoAGO zR6MP94(G0Z2n{edz-RUIde{IBorUtoxSX?OB-JDA7f?9(1Xg#^@aM3I_+gl?|LN^W zjVEM9l()4ao4jS#lCyg!T4xMzP)}3HPKxBz27PGL^s4UvDSFS)WIyb-mHEVw7mbL% z%O^ux`A9l84l~7S3sY5Q-mS-iY)|hLy^JS%+M?yk5Wmob;g{$nOfbVDwtqy@g82EDkd z+|P=Zc$uIi_@mAX-Cp=96+IR5UcnsF{L*I@{?Tu+s0pjlRS?j6STL^N-~l5es~i;53fbhn@!r-+{y9WepwxDt!GkgSkZW=T(}r~)#K2%uj-hFHs%40 za@n1;#w%~irK`Wh2N%b;q?g6&wUlvrBA&cUWf7vbYRp$BA}V$pRVt>8$l9OioA&Aa zuGKN}=;9dM`fn2cJNV<){1vqx&M9qzr@N`~6uHWM!Pub}lT#0~jHHaeFOQ+qKf*V7 z!UPAl6MsGlpL*sywP{e}s9H%gmnfW-9b?6G3 zmWtt?n=u(!J;A0oYfS`A&f!hsz4#-Wt8dIYb$rnKIH1=-k61>d4`P3n6+GWEoM2{0 zkN6gpE1U3JKtVA%V2~f3(dv$QP#cfxC}#g4^SqTlc#NXBksD14-S`@uX(zZL=gFLYMd z^T|I?PKO3mH}4x=9Aw$stP<1>XOqm!ho+2rmU@le=zZg2ZJE{60?*4-*7O{Biw4QT|>?C|tAo_<@n4;8Q-M z@7IZiLcQ+UxdrIg%_%hI?_kDse`T6&m}bRQznhf3`QwwM-GDLnPnWWi+*5}zPZrJJ zQLKfjyF{QJhb+x;CLYwC|H-Kj5$loOY!G?fE)$9_U~85oqtb%AN#9LI(>jFX8BvoNz5XNoNQ~ z4cL}0K~yN)zN_B_Ki@Kxa;yKCp74tjd8eXCFkc-|s3k6W(GzOh;#b=7fc~s#_7+#4 zb$#)49b|E>61wl5uja7Gy%Zfu=^p*|#T`p&_(zpKv9wUP$((Gl%8?OD8_`1aEue}8m&(B@^@H9 z_7UgO2#DS*drw2^L=pNZ557%ZF@`ApR{kUUX%YF5nwnDH_Oo}pHFPM?eDLwQ-t#WK zW_$ZTx?3&fk#<8SjY)7~-{qDRvAGeyI)%i~$AM=K{;=}P`6}q^^`1u3iWq0|`U;_i zno<1uT6;w=H`@ITwo82gXxDMkr>;$PVb{e+%q;0W7xl8`Ak}ST@_PH!7q?XK30US~ z-WRh;xjxmhuT!3OPV;eG9M`YN#YZ>0gT@|z+@Lj#R)V(n8F>hZ7_lqZl`v9NAL|6A3@-0d*FX?|gXJ zU`b9z&mQL$D(cfwTHWASo|jpe#loA=aVzE9lOgP0)cd@YLcg=Mi6T3=fh(hPZxGQT-u^Q>##od$n8aFlhNh(?ht zE5taS^<2FUk+Bgta-2&4(Qi}+ZjJ#DYS8c5w6@TeeUPpHGI7Qo(~98Etne~xJS*eP z08U#qCUyvr=>^+>olTrp^EJ34?e*|8Ytu<$8BtD6)$BC!F+ni(N=nn1tDzA4t8k+S zU#z<0DPIph;R@4jl~fv5d%0Pbs`CA{3m4nOQW5{R)UudZ9zZamf?c(a0=yxVCiaB} zb!4IVcsIqQo3sAy0%rZ99d4&)J`cR*BWb3rcT#zLWbaeH z)adAomgWI`~PrfViw zEAd^{Jhbryba@KN#6Hjxvz9K9!3u2M{CL1{6aTsy7F6MZJ-vXHPZdDjnld>Yb{kse z@#lCDMrVnMI$8 z6RH1j0i0Sj5=Wx>Kkp996HSfKan^e;4#n>znA<+E&l~n@R#oHAd?B0PSIHEY_amK_ zFp1;64xU%aO!@xo&qQc}E9K$DEA=qS9UEhYF7~OV0jq8j`qIhcn+XnfM?IuM`Zb2m z0-Ch6l!_H5am%SjA$_z7G}m6)^^=BC;`y;HGG+JMT=VQ%SI0soslM^|(L4)pa6ifq8>-I6gRMB~mvU}T7X-Zp&Lk3%}EW zWwa7Kxqf_nU z{q+GX-2&f33!dv%szw{(XNAsBTWqeEcv5gFD1Gnd9y+Ni2*}f-tvlkm{YJ%c+UG-F zfwb{kO2h14&boW!-V}V<(*cIIH>9!B&Z|W66OG!ftM8=5g(hv31YRmAY}Zg*63A%X z&BMPPmdIMF$(c7Sl63ZJgoEK)k`IT2j8)y*`{Rk@JB(703~VXl^~eLglWy%|^v04D z{$(A16CwuL>lwehgnrB!@f@6eN&1K#oNhVaQ`8+jaYLB~;9IR-sU}k<{97qa?%(xA zM$2$VP-F8PwuQNg7Z=xp_*=qqgNh!UAV~w_wvSvcF*Z(@FM;#F3q}7nF?uG4?Fyt} z(}5}>c{^(_g>KaCu@E(@r7(u&r95i?SoN+$l-klp*v#HcxYL=&r-#GPPL!2_HME{R zzIN}6_2Wk{`F75*d%7+mwYFTG#L0K$p5-OzISZ4iKOQv_3X1UHuYcFW#NUh`DLqm^ zj1@mvYxg#Zh_IQxNoGT?Ev$T1p=zr#ywa(>^Qvs^wi>VVb4@AlS5BiJC{>9q)wwmO zR;HcpWOVgX?O!Qwiy$aZMoL_H)53@rL?h@Pr5*e@s^unUBjMAJ{aY;Kk3C&g<%UFR zQnwU;H!#blXyppKt`Tw}13n6M5-p5qUE@#9t&zt8Cul|maKb0?f= zq1|r3;}8C{LGX)~q3=!JYku2~yqWdRBQe_=w-=^du+Ro^u*xTUXxR7bqsT8{4Xmxk zMkYjh=$px(NY)_DB@xsyK}RaY=Z9d9GtW?fb?`*N7mhDlc8xX@+|OeMOLF!84_j~H zR#n?}d(+Yai?vyT3S{B{i&7wP`yE`PL8xFO`9TgSlHe*qkV?rVH(b49CBdKc4a}iv* z7|O#n;PM(0MDYcz(&uhn0o4?TGtTl;MkyWbhf`S#t+{JVX9JteluVDhk!s?ZhdVW+7iFq8z_DmJE_IDuowuV6{1?`8$kECzUXQ;^@^i(z?DD2TBIU|~RAqBae z7iYF#=aqAW*XN`LEP~TO!Zdtxlf*2(a*?*43JkW=d2qehnysiWo+vCTi}(TognP?% zF?>u4<&f{iv*gtlR*!M$F^a=&W5~vs%&oimY5EzR(S1d6Mq4S= zZE;j^6Fx>P;~OjhjPnlc*VUUy^E+15q@mRV@xWqSC8Y1x4(twA-R(Qom`mcRh13Xu z0R=1y0HC+@qTm|(!DQ8-XqtJdSDZkVHWddChEWApc!gRu=vKHVH>pZ;im3X|8L}CI zT1E<`>_pi@($gsWH5Hq31m-PUQaBcJIx4qLKoSZovBR%E(f&SF$^P|7e5Iwqg7f*Y z;244@lHWhj@(s8>EHL(y-WdJzS~+|6xn{rLX#VMZnC*P=gxBnRdi#D`mliYPnm(W9 zN`ol(zDs zs-G-6kXSV*9Z|U{rikM;1At@F!ByVsWK*^5x}#CF7|HY&PPx-s!`5ehT&^9yMkPp# zxzAj{x5Nz^OeYmNGAO8RPIM^lw&x%y=i_#`Y@m4zwe4&8P_a@d2fb{X2YfW{+yFSa zVcwP%LwmeO?E+m!FbC+P}`1;NkvBJ{5dA$XM7wW;7KVS;#$!rP5~}OQXQu zrp3eNNf2626}Q#mx#IJlQP$Q|=d;%zc1^>$?`tbVXL`Te;s{>iJSx4vToyup0DD~z z%jpSA^T#B_cl8W8K87L>dBTm`=A<J^t6AP5lxQqcbr4>K!aKf_5hDDMsK3MJn(VoXVwAqed22c5<~N zv*da&*=97&R|yHo1AnRpi_^;GtMcKnv2|X^iWmlfq!^<*Zy=)`by6s)85n`h$wz9% z0xwQ#MS5H2m{&o&D_SKfg~@*xURNw(nl^|YOYN0=RWkt5CowdsO-$;xN+&-{vOOz{ zB6+k}!9okEuNoMCzID|2=vta1OdXGPyYN~2L?ti?^h4+ot;ki+9KQw zObpw8$>NEvc>8uAj= zd>jmvKONY+aE*>!!uPRP7kGOP$-EaAF+0_OeVMjX50HO`tu`IBirN?`E-|u-nBG4# zVB{MOjI2@j+xY=2DC}4#sY-^dxaJTjQX2N z&*T&tNHQBF~G-UG8*Y5Q3k54Qp*gb2f^7{pDhxRdCSHN z4Oy+aU|qr^HZE>oVCTI33j8PR+J-G!Q5;5fw-?0y>dF&*>H^oo0s_N|tL)CXJ!>a+$pX?<>yPV4GpCG0Q|p#Y3nR<#`^ntm@lCaIp1VK(@qN}8*a_8)T)P8n zR=1CZ4hxC_gysNj{YbE4iWM(eIJ1q(JzC>DQNgBxfZBrtYZrDR5%#UoErOoowNU2v7-aF6^v`Hd2-XcOb?B1^`!Q%IHTSGduCbPSan>#= zwTvH^ifRuS$EeENuO+}W!5X8<8cwIQY-7bE)LVb9{OlXr?-aRJ;%cW-UAm4W^YJAH z^UwTtzvn}(*Bm%v)Gs4_p>g%2QOaF%uuz>JtEYLu7Z-fwS8>(5UgV*Z<9E&sfr-}V zi?U=+T&(iBs+L(tN6zs1%(Ft1=0nWLJit-&^q6V1AYeeTQ_-1gaB|LDN&Y#Ml&NBJq*kWQ7YJ?YPUL*?=gA*%}TPB^(J>N+~ z9hLCkRjo4lP!{iI-Uv6xcrv@5?p2r*JlYX)GWjm;PZlcrT04a0kK7~5Bc_)%;8^pC zp;Ra-y@ys`S`eCrd?zA5J9s93x0PcssUej-sB+zB-r8kxmiKh&J}r!TtN%UxEt%0- zaFH<(?R|RCcsKB|ZR;BMVC!d()i1BELZlEqy{J~7>zSU@l?vtb%FhbN|3fZSkr#S4 zb)0JbrPwts)?HSLxUY67Y^ArtCK7NYDRCOft!k2KSoG{#!Y~O<$^W6}5*v)-FUU%5 z<=q7`WZ->tOevrp{5dm`*X98Fetl9?P=5IvnIaEd(xk-vVAkA#lR{=Z{>u`b&xPIg z9hlCEthA3`I9RoGiR-mv8YnKs5}K#SDT4v(tGnt4M1l1bM&(bxZjN<0APEn$$0H%l?&AOfWG|8d@4sCs65n6k=9Xno<{iFn4}&YU*iiUb1A zTd|Licz#3fKxFMVe}J*`-nz{Wf(Qyy2-w~1sFWo~nE9(eFA@{)&6pMo_r`Yz$tfj{ zJeYT*H&z~+R6P(G6tKlSA*>2o3H~_1Dgy|+`(g2_1pa}pkNs}QARr9rcy@kuYI@0}ay zwntYm|Dy$9Hsg!gTK98~tp38yyJvjs^_cm*AbQxdbDq3xE$zMZ(g>pypvkN0^Yvrh z)@AtAr%msB@JI91dyk1_cw>$0eJz|LpX;~&et#Gt_7#ZYYbwI6zX&CD#s|@A_4%Za zr4b9pUNQWb9&31mm>#AIh+fISra?!O-p07P(Wa~JP9#F9&*pAbr5SMcwKZ`wC0R(JCTV^YB*nZ%BnbjU$Sua9}|EV#wcZCWub7n1av%QcK|vcVRl!#;s7=?)u9 zr}A3KumR5TJy}=@EDdgT!2N7V^Deas{9JI#d_u+?F&4G$AsET!uQK9kNm1Q5*w>ND zr!Puq@7Z{T?S$7{mA);Rcx)l+o@GA6|C_^l!;r^6%aLtG3H-zUgegQ%fu-#6!iUOYrR520efO~V}} zZ5pT^p>g@MF`7_MGR6E^1V=SYT7^V{GS8RH2v#r3hB?=q>6Veqk^|tD^m$sGa+9ro z+!dFyPc>FVM=4XZz~YgvO9{&i*UTgsUN3F3l$L}Od=X#KQsV4iJ_gRLPh5U7&-ZtjMF$PYNN7er!4OeY>il=s)WjiDRczG*+6nC#1i>$YR#M3lphZ=dI`tKAe4 z`h1M>n?^7OMSmhAeu)ItQ~M#TY)ol$gJec2Kn6>cP2GFGlC=*k5F1&n@0GF82W0`G zdJQW(8LWG^gbj+KsTI>XoAVWdYNeO%M#!qoU-UN~Gq+6M(=H95U%s^Fko55V9%}oc z@?85cP|YghEp6h*Y%t0E{uk{t9pu*oQN`M%A`378eTsYuPJcn%$TP+Aq&dOsps9mZc3fM0HvMb;3;Nimnv|uVOchrj1d0*q zGKBn*fw5_}KcUolt%yK7ge|OdYa~m<2?gxX#8fPzvTAJbq0A}| zV@tjKCfpd&Yu`{xBI+IS;zDiqK%;b4S#||HzW7HHK<8kEAqsOUMS)n)LzuDk>Qfo%wMqcyl z>>x}7?LhwT3>GMW9e*AJ%;alxjfIcO1D+js?7t}1J1no(|EzyFjEkU`UBSCGSCNl zgUF?TqH_*yEbhxg;pvNf7fDI}*~m}96=Ge|1jVb`LB?LbL8yb2ct&ow3n$+=*7g#! zOL-*;8O0L0$UN8OVx3d-D~33c4k?gSn;FJq8w53L86r_H5pPUcg7g4^1Ij#Ait;Ty zIk4I=!M}s9B$Y?iM4N&GwW5N3BiFo11Ed*qPcq|htK@Jp3A&=O9BuR`qO99Q-^cq9 z&jpuQ%L%=%By9sgHS4j`BukR|UiqT9pt^o+e}>4{nH}=Wg6j=IgcI3dDCzv>D$fhJ8HNON)b$|dh1Nt=t5QXk*r@5blKAZy*^3~1PD3=N5 z&#m4GZ@g&R0^EPm#whiCSJ50k_7M>_)xzFt{WUvn6TA$;Ckzn8s7K+JbS#@5)(Um%xe=H;0>odAnt-E2zF+d$p4z!%vlCuHbLDQ`1@cK`gSlclpSuuX=e1x{X%e$0M zw0Tw-HmMkOv@N}+=6;cd#ku044d`<5EaV?HkG^sCRoV1b)&8n6yUS$khB^G!<{+r- zR98c7Y)6o~c6a9BrF3?kyZhPN=xD4lY&nAw+qfAssz@eekArUkk5WQ$3%tyMpU>iZ zM?AeSaHl% zMFsSe;12BlXM*^xS7b;^>=x=mJ2imIxBkYs=`umB7G`e*O2<|9vla&+D|;q>=9^jn z^KFp#dgb_oevkawu2c8BBa@$Jk#y$8LAD6>cha86nL0yiZ^C6>B(&q?67<1O@E9zF zKodjz4RL&wo>87I;B){2C!)H9rnL-4Gs0irN5Q7{5;sOSsQ<6mGM-T`7U<~NNcW1xUXPs|ORg{u zS}7)(hQDSPU32PAK#CJ1^PjJUrfJNDhq@9PHN*pi5i1qGvUE{bAAr^{mzc?Wv5O5< zK9$YF1p(PgRrhO2M+%G_I%oJO1_8Mi6v-r==HG&pM>EtO0vM!=IolstpTEX9Jg`9R z#YGbTIARC7k)9t77iO9^9|eSWCo6_lZd=+AJbJe%f;}_wO~-q__ayYZ$A}gmM{={H zW!P-JuIpFr9{~D{7a!tVgf~&?OvS3oiq(xuQ7ppQX^#KlqpO-20DbSp$z0Horo@0e! zCL)<^ceJ#H-Cey|Yq5o%ou5V&y>CeSHc3}}{0pr5a;#<&)?}wJd~;N@J|7b(aNi-t z&gc5}eR2k8KqLwnrEZap`8vfjB3uQ%ewWO#3xqHhQYuSPdJ?RNMA5{IsJ~h1wl-7v zi7GmYzrmH@v6WMSPBHX~c*}d`aHZ?p3J)o!7Y_b2|F#y%cxKTLB3zA+-UX#4*J{%26R~U3CDw zlxQnR#d*ot?j)uz=QveN^08Wcw`~anchB0PsFSH@Vj2cn31aqIz*!y5uy~x-AfF^6 zA};;8Wfh!QX}MtB%@pA>a&3WS;`^om*xMxC;v4a`Cr8gNAEpk=vfmcfjuUsb4xtfN z8hAZybdO;jVSPAE_PEphR zh6ps=QZN=<&mXIm5nx$ZXI{K6owDF$mS$Dt#k7fE|m+EwsYpGf#*zh>A5wfB(Q`%$QR9?P%_ zJ!eU`DzRAvi^TJLJB29`wsatMp4lbJ!80BP52Y12L%i&V@%yEyj|UrNb^SOoUB(M5 z(~Qb|$Si&@WwCP>tK65F&JC?S5?k*|Qry$fuf}>l?!8sqsQ=d(kyz=BSTQRz7dJPx zhaoD_=8eLBr?JCM4k*L8yf_h>_?WU}b`nMwpV^KQ@XY1~ zS!dVFs4t}{ZEnU|Q&yt{CZMKHUowp`Yu+^t8}+|%;|id0AW;?cJ1Go?(ensJEE_lR zbJRn`mlU(}1MYV?^)D4y-Zz;lL1rh9t&&XzLB9rN75xzF9?5ru4Y^yaKAR)OAvxc| zegX!xEHrR&(-keYhpVVaui3Q)_bGFR7@6M(M_G4X_E)Y=Mw?@^b}=-Bz9z~G3rexZ zg!zYKK4FxVAzU{mEl7yW57!JN$udvUeWeNSslbw;1jk^^YHL>U#ldq>4o~s5VhN*% zOzIEAKRVFj8qKI^o4?0n7(c{RLeFFVWbeIEM6U<5X^E@d&I3=spR12IZ=L9Lj!a9z z(5X@UdB4GH5DMOJWUb#*FovguVC14B#7@X{3p<7u)D)R1T3wdOO5><`W8@Jt*#r4B zid%F$5Zt0qIuKq!+p1FKG{SkSm+`k~8f+X?A!qKM>Oq}h4Uy##E$F!N6~U1yB4Wo3 z98@30D_;7K77)iuocEXrIV-{9AfpZ&>?^t158hu%2wrH)Gl3=iy4sh;J@w#JGBOLhnbxE5v0 ze(1el%KLrT{z>QR>T3u~9?9z0m-_uLF&Z0T{LUZO4_EAbO;Q;qQ}n(v?0XTXemtqY zW_TIg*3_YX1I%mxB5*Mnfe_Ip1K`s37iCXqWGo;b0)45OtWLZ?nV&?! zR2d&fDaj~N7>w;U98KmvP6|@cpP+3@(rOL8a8pCf2NS-7W>N;UaHtaeB$^tGEA1DO zgp59OiTMM366k-LH2ij2&oVQ|%JYe*1b-^#*fR!*t>495}=W z_MqD^^^JRoAUE&v!yVDbyG3yu(cQ(1v|{Jwvp?bi+;S^co5NiAEAOW%P1l?EwcYQt zS7m_g@bx5LE;5A{0Sq~GUu2T`hbHr4Og4NAg<%cFxR!BHpW5fPy_<_o83fM{hnIk8 zpwtvtpqTO4mJ*jMB!SLOH5`Ch7y3Y@H>oFr*dUG%CLYO3sbF( zn$MM6auMzgfMwayDSK5JWTIwJ1?`hwca$bE8vCa&PiJ$ws1)AC z*RRiAJFXnEe?Q_b&0NzzR0zZ!2^JO>5aPL}gQiy0TqZZS*Dv^Fx}VqiTlk(d!1k32xErtB}pv3FFi*{+O<2U_85C zn{bk<(8%SP?s7?V$8R5wXDz4&s<6a%IX90ok-DF2yj{DKpZdayFJ2T9=pHD&Kg6}y zpr8KiCh?TanCua%&+}UZ6mZc{dyz5JP>D*VfJ8O~;&z;eKv@^a@^#rCDV`l7PM+>&Vj?JUR}=< zCJP4v7Ljq-k~R=F@cD+YMA+F}1OM~vu+$QN&~9EXgm+L6n+v8`;^uVsi>bs-6?(g! z4#yfA$j`bP%mn>X+VMB|4g~mcv;kTaeItSr^4m{9-}I79EKk-0%`+-@l*UaeSre+y z0+GG#f~EYl+A2$Yf|`JIo65eqNNU_0I+|pg>sJ`whavZtT#WC?ZQzfuw~=SQ#TNsu zVi=tmHp(Xs+B*Ao5*vtxN;Nez5BsFd=`PFd#=!0RrdsG=W0zAaQ4uS=2#8BWj(H0c z3JJrbDvK3`mnkrF*DosS+1F!SNy>3=QGPWC8^ip*NI+)o1SABg#>tG{WeTGhaf*fP8R!RvD1-y^*}xw@2>?b%bHp zM$HW1jDPPJN@aF@vvW&?6*4GhuMs2}AQkFUnsZIXLG)=FN_~)6Gdn*2F3Ki&y7mm* zmz_1i;Tls3H$aA8wIVN)j!&+JT%O;-M~s>yf}2|+pki|SlgeRdvF2xT4LR`BlWZ;f zk{H=XngAfX0d{&}ZU5dQR1MBwQD<7Z7N29E*y&uCr}1r%9C8+E^U-A}sG+*}jKl+{ zG~#9Qoa8OTt29JXlDe5H!}Or@1SlYM{a6w-Ixr4wpk_E-2W}m4Q7=LcDO+r9xein% zv1R%9=SCHj5=MM#)!z5N@Juu=kI`>{_k5VT#63s$j9z8pohsm>ye+O7Y3G^wHRj15 zIFfUa^gN7U*^rC?2~y`rRx&N|{9jf#BVtP|f8)Gl6az!GGh^PUWy_g9gS>l(wD2pw zW$p>nX#*|FnP9BU=|XCPUb7dUM)ZP=Nq=b063N51(EOy>ys-L&jrlgV3dNv_EaPQW zFR?U(QE^|B{PEte51tnY%+o9ZKt1&*I@>T2=@AfH-)hM`Hen8zWo`(nbnM=HWm@3QugVjH=iqrGK z*!e>2&^fgkIP!re75+aqexG)Txl6>FY_DL(oMT+LKUa6~&+wjk_I%iL4&WY}w8?u2 z&M=9pbqfyH*&HpGN06eF)neLKF_d~&bNMh>4Yy#}l63_c!Mth!=F9@KUT+4g3cIr7 zFjQ-RXMCnJz~0(2cW_-e1cM#HbaomfIT%(O7+C$Y(?qh$6Q*j9|Mgj;MZ$b$y>zfu zs;lf`zWMQbu8Z76X7ZBZqGa~N&`dA|^5NrM_3f9cX7MJ|nyjaxrv2rWZTe3=35ZT}XqLdP0 zQ8`vNnfqG5*gn@UN$%K#Y||DE6DTlJI7Wc*$ZQTvelTJ;T-RqbUJ-{{UYf{{azp_M zi#jAi$?_0HY3E=d1K9GvAXBF5G@HT=JJ3%uaZ?z_YYhC=W+^uqm&@SR8XfOD@VOlM zO2H1hsQ|l_PNg7*$mUA63+x%&FG&`KZlLm}`hc-2_Dc7lgl}#aw%^H`;oB()TvOoe zOuL?sr1t7W=@lEy*n)(X3-da*KFiooDBAUsYHwY597AWQSztK0En!0@lBRFFuG^JqhoDeX1-+3L z2O*jIH`uCb_U3_+;%L&CuomU-?noM#<2v_IYv0evs9;gX3BkM8W%_AtHPb7K90x-b zUws$G81#b*Th*A7NNY<}5Vj!95NvM`ce6##APJ@xomf1sYY2s^q@VY6vMPI|kIh1+ zZB30!Y^SHN)X9K^fs+byQM2Ka$)cb&PqS9s$+8I5xoNn#vB`BLBWlS#=%FfXp!mnP z-DmwAv~pNv(PX>h@hxMct-|=|)RuHw_sdgcOs}Xzux$FrX{^=xFp=F&vB`&3cYHV7 znuRnHX$(qGKQ&Xch;oLD8KK5$)nJ+B!^zKL-G!bBRB8e$Cg>Fbo(Vj;Mo06vtc<$- zz1h76`eKG~&bodrVe^Be45-?tgh(wXu0#3k3|QOOvY4{yBfl-nkRr6XCllC9)Y2aV zp>-1@>O;vq`N9=3ivjG#9xSJ@p+#MF7;@+hO~>k_Cvy>=mTeS(rq~yT--GD0lkqL{ zMIH1?UJ;qg(1>C+h~>cSZ6o%cMALhiu`}J>t=i|hZRGtYsW0T40`Nt(5B$0Am#O8) zA_))zrQW}_ZY3lzbtQwnu_xC;{YkH}3t$_9fpW}1C11w43_;-O!HbX#b?;*f=CNlG ztzNR;>@afw9Yi4R$`LR-JT`&4q_^XGS12&Q3;=7ZRH&kr@)y8+iuZ51; z8O@*)j%tE2fDR`!by+SsJ^{aI(f|1WFI@F&M-SD@VrpaZge4%t(0(eo5Wwi%P~8gP zPjpG{{yMTN)-u5Nkfcnka+-P>+*IIQ!eMBQBV=JI@=}M&0#(*V4a=*PL5s2<-_Bva z;D!gMd2YRG(Kdlqiwpj$IAaC`0FRga7A9_wtCfVa73x|-1i5&vYc_z6P^Zt=jmjKA z7@o%u$>FxziV6OhQF(Cj230xN_Ei~?6@Tn?MW%i*x-m|uum~z@b3nj*QY z5f0z>{oB7k+8f^B=$l;TmeEjnvIiKu*JSuO@dIxHadouZe2Xn{Zf1%9iR0BMF$WZ- zgp(g4&-ap0dTYt({m!FRjX?Gf%%6IL*P9nj%FCEQtv1N=95uc?t81NU$7ksy)B15% zSa8vO_%|GnO|o|~IMKOcJCk+$iu10I^fRZc_!V}Fji|7;pt4ST{51hs&W~qWlHKa- z?`2mkMXx3^Ffb{J8#LmUkGVNY`vS4|GG}}xTUtuFqc)ly*)5HeRzDZQ?vATR#cAJy zIgh@9LNqzmmX+}}C)0m=GP!4fZfqazej84x$`^r3YOVyAr6}Lj-;F`NABK|kgJS}z z$_Zm;ppHG$21eRK!3U6kfP1{a`+d(DSNf>V^8xchowgC>5%ARd$&F{Q&wHG%T`Ucq z2gHV!^bDgP_E~#PNE6es)tcfNTp5RQ;9kxu6@f!KpJ;r9jczbO6Ht1?y7yl(^0z~3RkPuaiEU2s}B8tzaL?sjgIE&SsiB3-0 z2WJ`9Qe1fcM_G>!e4n zh{u#QhjaREwH?mvwoyt+Eq6IMirJ?zXNSmL&K(Y;Zp#)7rZHe5)^!b6PkPLZX`5fL zZ7POB$Z#VEafl3EzNAnljwG0glT(nv(MWW}5=Do3kL6#EowR*iTHx(vk#*<>VS zFzi00!dzCK)5bdQ8auuqmJPm_PP)Av^)!uVuZI%jQ=?jCB|R zk=vH^3bTl`6I_xNC#SMdXvP643w?5`DsPw4GSN*Z^9+l|7pQ~I1FVLJA$)Sf$$>UM zAYo;Rj#3^}6BPBY8{C<@2R-y&$5-$E41~r9L;5V-)7v#bs&Ld`%;I8IfAJ^)8BsWz z*_VOFlx-AA`0-icwi)9<-D|bBB#n^khVk#ab{?X=x^`zw3S?MXaBtUZt?!^(n;OeX z+{^InyMFQ8Y2vY~{VkcYTHLr;#?s6D`3>#0H9B=R4oHE+*sf;QD`Juow+&k$m>w;G zA(jSd#Rwh(#8&E|am8&9*xhNN7?y6_v3p|ogHveMs?zy@!)JJ1eWu<*?y!UfTh+85 zLUED^WgQ>2U;zk&|8!Yg-Ke@M*njzHx+{;WN}VyX5d{Ez)E-LzK2MG%Q)>PziiIXp zki!b^1IZrA@BZ^(pzlJ~mX;+IJw4i18|Dp=(G~w6MLv(H7osT_UM{eTu!uzZDK0Bx zo7g}p&#Zz2f}7fFn+V*dEf5*-5==94eFQZcVSwjJf#$E|h|AZ+Cevc|W@~ADie}AE#16-;<{yo0_MZjonIc_wJ`bFp$zfJ@FGREK`BM-2W&i=kMB_Zawy>)?#&Rl+q8XHxf_w!gq2b z3n;&hp&Z~WHQ8eeuLsX#6=Ex;UE!34+8NA z&*THby0zCRyG$^t4*PbBox?Sk(P8F#t*Huf1s!W^*`EpXGEv73iEUyCGTdWrfXnj3 zOD&p-NS@;BK3SU>-|7@v%=I8t#i?kLb*Z^+ek7VS2!Y{Zz?APqp^RP>_> z()w^UhJD_(C=@yxiZ`9YFK4>%*AYvQWEj*pb{I0{+YRcy0bZ8ZF)O&`@dJfGz%lRT zHE4Gay^R|g3sEyOQ@j|wmY(s+Kj#7|-77ql!;M^~h{;%x95y$Z}+Z+BY0IrNl z%vYzW3%CL-{;w;5L-(Gxj%(ZJ$N;bXV(47dAlaaIbN?3d65IK#(BdjWV!2BHs^inK z`R&p9{6X`MI?5VbBKkmyxXTjOEy86V12E*PBLz|uoR|&7ecB@?$RrnexLWC54uaC_ zhX)o=YQU{UBbtR)uL|3tWs=K2^;@~IgSh+QG@5OL_3UUOSI}Kv3Iqi?dF(SXh6**f z`>mo)WynOiWQ%`xJbr$mYJ@9s83&LU<}awrEjr@bZk_`O)gLYdfXl;PawzHBsYUi`;)~m+G^ydDdz@t|!wcza%8NUur#e z@7tS22KVjtJZe+l|LpOtBzfj_A@9bi`n{(h!x$I)ktqGP>UlN0kFw_iH(=K$Z&Bjy zNhry0HCm4e=9)B$S~=C)p_|M}*E8QRN#?PJ!|OZ=EV(P2f<#m$5!a9p88i5Z;gReS zkCRZ~rQl*U(DZGwx~8mQzJd}2<>F^Fy(Kz^fs9uxq%z`$E1eFP2?w|0fhG?#3_EaA zCXyVdUgmu$_K*hWCv3C*lV9yggW?*X8~u#%j_6t{Dc!geQWxAQs4LA=fEZo-3l~tw zIB&hYrSI;0xqpFokLQ{D!~BwEb(~jh-c!_i-E!s*i*q5I@+ zi%dwy$~WoTkV9k}F7BV%tnj(N_lv0{G;DT@{B`m zAcS{3{rvV@&Z~SYJFSux$QLdT5FwbES&xb?0!vd>1|D0AJt*2$bCx}ZGJUU{lahgf z4h9Ab4$cbI5)(re4w|~Tm}aCjYkDbV)Bn1!QI& z;8V4rOIXJF_XDr+gunq%56bji(lF>O z+xqM7ouCIV58v~_XYaGmAAD;+9DCoNo{_d2cuNUBx&K^SL{syY?#_O1)LvPb&`0Cz z$^}=|m??Gs?Fm;IHs?k&hJ`|0R-UZ6c*7E395q@%2c@et6OCHTvC#1G&}hM zf^ak0Ej#YW90!qub`0_kkq3YyXeof`rki4k(!zF;nn}J( zqKap+biGSa*X@Y-B9*82gq5nl#$z0GfMEKD)eGJ8IKGF^HFWhUbHDfC665MFmkp

TbzP@lN`=plAkZV|7ej))Lb9#cuV@&nu%g?v3o-lg$_Py^O?jEXt zG)efYsOZlZJo(RCF_pN8{Ur9*qs`-GT|^a*r~zW zPR^xcWv%n$A-0H&NaQs(MG}nVj2M0E!*=`dA>ks>(S&5 z{p6abP13_&l&`cP?sXI1b=BLu+=13n6JN8lHCu1fI%YtJ zMT9PL(wa}n!gP>Q6BXx%NsRD=n^7sGQ5x;#4ViQ4k9DbLpDMUHSR+KF8MtfR5)~DM zw-J#R&632IY3U_w(9t5;C9U@jL+HYo(8KLplWgoy3ra#DG#=$!mL+rq>F730bCf|KhznE%@EGjneJ-lbV%R z;s;Xqg=bp)(I0~Ihzl~8j3~vrNCSvq2^JZEfA??ZnGPn<`7My5kz3q+Zr!*3WDc~7@?{#G+wQ6hb~CaGp^dbTy4`7g zhQZ7Jyyt9j^i5{s)a>MpxT0y}>^Oz7?&42fo}|)4q>*{roL*l_=VG zF7-cJK%<^?9Bp5JxUom*4-1NH7_X(<+zHH#s3k*mRoBTl5>6Y**b%8c>{y<(A=~J= zG#=bZR~hQg-u+Yj)15GjgCdbZ5p+(zIDMB0rip^s8kJwgXU1C{N3dR}1#g=T9whuG zT+XkDI#R!Q{$%e3zPP4A?fa2~P5RTDwTN$De+ZS@2O_%m-%Z&>31}ja^fXvZ;zWx{U*G6bHAPw3?R# zwIoR?l<<8Y`R>}hMveoGGdbGD4VT;%?RSb&ecnCWKZV#N%W|4@T>M`8e2AgoID_-0 zUigz93AN7l#mf8OTUIL^FO-4&n}3W}dR$p#c;UvkC@teCb|PbK-TC>jb4+ zx|psqS(D$~g11v|yA8a5C+0A-JT>1Tzn}ZKT0;7>r|kH{R=0t_%6AiVVfVcDnrH<( z(mvPT5cIF6G_MgMG`DqX6VczW(4uhUF&D?*=^n<1CiNpU%A!Twe9?|$m#0q9_XQ`| zCcqT>MNeWICT3+6s%rXQltBxI^Bj|3m9>O1XJ<^7g11&x>< z-IvDet@w;|@Ir`8-^geq?ocOnV1%kjtY4TdhPti+P*UIC_@`qt=SSo3?eofpn?0PL zyRUxC-#_OGkB5_a)A16w>ao++|DZKgI?jF*Pv|tK4x`(UjGU9qx^nM(SSM}GNv>~O z&{2LAu7p;9dYmC9pFou=5HPm@lB_|n-%0+&WkiR#lqNt_QbniV5v&M_;0d5C-^a&V z!caz(4zkjPW6V{DE{IJN;WNU=K}D)ul`vfBdvfqDG_zaxfKPoB=+rSw>i5Zcpy20I z8TTq8WxQN0KeRa+0(-8tpb(uHMe8HA&tDunb>D9%f5%*R%&Cy0sxXx;aDCwH$|FQ6-%ti=Hu@S zDFftkAO;{In#iJL{N!+BVFoE~aj`!{qJNa2)HqEA-;HM(`(5w|jGI!jr+vh@peyTO zn|Fh)Q!;@>vFdgixdgTktv>CZjsCi?ZpgA;P{a%=lT8VI(|si=n~8YlfH<a30N?5Fuuo$c}3Ugq;UiPyivgl}744gX?j zW#rXGt?HSz-3k~rY`5Ak7>cAb4u2CqEPUtuOv?@jYhfk zg*M7j#0>5z+_V#i}SlHM6#)5r(BsRarCuULwTX z%vTlm02o;$VR z`4y1I#nlpIsUN!nPJZ_4^F4K;~F$(4o2Ho8bBWB?Lu=S47afe;^aM0LJ<21Hy+qP|6O=CN0 z+$4>y#Jn zV3=G4%Cn|!V*P1p+a{R(f|m2u&3YD(`ePRq=qb3$bit(+^_&8S=(u=T$2tRHS%$m# zSe+Cx)G{~Lzhi6hBY3d5OD&*Ua2LtS1DzQ+`*35-Z~2^LjPAMQj#qylZ|8Z)9$#^i zb0>6lB$RT-X7qZ5wYo`{7{s<)NnG#e3KTJA8~ykh@e8o@Ff%-icapB@xakqFx$E8l z#<`ftEve3IeUP(AZK@N?18c^R1R)tWpwWChK#P3A5!&7_<+}r-^)!e zd9-yY0Sp^w2m?Qzu$X2TS0#mIYMf``i9mQnf6)_Nc<`Ghsvu{%oW$H9E`&Uuk14Z; z`hdH)G1e%Cf;+4V#*eH?)t7j!$sy(~2+af=#Kr?PQd9O}co#p#wC?@F4)+lzo$cv1 zSvmVHtUqrnSM#Ak_6ap?pH#{NAKXN9lGKg%8TLB$-+c=(>lrqj>6oOm;d;xM*G!Pp ze6XU}kiBQoAxnsr4XijdGO{Wt&!b%KF{Q(hQ3J?S6Mn1rX7CCSMwm1pVvGemwB)tCVI7 zg2(ng7RvlChGWRKaA(y9t?rww;kVQJ$|WD6QBF9eUCpDXq;?Am-}3DbJv4|=fTzw9 zj{nKI9FZiZdbpS@Me@7obzniHm|b9JIxAICG+M`Uk*zc-X#8n{xIwS=_4`rvBo?R< z)em`BO5C|A>PQQc{o^!yCUQYkv%&FXi>AvfN*T8#m5zJpXersxD{kf~0J)Xnib-ka z6Wef&7JxY%xSp9{rOPfW|JAannDuwx~X!KVWhQmDS*AVmt*G`r~ zuqhWc1OeuEKWdJQ>zN$48_Bth)pfeMcMUhYTBqv3Q`fEDOM3?Bj_mX;TROn2n=`Abo+tZT zR<{IQ?kY0zgSt8CSX|+lJ$2=TMZ{)?jk;Cb5>xI0dT0Sp7MoaAHFG*97IX$xVfBQh zHSBQdR4n`)R%LYa4idBwTWyKbkVRqi4=9TQ9u-+`cVh)wO9TJCczo;WkuhgqKE-e@ zmIOfGW66Qf)y@f917FRSBf)8o!Qb9SeB>HsD=Yr;2pE305*KF833RX|{tlb;T7KDc z`6zy9+hQ8cAZ~-hX3Bbe-I-#Q{J66-emDHN@0CSfPQ-TnfHkv6UxOXL3XfVCz@mZ> zQ;$0$RS)6>U0s~kt{b394duOXi}&Hxf1}-TwVUfr@R+A8yR~`NShc8!+msh>bGy#> zI5Sk){SyCMLxt{;=|JbC1i{$=z4ztAYXM?cQfzKZBNo93>Kvh%;VnJ z6)a?bNIA@RoPRw*r~1H=1krI;8PdQZxJj-A!g3Tj(ek`JCK)d{e%2mdyFQCy%3TZj zgyRn!deRC|^T5qQzaFxe>@x&mhnr~7QS28mz_@j}Odi@9xtEg9uLSgYgn7sZzK{mI zs+}#e4`eGZNY6*aaY1CDuf{H~$08;(60Tr2qiiz^?!j#ujgUD}q8S_lzn&4H`w zNQ;^3NRYVV7Zs1{B?Ymn&XTa=30JAheV(%YIPKO+VE^e4AmZlG)eo4XWI)UwJ*Q-$ z%(o%*T}s`4yLlySndA$K;hP>v-o^Exv++1%H+!m)at1E2jT;Yro+jVW_1lIv0e|bb zEl18iaaUX`RK#y~o>+_1ECJ4dU!ehk7 zw3Kdm+yuoQFKj-m#LQXAorPbd@mX3-o@sHIG$RZmmKNUJAgdGMocZt{p$38!5Rp3A zp|8Tpqn;uSn28A57e*C(w-xKBL)@x87h0E#4n5ZOj-Xu52ayjkn6C!^%xURSylGK>O)pIWyWyWH~)@6KD#wF1v%nt7gyg-Z%opg6EcYq%P zuYJVN3|l91!0gW_r;oPnn{cnw-ViaV&Y~E`c6H$Mc+djV`({39DeZIa>zb612=T{h zX~JnX)n1KJo1D?(Y*r8zNvk7s&Y^U!U(She6p1FNhz;1|!mhlA%T^P$lRX`u|*3~DUk64UZ&lJp-);2)pwG!w3U`g<-$NO=@J>Tzx z>*Et3aAG@?+X~n7I-#@-*?mlP?-lg6UKwDk6tuo`A$R>Z;5CKneQ7DLBNyX(0-6N4 zV19kUZzS@}OG0__d)U?Jg!EuLVS4O0C&FRby(YZ@y~yi5*6n5(O%36W$xyhzx=UScE`$>1roEsxe0`VqU`i_8d=YI@+b zP=;rvLmT+&Io;%|J(wcsLgDovr3Pd4xrKL%M9UTCFl3t>cc!^YQ8KX>*0!4a?(r>3 zXG|PT!)%hb;{GO>{sbwO2-fgFiDeW>WxJ$zBp7KXB}<;@Sz7eYG~CkTT%F|)7CQ~x z#Gl06CkL;?PS5$TkljmD9o&p>LOmadf$TR32rp(KUR@WEub|!RECPR(K41UT^qrqq z1YT+7{{no}_WVX*{B!tcd3}5gVWyBO&~*haKZn2a7v10{vKX_-NiQxfj0)^Alb0!j4(A~J<33LT%l z;(BTu5M#pX@=&KpF}>RP)Zn%)6g1yzD2RWrpBvIur6 zTP3teYWarqGk`?PT+0i5!JqfDKa`g-T^a}k7!)tew&C+EV-+d)-7fDB1H_5VE_jHA z#V)cIGVdECd9BCx%~>$lS*=FP`hIzoZEltrea;$C$Cx-hXLz0V?c6N*9`tm(Ehs*7 z)CS5qP;WmH12&pCT73((J`S=Uc40o6wjW!j_)apq?~z`A!vK}Kp1O$F&KG)yFM;1YtWcS+)?_<2lT^x;&TF26kzXT@|IoxvSwz4H(|N-L ztB^%wi5s_%D9JsV31=AVTXtMvK<{}5GHcT2-E zj`QpZO^*CY!@m-*b=P3>l6&d!@)!hs7vekI2G)=ctlsY13`M}$XN>t`IqyFS{=smQoH<)z_|S65}Ocszp0?(M2Gz{3nMh<@!VXk z?b~oXBJ}w+VCU&Gd|C*&AH}J3=xEd|=he9~y=Qn_6Yp|owz*p0ym?LH5*>(Q6HdWg zn2+Pzu%0Lu(bgC+(2hC6qdYiSf`b3R(g4fAql}e;e9Dv3?Y>mcbuWQZk?sLKI~<}D z<01Pqj>6qxUdn6Cj@Ox|M{)f9r_NBxMn7F0Zcl^b9@J`j$;zPSiai*jjkR$F8pEKA z1`Vr8IGpxH#GLwUxXEpB-LyS|7}-8uy}99dL1Z6d9eM8}WFLZS^nZ{v7NVVSVl@%= z6^LcrmT-1x1g&e2^G&GLi8X>@?0#kqIB5lbXKXV!>Pcd_&wpKfdF=xL6^>5!^}I9f zn%qBM_gnc7=X7;+x+L#vr;FctVu)Wub~<_0TAH6KiRz^~F7VejmomXLPmIwc{tB-S zjSh1wt7_#__|jL7;8K|DPd2d1H#vj)W-`z!ibzdE?q_4ok}qrNX=udkQkw88-^m&K zlnWjP7Ht(xWo5pH@S%yrs|TcCtjJI8aknYjfC_I-Eft@6OXnILPPA0zfs)sV%$PU^ zH8e#H#ZU?OqPl)+FeTo289Evb4wF<~p0!AmG*803i`n+WI5o;h9w;aCEBnJer!6CL zv!87I1)1tOPw`n{R0@Ngter3Q1wxN)siMGEp<6=!XSon)!|L0d1K-ShTKmp(bE)IT z-ALQHzQ;Ou$8(!tg*;3|FW9N#r$9CA$#xeCJ$c%L&_-AS(*k{NcgWf?2h#Bt2;Uhe)%frdQtLzM>q!n zoYuclE8{Ux^~cR)?XFr z{GG6>9q^h7@<`%SB=yGSst?Rjg&-vtHkm1;obtTz{i+FB1%Zwz>-4xY+dyT0WLSEt zE$rXjK=}Qi6q8>STNZQ-j~t$(EdhSiKnN1O&9P~`yxg121eT8D2Vc%{JMLH>Tno)u zb(wp#BEjRRq87mhSczB;=J^`2Z|KQNA;AiFXD>wMan12Mj=v)=6vA;;V_W43c2HcS zyv5(6VJ?dgV^Q&jAcGVX?`P_ z1sFw(_3UYo{~O5|Bu0|_Mmzx1OumYyb$X~vq{yI<_J%A$EsNlzuD8HpTKO*C7t6=wLL-vCnbG6UMANkhPds9xIGZ>ykFOP4Hd4H2JJm3HJ20i~dSu#Q+xbulN zdX4L7TWULK>l-;Sc@z;Qn&+3pfBOv}cU#yn5F@4=@%E-G<`a-<&lgOdBwchF#;vZ9 zmj*OxSDKF{4|l^ZNdxvaNLT3SUMUb}u(B3#JcLvAxhrX%9VBql^$CCS4rAy4yXs~wc_!0;`H)SDs&BP^ z6tKq>_;mYLPv$>1P1v#B8-F|E!(hRhYSj z_7Gec0qz-1Peln_d}R_aIk|ln^8bY6dmM($KLv=}^XIe71QF%{AjqE%yIZ4?MG(op z?3=X2HEi3LlKX>8fui|)Nzfg*S*kjJFlp0P5K+2NV_ce=n_+4!F;ANOt3=;qi_DKG zB!5Z3N1nS3usxk}K6U3gdX`?Cwp(4QE;ZwtaZfaQ*9zs$7e$Yp){p z-VyoTxbQ7|ex@A4oRQ;RvZeuPnDIlFG#*D;#AD+g@kXI}O-;! z0oJ}vUl9=+-@*z<8l_z|rH5U4rj=t@7gb(t3&iG?q&XJAe6_Oj*)_hzo|SCv<;A$j z9Sc?Zd|Ye~XU#+iJn=BCWN$x%wrpf&bb&B#0?-@KLj0r+?NKXAVL@i}2Ogkc@(K?)wJTVW35qd~C1nVZD??G{7^@M%1ApuzHky2ek?Wr`5*ikEC=#_!#vAl;r%n49)ztANw2b(TQ=6P>0>Bbt72);z! z-J{fcV*sqWFT;X9Yy-~K^0!)2#&?3BSGerAfnIewRG+azL4VWVF$S|;0$*?-M*M{8 z^S|+<2n$&GDDNKBNB^2j8bNN;7T^mFmXuYJUWQfHCPJ(1j#h!GV;;tYSBS=mQ~%lp z%VWBH>Oo^};Tv6otfZ9K4Xt6zwX65Z)ippvPSv$Vm9Ov;t6S{V&Ws;i3Y;$Cx>E!S zU3bms6(jibpR_mhL;k5)m#7S%7zu;gYmCGezu zR{PV9G(R@x`|N+p4`Ss$8vuIrI!0#1uKg;F_C@Lf1{aePt221S(K$;X{V4 z;VN8W%NPcP zuK}9q;P&jhvMBw%F2!&}snPJvR1gj%=(YU01?G2o*|I_^BWqS|=DC-ZV(?~9pKG9g zaUTxDo`y|CK#@Obpmj3A@~C;~Rv6~FJacAH&Y0_QLb%?R(v02v`3m(yx#*vF>Y2L{ z0vU0yb>~m`dA{WK0<}IS`K0)C)q|!yqbGp>kSL#*k>kDcs$)CdxQMdU*K1v?YjUjh&B-;wZZ0CI+d6Io zC0`?Vs?8`8sn?CzZ`X=KNO@(xp7qTDOl|;L@2ls(BguY6!v>^5lJP-Y zG`XL%(J7))lOe{MQDUeAu$0k69k^?_SZ$;eF6YssBlkK`G ziqdzRPGET-K`Ya#?|SXnD4dJql)(X<8%|ZyMv05?#S|!TZ*Hmg{q4+SYfWaSDiuOV zahJQ0mYOKY>s?1S7rqLhBw`w^uw~3S46uG>Cx^+AOtA=~6W`@2C$^{7Z%(4VYjVJn zeg2Ls?vadcbF`WP{Nvk+dmpcj@{8?){OnEOLn5LTuONocaP)$*l9qsy%NBRTwOl&m zH_Z!;LtKvhQB&NMGdtEBc=tIB**Ii$egZfID&qkD@ySurS8Zl`{=M-04yhM4V3SeI zRL@er!Ce5{@u$|70Ut6Dg>mn@zgs5T*0+fSaR1ciBB%vbtGvr0fmB}KSqy`;XBzn} z&NT47-Z>HZ!(<9n+XyXe7T`rv zr^_}|=_GwMPjP{I-6KL+`{CJ5ZzBd{#fF zd`z|!`87N**bi~e7K^@&`9m)?>Hi?kc8h$%M@>PBOU^&NCV{_6( zsU1+Z+YZ>XYahBge&swI+}OTN_qy10V>&gD|9IPZQt3jmU!wV35c=p~?0N6iWazQE z=zgdM%#{jyeEzO(%|bYG>O9QCTcH>93HJ{4p54FejcrbPY81ozUOT;p2azr-WRxZJ zEuc)SfXXg*#6lDPOaYOaD)@d`zTLK8JhY&MGBavl`+p6W1twfJfv>FnVBt<`)m@s#!8E!%dbul^5@`vkmyoN{{H1iOz z!K22eQ5=9RF(gI*!Cs|Iz#tRGZOky;3Ix79+})u74|xsIK93%pdVbxyEMcGXzTYhy zL>#n5zOeLO`fu#3^y*8a|3(^+Y_1sbUaI>K(#b44XOfg0BuS$9NXdrs}h33fahKJB&|++no@9_u@S(F+-0F!k*dkm3i!aXUQj z*?0VPpq6R78Z~LAo4Mxy!U6s@U4pl0lZPlQsyyVKu^@?NPN(~Rx{xD72DTikDp?a% zKp;xQTUcv;EN7-It=Uc_5EPWrGrhCpGxNZY_vwK!{Aa`tDAvCd9Rr>Im1IjXP<8p+Y2r_ABv=ObqFK2MtKI_X9~6>`2^n|rUhSM#qGP| zl{cR2tzxClZ6-EBMqj;?Q6B}f#?voGVg)kg_)*h-qdy|6idrD_) zdN(In+n`({dAZ0Q%*(!k@1ug-#nV&T@PgfZH z0`JG;arx9Y4s1M8Ux?JIkt)pjd|VQYd5l>p6#HKys6~fzislgEoxcT2r>{n26d%R+ zmItiXqIf^Ch`bzoOiOcJ4)pZ*vW)Cv4qz;$lsxx{YNUU`ELjWEw$6bgWlCT;M|jKq zERO(%0+OY6iekdrXcuzpCd0e*x`Ng+&VS|dI=Z{weyDkQ=R4AJ-vYgx0xG#o2c8_) z`bYpw*lew4&Q`81AAk_rJ`ZpA?TLde%zFrPUt*v%L3DPiWe4t8w9qp0KWRAd{k_S- zoaR{UnhM~?vLW>8>?#eqEuL4o8wnT&jrBZ@$1jccppOjWMZ_E6#t4-81K&etjBn^f z-|=+WJCalFt@3z$xn)zBnTM{@ZxtdQ<+yAwV3X|PctSrgUzDfIJdL14>{53P_@RRd z+C=$DSL#U801^r3 zMfx)DYi9+Ec-wm?GV^*b->@t;+}vYX%N~*rMYH5bYV6?3h}!f=dk_l_v!3b*j)-N! zM}~5ekg=iaDn+1FOk(8s-Ds*=Vu+IUOoMR)RIJ()ii`n<0}U3 zPKl8lbT;-D7H(M-PZhqU7Dp5j&}SDg+g51+(TXgUM!?}QPWDNg*TBFp%%!EClMCYs z4DmXh;bHGs19sdGKNPZ0YPaHMHpkrh-GXgiNoZ^sGyf^2s#bSt3sTKHOrs#_9BFL6 z<5IGyoWT?}N;5R1;iSnTMt$W_`8hsEPctjS!Fe$?d1tk@=?I2Qpxb5L*bPRtz}vhs z9M_{~^%j>#!ud4RG3*%7;O7*$ALZg9^xi=C%$Rnaixe#T< zC;sITFR<|T^ggAQfSCI|>ZXB<=NpLW+aP)jL1}8eB$cauE3b4suP!m}-!)GxE*mRr zuK22nOjYV&b5YaFACx|I1#Jun=_1zg!pe{_2)S&8tcMzzO%5Rk!6((D^u>RAYv>=L z(>Ix;8UqpzT9jxkNm_}b2inXI&wEB;|F-)qUU zKX($H$i#1}$3_tc-H8JbD?KzbV4TGb3@NMF&@npwgSet4LpBWqlCzljstad_SlQ*T za`w@&92plS_g}Us6VQ;z*nUQ_eDm}Mj@>J4bkXl%hgJ^@Cc)LQ2OAqzkeda?Phhd%fv2jfC-YcXj+sGC37Jm4u@Z@l)$ zdfcE{7TmNLEB()thWs@CP_vN8xEVFwa>{BL8aI+rb`u)&xadF9KH42j4kf~wPzp}f zFtvimHp$TQh|Mer413!XqF7#0v zz&y7JfPf$^_ZP+v`<}ElmiQ%83SkhT~ z$nyKh_wxPX)JPreukm!^w6hwv=-%=-^QS*5+Ne}a(#2W!c1!5i*iOxHvmc1~PyTaJ zt=6|3P-H)ZaerMN6H7PnS60H?xW3WRPZHeVy1h^6JaEsLx#qAy4WEwB7t`Ec!N}b~ zN(~1qfise1z>}bl^`R*$jsNqs*hwbq=7^*JhdT%ubWpN68Pu`+9qQE!)v{KsJJfBA z;?1(8ZEuBVi(liwEy>CZTHRY-l^`#7Hp>@hLF3`i7~3YT>XTUAP3J>=*7nC3KKl^~ z)_yyYO&Q9?Z*2KxVWXqA?)y$Lo?i3;tjwpGy0&dF|C1uGkFLW68=iCcKY~mL!pXul z8U2u1h7*p7#_7xwDdvVONtMvi6449H(YlnmP-ndOc4{oqnN}p>B2`5yxv77sfJG|ECX*Gj&-%1$MsZyuDA(>DLCiBY%~aTdVQnJ%CN;HjwAxb<3QP` zR&}D%@`_jdvW;a=JJvLmMOV)-f2d=K{XzXt)_sOr06kBUFPIY!HDa0DEwr3(lHI(0 zT+6nCAMD#$K8IQ4Yv6x9K9ZubKkxzC3w63$x$FD&WCa?B{&Z?i3SZrn;g?! zQjVPY5eJNZ$?CscK#5_RD?!EWBy58@s$p+QM3z|45A!ZdO2GC(#Z-sh58%tS_KNck z!4qgn%$njH#$q2#@ zri*0gwORr7JdE9+c_Zte+!=Se4k-z;1LOL9jv9_N3ZOtzx>ow619QWuK3~S+GF+nU z;X0BETco8(Y<2XEFUDI?G@S?&mt&q8KP(YBWAbfTGC%JS+#(hkehf~Xtj`p~9aN@t zPXea5jP6{f@x^|6=}n<`5Q!ME{{jR|2%*neY0+yw8TgQvD{K(V$r5uy4kQ58fLcgS#l){Jx zMe#(B#@V5K-ZHzle5g^YWu^BYCxKM#PxxStasvBve!%5Nvw_;UvKg6!iKT{MA}BZ0}d*a z6{KejHmv_+oAZ|Uq05u%AX4)Bl(MIiMZcOL^c8Mg~hfMH_32Eh@$<8hnQv91))5<}YK(XgCVq1Ts;UZz-pdg~i137VMT9N`p<{x;F#7X|VyLXEBmh z%(61W&FJ6^^xp_Kdn%MV?F50xP^CQ<=`&>!LcTL7PHW(tK6_znV21%lPfZ3pBQX02 z1GPw_I3sHsca|g6u5@PiQH&ghbvoh*zklLVr~lDeB7>Vu$Ji(3jOW*Mm!=y8r?)5} z0-Xeix?54{Ign;?Obi=rrEPi}ihH6cf1f7X`+DNS`QFa|ofZW;U5bqKf&zMmX`M#VTkBHEq(FulRst9ZvhbwQtdicxP11|lC?Ez>B2i7~!(g45JqNl*hL!o&YK~HnKDP+DLyL<`H>oluY z!dWkkJNqdcD>%=ml;*a?$W`9l5-V3rO`tuXtlFq@y2RJ&;RZc|BO>PryRXJ;-2;1% zJ;67=)BvzpbiN|4e|X{CrO=7Ia=31=M?fSP3j-f|&&JMACN>?KvX2{CE+w1e({e66 zr;5-Ib1x<^nVY4?E^xn)ah(4u{yg0h8ryn=y_M^eu~_3-!J*KzL2t}JR&VpEMXEG{ z&7`u%u02;q8W1CZ0hD>rXT{^5A^r}V-iNFYxBbs?0I9nQL0K;TOjJFh)}JFtj?U}v z`G+jiH_%+Gok5O&p+SF1Y^DFyQS&>v8WBtt_CI+q3K||8va#)-U-S6N>Cg(Q#bCcx z=|Ja$b)!Eq%6m*zxzFSR)Pyx)l1rN${Zn$plCl$ff+kqtNCox=^nIu zo%@t;f4x(>kA1D;nWJ)1hzH(C0RpDa_X1i?TVMR_b#XqYS#b*I@}t*8?z)*hm!8*c z)MST`BQS@vt?<%CA(glUIn_|AIV$;vP?x1)zkyrpyy{SEQcDqLFIJYqOVb2bN$+Bs zC{R-px>7#)RM~ix$hrZhxes35g5st4`ULzuFPau={hH=E=0O!<`hDvVc?RG1|u*%+~>Z9;hrTRP9B- ztwEc5e}Dfa%=(m`A7BK0&3@uLyYe02>R3De44U9-JBxe9JHBW&02sT)H?FOixJ_4R z@qi@u3f48Yy->sb(B#E=uvMM9xZ3Em!)BDareU)Es<4X#gk{RPScQ5JMqdoz7^ke{1?_ILQfBh} zp$*1z+8CMcp^ZBmP+oM5ZX-u7eQpPc8=vq&54k|8&&V}jO-+?xBH^i*jmAz924Y@l zD`59c{G}j2?sOl2@3P`f@f8PRI?Io~;}jZ>)7f(#|6kgeNonG?QpbMh8r7B z(`Upw2O2J%Xxn&BW;@4ijFsJVF}c{QOA#Ecs5~Lx*B5^ddHO_XdU%L?ewzP|QYzZ5 zX7hG(X6>F$-!NjTtN4xSpv`!UA4McDo8|VNoKV+cU7;^j6#hd#0Q zz>x2ZkYSlUtl%qg#`yu@Pu0!Y)1}`VlGAzqnj7KAY}ETh_S3Dw3;mdttf1cQ*OZx` z{>RQy*xQQHNWH_g{R5w+J8zNsd`Cw+51Cyv0nannKFd+VinRg#*9OdaC;=hNEFgEF zKsh^xPa-{w_RJ72)_~ap?y;_!rWVa|5*c2pZrDwIy|;QaZWUIx_Ob0!qMT;9!&*2p zEKGWHP{?Q0N24I%xHDp?VP@mIo8jOE^UR6=3xyp&+@_WfB^Y9(?xnD#EeVZ42rD!a z9!;X;6uSOb16Va{Eq(n4Om*i>)>wqWFaMCGPLcV%Fe?)ae{gVeH-rtXC|&C5xTcn` znDbd%#Q6-I{`*)cFHj6d@NY0+`+;lc)y(hH-Y@V&@byGoQ0!JGc?L80$3gKX*yP5F z4FeS>%ql`7j3pKSWu8endWAFtq4dSMb=m@(XQEYxfC|Z_W)`koP&#>Xda2-*Qng51 z^|`gV&d-73094q;m9zUH*U3{JXa+GdQiNIN<)Xv}N&|Um;88|D86%jZ--aGzuTG87 z(8ea{j_s#3Z|&OJH;C~0TsQCqSAv*Y#!1bwwYzp~ud6_g=QV2<+X(rpb_*_YIq=w^F)|_v&1- zZ>M>WNAM=Rc8-|pLC$t%(K+0|)3U(lguug}CmnsjgJ0U|ER$$P1DMl%Ip0H$#*{v$ zP{^TBkw%5A04-9*wT5uW?5L&8R1<9Dy;ufWG7W zI~@DIpN%J-SpIo4Fa1ijxAu>0{R@yIT0gBQ`CZ^K>3OuGVH)!($cfz5VTl6t%dDFv zm3ZorXW0tQss%UAyF%E=8f^;e*;8kK=FYnd{4^z5mp7V9W$Xt@&c-5#!p=^_hczTGi;- za#rfM(y`^Zu(CN0<>Z#FcOK5g8$gHRqHh=Ui1DI!gPjn{YAg}w1^3L)wr7pyOre6V z3uWpZp*bjRWTpAXoJN{NGtn4n?uU#lg>u}ybbSRZao#HGfUHp=YfdaAMLX+{Xd-D@ zS_xH(bPcoY&s1f?3*ED4>vqjLJjogZyexuaVTK6?kt`Q6lB3NH-owja`2RYa)x{6~(;igC3e@tyLOt$L{q_L`{(9grz>uQGqj-t&*W{o0LX z20s*pq!dof6QH33CZv2Sp{6v)cf|e|TWLo2d83an?@W$lQ$Ye+^@(LQ-O9p-F{PIK zF2R>!cYkl;^gO}opVL5YK?Rkm6*;Z62?gn1Z%ZBF%r})y(izh5QmHNoGaB=>o-x4Vrzq zX2sHJPJhUG^&kZ(X5_XY!yzz?(N8{dvhUdvHz=}|J>5br`Fo>apJfio!zlWwcVGdO; zSzd!)B~8=;0l_YlARtnH`V0L8LNRbx4+7bumP*M%7N zsjpR}3E{z7w@_S?+2e>Hh@Sl)^9#Kl)d`~Q6tA!^FIkD`Ho{)z*EnB(Bs3j51?nHV zjj0^DZ=_$Izpcg-F0TY`bsc}Ix4sf6^?Z7G0Bbu(-i;z^Kjzy6M-uX;yui2ze9g~& z&d6tAP>_~q5Y7)-L<=#(WcJc}RmMf})i36vA+@n(q|Ho~bt&&f2^uSlQu7Ml%1Tg$ z7YKAol;|1kD8M-~2%<})QIRY8FQ7~Is9Oc!&3{;Aw1jCHgG8tOF?@`<1`eLB-*Hbc zSFc1(#{RSWXcVd{kcOZPTGu5(M`EmV@N_lLeHal1_JUrwPLZ62?q^vRb@&+E>L?

42!Tr!|JeX`zyUZ)I|%UZhhp#ZP6AeFfoRK8t6-V6>I3|2 zGW1J#q~TQTsw9O`Dp92MEV4nbhQwNx0IWz6cV%cziz@`WH+}Dn05zv}PG1 z)q!wS4WdX|I2dGkSB0_8*P7E=hgb9Q!kQ^(k*5611xf*%C?Uh!iOh}55 zw?8#CiN!?!ObYYjBPWRqt4%KA&v( z#NHdEM?nx(C!{loGvd$B`iaYTXbhbPH?hsL9yL~Z^)*u8_b0DnX`vEffZ|2UuExo~abn2I)|eDG3+F9YV2SkFBOpL(qs!G~6pu5| zL~>1{2V!q0CUDRVh%L$VvWEw}3;66O;q)pBJYWV~v<56~=AE?$UOeV(C^-3&1$=OA zdO6k(h_#;a>MtPNgtQXWSc&o*rTcyL#we$5NXx*D!lnU9jI=}Ga7`@+Gb_8YelGj~ zRY`%Wd3uwuI_DBp>VY)uMrcQ!*mIin;%4Sz+Ct$X_GIO{m2&=?1Vjr#@Xc8mLw?g92 z<2He%{O5AOW{aEOVgkSWM!bgy%Ka#hU~6Z_Q}+vrmryJW$qYA=lHK0VkVWpL@59fDZw{$w$B#6vOxioEo;j-jP^8Xc^ zXf(0XZzO`liBWQ=lgx$6{G>+b*A@AOVOExLDxxM)DK+wl_c|=QDEucCQ*|~y;Rrl6 zsMyozYs)))mCFx%ClSD+9}bbB9>DChT490`42 zXC6k$xI8F;*9~??oc`x8|s?>LUj+d1~oY?=(^kWu~l<=Ca z-_q8PBINoub0+oq);hJsF*@eI^CZM=;^^{t@v;TH@t+jL;fF0=up*m5fmoiwD<;ag z9>~ZEgvDB={@v`oTz2l=dEXZi4xi@QOuNrY6*j2bcx=woP%%&xau0QOB^?JjpUz%6 z&w)i5U=}7>MX3?zh|ceJs@vq#7;WKy zLs}V@p?CFqt|~p=_$Fs|Sb9cqg1Wn}!JqJIjvvDSpLsB6$6P&s9|><{Q?O8O}CL6qtsl8h| zE88#HQqySdVHEa^WkK~n(HC`QD8Mz}=Z#AOJXF&8kgG2J*>XAee99`uVeVaR$m z#W47XNWvQeur$1Yy#n-a&?WF+S0p3r#qWPqu}mwZJ{qb+lDAJcDjphpqKe|H&}`$pG6wDTcgRQta9HvZaD@fj*~kGci?^xCO7s=aLoUcvO( zTn9Y?TeOG{^PX<Aa|t!JG`bvi$|D`k?OgB?%5767(mMl77c2+QhuAE?C~k#(s`8K@YE#@JS+-iIhlOQ-b%JdNjan! z>#dy6%*A|sn3RIkY_xM-6wFRNbOaCV_Ebns=!-wJd|_&C#wIg|%E*QRuXvC{_An0k zk^6yy=VY)(U;#drz&c4`{|8>7sIpeZx7L50X6;n4+4pk`L^UzX$B9_mreB`wzs4JC zbRjfR;I+A&_F}|a^s>fSin>OJ*k=D37{Lp|CO%7#H7P^5b#Z}bnW7ftAu!@}#l4lX zCBY*h;kZDSyuYk*AoJjt3PI6FQ4($EZK)rhUdk?(f1iKuz;ZqE9GNT-bOp#hj85RZ}vN*4W?MslOE8|CRSHQ!TK-f3%O+&hYP@DEbd_>u>Nh=DE-6P?+$}DRGl)fX5V*p(lDxEt!Dq5Lmux-&)=H7j<3O2@O1ax zuG*2bN`UzCV)<%2_8o`}GpJ(B53}*))JTO*3eDjk`}?s7&2(i?O9~mZ)RCVq8|6Z{ zY|{DY*%-t%EEr3=(rlzNN0hkb7@g7$>IA#q1*ltn2jX2ph*m=DuM_#1U92KPl_fOO zPC0VMBEPnZ@`=V#`6*Cz4<`}em=uJ4+GDs(v_`X+MhZ^GG*u@D2fgRm@sm#?(sf%cKY?$ed+vG?-X!q;PB5D zb`Gwjk5;E2LDg%4h5_F|owzR<@AVnUvVei`*TMBiDv;2=MJ(F1 zlk^GHH-z-f#%n1l{s1J=HsHTur>TQ$FV<90jddRu7*ov zr8V5!0NE#F!L>-juB|2`WKVhtn6ZO5|a&tfP2ii=>+N>A0!KPKDGNZIaal2;_{_x~j3ka>&a)9rmrg`mjrh1oVwZP#icGMpDT zppFrz{t*E?#neIiURhvY!7M z|7jL1(^WHzPF;~~c?S7BTEqxJ!DQpQrC4FEpFjfKu@d-9Xvj1)#g%%gP*BD%sW@#5 zxnL9yuG6sT=}2fZ>)CMBBs8n==1?@C|F!zzi3yuDVl!o8>BWdB^gfn2z+~7@{fpbY zvva_(uAj)~_xq$9Z{$kK&|&1^X5o8@M+H0V>oqbz9?o$>Cn;LP1R@(s*!?4kZUNc*>@`|MYbZ>N$w;msTL%Yn0~o%8+?>3_8V#VO|z)YIvL$7=V# z1sDm=FQTv8!6JiR5GU%}R-^@-`6TDhnAavAg35G5MFTf8d3kC>S$Kp`m6;j3(0S38 zDiIo$SS26rU|P5e>qR%>lLUsK7jhI7hz`r4^og|hnP=k>S64Fo3ul8DiE!e}blu4m ze?`7i6Ndq5E7ghfC75k%Qv_;bWD1(ky&uCzFzL*2sK+c3IHkBX&=_JAHU5%3)rQaw zd}FJG%z)OV>Dxj~Tlbc&u@OQzK*$-Q{JBu#O`{6f%eWLU+e>l|T#ZFl3A*u+n(jn= zT)xVG{PSrZxS_+iK=7G$v0P!y2aE&eQuN zsOcdwNC6#qClIq#{$;^v4CL*_7+V2F_O4)NiVPOzwY4nd4(bu{NAWy0i#;XQXbu*G zg9@zrwI+PXhybd!$=82!$q&zZBaauxcHNyY_ysoz5KFmE8dtgRjhf1X0%nUwk)q&4 zjzoO-t6y~=u}(((uF)P3o*S329xw)k6TPY&3*MN~{wj1`F9vneAHL?HJrn#LsLn5B z?OM+J{x`rd^+Wgdapvh9lD2zC*p)G@+aaZi%XyF-69fAiHoTBbUX+d?EvE==+2V(V z^qEGUec>L@C%X7f%BZcQ?Nrc_t;kX|ck426t)0KhtG0qi_UG!HacT@5R}#BmSrHoZ zQ0|9VZF*+M(|>pXTJ}DGKUZeisHDMM${zdY{8JjF>aE$1UIp;V(|m>ge_;W5AQ>M# z1o#o$9A~j)vs3`=;(-QD7DjB^jpU#2J6!>BcW=-O$GcMkr&hj8pI+KuA7Z}D9tOSY z(dkT|-$i_%FAzS%1*KKI7QbVlQP-IJLcoaN2C&=j>N)6AvmNAE_eyHmd6>CgRkyqQ z_Ao2(_x5yZ7W`>e?bW~R|9bPGf=ameq+b7(wS{0Nwzw`OYm3YU8Ipr&231ZnZ>M`3 zORvpgl~EEB6F;Xf)SE^jobu6*-DMk-s&deDoQ(xtfXAW?n>7JjGnh}RsWBQeb}q=3 zLHTR!z8T@a2XhtcU^}&uQR{r+wu)elrX+&ZguoJC!R`=0uR+Ht=mJ!F(@9x$ zS;A0~JYUo6{u}X9cbt3@&TRKz!PhwB=V{fY`TVPir}C(ERj`cinfS<4H~j_W+VRy| z^pH30f~@o{>nD=-L;dDovaYMm{JIz2t<#?E36F7lzb}ud?~UQ7si>gZ*K3%b+RV;B zXgvFZ^dmwmG`K8}e}Kk!djXzkhuKwrUpoZu-G1L zP)DZe*J5`N<1>=fs_7k+7JMdfQ_=;{2p?@D0K&9LQO!9dtf(L*amv)(DYbcsSOaXr z#eZ^0GAkg)ka3N^6-X@qPhGIg>b!``TZfpzmJC)}6xC4TG{V-!T*dkDPG`PhN_`C} zC6A&Tknw8d^ZY#?C&geDgXc}dgFAr56(kdm&-NG=mlq-#)5i}Ok3>ix8AIKbl z%(#Nr?}e1CaVoQVQ#ElAZ_=n$Cd%iF(9|!WCGgrQQ1Pnahd|R9qY8gSo_FZI;FU;N5u8*;=_LpMxFIAFF(JA;}ctwP1TMtsVR%XH{CC#Ll-$y#Db zk3rI%QFJn>oDsw zf#OCzYXq$;H71Pau@f*0FId2+@{d@CBTneOm8IOirjTC<30j^NA4GNO2L9 zZ}xFLVUZX^>9n(u;5vb}9VJg;>cB82lf||ZAG5R#j;bVJVQu-pn2w??s~fwJ)X%~1 zuLH3g_Xk7I)gk(c_Cp`NN9<8gK+~Jx9Imz?qYD8N6M8Bs73BZXcJP>DBaq3|m=j>* zkx|GE2ca~WY^4nsEsf=w5~QGM?hXcn!p1(!fw&Q}MpIrx$Pw`>OY_OdA19QmA{1*(?a9XjhIiYG z0<&Xu$r=dc6@--Zl3#dO2vB`_RX$WmyW_{HAy}f8-D=@4&(99@&43q*F_&tY1jb5^ z7AG)B&|4_$GRn^wgK{O2?(3oN2z8%Q9})c@@}fZBzjV}`Uv&eX^|CO2q{Ag3sSB@9 zM+?D{e_)ur>yu?pKA$|EY1L=64<)M4pTMk={UABsqE5J@R#~PQfvk}+J~>RLkx!DZ zkfULjY)4&#QcECQ#6(<@;BZ7PKOvAdoLlBN{V&|}P+(b|iIL`8JDdCi9{BrD*=q{N zFD9shOCO}pKD*GNm>|cIN{nErOJ#UDklRkwZ~u= zt3=058kr-c!y1y4_`O%}e_(2KNgqfqZoGDln0v%{nf;~iDB??3jmp47WSeHH?s!)< z@&p2z1{rqPU}W0DiKzkJ7?%PI7EG2Ec}u8%0Ww97;tOCe!}7w7K+2X1TINu(esJG) zqdWXg^#1nUe*U-T^qK!3LRznUeM*7&U~6)r`O{K;%3b7~>4j!gibYcy<(OT}GFA_o zxlxYWy7_qn4#nDf@b!ZCLv))Ir1TdseHIQAnIfGZ6RX z&T>7DaeZLDM+m~cZ}M-B#Q8#%%BEWV8q;Ay62<(_()Bk;PS{#0KE#BoS@}fTs8X@Q z`l*t+am`4Rg%k+g_8F^Z8U_KO0IV6E!>rwpRr$GaRIu&CX(7`!eX~sxPZe}!3`%50 zJ)EcwEw!ERwUf5-Z`ZSzTv-j^#ZOl>e zvcFfEMCz1ocobR3vodm1^zX2aODpRcW!6w5smtlTt3NjOk43dz=6qZa!nz zpJ~+NrWafL_RMdcP`?6TSonhg({Dc&L941YHa`UUVR#wv^003piuQ0+n6&yTfu{as zISu5DW!|=>Sy1G<8PPD7-V(|L;GVv%c>$GT&Um`~2_9(U9-6;8%y39~+Qyn!m67|cF&uGf1}%gr zWEeZn=<<}VH;i;3WmOkr%;9`MYL>00yLqWVCe+e`B>uFyPiK7PoYpn6HZwB3?qtW} zj+CmV@LVSH6m!X~)ud6L$xoGElEbu_Wkuew?0v6NQBP8CY%LPXnCp8#8-*Z=b^#Ax zpUR#V;H-lVr)S-7aB{wQS;?o@xFSU%R|d=Y)jYvsPW@Do!5SXaEdh*AD(?ojkbg!) zSiC}@l)>wL@eIJimpJ?()}(OkJQb^)g*xVHdn5)9ioqADo5u&4Dm;ZP*(yOD$#DcX zv@rjxhck>fj(1k)!4;9*=N_Qx$M4}E$Enr7EQC$%bl4E&rRkbR)3iemNW^ole+2xI zJ3mVOko^r-=?V)gb;5==Repxj%uRlqECb-+~UkBvxZc8^ea{{y8_=k%}WDe>W519 z8@>tDrzB7FfOqu;2yJ^@}#fSO>dw>VLXWBf>(RA_rfl4`ren<{|5e&TGRqJk1 z?|Y_QWAIR~dtke>E}%P43v^nDz(4q-&Q>j}pxWl$W{)$om$CT1JE_8s$O4;M>?(;x zag8p&%>%lH0r)M=5b4oNA*5(2k*h_{V`?<+iz{Lx19Z|TVAw!%FedagX{deJ%F3ll zAz%q!k1KS?P~I5T$b1^#u!RtyQjD?c|7IHu&9}&Bl&ep_2V; zbl`ky)j@qlrc@qcx|kA((e2hr#-(7?JVVGAgu-A*lm) zQ}%DyNw1F)QI*V&J}^8Nh@CsrcgNJK>mz?h@xbFIrb}F)fbB$2ik)(%MB` z)nZFdzyDVYu+l^#g;GS;AC$h*|5vF(w8Dazt&3V~lF?q%BW&hN@xd}n0 zZpr7Iwhtmq!FA0x;)99J-HRHLwN3UtOsi~h>MHPLR{PW^FnUnmEDh#@U1sn_WaJa{3^tuxG%zcpSKMx&FY84(HGOQ2bbsWnBW^$!JeZMI$Nih zK|g-dF$KDXoKcw`MxLP@-0wu>ZZV7A5rgK72bv@#F+|W&f~TVSC}E&I2TQ6s>`ODS zZHVZVpwYOC%hHyFUURB;2`0vL{NHm&8M{TkYV3PKW?JcdJ##b*Nx!+0VkZ1_H_?Tp zlJ7g%m@l-r*9D7jc5mz%SkrdunK-$Yi|)2`-dXB)dXvJ}Xe;EVV#hQr6%S2$CL$*# zaZQijU@OwC1J=vom;7r|7t{X^KI`1Ryt#{A`2W6^g)&SkGYVt8Jjf)PNe#L-3jouq z$me*2bU1UjdQd|AiWjjmUT1#PAVs#iFt&N2y%sG|Xe(+AnId-hosbxox8KJk`PojMcY)^vJrzIC_s?v>jlc3Z6iwO#S-v|#D|6o$} z`bV@p*_n1DR1FPUjh|m6eaBMKPC`|07~}uF^%un4dv=~LJ)Tv@cU0Z`Jq-JIP1j)8 zJ?4OXBRDZv8&RnVkB`Y8zKK~={n2WB6oC^M#~2}o9|r4*PJ2U;()i^_4l9ut1A6t^jkf+qA(n?DF2ZSZKuc+nZ(=s{tT($HqSfmMM|gvt8T>N~xx z`-A8x5dg5qwa_87XnUfI{eUNE(n#sFHLu}=_$LM{>6mF?wsF`@SCc^|!mH*mAWcd3kEHXbMfcZb$iKf7RrBAJ8#XWIobNt*|NeBo zT=04y2io8T6;O`V>TJo33oqUi)C{gi1i(rv;3v5Kj+=}jAExjePivQr>SidUw#wrw z!?rN<#X^!jJsnmuM@Z2Rm!Z)1l~&*?6_ex0WgME)r(xD;w7xIGpb6+w0HF+N*w8bx zlU02LR?zIyZPETMbrDdE|4?0xoA$%Q!1w*LVD9SddO*G4V-?&eOt1QaBX!L85FSkI z3I}fcTY(82fjk5d3LF8q==dK=?DAiJIuCPM zzwqDVGMBZBUP(V*^>Hu6gBy z#otNlrw-9hrNbbz2tTUKu;)m^>htVXzuIt?t>;&Xfvik^^_WRl#%*=kBm%z5@RXxibd5O|O%|81mt*Ke?VfGHmdqD>3=f~hsH8(AWi%b4HE z|9FE@ip3%s8ut`N$^duoAlW0{^39Z1iJ+Fd3~MCh@R}(kGehM_8>#UkWBzBkpvNAx zlRx`C#51`2`g?D?q^KuVq5(0+4;`8GK_)Gm?L*9(3lIPNdW9oIa1J{16j0#k0+oR* z1=GnYcvAXW3$q{Qh>)@R>N2HJF(E1N8Sp0RFaUSe;(n$WvrxPkaa(08t%)%gT~~Ho z1j&%yI7!+v^^!tN`b2#*GNLBzVq|FV&gg1E?ZgLeO&xotul3{q&}1ASFqLm)UlADj zlUZ=PNk2iEMR2S5Y~`W6!fRBH%Y;1GhYC-K7(t0n5tVL24 ztuIAZ!6mOLVP>p1`{TA$5S}&3;g76YZLUG%k8yhx!~5Mv5vcs3ll5wqkX(7mj7~qh z#>(U)a;g$J%Taq0m^pFUr1>n3ZBA0X#p?V3IaR+&$iEYtM8>a+;w+nEc838T()GRv z`@ZYbPcUs={;u8gdMuEs zTpPNvEkWM(K^yJ5PEcH7)+~*Zn=E~|w8#mhm~NmkKm#jT=Mu~?)(Jiu;*%pY+(!at zc+_My%~d>2iFN7W1ssc)TSna5w3Rm`9zUS;po%_wTe|jY4K_$P{96vwAi3Lq#Gx~BH|2qyW9ETyTob?r2RWc?K^;kX0sXLKj}ib z;=g^0lBmuQvoNlR%*!yCY}Q=kBh;%EHxmxXiH-!PmW(p|#MDNY@TPZJbg=;UMKJTz zf(y=4D}!T@)n&{eDuPTDIIFOWaud2+vB$_WryRGaYH;NLiLkqzO_ySZgtuPxKtf}GyGlKkj~vLms@l`OEviB zj?rakxnctOSig&+S3Vx`Ph-eVJwEv9fYuI)7v_V`gN-i*;cm-uiI-?MdvbW22cZl@ zzH~h|Tvt36T)AhSdMXnw) zH@e^U%UW0(E&tKjCPlx&{zD5d#=Gx8b}N!JTkP>H<+d{Q`n)E_0dfO73I(z@ynoGz zxIuNAi&vLq`F%QbAf&viYc8?4Ibo@LG>3zZa@Yl4E&GAF!y@Ki*qFXFuX2LgSP@jp zUpet=*N7P4jkZUQHbj!(>W1B2nd%E}+bDP_BWL5`J_-~%Lb8ArWY+U~;0gzV4b-}P z{a(ZL<`~khM!S^v76a&dMv1!3=~<~id|9)6%8U9`YW?z3=CE~lYJR~#nK?stm}OJd zT_3=C6#$z)4=VK>2PwQ9#h6{Z4>3hh#F} zZDq$yVv=)KLYv|;Dk%enW!4fZMMEFMaTKyEZ`fqon0VH~=8~O>=&F<{k!G431vX1l z=!($Z=9u%}0N-1nTR`Kg-9P5$&&E7$HBbZ1kCvxI=CX$TjSBlKLaOd~2}hO|ObZ?R zn81u&dG_FZZhrV$BU-S>z#^c_m*PXYma0JLo_W`J^cVvDERwxqTVkF-gna%m(N`V| ze{S^SQ6D&L8%aeJk03XOy%w{|VvR{k2?w_|bPn@rTBt1q3o7ubJvUbXus^8MxB!0vHh%1!o%dmWM`LF10% zmqV#X$w=8P5TgKL=P7WJ*;A_mitC06kHqk~G zxeM=~jaPTeL<_&FdW5eQ;XoH}E=kVYqzk8?T*Tfkxp+)^dQ_*M$|i2ZWsQs9{yO3= zZmzzaZni1Cz34oH`o0s-6-~HP-Y$1^C;rfQFk1?cnOx@hq|qLmk=E^mTq2bs!CTBd zM!-J))4#i% zs8>avm9O9W81ogNSqp~di;1=DSc6W)yM$UrDPLE~yZobB{l>++{?JF=a$ILh(+2T- z<5xadoHTo5J|Y`G?R{VniqYk(%$yp`e4!WknmyQ$g+_N(7PD0bkVS%H_1Oum9Vkee z-Wm#WYcp1Q-4UgM9UIa0?=gNEc?MCaT_I$iN^^Q&Vcne$?Xd>>?*w+;sPX6z^fRfXQMn@X2 zoT-omJp7KT*C5vh+n^Li|XmvYlYytah53=x+!q^xI zm_2qG%@j8lR-szNLAER^u;AEVL5n^hW)l%jw#Ecx>M|H%&(N%Y`bRaMS_K+i9K`;s z1=LNoE~YoHUN#Y0O%qb~F{p;qb!@&8!}^&GUM^Mmw2=Ak$V{I6)cdxSGfe5ZL2#(` zltx2sZyqzwpPFlhV~~08Q_4$B-)y4i)(f?$*}Qx>>(_`Ma3rcxpx5Cm5H+U{SCd7A zDG0?Mu|UXAb7(X+sn6A@4~wA+cT9ZcPFlmp|Wo|6Iw+KGMGULf?a>z6C z`93X;lUx_CxpHHRU-NHqRl1oHVy zqXU>wdCy8RcH;|!*=dyvK}yn!tVn(6Ksjd$+r>!uDD_=$JgTKp{}$u#@V>RWNS1aV zP(r7XGfs_DcG9wN6=W5#O)zFmZP2w!YbL|5vSO;rLY0nQFBdqtK`yZ?x}=ntjH%+6 zN;MmUM2xL2II*a;B197za&Y)C$jqs}#99tp*z6~JlO|R-I28d^>O^1CITCbZerM(v zQZ1DrOoWuE`1&O*WM|lc(4?xZ2mWU$Dp!(dqxC6K>g!*pY`rV;P{46Y-GlM)yp!n;X-$fhxoL<`WuCG{oQy@?s6tzVqc zyN1gWJm^Xl94%cFOfg!jWK4vTlQN!cJ}xJ$_K}LZ$Y!7VJ?vunxO~RtxNhAACF`0_ z8%+n`{l6_(!%XARHho!NW@VUf*bIo7#ko+?L58)vOy1OoD?-VbV_zLE;8y5S$HLBT<8+gLvUPu$pA7(Zjv+`eBlf{cAb19!w~)Wo0MZ3glwRHOcxKWHLk8p&G*8^coPFXjq26xs3L>sHw5Kf}hVT zKQVeH47<%Ow!OC&7TfekW}iRwc$~`1u!tQBdsxN$iUg614PTDSu8n_Y_T4K7S#(WBDDBm>*Czr`8i($(IC@ec3 z;W2r84qH#z3HCWG5_bPTiX{2^x3Sr5^w?E5l_G|<%BsHd{KoW)(MlxMi!+f~84MJr z*S&Ih7&RDH(~tlmds);dYG*_e}_~pTk`T>^7Ke5`~z$VcTAOAfsIR~DARYy5fi2nE&TholjrN_TF=dkXaCKOR=?#opwESuk-gEW z{&Ilm^u2$_XHjSFC?`|q8hx!~$#l}j!;*;B^iaY>2F59kpH-;U>un3+sdY3(WDorC zE0~UjL#q)vi?qMXfCTfnmTK9mS-ctuS=t+U2uf(#GuX>Z11hfP%^9(}XH}Ikw^I$D zb!xMaxPMb$<-n&umOd+RR06dCDKPw5iO64~t0PICum!qcaBL(4rS=b^cxH3;xM8Y- zQMjogwRRSO4d2m$qY$>mPT0f{D5{GDiJ@67;fC303e7*pxhjF^v=_j+Dp-Kk~^eTkm+ zDXuqk=({z5t(xH-5nE9=$==~}FAxW5P@ax$u?ZOMjEWNvt2g{5CssFk4RDQl@c0r55 zI?|@&XU#*0yQ-L?{0>kzy{yUU6weREu!MkGgL~;;GN0X&u)lR_p$_dp#veZ;|HA4y zmOVg+lDIV{B9e46WsvpWn)@E~)9IOQ)73~7Jet-FR4>TzfvQy9$3k zI|U)DDmIU}o221XO{tfcWvcFa!W2Vh`B=TlxzvRnK^hp$>9*v@Yawk#wy9nX#m{*}V(xMay&50C$|`*xCqg zqPcu`;S-?V&3K-o^6j2f3PVm}Y(Yc2HrAXJd%9k=LaYRJJA0q~f>vL!o5pH2^_oOA zj=k>eZIZ%@hA&TNPHIZW<4iQw1g=iRH^{hZU=pH!6$4P58Yix0bP?)tt3)#)MGh#s zBpzxqSSAZ?sl(4a)O8H7z)%7-p}-Dis4#>IvBxzqjE0@d(PQdej|4I*F}*R!yKhv>S`eKCu_Om4x31C z8d;1#r^Xjzabn+UZW`*gDrb*Cxdgrg%^>eJ*QxX;p})esrg);7zHRP}{2aA1Di!2> z`aSzK_uI(oIXO0~T;#Z)eRGj~!dec`iVFCb*Pzx;p7MvktjO3!p(3=RxE#C+6@Mw_ z<+DK4JI%jIr#>(!{xrIjj$RsN1AEO{qAq$ucf)g+VkF6Sj{8G z)*X5;P@eSN7RNe6uIKMVZ|qR%F^2QoDmN0Cj-|+M8fK1rGx+KV*HeS$x<0zhF&-; zkirb2?vUn9!(GePaN9U=n*YSM%=da_hID{>f60;s8q z#g3TWpjuBbp6{f|E`b(@1n%jEAQr^7i8qa%0TyZY`Zct|6v$S%fU(~&8Apxf<^URh z#rre?=;q6%)NAAZYB@uI@(__?^o`&Z-2-#V}P+{a=6v+5gACwuC#8X4#P z-ITD~w7i3~^*X^ArH$a;2O;4OFVL&dygdnfyPxyUtEcmPmsgdc%cl9}ZPiFzuVX=U zXCJJYYFmQmH$Qa@Uo-|p?3=4Y9|n=<70-#F3K8qNf#sC|uM<@MDK`kwh`z#$h_Lp$ z{ERSx7u%J?RcpSYW(d6J^?7Tt_HUo_$=y*$e2fTaZud&Qu*0zs4?7e@Xqs9*2~`}9 zEA8himeK7D$xVC6v-!g1$H|8|YPRBU-kI(~Tj!{Xa5r#Q&y1Xj3rCe;tcL~rwQNrf z+W0~v;%e=tZNyQW{Z5=}7KvJ|Tlr;4oKS>fqC*Pp39W`lc}D_$WV0dr{N!ebhat0R zD99;Xe5gz`nSJnW_f){W{mxtYF*nZR8k{QnWoABo_D|n*cCD*pdv-5K18;Ftx^oce z;r#)#ZVlus8vRuQh~w@RauoCuR^2HvqK#|m`6_=>Y8PJQdZ!eFXE8a;3KvRdVY*kT z;rVhJ$CmRgY7EEuIpTmaU<%V{pN9p-?J+>P8rTu85ZhlwdC2E|i-vgM-IOYX!;VCCm3yOh|NHIam!T!*Ns1ld~^>>Q#$FVs{8MzO|B@dWX(m1(J8TKeP^rMWBget_20cmqk5+Ir!8=6IVk7*?`oe9 zH1n4yUjr3sCJ5cw%s=H_%@Ixk*3B;nZ`Uyq!#2VoJT*(&2nP9Ez<+-q^bO_qKzh96YmTESxCi9=R?*Ju;*7 zS!p7?Z5%VFc!$=#cfFw8y{G-o4c^$suY#p7Swd$x03rMwG|4}jbVC%CKtNL$Ytlky zIxx6Si21tRjwV7Ui+&L}S#m|Bd$EICK)*u@iFEbNA zk8c;XZqGhAy$*ZxLR7nP$!>VSDk6^i^AWQmJ3mr84k=1LH z$b3zRe=Z);&Hfs#0V0ZP1~-IgzvMp8L;)gDCD2N=N{+}8Ks0Ffj4-@Y0PR2OVAd^o z%3m9bP#ZvaD|5r0C2>w>3TC?;Vk=r2r>W^IW_tin1ld79eSk{M_U7o7_r5E z*4&9#GlVlWB&Eo^rXy$p;Pp%vK1#zt`-+7$Fl3|1Gkn!<@*Ywpik<}0!?kJDWg~sTO556*6;@ZUi)cGLcITbuBDb za`;#h^oACFks|BbU`Vu4t$&h%e3Q^eoiaL!IZ6cmc5NV-0hK22(RQC%i(Zs0SYu}@ zPyazMG=|7ub@$WmsH>~n+$X$5=p@+OQk?}qaOcUW$Mw-xN|!oaRN+nfl~E+#aNYI5 zI&f;JUqv^sL%{|lu6n7G=wU+8jrovVJgympZy+otoco&{#aFQZO@wOutHZ*xasx zHi-h$k0bb<*40f>OO9fWFI?fmm%ebMABtIpuuzYl+tvMUS+1##xBh`j2edEuj~l1+nUs$N;{U)O;Ut5vpOk2DAUDYkr|2@ zk58P6Wp+tg6h|M*DY5R30g~BXUvngcn`ibD_YHI5=Ch?$IzHq z_MfiVa06K4q{9bfCdJHLv}q}+p8J@^C{pYd#+H|80O;he*a^V&Km(8vj@YClF2-D| zpdBrqO^}I=ebUmTa2vL+57?*MgGG>q43!W|0V)gu!1I_4Atp0X`eZSINydw-3-MZ4 zVa2jy&5WJ4)QbfTIE+;>3Zn!LWK*#_w$3JDCKM?)=%xVu>TsLUy`@{W8aq3K&*dHp z`i4SdR@QA9+A%eXV@`$EaNu0dblshlkv{ZNg=9%5AA)u&MDYfwETh8Y!uVki!G z_e%K+C*^m*c0}-#)U5A(nibmhTzya~=-r3e$`)y}ZoK#s_$!RGv%A~3bTiWj>)>Pd z`~kC%KELyFr#u+5hriUt5VOgC@D<+m;t17to#Te&ZUhv=eE~P!QpzQ>@B3SQf|WeFb_p7KLcWRcMz;V zJq76uJE7j@Eu|}2-!_rWpEm!Z!#_poQ~X?vosk5B{IIgJ6{{Y%eITT$bJq(DyFcg6 z!@MfrK6`IIU0kxS1SYgN86L_lC-NJ{70YsaIbVp+%8QREjWWZZ3JFY}s|ymB#`i`u z2Ov^P2Txf^E`k1-tIP`)qB0XCJ3`KbFB7pnf%YHB(pB8NAm|7@N95eO)KshGzylZC zuq9uCRakE#%I~{Yz@=hZ&LS_HV<6RK3r~j@K`sC3)FA)uxg*otgVmBDqs^Co31{g~ zwa9p!$D8;Ai&r1e=f(2-M{I>$| znB3cjZyNa71UhA(3YA+ts$nXgs*FCpaH!R@3UKww$vT%55!Rq}b`V}PwMOAAcOmh)hQ_R`>voc8D6 zT6b$odeGB0R$}2H)J)ZKqen(xX**N4)aE`iSG|c$ zn;Cr*x%r0}2h-=PXEmp9Q{SyO`uL;%7_@A4WhH|DN$!XOdfLw=N}l)wm^zi5^Mq?p zyMBH@G;}^E&Kp>fyX9VhEg4u{@cHSQ*z}ZNbky_czOyj@Ju>duVY+BpRdJH2gVxsE zed4D9EymF=zh*m+4Qn^%Q^)v`0PD(_4j<NtLit5# zd|KxNF3ci_?6S1GZd=(NYS2*UIKpsjYWu!MHdnv1RHqj{T&*hBntZ0ajsNWJoV3&S z%O~K|xmk_88Y zs|a3`@xs;oc~xB(Smrm`KOh{~{1)*j4#11SPZ~j3y{W6r`sLNVA>jNwQ^Z=Q z?e!v!#e4f7fJ6ix1Pr~MzTNZ%4H8~XLp+^)-yUoEEWLmBW1>j*%p{GW;x~zHlvucMi)ewj#!OBY(eNsGhA_DXSIQ)XZHYQIhMn8mzC-u6=H3w{s_@TpAeSl6Js?Rh+9ybK$LaH=G{p%jEEBn%ON zgD?fV;8p`uysDwy)+$VaJWX27$_^~mdB``}&zQ|unVq(z3{{!_6q|F&*GiMtg-IDx zhn@#8*oIS2pI<^d{A7@jP@!@q?_C5UW(=27*;HL7s)^oA_1s4WLHk9zuHHH1zk~Iv zP?vSv{fU5R!)B;Y$U$DV(&d`NCEAFuxf2#{3FZ;B$Y%bk)ULypVdX{pbZf@nHpgE)l&*cy6F!Q(bYiMqYC@xTF206}T$Z{k~DZCA~K{6+)9 z>&Oqi>FYekPc(Qc_Y>0adn9hKg6N}vB*Q3CdB02TAoK7SACdb&RE4Z- z&*y(1iQHZ%eP++1Cz=q$TKVH7(nZ*%BYJoeK{BB_?&ST>Yvqp<7favK#V-C#N^s zk+(>93FK>iLp_z86`j%P<%1?P!K_p&>k`cX^F8eB)Hkh7lv@~ufT3e0=&}gJEodS6dX(L&2>5s(- zR0E;Qf@eo-mI?uE5Y&h@Nl?WRKr_FHGzPmR{gxftysvHlKutAgTywk?CZdvFgHAh>&Q3p5&>;2PW=f?FWCdt<>NxVyW1;On9-*5%jX)GFJaW&t3=T4_4Da+HH!feSMpm&dJp?{SOE6i*ry z{BR2#Rv{hxcBJ;@O$pO$vE=12-BI9hgyih9(M(|#@#D!%AX=Au_s#OZpZKZ2S`$}0 z_PRjc!9dc-kxZF2hp4_yf=4ZtXE%I&xU?D+lb^olOjniGQchK*d29sV4FCD~)mIg(AI+v7uC7g6fwq$S+5 zOp}&#_K6!bZ1Ue;0!SNP zmdeZ8K3K3}JT0adu98ee2qpLyqzm3mI(Mnj7^128LmyH<< zD*L>!jkU|fHR)j|1)5hbk2~htGWrKdp4cY?$CBm?UnjON~UH#$vFcu;lo%CePaqt=C2XLy{ zYTzYO7iUq#Bpca4iHtCTeWWyp&OCD|!e$OSc{)}NL*{E_f2n*rKo&TKR#r#dCqGk$J0G!Scj!~Darm+CjHgOa{GvW9f7fdJk z-0o<|Q#;W3wehnmdIbMMd*|Q1O!Qa`} z2H04>HB7sA6`uYzfi21c? z(GF3EYGcC!=K5`*ln1K_wQOKTq_dzpfGc6-;R=3?Pt>}u^UOW?r8X5xA3uq+J}kBM zJSH~zvry?RSvl{{o{ITeE8UF@6YUy$6i=~@ibMc~2W)<7>+5xOO{g&+=s-Nne7At4 zmM#c0_iI2!?fL@ExA5K*GHnYG)symWsnR|tDQ z<5!HS^vk=kXA>MDXOR2R&!iBJ&zEGwgu2)fT2e^%0DQkH2vtaOo#7aIOqJYyi+@QU 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 void; diff --git a/src/renderer/pages/home/components/routing.tsx b/packages/ui/src/pages/home/components/routing.tsx similarity index 99% rename from src/renderer/pages/home/components/routing.tsx rename to packages/ui/src/pages/home/components/routing.tsx index 4f54c8e..a35b8e0 100644 --- a/src/renderer/pages/home/components/routing.tsx +++ b/packages/ui/src/pages/home/components/routing.tsx @@ -9,8 +9,8 @@ import { RouterBuiltInAgentRuleConfig, RouteTargetControl, routingRuleRowMatchesQuery, Search, SelectControl, Toggle, translateOptions, Trash2, uniqueStrings, useAppText, useContext, useMemo, useState, X -} from "../shared"; -import { ROUTER_FALLBACK_MAX_RETRY_COUNT } from "../../../../shared/app"; +} from "../shared/index"; +import { ROUTER_FALLBACK_MAX_RETRY_COUNT } from "@ccr/core/contracts/app"; export function RoutingView({ addRule, config, diff --git a/src/renderer/pages/home/components/server.tsx b/packages/ui/src/pages/home/components/server.tsx similarity index 99% rename from src/renderer/pages/home/components/server.tsx rename to packages/ui/src/pages/home/components/server.tsx index 71e7eb3..378d254 100644 --- a/src/renderer/pages/home/components/server.tsx +++ b/packages/ui/src/pages/home/components/server.tsx @@ -4,7 +4,7 @@ import { Input, LoaderCircle, motion, numberValue, ProxyCertificateStatus, proxyCertificateTrustSteps, ProxyStatus, RefreshCw, ServerActionBusy, ShieldCheck, StatusBadge, Toggle, translateProxyCertificateMessage, useAppText -} from "../shared"; +} from "../shared/index"; export function ServerView({ actionBusy, actionError, diff --git a/src/renderer/pages/home/components/settings.tsx b/packages/ui/src/pages/home/components/settings.tsx similarity index 99% rename from src/renderer/pages/home/components/settings.tsx rename to packages/ui/src/pages/home/components/settings.tsx index 4fba842..4a6adea 100644 --- a/src/renderer/pages/home/components/settings.tsx +++ b/packages/ui/src/pages/home/components/settings.tsx @@ -11,7 +11,7 @@ import { TrayBalanceProgressConfig, TrayComponentVariants, TrayWidgetConfig, TrayWidgetType, TrayWidgetVariant, appLogoUrl, trayMascotIconUrls, arrayMove, defaultTrayWidgetVariant, isTraySingletonWidgetType, normalizeTrayWidget, normalizeTrayWidgets, Switch, Trash2, trayWidgetVariantOptions, useEffect, useMemo, useRef, useSensor, useSensors, useSortable, useState, X -} from "../shared"; +} from "../shared/index"; const settingsPageContentWidthClassName = "mx-auto w-full max-w-[900px]"; diff --git a/src/renderer/pages/home/components/share-cards.tsx b/packages/ui/src/pages/home/components/share-cards.tsx similarity index 99% rename from src/renderer/pages/home/components/share-cards.tsx rename to packages/ui/src/pages/home/components/share-cards.tsx index b356f20..ed67437 100644 --- a/src/renderer/pages/home/components/share-cards.tsx +++ b/packages/ui/src/pages/home/components/share-cards.tsx @@ -3,7 +3,7 @@ import { formatProviderAccountMeterValue, formatUsdCost, LoaderCircle, primaryProviderAccountMeter, providerAccountMeterRemainingRatio, providerAccountSnapshotKey, ReactNode, UsageStatsRange, UsageStatsSnapshot, useRef, useState, useAppText, type OverviewWidgetType, type ProviderAccountMeter, type ProviderAccountSnapshot -} from "../shared"; +} from "../shared/index"; import { buildTokenActivity, type TokenActivityCell } from "@/lib/usage-activity"; type ShareCardTone = "amber" | "blue" | "emerald" | "indigo" | "rose" | "slate" | "teal"; diff --git a/src/renderer/pages/home/components/update.tsx b/packages/ui/src/pages/home/components/update.tsx similarity index 99% rename from src/renderer/pages/home/components/update.tsx rename to packages/ui/src/pages/home/components/update.tsx index 1d29370..a8fba2b 100644 --- a/src/renderer/pages/home/components/update.tsx +++ b/packages/ui/src/pages/home/components/update.tsx @@ -1,7 +1,7 @@ import { AppCopy, AppUpdateStatus, Button, Check, CircleAlert, cn, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Download, LoaderCircle, RefreshCw, X -} from "../shared"; +} from "../shared/index"; export type UpdateActionBusy = "" | "check" | "download" | "install"; diff --git a/src/renderer/pages/home/components/virtual-models.tsx b/packages/ui/src/pages/home/components/virtual-models.tsx similarity index 99% rename from src/renderer/pages/home/components/virtual-models.tsx rename to packages/ui/src/pages/home/components/virtual-models.tsx index 6debc9d..6e7c2c4 100644 --- a/src/renderer/pages/home/components/virtual-models.tsx +++ b/packages/ui/src/pages/home/components/virtual-models.tsx @@ -12,7 +12,7 @@ import { useRef, useState, validateMcpServerDraft, virtualModelBaseModelSummary, VirtualModelDraft, virtualModelMatchesQuery, virtualModelMatchSummary, type KeyValueDraftRow, VirtualModelProfileConfig, virtualModelToolSummary, X -} from "../shared"; +} from "../shared/index"; const virtualModelTableGridClass = "grid-cols-[minmax(180px,0.9fr)_minmax(220px,1.1fr)_minmax(220px,1.1fr)_minmax(170px,0.85fr)_112px_96px]"; const virtualModelTableMinWidthClass = "min-w-[1100px]"; diff --git a/src/renderer/pages/home/index.html b/packages/ui/src/pages/home/index.html similarity index 100% rename from src/renderer/pages/home/index.html rename to packages/ui/src/pages/home/index.html diff --git a/src/renderer/pages/home/main.tsx b/packages/ui/src/pages/home/main.tsx similarity index 100% rename from src/renderer/pages/home/main.tsx rename to packages/ui/src/pages/home/main.tsx diff --git a/src/renderer/pages/home/shared/api-keys.ts b/packages/ui/src/pages/home/shared/api-keys.ts similarity index 97% rename from src/renderer/pages/home/shared/api-keys.ts rename to packages/ui/src/pages/home/shared/api-keys.ts index 88eba12..58bf972 100644 --- a/src/renderer/pages/home/shared/api-keys.ts +++ b/packages/ui/src/pages/home/shared/api-keys.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/common.ts b/packages/ui/src/pages/home/shared/common.ts similarity index 98% rename from src/renderer/pages/home/shared/common.ts rename to packages/ui/src/pages/home/shared/common.ts index 246a6fa..d386424 100644 --- a/src/renderer/pages/home/shared/common.ts +++ b/packages/ui/src/pages/home/shared/common.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/config.ts b/packages/ui/src/pages/home/shared/config.ts similarity index 96% rename from src/renderer/pages/home/shared/config.ts rename to packages/ui/src/pages/home/shared/config.ts index 538ae25..310a373 100644 --- a/src/renderer/pages/home/shared/config.ts +++ b/packages/ui/src/pages/home/shared/config.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/controls.tsx b/packages/ui/src/pages/home/shared/controls.tsx similarity index 98% rename from src/renderer/pages/home/shared/controls.tsx rename to packages/ui/src/pages/home/shared/controls.tsx index 3d312ed..266b6d8 100644 --- a/src/renderer/pages/home/shared/controls.tsx +++ b/packages/ui/src/pages/home/shared/controls.tsx @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/extensions.ts b/packages/ui/src/pages/home/shared/extensions.ts similarity index 98% rename from src/renderer/pages/home/shared/extensions.ts rename to packages/ui/src/pages/home/shared/extensions.ts index 0c51be2..877fd31 100644 --- a/src/renderer/pages/home/shared/extensions.ts +++ b/packages/ui/src/pages/home/shared/extensions.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/external.tsx b/packages/ui/src/pages/home/shared/external.tsx similarity index 97% rename from src/renderer/pages/home/shared/external.tsx rename to packages/ui/src/pages/home/shared/external.tsx index 0b93531..4aa2f32 100644 --- a/src/renderer/pages/home/shared/external.tsx +++ b/packages/ui/src/pages/home/shared/external.tsx @@ -101,7 +101,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -118,9 +118,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -136,7 +136,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -242,7 +242,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -250,7 +250,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetByIdentityInList, @@ -259,8 +259,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/fallbacks.ts b/packages/ui/src/pages/home/shared/fallbacks.ts similarity index 94% rename from src/renderer/pages/home/shared/fallbacks.ts rename to packages/ui/src/pages/home/shared/fallbacks.ts index 87d829d..fe77298 100644 --- a/src/renderer/pages/home/shared/fallbacks.ts +++ b/packages/ui/src/pages/home/shared/fallbacks.ts @@ -6,8 +6,8 @@ import type { ProxyCertificateStatus, ProxyNetworkSnapshot, ProxyStatus -} from "../../../../shared/app"; -import { createDefaultAppConfig } from "../../../../shared/default-config"; +} from "@ccr/core/contracts/app"; +import { createDefaultAppConfig } from "@ccr/core/config/default-config"; export const fallbackInfo: AppInfo = { appConfigDbFile: "Browser preview", diff --git a/src/renderer/pages/home/shared/i18n.tsx b/packages/ui/src/pages/home/shared/i18n.tsx similarity index 99% rename from src/renderer/pages/home/shared/i18n.tsx rename to packages/ui/src/pages/home/shared/i18n.tsx index b5e7dfc..4042095 100644 --- a/src/renderer/pages/home/shared/i18n.tsx +++ b/packages/ui/src/pages/home/shared/i18n.tsx @@ -1,5 +1,5 @@ import { createContext, useContext, useMemo } from "react"; -import { translateErrorMessage } from "../../../../shared/i18n"; +import { translateErrorMessage } from "@ccr/core/contracts/i18n"; type NavigationId = "onboarding" | "overview" | "observability" | "api-keys" | "server" | "profile" | "networking" | "logs" | "providers" | "models" | "routing" | "virtual-models" | "extensions"; type ResolvedLanguage = "en" | "zh"; diff --git a/src/renderer/pages/home/shared/index.tsx b/packages/ui/src/pages/home/shared/index.tsx similarity index 90% rename from src/renderer/pages/home/shared/index.tsx rename to packages/ui/src/pages/home/shared/index.tsx index bf40b7f..678d7b6 100644 --- a/src/renderer/pages/home/shared/index.tsx +++ b/packages/ui/src/pages/home/shared/index.tsx @@ -18,4 +18,4 @@ export * from "./routing"; export * from "./virtual-models"; export * from "./extensions"; export * from "./providers"; -export type { RouterBuiltInAgentRuleConfig, RouterBuiltInAgentRuleId, RouterBuiltInRulesConfig } from "../../../../shared/app"; +export type { RouterBuiltInAgentRuleConfig, RouterBuiltInAgentRuleId, RouterBuiltInRulesConfig } from "@ccr/core/contracts/app"; diff --git a/src/renderer/pages/home/shared/logs.ts b/packages/ui/src/pages/home/shared/logs.ts similarity index 99% rename from src/renderer/pages/home/shared/logs.ts rename to packages/ui/src/pages/home/shared/logs.ts index 9066f1d..5727531 100644 --- a/src/renderer/pages/home/shared/logs.ts +++ b/packages/ui/src/pages/home/shared/logs.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/motion.tsx b/packages/ui/src/pages/home/shared/motion.tsx similarity index 100% rename from src/renderer/pages/home/shared/motion.tsx rename to packages/ui/src/pages/home/shared/motion.tsx diff --git a/src/renderer/pages/home/shared/network.ts b/packages/ui/src/pages/home/shared/network.ts similarity index 98% rename from src/renderer/pages/home/shared/network.ts rename to packages/ui/src/pages/home/shared/network.ts index 676cb2d..7782274 100644 --- a/src/renderer/pages/home/shared/network.ts +++ b/packages/ui/src/pages/home/shared/network.ts @@ -1,4 +1,4 @@ -import type { ProxyNetworkExchange } from "../../../../shared/app"; +import type { ProxyNetworkExchange } from "@ccr/core/contracts/app"; export function networkExchangeMatchesQuery(exchange: ProxyNetworkExchange, query: string): boolean { if (!query) { diff --git a/src/renderer/pages/home/shared/options.ts b/packages/ui/src/pages/home/shared/options.ts similarity index 98% rename from src/renderer/pages/home/shared/options.ts rename to packages/ui/src/pages/home/shared/options.ts index 240d6bb..2415bb1 100644 --- a/src/renderer/pages/home/shared/options.ts +++ b/packages/ui/src/pages/home/shared/options.ts @@ -18,7 +18,7 @@ import { BUILTIN_FUSION_VISION_TOOL_NAME, BUILTIN_FUSION_WEB_SEARCH_TOOL_NAME, OVERVIEW_WIDGET_SIZE_VALUES -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentKind, AppConfig, @@ -40,7 +40,7 @@ import type { VirtualModelExecutionMode, VirtualModelFusionWebSearchProvider, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import anthropicProviderIconUrl from "@/assets/provider-icons/anthropic.png"; import bailianProviderIconUrl from "@/assets/provider-icons/bailian.ico"; import deepseekProviderIconUrl from "@/assets/provider-icons/deepseek.ico"; @@ -56,9 +56,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; type ViewId = "onboarding" | "overview" | "observability" | "api-keys" | "server" | "profile" | "networking" | "logs" | "providers" | "models" | "routing" | "virtual-models" | "extensions"; type NavigationId = ViewId; diff --git a/src/renderer/pages/home/shared/polling.ts b/packages/ui/src/pages/home/shared/polling.ts similarity index 100% rename from src/renderer/pages/home/shared/polling.ts rename to packages/ui/src/pages/home/shared/polling.ts diff --git a/src/renderer/pages/home/shared/profiles.ts b/packages/ui/src/pages/home/shared/profiles.ts similarity index 99% rename from src/renderer/pages/home/shared/profiles.ts rename to packages/ui/src/pages/home/shared/profiles.ts index bd092eb..285b464 100644 --- a/src/renderer/pages/home/shared/profiles.ts +++ b/packages/ui/src/pages/home/shared/profiles.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import zcodeLogoUrl from "@/assets/agent-logos/zcode.png"; @@ -117,9 +117,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -136,7 +136,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -235,7 +235,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -243,7 +243,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -251,8 +251,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/provider-accounts.ts b/packages/ui/src/pages/home/shared/provider-accounts.ts similarity index 97% rename from src/renderer/pages/home/shared/provider-accounts.ts rename to packages/ui/src/pages/home/shared/provider-accounts.ts index 335952f..3e726ae 100644 --- a/src/renderer/pages/home/shared/provider-accounts.ts +++ b/packages/ui/src/pages/home/shared/provider-accounts.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/providers.ts b/packages/ui/src/pages/home/shared/providers.ts similarity index 99% rename from src/renderer/pages/home/shared/providers.ts rename to packages/ui/src/pages/home/shared/providers.ts index bd3c4f4..5332284 100644 --- a/src/renderer/pages/home/shared/providers.ts +++ b/packages/ui/src/pages/home/shared/providers.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import zcodeLogoUrl from "@/assets/agent-logos/zcode.png"; @@ -117,9 +117,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -135,7 +135,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -235,7 +235,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -243,7 +243,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -251,8 +251,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/routing.ts b/packages/ui/src/pages/home/shared/routing.ts similarity index 98% rename from src/renderer/pages/home/shared/routing.ts rename to packages/ui/src/pages/home/shared/routing.ts index 397b133..bcc952a 100644 --- a/src/renderer/pages/home/shared/routing.ts +++ b/packages/ui/src/pages/home/shared/routing.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -135,7 +135,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -236,7 +236,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -244,7 +244,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -252,8 +252,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/services.tsx b/packages/ui/src/pages/home/shared/services.tsx similarity index 97% rename from src/renderer/pages/home/shared/services.tsx rename to packages/ui/src/pages/home/shared/services.tsx index 0ab4c34..21c41ed 100644 --- a/src/renderer/pages/home/shared/services.tsx +++ b/packages/ui/src/pages/home/shared/services.tsx @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/types.ts b/packages/ui/src/pages/home/shared/types.ts similarity index 97% rename from src/renderer/pages/home/shared/types.ts rename to packages/ui/src/pages/home/shared/types.ts index 2729e00..2a15c81 100644 --- a/src/renderer/pages/home/shared/types.ts +++ b/packages/ui/src/pages/home/shared/types.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -234,7 +234,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -242,7 +242,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -250,8 +250,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/home/shared/usage.ts b/packages/ui/src/pages/home/shared/usage.ts similarity index 99% rename from src/renderer/pages/home/shared/usage.ts rename to packages/ui/src/pages/home/shared/usage.ts index 76cc6c6..040988d 100644 --- a/src/renderer/pages/home/shared/usage.ts +++ b/packages/ui/src/pages/home/shared/usage.ts @@ -6,7 +6,7 @@ import type { UsageStatsRange, UsageStatsSnapshot, UsageTotals -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentFilterValue } from "./options"; function positiveInteger(value: unknown): number | undefined { diff --git a/src/renderer/pages/home/shared/virtual-models.ts b/packages/ui/src/pages/home/shared/virtual-models.ts similarity index 99% rename from src/renderer/pages/home/shared/virtual-models.ts rename to packages/ui/src/pages/home/shared/virtual-models.ts index c71cdd0..05c106c 100644 --- a/src/renderer/pages/home/shared/virtual-models.ts +++ b/packages/ui/src/pages/home/shared/virtual-models.ts @@ -99,7 +99,7 @@ import { Select } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; -import appLogoUrl from "../../../../../assets/logo.png"; +import appLogoUrl from "@/assets/logo.png"; import claudeCodeLogoUrl from "@/assets/agent-logos/claude-code.png"; import codexLogoUrl from "@/assets/agent-logos/codex.png"; import onboardingMascotSpriteUrl from "@/assets/onboarding/mascot-transition.svg"; @@ -116,9 +116,9 @@ import zaiGlobalCodingProviderIconUrl from "@/assets/provider-icons/zai-global-c import zaiGlobalGeneralProviderIconUrl from "@/assets/provider-icons/zai-global-general.svg"; import zhipuCnCodingProviderIconUrl from "@/assets/provider-icons/zhipu-cn-coding.png"; import zhipuCnGeneralProviderIconUrl from "@/assets/provider-icons/zhipu-cn-general.png"; -import trayCyanIconUrl from "../../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../../assets/tray-violet.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; import { BUILTIN_FUSION_TOOL_SERVER_NAME, BUILTIN_FUSION_VISION_TOOL_NAME, @@ -134,7 +134,7 @@ import { TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import type { AgentAnalysisFilter, AgentAnalysisSessionSelection, @@ -233,7 +233,7 @@ import type { VirtualModelFusionWebSearchProvider, VirtualModelProfileConfig, VirtualModelToolVisibility -} from "../../../../shared/app"; +} from "@ccr/core/contracts/app"; import { customProviderPresetId, defaultProviderAccountConfig, @@ -241,7 +241,7 @@ import { type ProviderIdentitySafetyIssue, type ProviderPreset, type ProviderPresetEndpoint -} from "../../../../shared/provider-presets"; +} from "@ccr/core/providers/presets/types"; import { findProviderPresetByBaseUrlInList, findProviderPresetInList, @@ -249,8 +249,8 @@ import { providerApiKeySafetyIssueInList, providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList -} from "../../../../shared/provider-preset-utils"; -import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "../../../../shared/provider-url"; +} from "@ccr/core/providers/presets/utils"; +import { normalizeProviderBaseUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; import { fallbackConfig, fallbackGatewayStatus, diff --git a/src/renderer/pages/tray/TrayApp.tsx b/packages/ui/src/pages/tray/TrayApp.tsx similarity index 99% rename from src/renderer/pages/tray/TrayApp.tsx rename to packages/ui/src/pages/tray/TrayApp.tsx index 4592206..7374208 100644 --- a/src/renderer/pages/tray/TrayApp.tsx +++ b/packages/ui/src/pages/tray/TrayApp.tsx @@ -7,7 +7,7 @@ import { import { AccountSummaryPanel, AnimatedUsageChart, ChartShell, ModelShareChart, RingMetrics, SourceGrid, StatsGrid, TokenActivityPanel, TokenMixPanel, TrayStatusStrip -} from "./components"; +} from "./components/index"; type TrayHeaderRange = Exclude; diff --git a/src/renderer/pages/tray/TrayDetailApp.tsx b/packages/ui/src/pages/tray/TrayDetailApp.tsx similarity index 99% rename from src/renderer/pages/tray/TrayDetailApp.tsx rename to packages/ui/src/pages/tray/TrayDetailApp.tsx index af0627e..23984a6 100644 --- a/src/renderer/pages/tray/TrayDetailApp.tsx +++ b/packages/ui/src/pages/tray/TrayDetailApp.tsx @@ -5,7 +5,7 @@ import { } from "./shared"; import { TrayStatusStrip, UsageDetailPanel -} from "./components"; +} from "./components/index"; export function TrayDetailApp({ provider }: { provider?: string }) { const t = useTrayText(); diff --git a/src/renderer/pages/tray/components/account-panel.tsx b/packages/ui/src/pages/tray/components/account-panel.tsx similarity index 100% rename from src/renderer/pages/tray/components/account-panel.tsx rename to packages/ui/src/pages/tray/components/account-panel.tsx diff --git a/src/renderer/pages/tray/components/index.ts b/packages/ui/src/pages/tray/components/index.ts similarity index 100% rename from src/renderer/pages/tray/components/index.ts rename to packages/ui/src/pages/tray/components/index.ts diff --git a/src/renderer/pages/tray/components/overview-panel.tsx b/packages/ui/src/pages/tray/components/overview-panel.tsx similarity index 100% rename from src/renderer/pages/tray/components/overview-panel.tsx rename to packages/ui/src/pages/tray/components/overview-panel.tsx diff --git a/src/renderer/pages/tray/components/source-grid.tsx b/packages/ui/src/pages/tray/components/source-grid.tsx similarity index 100% rename from src/renderer/pages/tray/components/source-grid.tsx rename to packages/ui/src/pages/tray/components/source-grid.tsx diff --git a/src/renderer/pages/tray/components/status-strip.tsx b/packages/ui/src/pages/tray/components/status-strip.tsx similarity index 100% rename from src/renderer/pages/tray/components/status-strip.tsx rename to packages/ui/src/pages/tray/components/status-strip.tsx diff --git a/src/renderer/pages/tray/components/usage-detail.tsx b/packages/ui/src/pages/tray/components/usage-detail.tsx similarity index 100% rename from src/renderer/pages/tray/components/usage-detail.tsx rename to packages/ui/src/pages/tray/components/usage-detail.tsx diff --git a/src/renderer/pages/tray/components/widgets.tsx b/packages/ui/src/pages/tray/components/widgets.tsx similarity index 100% rename from src/renderer/pages/tray/components/widgets.tsx rename to packages/ui/src/pages/tray/components/widgets.tsx diff --git a/src/renderer/pages/tray/index.html b/packages/ui/src/pages/tray/index.html similarity index 100% rename from src/renderer/pages/tray/index.html rename to packages/ui/src/pages/tray/index.html diff --git a/src/renderer/pages/tray/main.tsx b/packages/ui/src/pages/tray/main.tsx similarity index 100% rename from src/renderer/pages/tray/main.tsx rename to packages/ui/src/pages/tray/main.tsx diff --git a/src/renderer/pages/tray/shared.tsx b/packages/ui/src/pages/tray/shared.tsx similarity index 98% rename from src/renderer/pages/tray/shared.tsx rename to packages/ui/src/pages/tray/shared.tsx index 2a5be7e..1976d25 100644 --- a/src/renderer/pages/tray/shared.tsx +++ b/packages/ui/src/pages/tray/shared.tsx @@ -1,12 +1,12 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from "react"; import { createRoot } from "react-dom/client"; import { LoaderCircle, Power, RefreshCw } from "lucide-react"; -import appLogoUrl from "../../../../assets/logo.png"; -import trayCyanIconUrl from "../../../../assets/tray-cyan.png"; -import trayOrangeIconUrl from "../../../../assets/tray-orange.png"; -import trayVioletIconUrl from "../../../../assets/tray-violet.png"; -import { DEFAULT_TRAY_COMPONENT_VARIANTS, DEFAULT_TRAY_WIDGETS, DEFAULT_TRAY_WINDOW_MODULES, TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS } from "../../../shared/app"; -import { formatLocalizedErrorMessage } from "../../../shared/i18n"; +import appLogoUrl from "@/assets/logo.png"; +import trayCyanIconUrl from "@/assets/tray-cyan.png"; +import trayOrangeIconUrl from "@/assets/tray-orange.png"; +import trayVioletIconUrl from "@/assets/tray-violet.png"; +import { DEFAULT_TRAY_COMPONENT_VARIANTS, DEFAULT_TRAY_WIDGETS, DEFAULT_TRAY_WINDOW_MODULES, TRAY_SINGLETON_WIDGET_TYPES, TRAY_TOP_WIDGET_TYPES, TRAY_WINDOW_MODULE_IDS } from "@ccr/core/contracts/app"; +import { formatLocalizedErrorMessage } from "@ccr/core/contracts/i18n"; import type { AppConfig, ProviderAccountMeter, @@ -22,7 +22,7 @@ import type { UsageStatsRange, UsageStatsSnapshot, UsageTotals -} from "../../../shared/app"; +} from "@ccr/core/contracts/app"; export { createContext, useCallback, useContext, useEffect, useMemo, useState, createRoot, diff --git a/src/renderer/styles/globals.css b/packages/ui/src/styles/globals.css similarity index 99% rename from src/renderer/styles/globals.css rename to packages/ui/src/styles/globals.css index 420d8f9..b4f954d 100644 --- a/src/renderer/styles/globals.css +++ b/packages/ui/src/styles/globals.css @@ -3,7 +3,7 @@ @source "../components/**/*.{ts,tsx}"; @source "../lib/**/*.{ts,tsx}"; @source "../types/**/*.{ts,tsx}"; -@source "../../shared/**/*.{ts,tsx}"; +@source "../../../core/src/contracts/**/*.{ts,tsx}"; @theme inline { --color-background: var(--background); diff --git a/src/renderer/types/assets.d.ts b/packages/ui/src/types/assets.d.ts similarity index 100% rename from src/renderer/types/assets.d.ts rename to packages/ui/src/types/assets.d.ts diff --git a/src/renderer/types/electron.d.ts b/packages/ui/src/types/electron.d.ts similarity index 98% rename from src/renderer/types/electron.d.ts rename to packages/ui/src/types/electron.d.ts index 6e7ea80..97af330 100644 --- a/src/renderer/types/electron.d.ts +++ b/packages/ui/src/types/electron.d.ts @@ -72,8 +72,8 @@ import type { UsageStatsFilter, UsageStatsRange, UsageStatsSnapshot -} from "../../shared/app"; -import type { ProviderPreset } from "../../shared/provider-presets"; +} from "@ccr/core/contracts/app"; +import type { ProviderPreset } from "@ccr/core/providers/presets/types"; declare global { interface Window { diff --git a/src/main/web-client-bridge.ts b/packages/ui/src/web-client-bridge.ts similarity index 53% rename from src/main/web-client-bridge.ts rename to packages/ui/src/web-client-bridge.ts index 57fc840..d9456f1 100644 --- a/src/main/web-client-bridge.ts +++ b/packages/ui/src/web-client-bridge.ts @@ -1,3 +1,5 @@ +type CcrApi = NonNullable; + const rpcEndpoint = "/api/ccr/rpc"; const webAuthHeader = "x-ccr-web-auth"; const webAuthQueryParam = "ccr_web_token"; @@ -10,7 +12,7 @@ type RpcResponse = async function rpc(method: string, args: unknown[] = []): Promise { const response = await fetch(rpcEndpoint, { - body: JSON.stringify({ args, method }), + body: JSON.stringify({ args: trimTrailingUndefined(args), method }), headers: { "content-type": "application/json", ...(webAuthToken ? { [webAuthHeader]: webAuthToken } : {}) @@ -32,6 +34,14 @@ async function rpc(method: string, args: unknown[] = []): Promise { return payload.value; } +function trimTrailingUndefined(args: unknown[]): unknown[] { + let end = args.length; + while (end > 0 && args[end - 1] === undefined) { + end -= 1; + } + return end === args.length ? args : args.slice(0, end); +} + function readWebAuthToken(): string { const tokenFromUrl = readWebAuthTokenFromUrl(); if (tokenFromUrl) { @@ -84,76 +94,80 @@ async function selectPluginDirectory(): Promise { return rpc("selectPluginDirectory", [directory.trim()]); } -window.ccr = { - applyClaudeAppGateway: (config) => rpc("applyClaudeAppGateway", [config]) as ReturnType["applyClaudeAppGateway"]>, - applyProfile: () => rpc("applyProfile") as ReturnType["applyProfile"]>, - cancelBotGatewayQrLogin: (request) => rpc("cancelBotGatewayQrLogin", [request]) as ReturnType["cancelBotGatewayQrLogin"]>, - checkProviderConnectivity: (request) => rpc("checkProviderConnectivity", [request]) as ReturnType["checkProviderConnectivity"]>, - clearProxyNetworkCaptures: () => rpc("clearProxyNetworkCaptures") as ReturnType["clearProxyNetworkCaptures"]>, - closeBotGatewayQrWindow: (request) => rpc("closeBotGatewayQrWindow", [request]) as ReturnType["closeBotGatewayQrWindow"]>, +const webClientBridge: CcrApi = { + applyClaudeAppGateway: (config) => rpc("applyClaudeAppGateway", [config]) as ReturnType, + applyProfile: () => rpc("applyProfile") as ReturnType, + cancelBotGatewayQrLogin: (request) => rpc("cancelBotGatewayQrLogin", [request]) as ReturnType, + checkProviderConnectivity: (request) => rpc("checkProviderConnectivity", [request]) as ReturnType, + clearProxyNetworkCaptures: () => rpc("clearProxyNetworkCaptures") as ReturnType, + closeBotGatewayQrWindow: (request) => rpc("closeBotGatewayQrWindow", [request]) as ReturnType, closeTray: () => Promise.resolve(), - detectProviderIcon: (request) => rpc("detectProviderIcon", [request]) as ReturnType["detectProviderIcon"]>, - exportData: () => rpc("exportData") as ReturnType["exportData"]>, - fetchProviderManifest: (request) => rpc("fetchProviderManifest", [request]) as ReturnType["fetchProviderManifest"]>, - getAgentAnalysis: (filter) => rpc("getAgentAnalysis", [filter]) as ReturnType["getAgentAnalysis"]>, - getAgentTracePayload: (request) => rpc("getAgentTracePayload", [request]) as ReturnType["getAgentTracePayload"]>, - getAppInfo: () => rpc("getAppInfo") as ReturnType["getAppInfo"]>, - getConfig: () => rpc("getConfig") as ReturnType["getConfig"]>, - getGatewayStatus: () => rpc("getGatewayStatus") as ReturnType["getGatewayStatus"]>, - getLocalAgentProviderCandidates: () => rpc("getLocalAgentProviderCandidates") as ReturnType["getLocalAgentProviderCandidates"]>, - getOnboardingFinished: () => rpc("getOnboardingFinished") as ReturnType["getOnboardingFinished"]>, + detectProviderIcon: (request) => rpc("detectProviderIcon", [request]) as ReturnType, + exportData: () => rpc("exportData") as ReturnType, + fetchProviderManifest: (request) => rpc("fetchProviderManifest", [request]) as ReturnType, + getAgentAnalysis: (filter) => rpc("getAgentAnalysis", [filter]) as ReturnType, + getAgentTracePayload: (request) => rpc("getAgentTracePayload", [request]) as ReturnType, + getAppInfo: () => rpc("getAppInfo") as ReturnType, + getConfig: () => rpc("getConfig") as ReturnType, + getGatewayStatus: () => rpc("getGatewayStatus") as ReturnType, + getLocalAgentProviderCandidates: () => rpc("getLocalAgentProviderCandidates") as ReturnType, + getOnboardingFinished: () => rpc("getOnboardingFinished") as ReturnType, getPendingProviderDeepLinks: () => Promise.resolve([]), - getPluginMarketplace: () => rpc("getPluginMarketplace") as ReturnType["getPluginMarketplace"]>, - getProfileOpenCommand: (request) => rpc("getProfileOpenCommand", [request]) as ReturnType["getProfileOpenCommand"]>, - getProfileRuntimeStatus: () => rpc("getProfileRuntimeStatus") as ReturnType["getProfileRuntimeStatus"]>, - getProviderAccountSnapshots: (provider, options) => rpc("getProviderAccountSnapshots", [provider, options]) as ReturnType["getProviderAccountSnapshots"]>, - getProviderCatalogModels: (request) => rpc("getProviderCatalogModels", [request]) as ReturnType["getProviderCatalogModels"]>, - getProviderPresets: () => rpc("getProviderPresets") as ReturnType["getProviderPresets"]>, - getProxyCertificateStatus: () => rpc("getProxyCertificateStatus") as ReturnType["getProxyCertificateStatus"]>, - getProxyNetworkCaptures: () => rpc("getProxyNetworkCaptures") as ReturnType["getProxyNetworkCaptures"]>, - getProxyStatus: () => rpc("getProxyStatus") as ReturnType["getProxyStatus"]>, - getRequestLogDetail: (request) => rpc("getRequestLogDetail", [request]) as ReturnType["getRequestLogDetail"]>, - getRequestLogs: (filter) => rpc("getRequestLogs", [filter]) as ReturnType["getRequestLogs"]>, - getUpdateStatus: () => rpc("getUpdateStatus") as ReturnType["getUpdateStatus"]>, - getUsageStats: (range, filter) => rpc("getUsageStats", [range, filter]) as ReturnType["getUsageStats"]>, - importLocalAgentProvider: (request) => rpc("importLocalAgentProvider", [request]) as ReturnType["importLocalAgentProvider"]>, - installProxyCertificate: () => rpc("installProxyCertificate") as ReturnType["installProxyCertificate"]>, - listMcpServerTools: (serverName) => rpc("listMcpServerTools", [serverName]) as ReturnType["listMcpServerTools"]>, + getPluginMarketplace: () => rpc("getPluginMarketplace") as ReturnType, + getProfileOpenCommand: (request) => rpc("getProfileOpenCommand", [request]) as ReturnType, + getProfileRuntimeStatus: () => rpc("getProfileRuntimeStatus") as ReturnType, + getProviderAccountSnapshots: (provider, options) => rpc("getProviderAccountSnapshots", [provider, options]) as ReturnType, + getProviderCatalogModels: (request) => rpc("getProviderCatalogModels", [request]) as ReturnType, + getProviderPresets: () => rpc("getProviderPresets") as ReturnType, + getProxyCertificateStatus: () => rpc("getProxyCertificateStatus") as ReturnType, + getProxyNetworkCaptures: () => rpc("getProxyNetworkCaptures") as ReturnType, + getProxyStatus: () => rpc("getProxyStatus") as ReturnType, + getRequestLogDetail: (request) => rpc("getRequestLogDetail", [request]) as ReturnType, + getRequestLogs: (filter) => rpc("getRequestLogs", [filter]) as ReturnType, + getUpdateStatus: () => rpc("getUpdateStatus") as ReturnType, + getUsageStats: (range, filter) => rpc("getUsageStats", [range, filter]) as ReturnType, + importLocalAgentProvider: (request) => rpc("importLocalAgentProvider", [request]) as ReturnType, + installProxyCertificate: () => rpc("installProxyCertificate") as ReturnType, + listMcpServerTools: (serverName) => rpc("listMcpServerTools", [serverName]) as ReturnType, onBeforeQuit: noopSubscription, onOpenSettingsRequest: noopSubscription, onOpenUpdateRequest: noopSubscription, onProviderDeepLink: noopSubscription, onUpdateStatusChanged: noopSubscription, - openBotGatewayQrWindow: (request) => rpc("openBotGatewayQrWindow", [request]) as ReturnType["openBotGatewayQrWindow"]>, - openBuiltInBrowser: () => rpc("openBuiltInBrowser") as ReturnType["openBuiltInBrowser"]>, + openBotGatewayQrWindow: (request) => rpc("openBotGatewayQrWindow", [request]) as ReturnType, + openBuiltInBrowser: () => rpc("openBuiltInBrowser") as ReturnType, openExternal: (url) => { window.open(url, "_blank", "noopener,noreferrer"); return Promise.resolve(); }, - openProfile: (request) => rpc("openProfile", [request]) as ReturnType["openProfile"]>, - probeProvider: (request) => rpc("probeProvider", [request]) as ReturnType["probeProvider"]>, - probeProviderCandidates: (request) => rpc("probeProviderCandidates", [request]) as ReturnType["probeProviderCandidates"]>, - quitApp: () => rpc("quitApp") as ReturnType["quitApp"]>, - restartGateway: () => rpc("restartGateway") as ReturnType["restartGateway"]>, - restartProxy: () => rpc("restartProxy") as ReturnType["restartProxy"]>, - revealProxyCertificate: () => rpc("revealProxyCertificate") as ReturnType["revealProxyCertificate"]>, - resetCodexRateLimitCredit: (request) => rpc("resetCodexRateLimitCredit", [request]) as ReturnType["resetCodexRateLimitCredit"]>, - saveApiKeys: (apiKeys) => rpc("saveApiKeys", [apiKeys]) as ReturnType["saveApiKeys"]>, - saveConfig: (config, options) => rpc("saveConfig", [config, options]) as ReturnType["saveConfig"]>, - scanBotHandoffBluetoothTargets: () => rpc("scanBotHandoffBluetoothTargets") as ReturnType["scanBotHandoffBluetoothTargets"]>, - scanBotHandoffWifiTargets: () => rpc("scanBotHandoffWifiTargets") as ReturnType["scanBotHandoffWifiTargets"]>, - selectPluginDirectory: () => selectPluginDirectory() as ReturnType["selectPluginDirectory"]>, - setOnboardingFinished: () => rpc("setOnboardingFinished") as ReturnType["setOnboardingFinished"]>, - setProxyNetworkCaptureEnabled: (enabled) => rpc("setProxyNetworkCaptureEnabled", [enabled]) as ReturnType["setProxyNetworkCaptureEnabled"]>, + openProfile: (request) => rpc("openProfile", [request]) as ReturnType, + probeProvider: (request) => rpc("probeProvider", [request]) as ReturnType, + probeProviderCandidates: (request) => rpc("probeProviderCandidates", [request]) as ReturnType, + quitApp: () => rpc("quitApp") as ReturnType, + restartGateway: () => rpc("restartGateway") as ReturnType, + restartProxy: () => rpc("restartProxy") as ReturnType, + revealProxyCertificate: () => rpc("revealProxyCertificate") as ReturnType, + resetCodexRateLimitCredit: (request) => rpc("resetCodexRateLimitCredit", [request]) as ReturnType, + saveApiKeys: (apiKeys) => rpc("saveApiKeys", [apiKeys]) as ReturnType, + saveConfig: (config, options) => rpc("saveConfig", [config, options]) as ReturnType, + scanBotHandoffBluetoothTargets: () => rpc("scanBotHandoffBluetoothTargets") as ReturnType, + scanBotHandoffWifiTargets: () => rpc("scanBotHandoffWifiTargets") as ReturnType, + selectPluginDirectory: () => selectPluginDirectory() as ReturnType, + setOnboardingFinished: () => rpc("setOnboardingFinished") as ReturnType, + setProxyNetworkCaptureEnabled: (enabled) => rpc("setProxyNetworkCaptureEnabled", [enabled]) as ReturnType, setTrayDetailOpen: () => Promise.resolve(), showMainWindow: () => Promise.resolve(), - startBotGatewayQrLogin: (request) => rpc("startBotGatewayQrLogin", [request]) as ReturnType["startBotGatewayQrLogin"]>, - startGateway: () => rpc("startGateway") as ReturnType["startGateway"]>, - stopGateway: () => rpc("stopGateway") as ReturnType["stopGateway"]>, - stopProfile: (request) => rpc("stopProfile", [request]) as ReturnType["stopProfile"]>, - testProviderAccountConnector: (request) => rpc("testProviderAccountConnector", [request]) as ReturnType["testProviderAccountConnector"]>, - updateCheck: () => rpc("updateCheck") as ReturnType["updateCheck"]>, - updateDownload: () => rpc("updateDownload") as ReturnType["updateDownload"]>, - updateInstall: () => rpc("updateInstall") as ReturnType["updateInstall"]>, - waitBotGatewayQrLogin: (request) => rpc("waitBotGatewayQrLogin", [request]) as ReturnType["waitBotGatewayQrLogin"]> + startBotGatewayQrLogin: (request) => rpc("startBotGatewayQrLogin", [request]) as ReturnType, + startGateway: () => rpc("startGateway") as ReturnType, + stopGateway: () => rpc("stopGateway") as ReturnType, + stopProfile: (request) => rpc("stopProfile", [request]) as ReturnType, + testProviderAccountConnector: (request) => rpc("testProviderAccountConnector", [request]) as ReturnType, + updateCheck: () => rpc("updateCheck") as ReturnType, + updateDownload: () => rpc("updateDownload") as ReturnType, + updateInstall: () => rpc("updateInstall") as ReturnType, + waitBotGatewayQrLogin: (request) => rpc("waitBotGatewayQrLogin", [request]) as ReturnType }; + +if (!window.ccr) { + window.ccr = webClientBridge; +} diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json new file mode 100644 index 0000000..565e465 --- /dev/null +++ b/packages/ui/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src" + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts"] +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..79eb488 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,25 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + expect: { + timeout: 10_000 + }, + forbidOnly: !!process.env.CI, + fullyParallel: false, + projects: [ + { + name: "chromium", + use: { + ...devices["Desktop Chrome"] + } + } + ], + reporter: process.env.CI ? [["list"], ["html", { open: "never" }]] : "list", + testDir: "./tests/e2e", + timeout: 30_000, + use: { + actionTimeout: 10_000, + trace: "retain-on-failure" + }, + workers: 1 +}); diff --git a/scripts/generate-models-json.mjs b/scripts/generate-models-json.mjs index da17fad..de9b53c 100644 --- a/scripts/generate-models-json.mjs +++ b/scripts/generate-models-json.mjs @@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const projectRoot = path.resolve(__dirname, ".."); -const outputPath = path.join(projectRoot, "models.json"); +const outputPath = path.join(projectRoot, "packages", "core", "models.json"); const sources = { litellm: { diff --git a/tests/docker/docker-smoke.mjs b/tests/docker/docker-smoke.mjs new file mode 100644 index 0000000..5a60066 --- /dev/null +++ b/tests/docker/docker-smoke.mjs @@ -0,0 +1,267 @@ +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { randomUUID } from "node:crypto"; +import net from "node:net"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const projectRoot = path.resolve(__dirname, "..", ".."); +const imageName = process.env.CCR_DOCKER_TEST_IMAGE || "claude-code-router:local"; +const token = process.env.CCR_DOCKER_TEST_WEB_AUTH_TOKEN || "token+/&=#?"; +const testId = randomUUID().slice(0, 8); +const containerName = `ccr-docker-smoke-${testId}`; +const volumeName = `ccr-docker-smoke-${testId}`; +const startupTimeoutMs = 45_000; + +let containerStarted = false; +let volumeCreated = false; + +try { + if (process.env.CCR_DOCKER_TEST_SKIP_BUILD !== "1") { + run("docker", ["build", "-t", imageName, "."], { cwd: projectRoot }); + } + + const hostPort = await findAvailablePort(); + run("docker", ["volume", "create", volumeName]); + volumeCreated = true; + seedLegacyDockerConfig(volumeName); + + const containerId = run("docker", [ + "run", + "-d", + "--name", + containerName, + "-p", + `${hostPort}:8080`, + "-e", + `CCR_WEB_AUTH_TOKEN=${token}`, + "-e", + "CCR_PUBLIC_HOST=127.0.0.1", + "-e", + `CCR_PUBLIC_PORT=${hostPort}`, + "-v", + `${volumeName}:/data`, + imageName + ]).trim(); + containerStarted = true; + const baseUrl = `http://127.0.0.1:${hostPort}`; + + await waitFor(async () => { + const response = await fetch(baseUrl, { redirect: "manual" }).catch(() => undefined); + return response?.status === 302; + }, "Docker Nginx entrypoint"); + + const ports = dockerPorts(containerId); + assert.ok(ports["8080/tcp"], "Docker container must publish only the Nginx port"); + assert.equal(ports["3456/tcp"], undefined, "Docker container must not publish the internal gateway port"); + + const redirect = await fetch(baseUrl, { redirect: "manual" }); + assert.equal(redirect.status, 302); + const redirectLocation = redirect.headers.get("location"); + assert.equal(redirectLocation, `/pages/home/index.html?ccr_web_token=${encodeURIComponent(token)}`); + + const page = await fetch(`${baseUrl}${redirectLocation}`); + assert.equal(page.status, 200); + const pageHtml = await page.text(); + assert.match(pageHtml, /Claude Code Router/); + assert.match(pageHtml, /web-client-bridge\.js/); + + const bridge = await fetch(`${baseUrl}/assets/web-client-bridge.js`); + assert.equal(bridge.status, 200); + assert.match(await bridge.text(), /getProviderPresets/); + + await waitFor(async () => { + const response = await rpc(baseUrl, "getAppInfo", token).catch(() => undefined); + return response?.status === 200; + }, "Docker core management RPC"); + + const unauthorized = await rpc(baseUrl, "getAppInfo"); + assert.equal(unauthorized.status, 401); + assert.equal((await unauthorized.json()).ok, false); + + const appInfo = await rpc(baseUrl, "getAppInfo", token); + assert.equal(appInfo.status, 200); + const appInfoPayload = await appInfo.json(); + assert.equal(appInfoPayload.ok, true); + assert.equal(appInfoPayload.value.configDir, "/data/.claude-code-router"); + + const presets = await rpc(baseUrl, "getProviderPresets", token); + assert.equal(presets.status, 200); + const presetsPayload = await presets.json(); + assert.equal(presetsPayload.ok, true); + assert.ok(presetsPayload.value.some((preset) => preset.id === "openai")); + + const usageStatsWithNullFilter = await rpc(baseUrl, "getUsageStats", token, ["7d", null]); + assert.equal(usageStatsWithNullFilter.status, 200); + const usageStatsPayload = await usageStatsWithNullFilter.json(); + assert.equal(usageStatsPayload.ok, true); + assert.equal(usageStatsPayload.value.range, "7d"); + assert.doesNotMatch(run("docker", ["logs", containerId]), /Failed to read usage stats/); + + const config = await rpc(baseUrl, "getConfig", token); + assert.equal(config.status, 200); + const configPayload = await config.json(); + assert.equal(configPayload.ok, true); + assert.equal(configPayload.value.routerEndpoint, baseUrl); + assert.equal(configPayload.value.HOST, "127.0.0.1"); + assert.equal(configPayload.value.PORT, 3456); + assert.equal(configPayload.value.gateway.host, "127.0.0.1"); + assert.equal(configPayload.value.gateway.port, 3456); + + const gatewayHealth = await fetch(`${baseUrl}/health`); + assert.equal(gatewayHealth.status, 502, "Gateway health should be proxied by Nginx and fail while no models are configured"); + + const openRouterConfig = { + ...configPayload.value, + Providers: [ + { + apiKey: "test-openrouter-key", + baseUrl: "https://openrouter.ai/api/v1", + capabilities: [ + { baseUrl: "https://openrouter.ai/api/v1", source: "preset", type: "openai_chat_completions" }, + { baseUrl: "https://openrouter.ai/api/v1", source: "preset", type: "openai_responses" }, + { baseUrl: "https://openrouter.ai/api", endpoint: "https://openrouter.ai/api/v1/messages", source: "detected", type: "anthropic_messages" } + ], + models: ["qwen/qwen3.5-122b-a10b"], + name: "OpenRouter", + type: "openai_responses" + } + ], + preferredProvider: "OpenRouter" + }; + const savedConfig = await rpc(baseUrl, "saveConfig", token, [openRouterConfig, { applyProfile: false }]); + assert.equal(savedConfig.status, 200); + assert.equal((await savedConfig.json()).ok, true); + + const startedGateway = await rpc(baseUrl, "startGateway", token); + assert.equal(startedGateway.status, 200); + const startedGatewayPayload = await startedGateway.json(); + assert.equal(startedGatewayPayload.ok, true); + assert.equal(startedGatewayPayload.value.state, "running"); + + const restartedGateway = await rpc(baseUrl, "startGateway", token); + assert.equal(restartedGateway.status, 200); + const restartedGatewayPayload = await restartedGateway.json(); + assert.equal(restartedGatewayPayload.ok, true); + assert.equal(restartedGatewayPayload.value.state, "running"); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + const restartedGatewayStatus = await rpc(baseUrl, "getGatewayStatus", token); + assert.equal(restartedGatewayStatus.status, 200); + const restartedGatewayStatusPayload = await restartedGatewayStatus.json(); + assert.equal(restartedGatewayStatusPayload.ok, true); + assert.equal(restartedGatewayStatusPayload.value.state, "running"); + + const runningGatewayHealth = await fetch(`${baseUrl}/health`); + assert.equal(runningGatewayHealth.status, 200); + assert.equal((await runningGatewayHealth.json()).status, "running"); + + console.log(`Docker smoke test passed for ${imageName} on ${baseUrl}`); +} finally { + if (containerStarted) { + run("docker", ["rm", "-f", containerName], { allowFailure: true }); + } + if (volumeCreated) { + run("docker", ["volume", "rm", "-f", volumeName], { allowFailure: true }); + } +} + +function seedLegacyDockerConfig(volume) { + const script = ` +const fs = require("node:fs"); +const path = require("node:path"); +const Database = require("better-sqlite3"); +const configDir = "/data/.claude-code-router"; +const config = { + HOST: "0.0.0.0", + PORT: 3456, + Providers: [], + gateway: { + coreHost: "127.0.0.1", + corePort: 3457, + enabled: true, + host: "0.0.0.0", + port: 3456 + }, + routerEndpoint: "http://127.0.0.1:3456" +}; +fs.mkdirSync(configDir, { recursive: true, mode: 0o700 }); +fs.mkdirSync(path.join(configDir, "app-data"), { recursive: true, mode: 0o700 }); +fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify(config, null, 2) + "\\n", { mode: 0o600 }); +const db = new Database(path.join(configDir, "config.sqlite")); +db.exec("create table app_config (key TEXT PRIMARY KEY, value_json TEXT NOT NULL, updated_at TEXT NOT NULL)"); +db.prepare("insert into app_config (key, value_json, updated_at) values (?, ?, ?)").run("default", JSON.stringify(config), new Date().toISOString()); +db.close(); +`; + run("docker", ["run", "--rm", "-v", `${volume}:/data`, "--entrypoint", "node", imageName, "-e", script]); +} + +async function rpc(baseUrl, method, authToken, args = []) { + return fetch(`${baseUrl}/api/ccr/rpc`, { + body: JSON.stringify({ args, method }), + headers: { + "content-type": "application/json", + ...(authToken ? { "x-ccr-web-auth": authToken } : {}) + }, + method: "POST" + }); +} + +function dockerPorts(containerId) { + const raw = run("docker", ["inspect", "--format", "{{json .NetworkSettings.Ports}}", containerId]); + return JSON.parse(raw); +} + +async function waitFor(check, label) { + const startedAt = Date.now(); + let lastError; + while (Date.now() - startedAt < startupTimeoutMs) { + try { + if (await check()) { + return; + } + } catch (error) { + lastError = error; + } + await new Promise((resolve) => setTimeout(resolve, 500)); + } + const logs = containerStarted + ? run("docker", ["logs", containerName], { allowFailure: true }) + : ""; + throw new Error(`${label} was not ready within ${startupTimeoutMs}ms.${lastError ? ` Last error: ${lastError}` : ""}\n${logs}`); +} + +function findAvailablePort() { + return new Promise((resolve, reject) => { + const server = net.createServer(); + server.on("error", reject); + server.listen(0, "127.0.0.1", () => { + const address = server.address(); + const port = typeof address === "object" && address ? address.port : undefined; + server.close(() => { + if (!port) { + reject(new Error("Failed to allocate a Docker test port.")); + return; + } + resolve(port); + }); + }); + }); +} + +function run(command, args, options = {}) { + const result = spawnSync(command, args, { + cwd: options.cwd ?? projectRoot, + encoding: "utf8", + stdio: options.stdio ?? "pipe" + }); + if (result.status !== 0 && !options.allowFailure) { + throw new Error([ + `${command} ${args.join(" ")} failed with code ${result.status ?? "unknown"}`, + result.stdout, + result.stderr + ].filter(Boolean).join("\n")); + } + return result.stdout.trim(); +} diff --git a/tests/e2e/cli-web.spec.ts b/tests/e2e/cli-web.spec.ts new file mode 100644 index 0000000..095a443 --- /dev/null +++ b/tests/e2e/cli-web.spec.ts @@ -0,0 +1,230 @@ +import { expect, test, type APIRequestContext } from "@playwright/test"; +import { spawn, type ChildProcessByStdio } from "node:child_process"; +import { mkdtempSync, rmSync } from "node:fs"; +import type { Readable } from "node:stream"; +import net from "node:net"; +import os from "node:os"; +import path from "node:path"; + +const projectRoot = path.resolve(__dirname, "..", ".."); +const cliPath = path.join(projectRoot, "packages", "cli", "dist", "main", "cli.js"); +const host = "127.0.0.1"; +const cliWebAuthToken = "playwright-cli-web-auth-token"; +const startupTimeoutMs = 10_000; +const shutdownTimeoutMs = 5_000; + +type CliWebRuntime = { + baseUrl: string; + child: CliWebChild; + testHome: string; + token: string; +}; + +type CliWebChild = ChildProcessByStdio; + +let runtime: CliWebRuntime | undefined; + +test.beforeAll(async () => { + runtime = await startCliWebServer(); +}); + +test.afterAll(async () => { + if (!runtime) { + return; + } + await stopCliWebServer(runtime.child); + rmSync(runtime.testHome, { force: true, recursive: true }); + runtime = undefined; +}); + +test("uses CCR_WEB_AUTH_TOKEN for CLI web authentication", async () => { + const current = requireRuntime(); + expect(current.token).toBe(cliWebAuthToken); +}); + +test("serves the management UI in a browser", async ({ page }) => { + const current = requireRuntime(); + await page.goto(`${current.baseUrl}/?ccr_web_token=${current.token}`); + await expect(page).toHaveTitle("Claude Code Router"); + await expect(page.locator("#root")).toBeAttached(); + await expect(page.locator("body")).toContainText(/Configure provider|Connect agent|Let's start/, { timeout: 15_000 }); + await expect(page.evaluate(() => Boolean(window.ccr?.getAppInfo))).resolves.toBe(true); + await expect(page.evaluate(async () => { + const presets = await window.ccr?.getProviderPresets?.(); + return presets?.map((preset) => preset.id) ?? []; + })).resolves.toContain("openai"); + + await page.getByRole("button", { name: /Select preset provider|选择 预设供应商/ }).first().click(); + await expect(page.getByRole("option", { name: "OpenAI" })).toBeVisible(); +}); + +test("serves static assets used by the web UI", async ({ request }) => { + const current = requireRuntime(); + await expectStaticAsset(request, current.baseUrl, "/assets/main.js", "text/javascript"); + await expectStaticAsset(request, current.baseUrl, "/assets/main.css", "text/css"); + await expectStaticAsset(request, current.baseUrl, "/assets/web-client-bridge.js", "text/javascript"); +}); + +test("handles authenticated web RPC requests", async ({ request }) => { + const current = requireRuntime(); + const response = await request.post(`${current.baseUrl}/api/ccr/rpc`, { + data: { args: [], method: "getAppInfo" }, + headers: { + "x-ccr-web-auth": current.token + } + }); + const payload = await response.json(); + + expect(response.status()).toBe(200); + expect(payload.ok).toBe(true); + expect(payload.value.name).toBe("Claude Code Router"); + expect(payload.value.configDir).toContain(current.testHome); + expect(payload.value.appConfigDbFile).toContain("config.sqlite"); + expect(payload.value.usageDbFile).toContain("usage.sqlite"); +}); + +test("rejects RPC requests without the web auth token", async ({ request }) => { + const current = requireRuntime(); + const response = await request.post(`${current.baseUrl}/api/ccr/rpc`, { + data: { args: [], method: "getAppInfo" } + }); + const payload = await response.json(); + + expect(response.status()).toBe(401); + expect(payload.ok).toBe(false); +}); + +async function expectStaticAsset( + request: APIRequestContext, + baseUrl: string, + pathname: string, + expectedContentType: string +) { + const response = await request.head(`${baseUrl}${pathname}`); + expect(response.status()).toBe(200); + expect(response.headers()["content-type"]).toContain(expectedContentType); + expect(Number(response.headers()["content-length"] ?? "0")).toBeGreaterThan(0); +} + +async function startCliWebServer(): Promise { + const port = await findAvailablePort(); + const testHome = mkdtempSync(path.join(os.tmpdir(), "ccr-playwright-home-")); + const child = spawn(process.execPath, [ + cliPath, + "serve", + "--no-gateway", + "--host", + host, + "--port", + String(port), + "--no-open" + ], { + cwd: projectRoot, + env: { + ...process.env, + CCR_INTERNAL_APP_DATA_DIR: path.join(testHome, "app-data"), + CCR_INTERNAL_HOME_DIR: testHome, + CCR_INTERNAL_USER_DATA_DIR: path.join(testHome, "user-data"), + CCR_WEB_AUTH_TOKEN: cliWebAuthToken, + HOME: testHome + }, + stdio: ["ignore", "pipe", "pipe"] + }); + + let stdout = ""; + let stderr = ""; + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (chunk) => { + stdout += chunk; + }); + child.stderr.on("data", (chunk) => { + stderr += chunk; + }); + + const service = await new Promise<{ baseUrl: string; token: string }>((resolve, reject) => { + const timer = setTimeout(() => { + reject(new Error(`CCR web service did not start within ${startupTimeoutMs}ms.\nstdout:\n${stdout}\nstderr:\n${stderr}`)); + }, startupTimeoutMs); + + const cleanup = () => { + clearTimeout(timer); + child.stdout.off("data", onStdout); + child.off("exit", onExit); + }; + const onExit = (code: number | null, signal: NodeJS.Signals | null) => { + cleanup(); + reject(new Error(`CCR web service exited during startup code=${code ?? "null"} signal=${signal ?? "null"}.\nstdout:\n${stdout}\nstderr:\n${stderr}`)); + }; + const onStdout = () => { + const match = stdout.match(/CCR web management is running at (http:\/\/[^\s]+)/); + if (!match) { + return; + } + cleanup(); + const url = new URL(match[1]); + resolve({ + baseUrl: `${url.protocol}//${url.host}`, + token: url.searchParams.get("ccr_web_token") ?? "" + }); + }; + + child.on("exit", onExit); + child.stdout.on("data", onStdout); + }); + + if (!service.token) { + await stopCliWebServer(child); + rmSync(testHome, { force: true, recursive: true }); + throw new Error("CCR web service started without a web auth token."); + } + + return { + ...service, + child, + testHome + }; +} + +async function stopCliWebServer(child: CliWebChild): Promise { + if (child.exitCode !== null) { + return; + } + + child.kill("SIGINT"); + await new Promise((resolve) => { + const timer = setTimeout(() => { + child.kill("SIGKILL"); + resolve(); + }, shutdownTimeoutMs); + child.once("exit", () => { + clearTimeout(timer); + resolve(); + }); + }); +} + +async function findAvailablePort(): Promise { + return new Promise((resolve, reject) => { + const server = net.createServer(); + server.on("error", reject); + server.listen(0, host, () => { + const address = server.address(); + const port = typeof address === "object" && address ? address.port : undefined; + server.close(() => { + if (!port) { + reject(new Error("Failed to allocate a local test port.")); + return; + } + resolve(port); + }); + }); + }); +} + +function requireRuntime(): CliWebRuntime { + if (!runtime) { + throw new Error("CLI web runtime was not started."); + } + return runtime; +} diff --git a/tests/main/app-paths.test.mjs b/tests/main/app-paths.test.mjs new file mode 100644 index 0000000..54e4b7e --- /dev/null +++ b/tests/main/app-paths.test.mjs @@ -0,0 +1,59 @@ +import assert from "node:assert/strict"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { APP_STORAGE_NAME, resolveRuntimeConfigDir, resolveRuntimeDataDir } from "../../packages/core/src/runtime/app-paths.ts"; + +test("runtime config and data dirs default to the shared CCR storage", () => { + withRuntimePathEnv({ + appData: path.join(os.tmpdir(), "ccr-app-paths-app-data"), + home: path.join(os.tmpdir(), "ccr-app-paths-home") + }, () => { + const configDir = resolveRuntimeConfigDir(); + const expectedConfigDir = process.platform === "win32" + ? path.join(os.tmpdir(), "ccr-app-paths-app-data", APP_STORAGE_NAME) + : path.join(os.tmpdir(), "ccr-app-paths-home", `.${APP_STORAGE_NAME}`); + const expectedDataDir = process.platform === "win32" + ? expectedConfigDir + : path.join(expectedConfigDir, "app-data"); + + assert.equal(configDir, expectedConfigDir); + assert.equal(resolveRuntimeDataDir(), expectedDataDir); + }); +}); + +test("runtime data dir still allows explicit test and deployment overrides", () => { + withRuntimePathEnv({ + appData: path.join(os.tmpdir(), "ccr-app-paths-override-app-data"), + home: path.join(os.tmpdir(), "ccr-app-paths-override-home"), + userData: path.join(os.tmpdir(), "ccr-app-paths-override-user-data") + }, () => { + assert.equal(resolveRuntimeDataDir(), path.join(os.tmpdir(), "ccr-app-paths-override-user-data")); + }); +}); + +function withRuntimePathEnv(paths, run) { + const previous = { + appData: process.env.CCR_INTERNAL_APP_DATA_DIR, + home: process.env.CCR_INTERNAL_HOME_DIR, + userData: process.env.CCR_INTERNAL_USER_DATA_DIR + }; + try { + setOptionalEnv("CCR_INTERNAL_APP_DATA_DIR", paths.appData); + setOptionalEnv("CCR_INTERNAL_HOME_DIR", paths.home); + setOptionalEnv("CCR_INTERNAL_USER_DATA_DIR", paths.userData); + run(); + } finally { + setOptionalEnv("CCR_INTERNAL_APP_DATA_DIR", previous.appData); + setOptionalEnv("CCR_INTERNAL_HOME_DIR", previous.home); + setOptionalEnv("CCR_INTERNAL_USER_DATA_DIR", previous.userData); + } +} + +function setOptionalEnv(name, value) { + if (value === undefined) { + delete process.env[name]; + return; + } + process.env[name] = value; +} diff --git a/tests/main/bot-gateway-env.test.mjs b/tests/main/bot-gateway-env.test.mjs index a4e8acb..ad262f9 100644 --- a/tests/main/bot-gateway-env.test.mjs +++ b/tests/main/bot-gateway-env.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { botGatewayProfileEnv } from "../../src/main/bot-gateway-env.ts"; +import { botGatewayProfileEnv } from "../../packages/core/src/agents/bot-gateway/env.ts"; function botGateway(overrides = {}) { return { diff --git a/tests/main/claude-app-cdp.test.mjs b/tests/main/claude-app-cdp.test.mjs index d61252f..51decd4 100644 --- a/tests/main/claude-app-cdp.test.mjs +++ b/tests/main/claude-app-cdp.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { shouldEnableClaudeAppDesignCdp } from "../../src/main/claude-app-cdp.ts"; +import { shouldEnableClaudeAppDesignCdp } from "../../packages/core/src/agents/claude-app/cdp.ts"; test("Claude App Design CDP is opt-in even when Claude Design is configured", (t) => { const previous = process.env.CCR_CLAUDE_APP_DESIGN_CDP; diff --git a/tests/main/claude-app-launch.test.mjs b/tests/main/claude-app-launch.test.mjs index 8ad9ee6..47b9a0d 100644 --- a/tests/main/claude-app-launch.test.mjs +++ b/tests/main/claude-app-launch.test.mjs @@ -3,7 +3,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; -import { claudeAppLaunchCommand } from "../../src/main/claude-app-launch.ts"; +import { claudeAppLaunchCommand } from "../../packages/core/src/agents/claude-app/launch.ts"; test("claudeAppLaunchCommand opens macOS app bundles through LaunchServices", (t) => { const tempDir = mkdtempForTest(); diff --git a/tests/main/claude-environment.test.mjs b/tests/main/claude-environment.test.mjs index d5eb487..bcefbf8 100644 --- a/tests/main/claude-environment.test.mjs +++ b/tests/main/claude-environment.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { claudeCodeUtcTimezoneEnvOverride, isChinaTimeZone } from "../../src/main/claude-environment.ts"; +import { claudeCodeUtcTimezoneEnvOverride, isChinaTimeZone } from "../../packages/core/src/agents/claude-code/environment.ts"; test("detects China time zones used by Claude Code", () => { assert.equal(isChinaTimeZone("Asia/Shanghai"), true); diff --git a/tests/main/codex-app-model-catalog.test.mjs b/tests/main/codex-app-model-catalog.test.mjs index fae302d..7992a89 100644 --- a/tests/main/codex-app-model-catalog.test.mjs +++ b/tests/main/codex-app-model-catalog.test.mjs @@ -3,7 +3,7 @@ import { mkdtempSync, readFileSync, rmSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; -import { writeCodexCompatibleAppModelCatalog } from "../../src/main/codex-app-launch.ts"; +import { writeCodexCompatibleAppModelCatalog } from "../../packages/core/src/agents/codex/app-launch.ts"; test("Codex App model catalog write includes patch bridge capabilities", () => { const configDir = mkdtempSync(path.join(os.tmpdir(), "ccr-codex-app-catalog-")); diff --git a/tests/main/codex-cli-middleware-runtime.test.mjs b/tests/main/codex-cli-middleware-runtime.test.mjs index 4a06989..b8997b7 100644 --- a/tests/main/codex-cli-middleware-runtime.test.mjs +++ b/tests/main/codex-cli-middleware-runtime.test.mjs @@ -4,7 +4,7 @@ import { chmodSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; -import { codexCliMiddlewareRuntimeScript } from "../../src/main/codex-cli-middleware-runtime.ts"; +import { codexCliMiddlewareRuntimeScript } from "../../packages/core/src/agents/codex/cli-middleware-runtime.ts"; test("generated Codex CLI middleware runtime is valid JavaScript", () => { const dir = mkdtempSync(path.join(os.tmpdir(), "ccr-runtime-check-")); diff --git a/tests/main/codex-model-catalog.test.mjs b/tests/main/codex-model-catalog.test.mjs index 91e250c..1e68471 100644 --- a/tests/main/codex-model-catalog.test.mjs +++ b/tests/main/codex-model-catalog.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { buildCodexModelCatalog } from "../../src/main/codex-model-catalog.ts"; +import { buildCodexModelCatalog } from "../../packages/core/src/agents/codex/model-catalog.ts"; function catalogModelFor(config, slug) { const catalog = buildCodexModelCatalog(config, slug); diff --git a/tests/main/codex-patch-bridge.test.mjs b/tests/main/codex-patch-bridge.test.mjs index 0b71eb7..07879d7 100644 --- a/tests/main/codex-patch-bridge.test.mjs +++ b/tests/main/codex-patch-bridge.test.mjs @@ -4,7 +4,7 @@ import { prepareCodexApplyPatchBridgeRequest, transformCodexApplyPatchBridgeResponseValue, transformCodexApplyPatchBridgeSseEvent -} from "../../src/server/gateway/service.ts"; +} from "../../packages/core/src/gateway/service.ts"; const config = { Providers: [], diff --git a/tests/main/deep-link.test.mjs b/tests/main/deep-link.test.mjs index 95049ae..87e659f 100644 --- a/tests/main/deep-link.test.mjs +++ b/tests/main/deep-link.test.mjs @@ -6,7 +6,7 @@ import { parseProviderDeepLinkPayload, parseProviderManifestDeepLinkPayload, parseProviderManifestPayload -} from "../../src/shared/deep-link.ts"; +} from "../../packages/core/src/contracts/deep-link.ts"; function base64UrlJson(value) { return Buffer.from(JSON.stringify(value), "utf8") diff --git a/tests/main/gateway-virtual-models.test.mjs b/tests/main/gateway-virtual-models.test.mjs index 06e9895..aae1aa0 100644 --- a/tests/main/gateway-virtual-models.test.mjs +++ b/tests/main/gateway-virtual-models.test.mjs @@ -20,7 +20,7 @@ import { transformOpenAiResponsesHostedWebSearchResponseValue, transformOpenAiResponsesHostedWebSearchSseText, normalizeCoreGatewayVirtualModelProfiles -} from "../../src/server/gateway/service.ts"; +} from "../../packages/core/src/gateway/service.ts"; test("gateway config rewrites Fusion fixed base and vision models to core provider selectors", () => { const providerName = "Zhipu AI (China) - Coding Plan"; diff --git a/tests/main/local-agent-provider-codex.test.mjs b/tests/main/local-agent-provider-codex.test.mjs index e656f45..dd687fe 100644 --- a/tests/main/local-agent-provider-codex.test.mjs +++ b/tests/main/local-agent-provider-codex.test.mjs @@ -6,8 +6,8 @@ import { codexProviderAccountConfig, codexRateLimitResetCreditDetails, normalizeCodexProviderAccountConfig -} from "../../src/main/local-agent-providers/codex.ts"; -import { localAgentProviderApiKey } from "../../src/main/local-agent-providers/shared.ts"; +} from "../../packages/core/src/agents/local-providers/codex.ts"; +import { localAgentProviderApiKey } from "../../packages/core/src/agents/local-providers/shared.ts"; test("Codex provider account config includes manual reset meter", () => { const config = codexProviderAccountConfig(); diff --git a/tests/main/model-catalog-file.test.mjs b/tests/main/model-catalog-file.test.mjs index 4a012e7..26a0b16 100644 --- a/tests/main/model-catalog-file.test.mjs +++ b/tests/main/model-catalog-file.test.mjs @@ -3,7 +3,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import test from "node:test"; -import { loadModelCatalogPayload, modelCatalogPathCandidates } from "../../src/main/model-catalog-file.ts"; +import { loadModelCatalogPayload, modelCatalogPathCandidates } from "../../packages/core/src/models/catalog-file.ts"; test("modelCatalogPathCandidates prefers env paths and removes duplicates", () => { const previousCatalogPath = process.env.CCR_MODEL_CATALOG_PATH; diff --git a/tests/main/profile-launch-core.test.mjs b/tests/main/profile-launch-core.test.mjs index 1401e58..38623ba 100644 --- a/tests/main/profile-launch-core.test.mjs +++ b/tests/main/profile-launch-core.test.mjs @@ -11,7 +11,7 @@ import { resolveClaudeCodeSettingsFile, resolveCodexConfigFile, resolveProfileOpenSurface -} from "../../src/main/profile-launch-core.ts"; +} from "../../packages/core/src/profiles/launch-core.ts"; const claudeProfile = { agent: "claude-code", diff --git a/tests/main/profile-service.test.mjs b/tests/main/profile-service.test.mjs index fde0b40..477ba46 100644 --- a/tests/main/profile-service.test.mjs +++ b/tests/main/profile-service.test.mjs @@ -3,9 +3,9 @@ import { existsSync, mkdtempSync, mkdirSync, readdirSync, readFileSync, rmSync, import os from "node:os"; import path from "node:path"; import test from "node:test"; -import { createDefaultAppConfig } from "../../src/shared/default-config.ts"; -import { CONFIGDIR } from "../../src/main/constants.ts"; -import { applyProfileConfig, cleanupGeneratedBinBackups, restoreInactiveGlobalProfileConfigs } from "../../src/main/profile-service.ts"; +import { createDefaultAppConfig } from "../../packages/core/src/config/default-config.ts"; +import { CONFIGDIR } from "../../packages/core/src/config/constants.ts"; +import { applyProfileConfig, cleanupGeneratedBinBackups, restoreInactiveGlobalProfileConfigs } from "../../packages/core/src/profiles/service.ts"; test("profile service cleans stale generated bin backups only", () => { const configDir = mkdtempSync(path.join(os.tmpdir(), "ccr-generated-bin-cleanup-")); diff --git a/tests/main/provider-preset-utils.test.mjs b/tests/main/provider-preset-utils.test.mjs index 3aea090..a131d2e 100644 --- a/tests/main/provider-preset-utils.test.mjs +++ b/tests/main/provider-preset-utils.test.mjs @@ -7,7 +7,7 @@ import { providerEndpointCanReceiveProviderApiKeyInList, providerIdentitySafetyIssueInList, providerPresetMatchesBaseUrl -} from "../../src/shared/provider-preset-utils.ts"; +} from "../../packages/core/src/providers/presets/utils.ts"; const openAiPreset = { aliases: ["OpenAI", "ChatGPT"], @@ -28,8 +28,16 @@ const anthropicPreset = { const presets = [openAiPreset, anthropicPreset]; test("provider preset matching accepts endpoint subpaths but rejects different hosts", () => { + const openRouterPreset = { + aliases: ["openrouter"], + endpoints: [{ baseUrl: "https://openrouter.ai/api/v1", protocols: ["openai_chat_completions"] }], + id: "openrouter", + name: "OpenRouter" + }; + assert.equal(providerPresetMatchesBaseUrl(openAiPreset, "https://api.openai.com/v1/chat/completions"), true); assert.equal(providerPresetMatchesBaseUrl(openAiPreset, "https://api.openai.com"), true); + assert.equal(providerPresetMatchesBaseUrl(openRouterPreset, "https://openrouter.ai/api"), true); assert.equal(providerPresetMatchesBaseUrl(openAiPreset, "https://proxy.example.com/v1"), false); assert.equal(findProviderPresetByBaseUrlInList(presets, "api.anthropic.com/v1/messages")?.id, "anthropic"); }); diff --git a/tests/main/provider-probe.test.mjs b/tests/main/provider-probe.test.mjs index 676db01..fdd0894 100644 --- a/tests/main/provider-probe.test.mjs +++ b/tests/main/provider-probe.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { isProviderProtocolEndpointSupportedForProbe } from "../../src/main/provider-probe.ts"; +import { isProviderProtocolEndpointSupportedForProbe } from "../../packages/core/src/providers/probe.ts"; test("protocol support probe does not treat Gemini auth errors as every protocol", () => { const message = "HTTP 403: API key not valid. Please pass a valid API key."; diff --git a/tests/main/provider-url.test.mjs b/tests/main/provider-url.test.mjs index f1e4d46..6270779 100644 --- a/tests/main/provider-url.test.mjs +++ b/tests/main/provider-url.test.mjs @@ -5,7 +5,7 @@ import { parseProviderBaseUrl, providerBaseUrlForProtocol, providerUrlWithDefaultScheme -} from "../../src/shared/provider-url.ts"; +} from "../../packages/core/src/providers/url.ts"; test("provider URL parsing strips endpoint paths and unsafe URL parts", () => { const parsed = parseProviderBaseUrl("https://user:secret@api.example.com/v1/chat/completions?token=secret#section"); diff --git a/tests/main/request-log-store.test.mjs b/tests/main/request-log-store.test.mjs index 901de11..d5b5895 100644 --- a/tests/main/request-log-store.test.mjs +++ b/tests/main/request-log-store.test.mjs @@ -3,7 +3,7 @@ import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import test from "node:test"; -import { RequestLogStore } from "../../src/main/request-log-store.ts"; +import { RequestLogStore } from "../../packages/core/src/observability/request-log-store.ts"; test("RequestLogStore keeps list rows lightweight and detail rows complete", async () => { const dir = mkdtempSync(path.join(tmpdir(), "ccr-request-log-test-")); diff --git a/tests/main/router-builtins.test.mjs b/tests/main/router-builtins.test.mjs index 095b842..ac29abe 100644 --- a/tests/main/router-builtins.test.mjs +++ b/tests/main/router-builtins.test.mjs @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { ClaudeCodeRouterPlugin } from "../../src/server/gateway/claude-code-router-plugin.ts"; -import { prepareGatewayUpstreamAttemptForTest } from "../../src/server/gateway/service.ts"; +import { ClaudeCodeRouterPlugin } from "../../packages/core/src/gateway/claude-code-router-plugin.ts"; +import { prepareGatewayUpstreamAttemptForTest } from "../../packages/core/src/gateway/service.ts"; function createRouterPlugin(options = {}) { const agent = options.agent ?? "claude-code"; diff --git a/tests/main/usage-normalization.test.mjs b/tests/main/usage-normalization.test.mjs index 0876d04..4da80e9 100644 --- a/tests/main/usage-normalization.test.mjs +++ b/tests/main/usage-normalization.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { normalizeUsageInputTokens } from "../../src/main/usage-normalization.ts"; +import { normalizeUsageInputTokens } from "../../packages/core/src/usage/normalization.ts"; test("normalizeUsageInputTokens subtracts cache tokens for OpenAI-compatible protocols", () => { const usage = normalizeUsageInputTokens( diff --git a/tests/main/usage-store.test.mjs b/tests/main/usage-store.test.mjs index ce8d20c..48c3890 100644 --- a/tests/main/usage-store.test.mjs +++ b/tests/main/usage-store.test.mjs @@ -3,7 +3,7 @@ import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import test from "node:test"; -import { UsageStore } from "../../src/main/usage-store.ts"; +import { UsageStore } from "../../packages/core/src/usage/store.ts"; test("UsageStore aggregates stats in SQLite without loading all events", async () => { const dir = mkdtempSync(path.join(tmpdir(), "ccr-usage-test-")); @@ -103,3 +103,35 @@ test("UsageStore excludes proxy rows by default and includes them on request", a rmSync(dir, { force: true, recursive: true }); } }); + +test("UsageStore treats null web RPC usage filters as empty filters", async () => { + const dir = mkdtempSync(path.join(tmpdir(), "ccr-usage-null-filter-test-")); + try { + const store = new UsageStore(path.join(dir, "usage.sqlite")); + + await store.record({ + createdAt: new Date().toISOString(), + durationMs: 10, + method: "POST", + model: "alpha-model", + path: "/v1/messages", + provider: "alpha", + requestId: "req-null-filter", + statusCode: 200, + usage: { + inputTokens: 3, + outputTokens: 4 + } + }); + + const stats = await store.getStats("7d", null); + assert.equal(stats.range, "7d"); + assert.equal(stats.totals.requestCount, 1); + + const defaultRangeStats = await store.getStats(null, null); + assert.equal(defaultRangeStats.range, "7d"); + assert.equal(defaultRangeStats.totals.requestCount, 1); + } finally { + rmSync(dir, { force: true, recursive: true }); + } +}); diff --git a/tests/main/web-client-bridge-html.test.mjs b/tests/main/web-client-bridge-html.test.mjs new file mode 100644 index 0000000..1a9295d --- /dev/null +++ b/tests/main/web-client-bridge-html.test.mjs @@ -0,0 +1,13 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import path from "node:path"; +import test from "node:test"; + +test("home renderer HTML loads the web client bridge before the app bundle", () => { + const projectRoot = process.cwd(); + const buildConfig = readFileSync(path.join(projectRoot, "build", "esbuild.config.mjs"), "utf8"); + const webBridge = readFileSync(path.join(projectRoot, "packages", "ui", "src", "web-client-bridge.ts"), "utf8"); + + assert.match(buildConfig, /beforeModuleScriptTags:\s*\[\s*' + + diff --git a/extension/chrome/popup.js b/extension/chrome/popup.js new file mode 100644 index 0000000..4b12be1 --- /dev/null +++ b/extension/chrome/popup.js @@ -0,0 +1,297 @@ +const importUrlInput = document.getElementById("import-url"); +const importButton = document.getElementById("import-button"); +const statusElement = document.getElementById("status"); + +document.addEventListener("DOMContentLoaded", () => { + void restoreLastImportUrl(); +}); + +importButton.addEventListener("click", () => { + void runImport(); +}); + +async function restoreLastImportUrl() { + const stored = await chrome.storage.local.get(["lastImportUrl"]); + if (typeof stored.lastImportUrl === "string") { + importUrlInput.value = stored.lastImportUrl; + } +} + +async function runImport() { + const importUrl = normalizeImportUrl(importUrlInput.value); + if (!importUrl) { + setStatus("Paste the import URL copied from CCR.", "error"); + return; + } + + importButton.disabled = true; + try { + await chrome.storage.local.set({ lastImportUrl: importUrl }); + setStatus("Reading CCR import job..."); + const job = await fetchImportJob(importUrl); + const domains = Array.isArray(job.domains) ? job.domains.map(normalizeDomain).filter(Boolean) : []; + if (domains.length === 0) { + throw new Error("CCR import job does not include any domains."); + } + + await ensureHostPermissions(domains); + + setStatus(`Reading cookies for ${domains.join(", ")}...`); + const cookies = await readCookiesForDomains(domains); + setStatus(`Reading localStorage for ${domains.join(", ")}...`); + const localStorageEntries = await readLocalStorageForDomains(domains, cookies); + + if (cookies.length === 0 && localStorageEntries.length === 0) { + throw new Error("No cookies or localStorage entries were found for the selected domains."); + } + + setStatus(`Sending ${cookies.length} cookies and ${localStorageItemCount(localStorageEntries)} localStorage items to CCR...`); + const result = await submitCookies(importUrl, cookies, localStorageEntries, domains); + setStatus( + `Imported ${result.cookieImported ?? 0} cookies and ${result.localStorageImported ?? 0} localStorage items. Skipped ${result.skipped ?? 0}.`, + "ok" + ); + } catch (error) { + setStatus(formatError(error), "error"); + } finally { + importButton.disabled = false; + } +} + +async function fetchImportJob(importUrl) { + const response = await fetch(importUrl, { + headers: { + "x-ccr-login-import": "chrome-extension" + } + }); + const body = await response.json().catch(() => ({})); + if (!response.ok) { + throw new Error(body?.error?.message || `CCR import job request failed (${response.status}).`); + } + if (!body.job || body.job.status !== "pending") { + throw new Error(`CCR import job is ${body.job?.status || "unavailable"}.`); + } + return body.job; +} + +async function submitCookies(importUrl, cookies, localStorage, domains) { + const response = await fetch(`${importUrl.replace(/\/+$/, "")}/cookies`, { + body: JSON.stringify({ + cookies, + domains, + localStorage, + source: { + browser: "chrome", + extension: chrome.runtime.getManifest().version + } + }), + headers: { + "content-type": "application/json", + "x-ccr-login-import": "chrome-extension" + }, + method: "POST" + }); + const body = await response.json().catch(() => ({})); + if (!response.ok) { + throw new Error(body?.error?.message || `CCR cookie import failed (${response.status}).`); + } + return body.result || {}; +} + +async function readCookiesForDomains(domains) { + const cookies = []; + for (const domain of domains) { + cookies.push(...await chrome.cookies.getAll({ domain })); + } + return dedupeCookies(cookies); +} + +async function readLocalStorageForDomains(domains, cookies) { + const origins = localStorageOriginsForDomains(domains, cookies); + const entries = []; + for (const origin of origins) { + const entry = await readLocalStorageForOrigin(origin).catch(() => undefined); + if (entry && Object.keys(entry.items).length > 0) { + entries.push(entry); + } + } + return entries; +} + +async function readLocalStorageForOrigin(origin) { + const tab = await chrome.tabs.create({ + active: false, + url: `${origin}/` + }); + try { + await waitForTabLoad(tab.id, 15000); + const [frameResult] = await chrome.scripting.executeScript({ + func: () => { + const items = {}; + for (let index = 0; index < window.localStorage.length; index += 1) { + const key = window.localStorage.key(index); + if (key !== null) { + items[key] = window.localStorage.getItem(key) || ""; + } + } + return { + items, + origin: window.location.origin + }; + }, + target: { tabId: tab.id }, + world: "MAIN" + }); + const result = frameResult?.result; + if (!result || !allowedLocalStorageOrigin(result.origin, origin)) { + return undefined; + } + return result; + } finally { + if (tab.id !== undefined) { + await chrome.tabs.remove(tab.id).catch(() => undefined); + } + } +} + +function waitForTabLoad(tabId, timeoutMs) { + return new Promise((resolve) => { + let done = false; + const finish = () => { + if (done) { + return; + } + done = true; + chrome.tabs.onUpdated.removeListener(listener); + window.clearTimeout(timeout); + resolve(); + }; + const listener = (updatedTabId, changeInfo) => { + if (updatedTabId === tabId && changeInfo.status === "complete") { + finish(); + } + }; + const timeout = window.setTimeout(finish, timeoutMs); + chrome.tabs.onUpdated.addListener(listener); + }); +} + +function dedupeCookies(cookies) { + const seen = new Set(); + const result = []; + for (const cookie of cookies) { + const partitionKey = cookie.partitionKey ? JSON.stringify(cookie.partitionKey) : ""; + const key = `${cookie.storeId || ""}\n${cookie.domain}\n${cookie.path}\n${cookie.name}\n${partitionKey}`; + if (!seen.has(key)) { + seen.add(key); + result.push(cookie); + } + } + return result; +} + +function localStorageOriginsForDomains(domains, cookies) { + const origins = new Set(); + for (const domain of domains) { + if (domain === "localhost" || isIpAddress(domain)) { + origins.add(`http://${domain}`); + origins.add(`https://${domain}`); + } else { + origins.add(`https://${domain}`); + } + } + + for (const cookie of cookies) { + const host = normalizeDomain(cookie.domain); + if (!host || !cookie.hostOnly || !domains.some((domain) => host === domain || host.endsWith(`.${domain}`))) { + continue; + } + origins.add(`${cookie.secure === false ? "http" : "https"}://${host}`); + } + + return [...origins]; +} + +function allowedLocalStorageOrigin(actualOrigin, requestedOrigin) { + try { + const actual = new URL(actualOrigin); + const requested = new URL(requestedOrigin); + return actual.protocol === requested.protocol && actual.hostname === requested.hostname && actual.port === requested.port; + } catch { + return false; + } +} + +function localStorageItemCount(entries) { + return entries.reduce((count, entry) => count + Object.keys(entry.items || {}).length, 0); +} + +function originsForDomains(domains) { + return [...new Set(domains.flatMap((domain) => { + if (domain === "localhost" || isIpAddress(domain)) { + return [ + `http://${domain}/*`, + `https://${domain}/*` + ]; + } + return [ + `http://${domain}/*`, + `https://${domain}/*`, + `http://*.${domain}/*`, + `https://*.${domain}/*` + ]; + }))]; +} + +async function ensureHostPermissions(domains) { + const origins = originsForDomains(domains); + const granted = await chrome.permissions.contains({ origins }); + if (granted) { + return; + } + throw new Error( + [ + `CCR Login Import does not have Chrome site access for ${domains.join(", ")}.`, + "Reload the unpacked extension after updating it, then grant the extension site access for the requested domains in Chrome extensions settings." + ].join(" ") + ); +} + +function normalizeImportUrl(value) { + const raw = value.trim(); + if (!raw) { + return ""; + } + try { + const url = new URL(raw); + if (!["127.0.0.1", "localhost"].includes(url.hostname)) { + return ""; + } + if (!/^\/chrome-import\/jobs\/[^/]+\/?$/.test(url.pathname)) { + return ""; + } + url.pathname = url.pathname.replace(/\/+$/, ""); + return url.toString(); + } catch { + return ""; + } +} + +function normalizeDomain(value) { + return typeof value === "string" + ? value.trim().replace(/^\*\./, "").replace(/^\./, "").toLowerCase() + : ""; +} + +function isIpAddress(value) { + return /^\d{1,3}(?:\.\d{1,3}){3}$/.test(value) || value.includes(":"); +} + +function setStatus(message, kind = "") { + statusElement.textContent = message; + statusElement.className = `status ${kind}`.trim(); +} + +function formatError(error) { + return error instanceof Error ? error.message : String(error); +} diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 5e4e4bc..0cf78ab 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -700,6 +700,10 @@ function parseToolHub(value: unknown): Partial | undefined { if (typeof value.enabled === "boolean") { toolHub.enabled = value.enabled; } + const browserAutomation = value.browserAutomation ?? value.browser_automation; + if (typeof browserAutomation === "boolean") { + toolHub.browserAutomation = browserAutomation; + } const maxTools = readNumber(value.maxTools ?? value.max_tools); if (maxTools !== undefined) { toolHub.maxTools = clampNumber(maxTools, 1, 20); diff --git a/packages/core/src/config/default-config.ts b/packages/core/src/config/default-config.ts index 63933cb..58a3868 100644 --- a/packages/core/src/config/default-config.ts +++ b/packages/core/src/config/default-config.ts @@ -170,6 +170,7 @@ export function createDefaultAppConfig(options: DefaultAppConfigOptions): AppCon trayWidgets: DEFAULT_TRAY_WIDGETS, trayWindowModules: DEFAULT_TRAY_WINDOW_MODULES, toolHub: { + browserAutomation: false, enabled: false, llm: { apiKey: "", diff --git a/packages/core/src/contracts/app.ts b/packages/core/src/contracts/app.ts index 4c00d21..685a35e 100644 --- a/packages/core/src/contracts/app.ts +++ b/packages/core/src/contracts/app.ts @@ -642,6 +642,7 @@ export type ToolHubLlmConfig = { }; export type ToolHubConfig = { + browserAutomation: boolean; enabled: boolean; llm: ToolHubLlmConfig; mcpServers: GatewayMcpServerConfig[]; @@ -1468,12 +1469,91 @@ export type BuiltInBrowserTabState = { url: string; }; +export type BuiltInBrowserAutomationHandoffKind = + | "blocked" + | "human_verification" + | "login_required" + | "other" + | "verification_code"; + +export type BuiltInBrowserAutomationHandoff = { + id: string; + kind: BuiltInBrowserAutomationHandoffKind; + message: string; + reason?: string; + requestedAt: number; + sessionId?: string; + status: "pending"; + tabId?: string; +}; + export type BuiltInBrowserState = { activeTabId?: string; apps: InstalledBrowserApp[]; + automationHandoff?: BuiltInBrowserAutomationHandoff; tabs: BuiltInBrowserTabState[]; }; +export type ChromeLoginImportTarget = "browser" | "browser-and-web-search"; + +export type ChromeLoginImportStatus = + | "completed" + | "expired" + | "failed" + | "pending"; + +export type ChromeLoginImportRequest = { + domains: string[]; + openConfirmationPage?: boolean; + target?: ChromeLoginImportTarget; +}; + +export type ChromeLoginImportResult = { + completedAt: number; + cookieImported: number; + cookieSkipped: number; + domains: string[]; + errors?: string[]; + imported: number; + localStorageImported: number; + localStorageSkipped: number; + partitions: string[]; + skipped: number; +}; + +export type ChromeLoginImportJob = { + confirmUrl: string; + createdAt: number; + domains: string[]; + endpointUrl: string; + expiresAt: number; + id: string; + importUrl: string; + result?: ChromeLoginImportResult; + status: ChromeLoginImportStatus; + target: ChromeLoginImportTarget; +}; + +export type ChromeLoginImportCookie = { + domain: string; + expirationDate?: number; + hostOnly?: boolean; + httpOnly?: boolean; + name: string; + partitionKey?: unknown; + path?: string; + sameSite?: "lax" | "no_restriction" | "strict" | "unspecified"; + secure?: boolean; + session?: boolean; + storeId?: string; + value: string; +}; + +export type ChromeLoginImportLocalStorage = { + items: Record; + origin: string; +}; + export type ProxyCertificateInstallResult = { caCertFile: string; manualCommand?: string; diff --git a/packages/core/src/contracts/ipc-channels.ts b/packages/core/src/contracts/ipc-channels.ts index 103b0b0..f24d040 100644 --- a/packages/core/src/contracts/ipc-channels.ts +++ b/packages/core/src/contracts/ipc-channels.ts @@ -73,10 +73,13 @@ export const IPC_CHANNELS = { browserBack: "ccr:browser:back", browserCloseTab: "ccr:browser:close-tab", browserForward: "ccr:browser:forward", + browserGetChromeLoginImport: "ccr:browser:get-chrome-login-import", browserGetState: "ccr:browser:get-state", browserNavigate: "ccr:browser:navigate", browserNewTab: "ccr:browser:new-tab", browserReload: "ccr:browser:reload", + browserResolveAutomationHandoff: "ccr:browser:resolve-automation-handoff", browserSelectTab: "ccr:browser:select-tab", + browserStartChromeLoginImport: "ccr:browser:start-chrome-login-import", browserStateChanged: "ccr:browser:state-changed" } as const; diff --git a/packages/core/src/gateway/claude-code-router-plugin.ts b/packages/core/src/gateway/claude-code-router-plugin.ts index b42ea83..c39b3f9 100644 --- a/packages/core/src/gateway/claude-code-router-plugin.ts +++ b/packages/core/src/gateway/claude-code-router-plugin.ts @@ -365,9 +365,11 @@ function injectClaudeCodeToolHubInstructions(body: Record, conf const invokeName = toolNames.invoke ?? "tool_hub.invoke"; appendSystemInstruction(body, [ ccrToolHubSystemInstructionMarker, - `You MUST call ${toolNames.resolve} before answering any request that asks about external services, installed MCP capabilities, business APIs, orders, coupons, stores, accounts, available tools, or capabilities that are not already obvious from the eager tools.`, - `Do this even if the user did not mention ToolHub or ${toolNames.resolve}. Only skip ${toolNames.resolve} when the request is clearly local code/file/shell work or simple conversation that does not need an external or MCP capability.`, - `If ${toolNames.resolve} returns selected tools, use ${invokeName} to call the selected tool instead of telling the user that no such capability is available.`, + `The ToolHub search/resolution tool is ${toolNames.resolve}; call this actual tool, do not merely mention its name in text.`, + `You MUST call the ToolHub search/resolution tool ${toolNames.resolve} before answering any request that asks about external services, installed MCP capabilities, business APIs, orders, coupons, stores, accounts, available tools, or capabilities that are not already obvious from the eager tools.`, + `Do this even if the user did not mention ToolHub or ${toolNames.resolve}. Only skip the ToolHub search/resolution tool when the request is clearly local code/file/shell work or simple conversation that does not need an external or MCP capability.`, + `If ${toolNames.resolve} returns selected tools, call the ToolHub invocation tool ${invokeName} to run the selected tool instead of telling the user that no such capability is available.`, + "When the ToolHub resolve result includes executionPlanJs or workflowSketch, treat that JavaScript as the invocation dependency plan: await means serial order, and only callTool calls grouped inside the same Promise.all may be issued in parallel.", "Use the user's request as the task and include concise context when resolving. Do not ask the user to name the MCP tool unless the task is genuinely ambiguous after resolution." ].join("\n")); } @@ -380,16 +382,40 @@ function claudeCodeToolHubToolNames(tools: unknown[]): { invoke?: string; resolv } const name = claudeCodeToolName(tool); const normalized = normalizeClaudeCodeToolName(name); - if (!names.resolve && normalized.endsWith("toolhubresolve")) { + if (normalized.endsWith("toolhubresolve") && shouldUseClaudeCodeToolHubName(names.resolve, name)) { names.resolve = name; } - if (!names.invoke && normalized.endsWith("toolhubinvoke")) { + if (normalized.endsWith("toolhubinvoke") && shouldUseClaudeCodeToolHubName(names.invoke, name)) { names.invoke = name; } } return names; } +function shouldUseClaudeCodeToolHubName(current: string | undefined, candidate: string | undefined): boolean { + if (!candidate) { + return false; + } + if (!current) { + return true; + } + return claudeCodeToolHubNameScore(candidate) > claudeCodeToolHubNameScore(current); +} + +function claudeCodeToolHubNameScore(name: string): number { + const normalized = name.toLowerCase(); + if (normalized.startsWith("mcp__ccr-toolhub__") || normalized.startsWith("mcp__ccr_toolhub__")) { + return 3; + } + if (normalized.startsWith("mcp__") && normalized.includes("toolhub")) { + return 2; + } + if (normalized.startsWith("mcp__")) { + return 1; + } + return 0; +} + function appendSystemInstruction(body: Record, instruction: string): void { if (systemContainsInstruction(body.system, ccrToolHubSystemInstructionMarker)) { return; diff --git a/packages/core/src/gateway/service.ts b/packages/core/src/gateway/service.ts index a10ff09..ef274df 100644 --- a/packages/core/src/gateway/service.ts +++ b/packages/core/src/gateway/service.ts @@ -45,7 +45,7 @@ import { loadPersistedApiKeys } from "@ccr/core/config/api-key-store"; import { codexDefaultBaseUrl, readCodexAuth } from "@ccr/core/agents/local-providers/service"; import { fetchWithSystemProxy, getSystemProxyUrlForProtocol } from "@ccr/core/proxy/system-proxy-fetch"; import { handleNetworkCaptureMcpRequest, isNetworkCaptureMcpPath } from "@ccr/core/mcp/network-capture-mcp"; -import { TOOL_HUB_MCP_SERVER_NAME, toolHubMcpRuntimeConfig } from "@ccr/core/mcp/toolhub-config"; +import { BROWSER_AUTOMATION_MCP_PATH, TOOL_HUB_MCP_SERVER_NAME, browserAutomationMcpEnabled, toolHubBuiltInBackendServers, toolHubMcpRuntimeConfig, toolHubRequestTimeoutMs } from "@ccr/core/mcp/toolhub-config"; import { pluginService } from "@ccr/core/plugins/service"; import { proxyService } from "@ccr/core/proxy/service"; import { createSseErrorDetector, recordGatewayRequestLog, updateGatewayRequestLogFromRawTrace, type RequestLogRawTraceUpdateInput } from "@ccr/core/observability/request-log-store"; @@ -130,6 +130,7 @@ export type BrowserWebSearchMcpRegistration = { export type BrowserWebSearchProtocolResult = { content?: string; + diagnostics?: string[]; snippet?: string; title: string; url: string; @@ -151,6 +152,11 @@ export type BrowserWebSearchMcpIntegration = { stopBrowserWebSearchMcpServers: () => Promise; }; +export type BrowserAutomationMcpIntegration = { + handleBrowserAutomationMcpRequest: (request: IncomingMessage, response: ServerResponse) => Promise; + stopBrowserAutomationMcpServer: () => Promise; +}; + type CoreGatewayHealth = { runtimeId?: string; status?: string; @@ -312,6 +318,7 @@ const persistedApiKeyCacheTtlMs = 1000; let persistedApiKeyCache: { loadedAt: number; values: ApiKeyConfig[] } | undefined; class GatewayService { + private browserAutomationMcpIntegration?: BrowserAutomationMcpIntegration; private browserWebSearchMcpIntegration?: BrowserWebSearchMcpIntegration; private child?: ChildProcess; private config?: AppConfig; @@ -332,6 +339,10 @@ class GatewayService { this.browserWebSearchMcpIntegration = integration; } + setBrowserAutomationMcpIntegration(integration: BrowserAutomationMcpIntegration): void { + this.browserAutomationMcpIntegration = integration; + } + async start(config: AppConfig): Promise { const coreHostError = loopbackCoreHostError(config.gateway.coreHost); if (coreHostError) { @@ -439,6 +450,9 @@ class GatewayService { await this.browserWebSearchMcpIntegration?.stopBrowserWebSearchMcpServers().catch((error) => { console.warn(`[gateway] Failed to stop browser web search MCP: ${formatError(error)}`); }); + await this.browserAutomationMcpIntegration?.stopBrowserAutomationMcpServer().catch((error) => { + console.warn(`[gateway] Failed to stop browser automation MCP: ${formatError(error)}`); + }); this.status = { ...this.status, @@ -551,6 +565,31 @@ class GatewayService { return; } + if (path === BROWSER_AUTOMATION_MCP_PATH || path === `${BROWSER_AUTOMATION_MCP_PATH}/`) { + if (!browserAutomationMcpEnabled(this.config)) { + sendJson(response, 404, { + error: { + message: "CCR browser automation MCP is disabled." + } + }); + return; + } + const authorization = await authorize(request, response, this.config); + if (!authorization.ok) { + return; + } + if (!this.browserAutomationMcpIntegration) { + sendJson(response, 503, { + error: { + message: "CCR browser automation MCP is only available in the Electron desktop app." + } + }); + return; + } + await this.browserAutomationMcpIntegration.handleBrowserAutomationMcpRequest(request, response); + return; + } + if (isNetworkCaptureMcpPath(path)) { if (!this.config.proxy.captureNetwork) { sendJson(response, 404, { error: { message: "Network capture MCP is disabled." } }); @@ -819,6 +858,15 @@ class GatewayService { sinceMs: startedAt - 1_000 }); + if (hostedWebSearchProtocolContext && !this.browserWebSearchMcpIntegration) { + const message = browserWebSearchUnavailableMessage(hostedWebSearchProtocolContext.toolName); + const responseHeaders = new Headers({ "content-type": "application/json; charset=utf-8" }); + const responseBody = JSON.stringify({ error: { message } }); + writeRequestLog(503, responseHeaders, responseBody, false, message); + sendJson(response, 503, { error: { message } }); + return; + } + if (hostedWebSearchProtocolContext && this.browserWebSearchMcpIntegration) { const records = await selectHostedWebSearchProtocolRecords( hostedWebSearchProtocolContext, @@ -1546,16 +1594,20 @@ function bundledFusionToolFallbackMcpEntryPath(): string { function toolHubMcpServer(config: AppConfig, backendServers: unknown[]): GatewayMcpServerConfig | undefined { const toolHub = config.toolHub; - const runtimeConfig = toolHubMcpRuntimeConfig(config, backendServers); + const runtimeBackendServers = [ + ...toolHubBuiltInBackendServers(config), + ...backendServers + ]; + const runtimeConfig = toolHubMcpRuntimeConfig(config, runtimeBackendServers); if (!toolHub?.enabled || !runtimeConfig) { return undefined; } return { ...runtimeConfig, - name: uniqueMcpServerName(TOOL_HUB_MCP_SERVER_NAME, backendServers), + name: uniqueMcpServerName(TOOL_HUB_MCP_SERVER_NAME, runtimeBackendServers), protocolVersion: "2024-11-05", - requestTimeoutMs: Math.max(toolHub.requestTimeoutMs ?? 60000, 60000), + requestTimeoutMs: toolHubRequestTimeoutMs(config, runtimeBackendServers), startupTimeoutMs: 600000, stdioMessageMode: "content-length", transport: "stdio" @@ -1565,53 +1617,121 @@ function toolHubMcpServer(config: AppConfig, backendServers: unknown[]): Gateway export function fusionFallbackToolDefinitions( profiles: unknown[], backedToolNames: Set = new Set() -): Array<{ description?: string; inputSchema?: Record; name: string }> { - const byName = new Map; name: string }>(); +): FusionFallbackToolDefinition[] { + const byName = new Map(); for (const profile of profiles) { - if (!isRecord(profile) || profile.enabled === false || !Array.isArray(profile.tools)) { + if (!isRecord(profile) || profile.enabled === false) { continue; } - for (const tool of profile.tools) { - if (!isRecord(tool)) { - continue; - } - const name = stringValue(tool.name); - if (!name) { - continue; - } - if (backedToolNames.has(name)) { - continue; - } - const existing = byName.get(name); - const description = stringValue(tool.description); - const inputSchema = isRecord(tool.inputSchema) - ? tool.inputSchema - : isRecord(tool.input_schema) - ? tool.input_schema - : undefined; - if (existing) { - if (!existing.description && description) { - existing.description = description; + if (Array.isArray(profile.tools)) { + for (const tool of profile.tools) { + if (!isRecord(tool)) { + continue; } - if (!existing.inputSchema && inputSchema) { - existing.inputSchema = inputSchema; + const name = stringValue(tool.name); + if (!name) { + continue; + } + if (backedToolNames.has(name)) { + continue; } - continue; - } - byName.set(name, { - ...(description ? { description } : {}), - ...(inputSchema ? { inputSchema } : {}), - name - }); + const existing = byName.get(name); + const description = stringValue(tool.description); + const inputSchema = isRecord(tool.inputSchema) + ? tool.inputSchema + : isRecord(tool.input_schema) + ? tool.input_schema + : undefined; + const unavailableMessage = fusionFallbackToolUnavailableMessage(profile, name); + if (existing) { + if (!existing.description && description) { + existing.description = description; + } + if (!existing.inputSchema && inputSchema) { + existing.inputSchema = inputSchema; + } + if (!existing.unavailableMessage && unavailableMessage) { + existing.unavailableMessage = unavailableMessage; + } + continue; + } + + byName.set(name, { + ...(description ? { description } : {}), + ...(inputSchema ? { inputSchema } : {}), + ...(unavailableMessage ? { unavailableMessage } : {}), + name + }); + } + } + + const browserFallback = browserWebSearchFallbackToolDefinition(profile, backedToolNames); + if (browserFallback && !byName.has(browserFallback.name)) { + byName.set(browserFallback.name, browserFallback); } } return [...byName.values()]; } +type FusionFallbackToolDefinition = { + description?: string; + inputSchema?: Record; + name: string; + unavailableMessage?: string; +}; + +function fusionFallbackToolUnavailableMessage(profile: unknown, toolName: string): string | undefined { + if (!isRecord(profile)) { + return undefined; + } + const metadata = isRecord(profile.metadata) ? profile.metadata : undefined; + const fusionWebSearch = isRecord(metadata?.fusionWebSearch) ? metadata.fusionWebSearch : undefined; + const webSearchConfig = readFusionWebSearchConfig(fusionWebSearch); + if (webSearchConfig?.provider !== "browser" || webSearchConfig.toolName !== toolName) { + return undefined; + } + return browserWebSearchUnavailableMessage(toolName); +} + +function browserWebSearchUnavailableMessage(toolName: string): string { + return [ + `Fusion MCP tool "${toolName}" is unavailable because In-app Browser web search requires CCR Desktop.`, + "This runtime did not register the Electron browser web search integration, so the hidden browser search tool cannot run here.", + "Run the profile in CCR Desktop or switch the Fusion web search provider to Brave, Bing, Google CSE, Serper, SerpAPI, Tavily, or Exa." + ].join(" "); +} + +function browserWebSearchFallbackToolDefinition( + profile: Record, + backedToolNames: Set +): FusionFallbackToolDefinition | undefined { + const metadata = isRecord(profile.metadata) ? profile.metadata : undefined; + const fusionWebSearch = isRecord(metadata?.fusionWebSearch) ? metadata.fusionWebSearch : undefined; + const webSearchConfig = readFusionWebSearchConfig(fusionWebSearch); + if (webSearchConfig?.provider !== "browser" || !webSearchConfig.toolName || backedToolNames.has(webSearchConfig.toolName)) { + return undefined; + } + return { + description: "Fallback registration for CCR In-app Browser web search when the Electron browser integration is unavailable.", + inputSchema: { + additionalProperties: true, + properties: { + count: { maximum: 20, minimum: 1, type: "number" }, + prompt: { type: "string" }, + query: { type: "string" } + }, + required: ["prompt"], + type: "object" + }, + name: webSearchConfig.toolName, + unavailableMessage: fusionFallbackToolUnavailableMessage(profile, webSearchConfig.toolName) + }; +} + export function fusionToolNamesBackedByMcpServers(servers: unknown[]): Set { const names = new Set(); for (const server of servers) { @@ -3037,7 +3157,8 @@ function hostedWebSearchEvidenceText(records: BrowserWebSearchProtocolRecord[], const content = focusedWebSearchContent(result.content, queryHint); const details = [ result.snippet ? `Search snippet: ${result.snippet}` : "", - content ? `Extracted page content: ${content}` : "" + content ? `Extracted page content: ${content}` : "", + result.diagnostics?.length ? `Diagnostics: ${result.diagnostics.join("; ")}` : "" ].filter(Boolean).join("\n"); return [ `${resultIndex + 1}. ${result.title}`, @@ -5201,7 +5322,8 @@ function anthropicWebSearchResultBlock(result: BrowserWebSearchProtocolResult): function anthropicWebSearchResultSnippet(result: BrowserWebSearchProtocolResult): string | undefined { const parts = [ result.snippet ? `Search snippet: ${sanitizeWebSearchEvidenceText(result.snippet)}` : "", - result.content ? `Extracted page content: ${sanitizeWebSearchEvidenceText(result.content)}` : "" + result.content ? `Extracted page content: ${sanitizeWebSearchEvidenceText(result.content)}` : "", + result.diagnostics?.length ? `Diagnostics: ${result.diagnostics.join("; ")}` : "" ].filter(Boolean); return parts.length > 0 ? parts.join("\n") : undefined; } diff --git a/packages/core/src/mcp/fusion-tool-fallback-mcp.ts b/packages/core/src/mcp/fusion-tool-fallback-mcp.ts index a67635d..87c9b62 100644 --- a/packages/core/src/mcp/fusion-tool-fallback-mcp.ts +++ b/packages/core/src/mcp/fusion-tool-fallback-mcp.ts @@ -28,6 +28,7 @@ type McpTool = { description: string; inputSchema: JsonValue; name: string; + unavailableMessage?: string; }; type ToolCallResult = { @@ -134,10 +135,11 @@ async function handleJsonRpcRequest(payload: unknown): Promise item.name === toolLabel); const knownSuffix = toolNames.has(toolLabel) ? "" : " The requested tool was not in the fallback catalog."; return { content: [{ - text: + text: tool?.unavailableMessage || `Fusion MCP tool "${toolLabel}" is temporarily unavailable. ` + "CCR registered a fallback definition because the real MCP server did not provide the tool during discovery. " + `Check the Fusion MCP server logs and retry.${knownSuffix}`, @@ -179,7 +181,8 @@ function readFallbackTools(): McpTool[] { readString(item.description) || `Fallback registration for Fusion MCP tool "${name}". The real MCP server should handle successful calls.`, inputSchema: isRecord(item.inputSchema) ? item.inputSchema as JsonValue : objectSchema({}), - name + name, + unavailableMessage: readString(item.unavailableMessage) }); } return result; diff --git a/packages/core/src/mcp/toolhub-config.ts b/packages/core/src/mcp/toolhub-config.ts index be91a80..5c4cbd1 100644 --- a/packages/core/src/mcp/toolhub-config.ts +++ b/packages/core/src/mcp/toolhub-config.ts @@ -1,9 +1,13 @@ import { join as pathJoin } from "node:path"; import { CONFIGDIR } from "@ccr/core/config/constants"; -import type { AppConfig } from "@ccr/core/contracts/app"; +import type { AppConfig, GatewayMcpServerConfig } from "@ccr/core/contracts/app"; export const TOOL_HUB_MCP_SERVER_NAME = "ccr-toolhub"; export const TOOL_HUB_MCP_RUNTIME_FILE_NAME = "toolhub-mcp.js"; +export const BROWSER_AUTOMATION_MCP_SERVER_NAME = "ccr-browser-automation"; +export const BROWSER_AUTOMATION_MCP_PATH = "/__ccr/browser-automation/mcp"; +export const BROWSER_AUTOMATION_HANDOFF_TIMEOUT_MS = 600000; +export const TOOL_HUB_DEFAULT_REQUEST_TIMEOUT_MS = 60000; export type ToolHubMcpRuntimeConfig = { args: string[]; @@ -26,17 +30,53 @@ export type ToolHubClaudeCodeMcpConfig = { mcpServers: Record; }; -export function toolHubBackendServers(config: AppConfig | undefined, extraServers: unknown[] = []): unknown[] { +export function toolHubBackendServers( + config: AppConfig | undefined, + extraServers: unknown[] = [], + options: { + apiKey?: string; + includeBuiltIns?: boolean; + } = {} +): unknown[] { return [ + ...(options.includeBuiltIns === false ? [] : toolHubBuiltInBackendServers(config, options)), ...(Array.isArray(config?.agent?.mcpServers) ? config.agent.mcpServers : []), ...(Array.isArray(config?.toolHub?.mcpServers) ? config.toolHub.mcpServers : []), ...extraServers ].filter(isToolHubBackendServer); } +export function toolHubBuiltInBackendServers( + config: AppConfig | undefined, + options: { + apiKey?: string; + } = {} +): GatewayMcpServerConfig[] { + if (!config || !browserAutomationMcpEnabled(config) || !hasGatewayEndpoint(config)) { + return []; + } + + return [ + { + apiKey: options.apiKey || firstConfiguredApiKey(config), + headers: {}, + name: BROWSER_AUTOMATION_MCP_SERVER_NAME, + protocolVersion: "2024-11-05", + requestTimeoutMs: BROWSER_AUTOMATION_HANDOFF_TIMEOUT_MS, + startupTimeoutMs: 60000, + transport: "streamable-http", + url: `${gatewayEndpoint(config)}${BROWSER_AUTOMATION_MCP_PATH}` + } + ]; +} + +export function browserAutomationMcpEnabled(config: AppConfig | undefined): boolean { + return Boolean(config?.toolHub?.enabled && config.toolHub.browserAutomation); +} + export function toolHubMcpRuntimeConfig( config: AppConfig | undefined, - backendServers = toolHubBackendServers(config), + backendServers?: unknown[], options: { command?: string; entryPath?: string; @@ -52,10 +92,14 @@ export function toolHubMcpRuntimeConfig( return undefined; } - const normalizedBackendServers = backendServers.filter(isToolHubBackendServer); + const resolvedBackendServers = backendServers ?? toolHubBackendServers(config, [], { + apiKey: options.resolver?.apiKey + }); + const normalizedBackendServers = resolvedBackendServers.filter(isToolHubBackendServer); if (normalizedBackendServers.length === 0) { return undefined; } + const requestTimeoutMs = toolHubRequestTimeoutMs(config, normalizedBackendServers); return { args: [options.entryPath ?? bundledToolHubMcpEntryPath()], @@ -68,11 +112,18 @@ export function toolHubMcpRuntimeConfig( TOOLHUB_OPENAI_API_KEY: options.resolver?.apiKey ?? toolHub.llm?.apiKey ?? "", TOOLHUB_OPENAI_BASE_URL: options.resolver?.baseUrl ?? toolHub.llm?.baseUrl ?? "https://api.openai.com/v1", TOOLHUB_OPENAI_MODEL: options.resolver?.model ?? toolHub.llm?.model ?? "", - TOOLHUB_REQUEST_TIMEOUT_MS: String(toolHub.requestTimeoutMs ?? 60000) + TOOLHUB_REQUEST_TIMEOUT_MS: String(requestTimeoutMs) } }; } +export function toolHubRequestTimeoutMs(config: AppConfig | undefined, backendServers?: unknown[]): number { + const configuredTimeout = positiveInteger(config?.toolHub?.requestTimeoutMs, TOOL_HUB_DEFAULT_REQUEST_TIMEOUT_MS); + const backendTimeouts = (backendServers ?? toolHubBackendServers(config)) + .map((server) => isRecord(server) ? positiveInteger(server.requestTimeoutMs, 0) : 0); + return Math.max(configuredTimeout, ...backendTimeouts); +} + export function toolHubClaudeCodeMcpConfig( config: AppConfig | undefined, options: Parameters[2] = {} @@ -82,7 +133,9 @@ export function toolHubClaudeCodeMcpConfig( return undefined; } - const backendServers = toolHubBackendServers(config); + const backendServers = toolHubBackendServers(config, [], { + apiKey: options.resolver?.apiKey + }); if (backendServers.length === 0) { return undefined; } @@ -126,6 +179,39 @@ function stringValue(value: unknown): string | undefined { return typeof value === "string" && value.trim() ? value.trim() : undefined; } +function firstConfiguredApiKey(config: AppConfig): string | undefined { + return (Array.isArray(config.APIKEYS) ? config.APIKEYS : []) + .find((apiKey) => apiKey.key.trim())?.key.trim() || stringValue(config.APIKEY); +} + +function positiveInteger(value: unknown, fallback: number): number { + return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : fallback; +} + +function gatewayEndpoint(config: AppConfig): string { + return `http://${formatHost(clientGatewayHost(config.gateway.host))}:${config.gateway.port}`; +} + +function hasGatewayEndpoint(config: AppConfig): boolean { + const gateway = (config as Partial).gateway; + return Boolean(gateway && stringValue(gateway.host) && Number.isFinite(gateway.port)); +} + +function formatHost(host: string): string { + return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host; +} + +function clientGatewayHost(host: string): string { + const value = stringValue(host) ?? "127.0.0.1"; + if (value === "0.0.0.0") { + return "127.0.0.1"; + } + if (value === "::" || value === "[::]") { + return "::1"; + } + return value; +} + function uniqueStrings(values: string[]): string[] { const seen = new Set(); const result: string[] = []; diff --git a/packages/core/src/mcp/toolhub-mcp.ts b/packages/core/src/mcp/toolhub-mcp.ts index 1a5e9c7..9d440d9 100644 --- a/packages/core/src/mcp/toolhub-mcp.ts +++ b/packages/core/src/mcp/toolhub-mcp.ts @@ -149,6 +149,8 @@ type LlmToolResolution = { type ResolveOutput = { alreadyResolved?: boolean; + executionPlanInstructions?: string; + executionPlanJs?: string; nextAction?: { confirmationRequiredFor: string[]; firstAction: { @@ -186,6 +188,17 @@ const defaultRequestTimeoutMs = 60_000; const defaultMaxTools = 10; const discoveryCacheMaxAgeMs = 10_000; const repeatedResolveWindowMs = 5 * 60_000; +const executionPlanInstructions = [ + "Treat executionPlanJs as the dependency plan for ToolHub invocations.", + "Each callTool(toolName, args) call maps to one tool_hub.invoke call with tool=toolName and args=args.", + "await means the next ToolHub invocation depends on the previous result and must not be started early.", + "Only callTool calls inside the same Promise.all([...]) expression may be issued as parallel tool calls.", + "If a later call needs values from an earlier result, wait for that earlier result and fill the arguments after it returns." +].join(" "); +const reservedJavaScriptWords = new Set( + "await break case catch class const continue debugger default delete do else enum export extends false finally for function if import in instanceof new null return super switch this throw true try typeof var void while with yield" + .split(" ") +); type RuntimeLike = { callTool(params: unknown): Promise; @@ -365,15 +378,25 @@ class ToolHubRuntime { await this.registry.refreshServers(undefined, true); catalog = await this.registry.listTools(); } + if (taskWantsChromeLoginImport(task) && !catalogHasChromeLoginImportTool(catalog)) { + await this.registry.refreshServers(["ccr-browser-automation"], true); + catalog = await this.registry.listTools(); + } if (catalog.length === 0) { throw new Error("No MCP tools are available to resolve."); } const cached = this.findRecentlyResolvedTask(scopeKey, taskHash); if (cached) { - const selectedTools = cached.toolNames - .map((toolName) => this.resolveCatalogEntry(toolName, catalog)) - .filter((entry): entry is CatalogEntry => Boolean(entry)); + const selectedTools = expandToolBundleWithCompanionTools( + uniqueToolEntries([ + ...cached.toolNames + .map((toolName) => this.resolveCatalogEntry(toolName, catalog)) + .filter((entry): entry is CatalogEntry => Boolean(entry)), + ...getDeterministicTaskTools(task, catalog) + ]), + catalog + ); if (selectedTools.length > 0) { this.markToolsLoaded(scopeKey, selectedTools); return toolResult(this.buildRepeatedResolveOutput(selectedTools)); @@ -383,9 +406,15 @@ class ToolHubRuntime { const inFlightResolve = session.inFlightResolves.get(taskHash); if (inFlightResolve) { const output = await inFlightResolve; - const selectedTools = output.selectedTools - .map((tool) => this.resolveCatalogEntry(tool.toolName, catalog) ?? tool) - .filter((entry): entry is CatalogEntry => Boolean(entry)); + const selectedTools = expandToolBundleWithCompanionTools( + uniqueToolEntries([ + ...output.selectedTools + .map((tool) => this.resolveCatalogEntry(tool.toolName, catalog) ?? tool) + .filter((entry): entry is CatalogEntry => Boolean(entry)), + ...getDeterministicTaskTools(task, catalog) + ]), + catalog + ); if (selectedTools.length > 0) { this.markToolsLoaded(scopeKey, selectedTools); return toolResult(this.buildRepeatedResolveOutput(selectedTools)); @@ -461,7 +490,13 @@ class ToolHubRuntime { usedLlm = false; } - let selectedTools = resolution.selectedTools; + let selectedTools = expandToolBundleWithCompanionTools( + uniqueToolEntries([ + ...resolution.selectedTools, + ...getDeterministicTaskTools(input.task, input.catalog) + ]), + input.catalog + ); if (input.withoutSideEffects) { selectedTools = selectedTools.filter((tool) => !tool.invocation.sideEffect); } @@ -471,13 +506,14 @@ class ToolHubRuntime { this.markToolsLoaded(input.scopeKey, selectedTools); this.rememberResolvedTask(input.scopeKey, input.taskHash, selectedTools); + const executionPlanJs = buildExecutionPlanJs(resolution.workflowSketch, selectedTools); return { - ...this.buildResolveOutput(resolution.summary, selectedTools), + ...this.buildResolveOutput(resolution.summary, selectedTools, executionPlanJs), plannedSteps: resolution.plannedSteps, referencedTokens: resolution.referencedTokens, retriever, usedLlm, - workflowSketch: resolution.workflowSketch + workflowSketch: executionPlanJs }; } @@ -587,39 +623,50 @@ class ToolHubRuntime { return catalog.find((entry) => entry.toolName === resolvedName); } - private buildResolveOutput(summary: string, selectedTools: CatalogEntry[]): ResolveOutput { + private buildResolveOutput(summary: string, selectedTools: CatalogEntry[], executionPlanJs = buildSequentialExecutionPlanJs(selectedTools)): ResolveOutput { return { + executionPlanInstructions, + executionPlanJs, nextAction: this.buildResolveNextAction(selectedTools), reasoningSummary: summary, runtimeContext: { - availableContextKeys: ["selectedTools"], - summary: selectedTools.map((tool) => `${tool.toolName}: ${tool.description || tool.title}`) + availableContextKeys: ["selectedTools", "executionPlanJs", "executionPlanInstructions"], + summary: [ + ...selectedTools.map((tool) => `${tool.toolName}: ${tool.description || tool.title}`), + "Follow executionPlanJs for dependency ordering before invoking selected tools." + ] }, selectedToolNames: selectedTools.map((tool) => tool.toolName), selectedTools, - tsDefinitions: buildTsDefinitions(selectedTools) + tsDefinitions: buildTsDefinitions(selectedTools), + workflowSketch: executionPlanJs }; } private buildRepeatedResolveOutput(selectedTools: CatalogEntry[]): ResolveOutput { const nextAction = this.buildResolveNextAction(selectedTools); + const executionPlanJs = buildSequentialExecutionPlanJs(selectedTools); return { alreadyResolved: true, + executionPlanInstructions, + executionPlanJs, nextAction, reasoningSummary: [ "This task has already been resolved in the current ToolHub session.", nextAction.instruction ].join(" "), runtimeContext: { - availableContextKeys: ["selectedTools", "nextAction"], + availableContextKeys: ["selectedTools", "nextAction", "executionPlanJs", "executionPlanInstructions"], summary: [ "The selected tools are already loaded for tool_hub.invoke.", "Do not repeat discovery for this task.", - nextAction.instruction + nextAction.instruction, + "Follow executionPlanJs for dependency ordering before invoking selected tools." ] }, selectedToolNames: selectedTools.map((tool) => tool.toolName), - selectedTools + selectedTools, + workflowSketch: executionPlanJs }; } @@ -646,7 +693,10 @@ class ToolHubRuntime { toolName: firstTool?.toolName, type: "invoke_tool" as const }; - const instructionParts = ["Do not call tool_hub.resolve again for this task."]; + const instructionParts = [ + "Do not call tool_hub.resolve again for this task.", + "Follow executionPlanJs: await is serial dependency; only Promise.all groups may run in parallel." + ]; if (firstTool && firstRequiredArguments.length > 0) { instructionParts.push(`Ask the user for missing required arguments for ${firstTool.toolName}: ${firstRequiredArguments.join(", ")}.`); } else if (firstTool) { @@ -790,7 +840,7 @@ class ToolHubRegistry { private async ensureDiscoveryFresh(maxAgeMs = discoveryCacheMaxAgeMs): Promise { const staleServerNames = [...this.entries.values()] - .filter((entry) => !entry.loadedFromCache && (!entry.lastCheckedAt || Date.now() - entry.lastCheckedAt > maxAgeMs)) + .filter((entry) => !entry.lastCheckedAt || Date.now() - entry.lastCheckedAt > maxAgeMs) .map((entry) => entry.config.name); if (staleServerNames.length === 0) { return; @@ -1752,6 +1802,7 @@ function metaTools(): Array<{ description: string; inputSchema: Record\", {...}) or tools.call(\"\", {...}).", "3. Call the tree-sitter tool on that sketch.", "4. Inspect the tree-sitter result and check whether the bundle is complete end-to-end.", @@ -2000,10 +2051,18 @@ function buildSearchSystemPrompt(catalog: SearchCatalogItem[], topK: number): st "{ \"summary\": string, \"steps\": string[], \"workflowSketch\": string, \"toolNames\": string[] }", "Rules:", "- toolNames must be exact catalog names or aliases.", + "- workflowSketch is the dependency plan that will be returned to the caller as executionPlanJs.", + "- In workflowSketch, use await to show serial dependencies and Promise.all([...]) only for tool calls that are independent and safe to invoke in parallel.", + "- Do not put side-effecting calls in Promise.all unless they are truly independent and safe to run concurrently.", "- In workflowSketch, prefer string-literal tool calls over reconstructed member chains whenever possible.", "- Include prerequisite and downstream tools you will likely need after the first action.", "- Never invent tool names.", "- Generic browser automation tools are a strong match for web tasks such as ordering, buying, booking, delivery, or checkout when no domain-specific MCP tool exists.", + "- For browser navigation/open calls, prefer omitting waitUntil or using waitUntil: \"interactive\" so the agent can inspect and act as soon as the page is usable.", + "- Do not use waitUntil: \"network_idle\" for Gmail, Google sign-in, SPAs, mail, chat, auth, checkout, verification, or pages with long-lived requests. Use network_idle only when the user explicitly asks for network quiescence.", + "- When selecting CCR browser automation tools, include the human-handoff follow-up tools needed if login, CAPTCHA, verification, blocked navigation, or manual confirmation appears.", + "- For CCR browser automation bundles, include browser_handoff_request and browser_handoff_wait when the workflow may need user help. Do not assume all browser tools are preloaded.", + "- For importing existing Chrome login state into CCR's in-app browser, select browser_chrome_login_import and include browser_chrome_login_import_status to check completion.", "- If using member-call syntax, prefer tools.(...) or mcp..(...).", "- For lookup tasks, do not stop at opening or navigating. Include the tools needed to read or extract the answer.", "- If the catalog has no strong match, return an empty toolNames array.", @@ -2312,6 +2371,21 @@ function getLocalFallbackPreferredTools(taskText: string): Map { add("browser_screenshot", 60); add("browser_events_await", 44); } + if (hasAnyToken(tokens, [ + "auth", + "chrome", + "cookie", + "cookies", + "import", + "localstorage", + "login", + "session", + "signin", + "storage" + ]) || /chrome|cookie|cookies|localstorage|local storage|登录态|登录状态|导入登录|浏览器登录|本地存储/.test(normalizedText)) { + add("browser_chrome_login_import", 128); + add("browser_chrome_login_import_status", 92); + } if (hasAnyToken(tokens, ["address", "delivery", "geo", "geolocation", "location", "nearby", "pickup", "store"]) || /地址|定位|位置|附近|配送|外卖|自取|门店/.test(normalizedText)) { add("location_permission_status", 90); @@ -2324,6 +2398,28 @@ function getLocalFallbackPreferredTools(taskText: string): Map { return scores; } +function getDeterministicTaskTools(taskText: string, catalog: CatalogEntry[]): CatalogEntry[] { + if (!taskWantsChromeLoginImport(taskText)) { + return []; + } + return [ + catalog.find((tool) => isBrowserAutomationTool(tool) && tool.remoteToolName === "browser_chrome_login_import"), + catalog.find((tool) => isBrowserAutomationTool(tool) && tool.remoteToolName === "browser_chrome_login_import_status") + ].filter((tool): tool is CatalogEntry => Boolean(tool)); +} + +function taskWantsChromeLoginImport(taskText: string): boolean { + const normalizedText = taskText.toLowerCase(); + return normalizedText.includes("browser_chrome_login_import") || + /(chrome|谷歌浏览器|浏览器).*(login|signin|auth|cookie|localstorage|local storage|session|登录态|登录状态|登录信息|本地存储|cookie)/.test(normalizedText) || + /(import|导入|迁移|同步).*(chrome|谷歌浏览器).*(login|signin|auth|cookie|localstorage|local storage|session|登录态|登录状态|登录信息|本地存储)/.test(normalizedText) || + /(登录态|登录状态|登录信息).*(chrome|谷歌浏览器|浏览器).*(导入|迁移|同步)/.test(normalizedText); +} + +function catalogHasChromeLoginImportTool(catalog: CatalogEntry[]): boolean { + return catalog.some((tool) => isBrowserAutomationTool(tool) && tool.remoteToolName === "browser_chrome_login_import"); +} + function tokenizeLocalSearchText(text: string): string[] { const tokens = text .replace(/([a-z0-9])([A-Z])/g, "$1 $2") @@ -2350,10 +2446,113 @@ function uniqueToolEntries(entries: CatalogEntry[]): CatalogEntry[] { return output; } +function expandToolBundleWithCompanionTools(selectedTools: CatalogEntry[], catalog: CatalogEntry[]): CatalogEntry[] { + const output = uniqueToolEntries(selectedTools); + const selectedNames = new Set(output.map((tool) => tool.toolName)); + const selectedRemoteNames = new Set(output.map((tool) => tool.remoteToolName)); + const hasBrowserAutomationTool = output.some((tool) => isBrowserAutomationTool(tool)); + const hasHandoffRequestTool = output.some((tool) => + isBrowserAutomationTool(tool) && + (tool.remoteToolName === "browser_handoff_request" || tool.remoteToolName === "askHumanHelp") + ); + const hasChromeLoginImportTool = output.some((tool) => + isBrowserAutomationTool(tool) && + tool.remoteToolName === "browser_chrome_login_import" + ); + const companionRemoteNames = new Set(); + + if (hasBrowserAutomationTool) { + companionRemoteNames.add("browser_handoff_request"); + companionRemoteNames.add("browser_handoff_status"); + companionRemoteNames.add("browser_handoff_wait"); + } + if (hasHandoffRequestTool) { + companionRemoteNames.add("browser_handoff_wait"); + } + if (hasChromeLoginImportTool) { + companionRemoteNames.add("browser_chrome_login_import_status"); + } + + for (const remoteToolName of companionRemoteNames) { + if (selectedRemoteNames.has(remoteToolName)) { + continue; + } + const companion = catalog.find((tool) => + isBrowserAutomationTool(tool) && + tool.remoteToolName === remoteToolName && + !selectedNames.has(tool.toolName) + ); + if (companion) { + output.push(companion); + selectedNames.add(companion.toolName); + selectedRemoteNames.add(companion.remoteToolName); + } + } + + return output; +} + +function isBrowserAutomationTool(tool: CatalogEntry): boolean { + return tool.serverName === "ccr-browser-automation" || + tool.serverId === "ccr-browser-automation" || + tool.serverNamespace === "ccr_browser_automation" || + tool.toolName.startsWith("mcp.ccr_browser_automation."); +} + function buildLocalFallbackWorkflowSketch(selectedTools: CatalogEntry[]): string | undefined { - return selectedTools.length > 0 - ? selectedTools.slice(0, 5).map((tool) => `await callTool(${JSON.stringify(tool.toolName)}, {});`).join("\n") - : undefined; + return selectedTools.length > 0 ? buildSequentialExecutionPlanJs(selectedTools) : undefined; +} + +function isBrowserNavigationTool(tool: CatalogEntry): boolean { + return isBrowserAutomationTool(tool) && ( + tool.toolName.endsWith("browser_session_open") || + tool.toolName.endsWith("browser_navigate") || + tool.remoteToolName === "browser_session_open" || + tool.remoteToolName === "browser_navigate" + ); +} + +function buildExecutionPlanJs(workflowSketch: string | undefined, selectedTools: CatalogEntry[]): string { + const trimmed = typeof workflowSketch === "string" ? workflowSketch.trim() : ""; + return trimmed || buildSequentialExecutionPlanJs(selectedTools); +} + +function buildSequentialExecutionPlanJs(selectedTools: CatalogEntry[]): string { + if (selectedTools.length === 0) { + return [ + "async function runWithToolHub() {", + " // Ask the user for missing task details before invoking tools.", + "}" + ].join("\n"); + } + const lines = [ + "async function runWithToolHub() {", + " // Invoke calls in this order unless the plan explicitly uses Promise.all." + ]; + selectedTools.forEach((tool, index) => { + lines.push(` const step${index + 1} = await callTool(${JSON.stringify(tool.toolName)}, ${buildExecutionPlanArgs(tool)});`); + }); + lines.push("}"); + return lines.join("\n"); +} + +function buildExecutionPlanArgs(tool: CatalogEntry): string { + if (isBrowserNavigationTool(tool)) { + return "{ url, waitUntil: \"interactive\" }"; + } + const required = getSchemaRequiredProperties(tool.inputSchema); + if (required.length === 0) { + return "{}"; + } + return `{ ${required.map((key) => `${JSON.stringify(key)}: ${toPlanVariableName(key)}`).join(", ")} }`; +} + +function toPlanVariableName(value: string): string { + const identifier = toIdentifier(value).replace(/^[A-Z]/, (match) => match.toLowerCase()); + if (!identifier || reservedJavaScriptWords.has(identifier)) { + return "value"; + } + return identifier; } function inferInvocation(tool: ToolDefinition): ToolInvocation { diff --git a/packages/electron/src/main/browser-automation-mcp.ts b/packages/electron/src/main/browser-automation-mcp.ts new file mode 100644 index 0000000..884905f --- /dev/null +++ b/packages/electron/src/main/browser-automation-mcp.ts @@ -0,0 +1,3517 @@ +import type { IncomingMessage, ServerResponse } from "node:http"; +import { randomUUID } from "node:crypto"; +import type { Event as ElectronEvent, WebContents } from "electron"; +import { loadAppConfig } from "@ccr/core/config/config"; +import type { + BuiltInBrowserAutomationHandoff, + BuiltInBrowserAutomationHandoffKind, + BuiltInBrowserState, + BuiltInBrowserTabState +} from "@ccr/core/contracts/app"; +import type { BrowserAutomationMcpIntegration } from "@ccr/core/gateway/service"; +import { BROWSER_AUTOMATION_MCP_PATH } from "@ccr/core/mcp/toolhub-config"; +import { builtInBrowserService, type BrowserAutomationEvent } from "./built-in-browser"; +import { chromeLoginImportService } from "./chrome-login-import"; + +type JsonPrimitive = boolean | null | number | string; +type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }; + +type JsonRpcRequest = { + id?: null | number | string; + jsonrpc?: string; + method?: string; + params?: unknown; +}; + +type JsonRpcResponse = + | { + id: null | number | string; + jsonrpc: "2.0"; + result: JsonValue; + } + | { + error: { + code: number; + data?: JsonValue; + message: string; + }; + id: null | number | string; + jsonrpc: "2.0"; + }; + +type McpTool = { + description: string; + inputSchema: JsonValue; + name: string; + tags?: string[]; + title?: string; +}; + +type ToolCallResult = { + content: Array<{ text: string; type: "text" }>; + isError?: boolean; +}; + +type BrowserTarget = { + axNodeId?: string; + backendNodeId?: number; + exact?: boolean; + index?: number; + ref?: string; + role?: string; + selector?: string; + text?: string; +}; + +type BrowserSessionRef = { + frameId?: string; + sessionId: string; + tabId: string; + userId?: string; +}; + +type AttachedSession = { + attachedAt: number; + leaseId: string; + observeOnly: boolean; + ref: BrowserSessionRef; +}; + +type EventSubscription = { + channels: Set; + dropped: boolean; + events: BrowserAutomationEvent[]; + ref?: BrowserSessionRef; + redactTextInput: boolean; + sampleMouseMove: boolean; + startedAt: number; + subscriptionId: string; + unsubscribe: () => void; +}; + +type AxSnapshotResult = { + handoff?: BuiltInBrowserAutomationHandoff; + handoffRequired?: boolean; + humanHelp?: Record; + humanHelpRequired?: boolean; + nextAction?: "human_help"; + nodes: Array>; + scope: string; + session: BrowserSessionRef; + title: string; + url: string; +}; + +type BrowserHandoffDetection = { + kind: BuiltInBrowserAutomationHandoffKind; + message: string; + reason: string; +}; + +type ReadinessResult = { + matched: boolean; + matchedEvent?: string; + navigationError?: { + errorCode?: number; + errorDescription?: string; + url?: string; + }; + readinessUrl?: string; + timedOut: boolean; +}; + +type ReadinessEvent = { + errorCode?: number; + errorDescription?: string; + isMainFrame?: boolean; + type: string; + url?: string; +}; + +type NavigationReadinessContext = { + expectedUrl?: string; + previousUrl?: string; +}; + +const protocolVersion = "2024-11-05"; +const automationEventReplayMs = 15_000; +const defaultAxSnapshotLimit = 60; +const defaultSnapshotMaxElements = 80; +const defaultSnapshotMaxText = 3_000; +const defaultEventAwaitTimeoutMs = 10_000; +const defaultJavascriptTimeoutMs = 8_000; +const maxMcpRequestBytes = 2 * 1024 * 1024; +const maxSubscriptionEvents = 512; +const maxToolResultChars = 60_000; +const maxToolResultArrayItems = 120; +const maxToolResultStringChars = 2_000; +const maxToolResultObjectKeys = 120; +const maxSnapshotResultElements = 80; +const maxAxSnapshotResultNodes = 80; +const maxBrowserResultTextChars = 3_000; +const defaultWaitTimeoutMs = 10_000; + +const sessionSchema = objectSchema({ + frameId: { description: "Optional frame id. CCR currently targets the main frame.", type: "string" }, + sessionId: { description: "Browser automation session id.", type: "string" }, + tabId: { description: "Built-in browser tab id.", type: "string" }, + userId: { description: "Optional logical user id.", type: "string" } +}, ["sessionId", "tabId"]); + +const cursorSchema = objectSchema({ + dropped: { description: "Whether earlier events were dropped before this cursor.", type: "boolean" }, + seq: { description: "Last observed event sequence.", type: "number" }, + subscriptionId: { description: "Event subscription id.", type: "string" }, + ts: { description: "Last observed event timestamp.", type: "number" } +}, ["subscriptionId", "seq"]); + +const targetSchema = objectSchema({ + axNodeId: { description: "Accessibility node id returned by browser_ax_snapshot/query. In CCR this is a stable element ref.", type: "string" }, + backendNodeId: { description: "Reserved for CDP-compatible clients. CSS/ref targeting is preferred in CCR.", type: "number" }, + exact: { description: "Match text/name exactly instead of by substring.", type: "boolean" }, + index: { description: "Zero-based match index when text/role resolves multiple elements.", minimum: 0, type: "number" }, + ref: { description: "Element ref returned by browser_snapshot. Refs are CSS selectors.", type: "string" }, + role: { description: "Accessible or implicit role such as button, link, textbox, combobox, checkbox.", type: "string" }, + selector: { description: "CSS selector. Used directly when provided.", type: "string" }, + text: { description: "Visible text, accessible name, placeholder, label, or value to match.", type: "string" } +}); + +const browserAutomationTools: McpTool[] = [ + ...browserPublicAutomationTools(), + ...browserLegacyAliasTools() +].filter((tool, index, tools) => tools.findIndex((candidate) => candidate.name === tool.name) === index); + +function browserPublicAutomationTools(): McpTool[] { + return [ + { + description: "Open a URL or attach an existing CCR built-in browser tab and create an automation session.", + inputSchema: objectSchema({ + observeOnly: { description: "If true, action tools reject this session.", type: "boolean" }, + tabId: { description: "Existing tab id to attach.", type: "string" }, + timeoutMs: { description: "Optional navigation timeout in milliseconds.", type: "number" }, + url: { description: "Optional URL or search query to open.", type: "string" }, + userId: { description: "Optional logical user id.", type: "string" }, + waitUntil: { description: "Readiness condition. Default/recommended: interactive, which waits until the page is inspectable/actionable and avoids long-lived network requests. network_idle is rarely appropriate for SPAs, Google, mail, chat, auth, or streaming pages.", enum: ["none", "interactive", "domcontentloaded", "load", "network_idle"], type: "string" }, + windowId: { description: "Ignored in CCR; included for agentic-browser compatibility.", type: "string" } + }), + name: "browser_session_open", + tags: ["browser", "automation", "session", "open", "tab"], + title: "Open Browser Automation Session" + }, + { + description: "Detach and release a browser automation session. Does not close the tab.", + inputSchema: objectSchema({ session: sessionSchema }, ["session"]), + name: "browser_session_close", + tags: ["browser", "automation", "session", "close"], + title: "Close Browser Automation Session" + }, + { + description: "Create a new CCR built-in browser tab. Accepts an existing session or the fixed CCR browser window id.", + inputSchema: objectSchema({ + activate: { description: "Whether the new tab should become active. Defaults to true.", type: "boolean" }, + session: sessionSchema, + url: { description: "Optional URL or search query.", type: "string" }, + windowId: { description: "Ignored in CCR; included for compatibility.", type: "string" } + }), + name: "browser_tab_create", + tags: ["browser", "automation", "tab", "create"], + title: "Create Browser Tab" + }, + { + description: "List all CCR built-in browser tabs with active, loading, URL, title, and navigation state.", + inputSchema: objectSchema({ + session: sessionSchema, + windowId: { description: "Ignored in CCR; included for compatibility.", type: "string" } + }), + name: "browser_tab_list", + tags: ["browser", "automation", "tab", "list"], + title: "List Browser Tabs" + }, + { + description: "Activate an existing CCR built-in browser tab.", + inputSchema: objectSchema({ + session: sessionSchema, + tabId: { description: "Tab id to activate. Defaults to session.tabId.", type: "string" } + }), + name: "browser_tab_activate", + tags: ["browser", "automation", "tab", "activate", "focus"], + title: "Activate Browser Tab" + }, + { + description: "Close a CCR built-in browser tab.", + inputSchema: objectSchema({ + session: sessionSchema, + tabId: { description: "Tab id to close. Defaults to session.tabId.", type: "string" } + }), + name: "browser_tab_close", + tags: ["browser", "automation", "tab", "close"], + title: "Close Browser Tab" + }, + { + description: "Navigate the session tab or a specified tab to a URL or search query.", + inputSchema: objectSchema({ + session: sessionSchema, + tabId: { description: "Optional tab id. Defaults to session.tabId or active tab.", type: "string" }, + timeoutMs: { description: "Optional navigation timeout in milliseconds.", maximum: 120000, minimum: 100, type: "number" }, + url: { description: "URL or search query to load.", type: "string" }, + waitUntil: { description: "Readiness condition. Default/recommended: interactive, which waits until the page is inspectable/actionable and avoids long-lived network requests. network_idle is rarely appropriate for SPAs, Google, mail, chat, auth, or streaming pages.", enum: ["none", "interactive", "domcontentloaded", "load", "network_idle"], type: "string" } + }, ["url"]), + name: "browser_navigate", + tags: ["browser", "automation", "navigate", "tab", "url"], + title: "Navigate Browser Tab" + }, + { + description: "Go back in the session tab or a specified tab.", + inputSchema: objectSchema({ session: sessionSchema, tabId: { type: "string" } }), + name: "browser_tab_go_back", + tags: ["browser", "automation", "tab", "back"], + title: "Browser Tab Back" + }, + { + description: "Go forward in the session tab or a specified tab.", + inputSchema: objectSchema({ session: sessionSchema, tabId: { type: "string" } }), + name: "browser_tab_go_forward", + tags: ["browser", "automation", "tab", "forward"], + title: "Browser Tab Forward" + }, + { + description: "Reload the session tab or a specified tab.", + inputSchema: objectSchema({ session: sessionSchema, tabId: { type: "string" } }), + name: "browser_tab_reload", + tags: ["browser", "automation", "tab", "reload"], + title: "Reload Browser Tab" + }, + { + description: "Read a condensed accessibility-oriented page snapshot. Nodes include axNodeId/ref, role, name, value, text, and rect.", + inputSchema: objectSchema({ + includeIgnored: { description: "Included for compatibility; CCR returns visible/high-signal nodes.", type: "boolean" }, + limit: { description: "Maximum nodes to return.", maximum: 300, minimum: 1, type: "number" }, + maxDepth: { description: "Included for compatibility.", type: "number" }, + rootAxNodeId: { description: "Optional root node/ref to scope the snapshot.", type: "string" }, + scope: { description: "full or outline. CCR returns the same compact shape for both.", enum: ["full", "outline"], type: "string" }, + session: sessionSchema + }, ["session"]), + name: "browser_ax_snapshot", + tags: ["browser", "automation", "accessibility", "snapshot", "dom", "page"], + title: "Browser Accessibility Snapshot" + }, + { + description: "Search the page accessibility outline by role, accessible name, visible text, label, placeholder, or value.", + inputSchema: objectSchema({ + includeIgnored: { description: "Included for compatibility; CCR searches visible/high-signal nodes.", type: "boolean" }, + limit: { description: "Maximum matches.", maximum: 300, minimum: 1, type: "number" }, + name: { description: "Accessible name filter.", type: "string" }, + role: { description: "Role filter such as button, link, textbox, combobox.", type: "string" }, + rootAxNodeId: { description: "Optional root node/ref to scope the query.", type: "string" }, + session: sessionSchema, + text: { description: "Visible text/value/description filter.", type: "string" } + }, ["session"]), + name: "browser_ax_query", + tags: ["browser", "automation", "accessibility", "query", "find"], + title: "Query Browser Accessibility Tree" + }, + { + description: "Click an element resolved by axNodeId/ref, CSS selector, role, or text.", + inputSchema: objectSchema({ force: { type: "boolean" }, session: sessionSchema, target: targetSchema }, ["session", "target"]), + name: "browser_element_click", + tags: ["browser", "automation", "element", "click"], + title: "Click Browser Element" + }, + { + description: "Set text into an input, textarea, contenteditable, or textbox-like element.", + inputSchema: objectSchema({ + replace: { description: "Replace existing text. Defaults to true.", type: "boolean" }, + session: sessionSchema, + target: targetSchema, + text: { type: "string" } + }, ["session", "target", "text"]), + name: "browser_element_input", + tags: ["browser", "automation", "element", "input", "type", "form"], + title: "Input Browser Element" + }, + { + description: "Select an option on a native select by value or visible label.", + inputSchema: objectSchema({ + exact: { type: "boolean" }, + session: sessionSchema, + target: targetSchema, + value: { description: "Requested option value or visible label.", type: "string" } + }, ["session", "target", "value"]), + name: "browser_element_select", + tags: ["browser", "automation", "element", "select", "form"], + title: "Select Browser Element Option" + }, + { + description: "Press a keyboard key against a resolved target or the current focused element.", + inputSchema: objectSchema({ + key: { description: "Keyboard key, e.g. Enter, Tab, Escape, ArrowDown.", type: "string" }, + session: sessionSchema, + target: targetSchema + }, ["session", "key"]), + name: "browser_element_press", + tags: ["browser", "automation", "element", "press", "keyboard"], + title: "Press Browser Element Key" + }, + { + description: "Scroll the page or a target element.", + inputSchema: objectSchema({ + amount: { description: "Scroll amount in CSS pixels.", type: "number" }, + direction: { description: "Scroll direction.", enum: ["up", "down"], type: "string" }, + session: sessionSchema, + target: targetSchema + }, ["session"]), + name: "browser_element_scroll", + tags: ["browser", "automation", "element", "scroll"], + title: "Scroll Browser Element" + }, + { + description: "Subscribe to browser automation events such as tab, navigation, DOM-ready, runtime, and loading signals.", + inputSchema: objectSchema({ + channels: { description: "Event channels: tab, navigation, dom, runtime, dialog, download, input, focus, selection, handoff.", items: { type: "string" }, type: "array" }, + redactTextInput: { type: "boolean" }, + sampleMouseMove: { type: "boolean" }, + session: sessionSchema + }, ["session", "channels"]), + name: "browser_events_subscribe", + tags: ["browser", "automation", "events", "subscribe"], + title: "Subscribe Browser Events" + }, + { + description: "Read buffered browser automation events from a subscription.", + inputSchema: objectSchema({ + cursor: cursorSchema, + limit: { maximum: 200, minimum: 1, type: "number" }, + subscriptionId: { type: "string" } + }, ["subscriptionId"]), + name: "browser_events_read", + tags: ["browser", "automation", "events", "read"], + title: "Read Browser Events" + }, + { + description: "Wait for browser automation events matching kind, tabId, URL pattern, title pattern, or summary pattern.", + inputSchema: objectSchema({ + coalesceMs: { description: "Small delay after a first match to collect related events.", type: "number" }, + cursor: cursorSchema, + kinds: { items: { type: "string" }, type: "array" }, + maxEvents: { maximum: 50, minimum: 1, type: "number" }, + summaryPattern: { type: "string" }, + tabId: { type: "string" }, + timeoutMs: { maximum: 120000, minimum: 100, type: "number" }, + titlePattern: { type: "string" }, + urlPattern: { type: "string" }, + subscriptionId: { type: "string" } + }, ["subscriptionId"]), + name: "browser_events_await", + tags: ["browser", "automation", "events", "await", "wait"], + title: "Await Browser Events" + }, + { + description: "Unsubscribe from browser automation events and release the subscription.", + inputSchema: objectSchema({ subscriptionId: { type: "string" } }, ["subscriptionId"]), + name: "browser_events_unsubscribe", + tags: ["browser", "automation", "events", "unsubscribe"], + title: "Unsubscribe Browser Events" + }, + { + description: "Handle a currently open JavaScript alert/confirm/prompt dialog using Chromium CDP.", + inputSchema: objectSchema({ + accept: { type: "boolean" }, + promptText: { type: "string" }, + session: sessionSchema + }, ["session", "accept"]), + name: "browser_dialog_handle", + tags: ["browser", "automation", "dialog", "alert", "confirm", "prompt"], + title: "Handle Browser Dialog" + }, + { + description: "Request human intervention for the current browser task. Shows the hidden browser window and displays the requested action in the top toolbar.", + inputSchema: objectSchema({ + kind: { + description: "Type of help needed.", + enum: ["login_required", "verification_code", "human_verification", "blocked", "other"], + type: "string" + }, + message: { description: "Short instruction shown in the top toolbar.", type: "string" }, + reason: { description: "Why automation is blocked.", type: "string" }, + session: sessionSchema, + tabId: { description: "Optional tab id. Defaults to session.tabId or active tab.", type: "string" } + }, ["reason"]), + name: "browser_handoff_request", + tags: ["browser", "automation", "human", "handoff", "intervention"], + title: "Request Browser Human Handoff" + }, + { + description: "Read the current browser human handoff status.", + inputSchema: objectSchema({}), + name: "browser_handoff_status", + tags: ["browser", "automation", "human", "handoff"], + title: "Browser Handoff Status" + }, + { + description: "Wait until the user clicks Done or Hide on the current browser handoff toolbar. Use after browser_handoff_request or a tool result with humanHelpRequired.", + inputSchema: objectSchema({ + handoffId: { description: "Optional handoff id returned by browser_handoff_request or humanHelp.handoff.", type: "string" }, + session: sessionSchema, + tabId: { description: "Optional tab id. Defaults to session.tabId.", type: "string" }, + timeoutMs: { description: "Maximum wait time in milliseconds.", maximum: 600000, minimum: 100, type: "number" } + }), + name: "browser_handoff_wait", + tags: ["browser", "automation", "human", "handoff", "wait"], + title: "Wait For Browser Human Handoff" + }, + { + description: "Clear the current browser human handoff prompt.", + inputSchema: objectSchema({ + status: { enum: ["completed", "dismissed"], type: "string" } + }), + name: "browser_handoff_clear", + tags: ["browser", "automation", "human", "handoff"], + title: "Clear Browser Handoff" + }, + { + description: "Ask the user to confirm importing Chrome cookies and localStorage for selected domains into CCR's in-app browser. Opens a browser confirmation page; the Chrome extension performs the import after user confirmation.", + inputSchema: objectSchema({ + domain: { description: "Single domain to import, such as github.com. Used with domains if both are provided.", type: "string" }, + domains: { description: "Domains to import. If omitted, CCR derives the current tab hostname.", items: { type: "string" }, type: "array" }, + openConfirmationPage: { description: "Open the system browser confirmation page. Defaults to true.", type: "boolean" }, + session: sessionSchema, + tabId: { description: "Optional tab id used to derive a domain when domain/domains are omitted.", type: "string" }, + target: { description: "Target CCR browser storage partition.", enum: ["browser", "browser-and-web-search"], type: "string" } + }), + name: "browser_chrome_login_import", + tags: ["browser", "automation", "chrome", "login", "import", "cookies", "localStorage"], + title: "Import Chrome Login State" + }, + { + description: "Read the status of a Chrome login import job created by browser_chrome_login_import.", + inputSchema: objectSchema({ + jobId: { description: "Chrome login import job id.", type: "string" } + }, ["jobId"]), + name: "browser_chrome_login_import_status", + tags: ["browser", "automation", "chrome", "login", "import", "status"], + title: "Chrome Login Import Status" + } + ]; +} + +function browserLegacyAliasTools(): McpTool[] { + return [ + { + description: "Open or focus the CCR built-in browser window. Optionally navigate the active tab to a URL.", + inputSchema: objectSchema({ + url: { description: "Optional URL or search query to load in the active tab.", type: "string" } + }), + name: "browser_open", + tags: ["browser", "automation", "open", "focus"], + title: "Open Browser" + }, + { + description: "List CCR built-in browser tabs, including active tab, URL, title, and loading state.", + inputSchema: objectSchema({}), + name: "browser_tabs", + tags: ["browser", "automation", "tabs"], + title: "List Browser Tabs" + }, + { + description: "Open a new CCR built-in browser tab. Optionally load a URL or search query.", + inputSchema: objectSchema({ + url: { description: "Optional URL or search query to load in the new tab.", type: "string" } + }), + name: "browser_tab_new", + tags: ["browser", "automation", "tab"], + title: "New Browser Tab" + }, + { + description: "Focus a CCR built-in browser tab by tabId.", + inputSchema: objectSchema({ + tabId: { description: "Tab id returned by browser_tabs or browser_tab_new.", type: "string" } + }, ["tabId"]), + name: "browser_tab_activate", + tags: ["browser", "automation", "tab", "focus"], + title: "Activate Browser Tab" + }, + { + description: "Close a CCR built-in browser tab by tabId.", + inputSchema: objectSchema({ + tabId: { description: "Tab id returned by browser_tabs or browser_tab_new.", type: "string" } + }, ["tabId"]), + name: "browser_tab_close", + tags: ["browser", "automation", "tab"], + title: "Close Browser Tab" + }, + { + description: "Navigate a CCR built-in browser tab to a URL or search query.", + inputSchema: objectSchema({ + tabId: { description: "Optional tab id. Defaults to the active tab.", type: "string" }, + url: { description: "URL or search query to load.", type: "string" } + }, ["url"]), + name: "browser_navigate", + tags: ["browser", "automation", "navigate", "url"], + title: "Navigate Browser" + }, + { + description: "Capture page text plus interactable element refs for browser automation. Use returned refs for browser_click, browser_type, browser_select, browser_press_key, or browser_scroll.", + inputSchema: objectSchema({ + maxElements: { description: "Maximum interactable elements to include.", maximum: 300, minimum: 1, type: "number" }, + maxText: { description: "Maximum page text characters to include.", maximum: 20000, minimum: 0, type: "number" }, + tabId: { description: "Optional tab id. Defaults to the active tab.", type: "string" } + }), + name: "browser_snapshot", + tags: ["browser", "automation", "snapshot", "accessibility", "dom", "page"], + title: "Browser Page Snapshot" + }, + { + description: "Click an element in the CCR built-in browser by ref, CSS selector, role, or visible text.", + inputSchema: objectSchema({ + tabId: { description: "Optional tab id. Defaults to the active tab.", type: "string" }, + target: targetSchema + }, ["target"]), + name: "browser_click", + tags: ["browser", "automation", "click", "element"], + title: "Click Browser Element" + }, + { + description: "Type text into an input, textarea, select-like textbox, or contenteditable element in the CCR built-in browser.", + inputSchema: objectSchema({ + replaceExisting: { description: "Replace existing text. Defaults to true.", type: "boolean" }, + tabId: { description: "Optional tab id. Defaults to the active tab.", type: "string" }, + target: targetSchema, + text: { description: "Text to type.", type: "string" } + }, ["target", "text"]), + name: "browser_type", + tags: ["browser", "automation", "type", "input", "form"], + title: "Type In Browser Element" + }, + { + description: "Choose an option in a select element by value or visible label.", + inputSchema: objectSchema({ + exact: { description: "Match label exactly instead of by substring.", type: "boolean" }, + label: { description: "Visible option label to choose.", type: "string" }, + tabId: { description: "Optional tab id. Defaults to the active tab.", type: "string" }, + target: targetSchema, + value: { description: "Option value to choose.", type: "string" } + }, ["target"]), + name: "browser_select", + tags: ["browser", "automation", "select", "form"], + title: "Select Browser Option" + }, + { + description: "Press a keyboard key in the CCR built-in browser. Optionally focus an element first.", + inputSchema: objectSchema({ + key: { description: "Electron keyCode, for example Enter, Tab, Escape, Backspace, ArrowDown.", type: "string" }, + tabId: { description: "Optional tab id. Defaults to the active tab.", type: "string" }, + target: targetSchema + }, ["key"]), + name: "browser_press_key", + tags: ["browser", "automation", "keyboard", "press"], + title: "Press Browser Key" + }, + { + description: "Scroll the page or a target element in the CCR built-in browser.", + inputSchema: objectSchema({ + deltaX: { description: "Horizontal scroll delta in pixels.", type: "number" }, + deltaY: { description: "Vertical scroll delta in pixels.", type: "number" }, + tabId: { description: "Optional tab id. Defaults to the active tab.", type: "string" }, + target: targetSchema + }), + name: "browser_scroll", + tags: ["browser", "automation", "scroll"], + title: "Scroll Browser" + }, + { + description: "Wait until a browser page condition is true: URL includes/matches, selector is visible, or text is visible.", + inputSchema: objectSchema({ + selector: { description: "CSS selector that must be visible.", type: "string" }, + tabId: { description: "Optional tab id. Defaults to the active tab.", type: "string" }, + text: { description: "Text that must be visible in document body.", type: "string" }, + timeoutMs: { description: "Maximum wait time in milliseconds.", maximum: 120000, minimum: 100, type: "number" }, + urlIncludes: { description: "Substring that must appear in the URL.", type: "string" }, + urlMatches: { description: "JavaScript regular expression that must match the URL.", type: "string" } + }), + name: "browser_wait_for", + tags: ["browser", "automation", "wait", "url", "selector", "text"], + title: "Wait For Browser Page" + }, + { + description: "Agentic-browser-compatible alias for browser_handoff_request. Request human help for login, verification, CAPTCHA, or other manual-only blockers.", + inputSchema: objectSchema({ + browserSessionId: { type: "string" }, + kind: { enum: ["login_required", "verification_code", "human_verification", "blocked", "other"], type: "string" }, + message: { type: "string" }, + reason: { type: "string" }, + tabId: { type: "string" } + }, ["reason", "kind"]), + name: "askHumanHelp", + tags: ["browser", "automation", "human", "handoff", "intervention"], + title: "Ask Human Help" + } + ]; +} + +class BrowserAutomationMcpService implements BrowserAutomationMcpIntegration { + private readonly sessions = new Map(); + private readonly subscriptions = new Map(); + + async handleBrowserAutomationMcpRequest(request: IncomingMessage, response: ServerResponse): Promise { + response.setHeader("MCP-Protocol-Version", protocolVersion); + const path = request.url ? new URL(request.url, "http://127.0.0.1").pathname : "/"; + + if (request.method === "GET" && (path === BROWSER_AUTOMATION_MCP_PATH || path === `${BROWSER_AUTOMATION_MCP_PATH}/`)) { + sendJson(response, 200, { + endpoint: BROWSER_AUTOMATION_MCP_PATH, + name: "ccr-browser-automation", + protocol: "mcp", + transport: "streamable-http" + }); + return; + } + + if (request.method !== "POST") { + sendJson(response, 405, { error: { message: "Browser automation MCP endpoint only supports GET and POST." } }); + return; + } + + let payload: unknown; + try { + payload = JSON.parse((await readRequestBody(request, maxMcpRequestBytes)).toString("utf8")) as unknown; + } catch (error) { + sendJson(response, 400, jsonRpcError(null, -32700, `Invalid JSON-RPC request: ${formatError(error)}`)); + return; + } + + const requests = Array.isArray(payload) ? payload : [payload]; + const responses = await Promise.all(requests.map((item) => this.handleJsonRpcRequest(item))); + const filtered = responses.filter((item): item is JsonRpcResponse => Boolean(item)); + if (filtered.length === 0) { + response.writeHead(204); + response.end(); + return; + } + + sendJson(response, 200, Array.isArray(payload) ? filtered : filtered[0]); + } + + async stopBrowserAutomationMcpServer(): Promise { + for (const subscription of this.subscriptions.values()) { + subscription.unsubscribe(); + } + this.subscriptions.clear(); + this.sessions.clear(); + // The gateway owns this MCP route. Browser tabs remain user-visible state and are + // intentionally not closed when the gateway restarts. + } + + private async handleJsonRpcRequest(payload: unknown): Promise { + if (!isRecord(payload)) { + return jsonRpcError(null, -32600, "JSON-RPC request must be an object."); + } + + const request = payload as JsonRpcRequest; + const id = request.id ?? null; + if (request.id === undefined && request.method?.startsWith("notifications/")) { + return undefined; + } + if (request.jsonrpc !== "2.0" || !request.method) { + return jsonRpcError(id, -32600, "Invalid JSON-RPC 2.0 request."); + } + + try { + switch (request.method) { + case "initialize": + return jsonRpcResult(id, { + capabilities: { + tools: {} + }, + protocolVersion, + serverInfo: { + name: "ccr-browser-automation", + title: "CCR Browser Automation", + version: "1.0.0" + } + }); + case "ping": + return jsonRpcResult(id, {}); + case "tools/list": + return jsonRpcResult(id, { tools: browserAutomationTools as unknown as JsonValue }); + case "tools/call": + return jsonRpcResult(id, await this.callTool(request.params) as unknown as JsonValue); + default: + return jsonRpcError(id, -32601, `Unsupported MCP method: ${request.method}`); + } + } catch (error) { + return jsonRpcError(id, -32603, formatError(error)); + } + } + + private async callTool(params: unknown): Promise { + if (!isRecord(params) || typeof params.name !== "string") { + throw new Error("tools/call params must include a tool name."); + } + const args = isRecord(params.arguments) ? params.arguments : {}; + try { + const result = await this.runTool(params.name, args); + return textResult(formatToolResult(params.name, result)); + } catch (error) { + return { + ...textResult(formatError(error)), + isError: true + }; + } + } + + private async runTool(name: string, args: Record): Promise { + switch (name) { + case "browser_session_open": + return await this.openSession(args); + case "browser_session_close": + return this.closeSession(args); + case "browser_tab_create": + return await this.createTab(args); + case "browser_tab_list": + await this.ensureBrowserOpen(); + return browserWindowState(); + case "browser_tab_activate": { + await this.ensureBrowserOpen(); + const session = this.resolveOptionalSession(args); + const tabId = readString(args.tabId) || session?.ref.tabId; + if (!tabId) { + throw new Error("browser_tab_activate requires tabId or session."); + } + const state = builtInBrowserService.selectAutomationTab(tabId); + return { + ...browserWindowState(state), + tab: summarizeTab(requiredTabState(state, tabId), state.activeTabId) + }; + } + case "browser_tab_close": { + await this.ensureBrowserOpen(); + const session = this.resolveOptionalSession(args); + this.assertCanMutate(session); + const tabId = readString(args.tabId) || session?.ref.tabId; + if (!tabId) { + throw new Error("browser_tab_close requires tabId or session."); + } + const state = builtInBrowserService.closeAutomationTab(tabId); + this.removeSessionsForTab(tabId); + return browserWindowState(state); + } + case "browser_navigate": { + await this.ensureBrowserOpen(); + const session = this.resolveOptionalSession(args); + this.assertCanMutate(session); + const tabId = readString(args.tabId) || session?.ref.tabId || builtInBrowserService.getAutomationState().activeTabId; + if (!tabId) { + throw new Error("browser_navigate could not resolve an active tab."); + } + const url = requiredString(args.url, "browser_navigate requires url."); + const waitUntil = normalizeWaitUntil(readString(args.waitUntil), "interactive"); + const readiness = await waitForReadiness( + builtInBrowserService.getAutomationWebContents(tabId), + waitUntil, + clampInteger(readNumber(args.timeoutMs) ?? defaultWaitTimeoutMs, 100, 120000), + () => builtInBrowserService.navigateAutomationTab(url, tabId), + url + ); + const state = builtInBrowserService.getAutomationState(); + const webContents = builtInBrowserService.getAutomationWebContents(tabId); + const handoff = await maybeRequestHandoffAfterNavigation(webContents, readiness, { + requestedUrl: url, + session: session?.ref, + tabId, + waitUntil + }); + return { + ...browserWindowState(state), + ...(handoff ? humanHelpResultFields(handoff) : {}), + session: session?.ref, + tab: summarizeTab(requiredTabState(state, tabId), state.activeTabId), + ...readiness + }; + } + case "browser_tab_go_back": { + await this.ensureBrowserOpen(); + const session = this.resolveOptionalSession(args); + this.assertCanMutate(session); + return browserWindowState(builtInBrowserService.goBackAutomationTab(readString(args.tabId) || session?.ref.tabId)); + } + case "browser_tab_go_forward": { + await this.ensureBrowserOpen(); + const session = this.resolveOptionalSession(args); + this.assertCanMutate(session); + return browserWindowState(builtInBrowserService.goForwardAutomationTab(readString(args.tabId) || session?.ref.tabId)); + } + case "browser_tab_reload": { + await this.ensureBrowserOpen(); + const session = this.resolveOptionalSession(args); + this.assertCanMutate(session); + return browserWindowState(builtInBrowserService.reloadAutomationTab(readString(args.tabId) || session?.ref.tabId)); + } + case "browser_ax_snapshot": { + await this.ensureBrowserOpen(); + const session = this.requireSession(args); + return await captureAxSnapshot( + builtInBrowserService.getAutomationWebContents(session.ref.tabId), + session.ref, + { + limit: clampInteger(readNumber(args.limit) ?? defaultAxSnapshotLimit, 1, 300), + rootAxNodeId: readString(args.rootAxNodeId), + scope: readString(args.scope) === "outline" ? "outline" : "full" + } + ); + } + case "browser_ax_query": { + await this.ensureBrowserOpen(); + const session = this.requireSession(args); + return await queryAxSnapshot( + builtInBrowserService.getAutomationWebContents(session.ref.tabId), + session.ref, + { + limit: clampInteger(readNumber(args.limit) ?? 50, 1, 300), + name: readString(args.name), + role: readString(args.role), + rootAxNodeId: readString(args.rootAxNodeId), + text: readString(args.text) + } + ); + } + case "browser_element_click": { + await this.ensureBrowserOpen(); + const session = this.requireSession(args); + this.assertCanMutate(session); + const webContents = builtInBrowserService.getAutomationWebContents(session.ref.tabId); + const result = await runTargetAction(webContents, "click", readTarget(args.target)); + return await pageActionResult(webContents, session.ref, "click", result); + } + case "browser_element_input": { + await this.ensureBrowserOpen(); + const session = this.requireSession(args); + this.assertCanMutate(session); + const webContents = builtInBrowserService.getAutomationWebContents(session.ref.tabId); + const result = await runTargetAction(webContents, "type", readTarget(args.target), { + replaceExisting: args.replace !== false, + text: typeof args.text === "string" ? args.text : "" + }); + return await pageActionResult(webContents, session.ref, "input", result); + } + case "browser_element_select": { + await this.ensureBrowserOpen(); + const session = this.requireSession(args); + this.assertCanMutate(session); + const value = requiredString(args.value, "browser_element_select requires value."); + const webContents = builtInBrowserService.getAutomationWebContents(session.ref.tabId); + const result = await runTargetAction(webContents, "select", readTarget(args.target), { + exact: args.exact === true, + label: value, + value + }); + return await pageActionResult(webContents, session.ref, "select", result); + } + case "browser_element_press": { + await this.ensureBrowserOpen(); + const session = this.requireSession(args); + this.assertCanMutate(session); + const webContents = builtInBrowserService.getAutomationWebContents(session.ref.tabId); + const result = await pressKey( + webContents, + requiredString(args.key, "browser_element_press requires key."), + isRecord(args.target) ? readTarget(args.target) : undefined + ); + return await pageActionResult(webContents, session.ref, "press", result); + } + case "browser_element_scroll": { + await this.ensureBrowserOpen(); + const session = this.requireSession(args); + this.assertCanMutate(session); + const amount = Math.abs(readNumber(args.amount) ?? 700); + const direction = readString(args.direction) === "up" ? "up" : "down"; + const webContents = builtInBrowserService.getAutomationWebContents(session.ref.tabId); + const result = await runTargetAction( + webContents, + "scroll", + isRecord(args.target) ? readTarget(args.target) : undefined, + { + deltaX: 0, + deltaY: direction === "up" ? -amount : amount + } + ); + return await pageActionResult(webContents, session.ref, "scroll", result); + } + case "browser_events_subscribe": + await this.ensureBrowserOpen(); + return this.subscribeEvents(args); + case "browser_events_read": + return this.readEvents(args); + case "browser_events_await": + return await this.awaitEvents(args); + case "browser_events_unsubscribe": + return this.unsubscribeEvents(args); + case "browser_dialog_handle": { + await this.ensureBrowserOpen(); + const session = this.requireSession(args); + this.assertCanMutate(session); + const webContents = builtInBrowserService.getAutomationWebContents(session.ref.tabId); + return await handleJavaScriptDialog(webContents, args.accept === true, readString(args.promptText), session.ref); + } + case "browser_handoff_request": + case "askHumanHelp": + await this.ensureBrowserOpen(); + return this.requestHandoff(args); + case "browser_handoff_status": + await this.ensureBrowserOpen(); + return { + handoff: builtInBrowserService.getAutomationState().automationHandoff, + state: browserWindowState() + }; + case "browser_handoff_wait": + await this.ensureBrowserOpen(); + return await this.waitForHandoff(args); + case "browser_handoff_clear": + return { + handoff: undefined, + state: builtInBrowserService.resolveAutomationHandoff(readString(args.status) === "dismissed" ? "dismissed" : "completed") + }; + case "browser_chrome_login_import": { + await this.ensureBrowserOpen(); + const session = this.resolveOptionalSession(args); + const domains = resolveChromeLoginImportDomains(args, session?.ref.tabId); + const job = await chromeLoginImportService.createJob({ + domains, + openConfirmationPage: args.openConfirmationPage !== false, + target: readString(args.target) === "browser-and-web-search" ? "browser-and-web-search" : "browser" + }); + return { + humanHelpRequired: true, + job, + nextAction: "user_confirm_chrome_import", + state: browserWindowState(), + summary: `Opened Chrome login import confirmation for ${job.domains.join(", ")}.` + }; + } + case "browser_chrome_login_import_status": { + const jobId = requiredString(args.jobId, "browser_chrome_login_import_status requires jobId."); + const job = chromeLoginImportService.getJob(jobId); + if (!job) { + throw new Error(`Chrome login import job was not found: ${jobId}`); + } + return { job }; + } + case "browser_open": { + const state = await this.ensureBrowserVisible(); + const url = readString(args.url); + return url ? await builtInBrowserService.navigateAutomationTab(url, state.activeTabId) : builtInBrowserService.getAutomationState(); + } + case "browser_tabs": + await this.ensureBrowserOpen(); + return builtInBrowserService.getAutomationState(); + case "browser_tab_new": { + await this.ensureBrowserOpen(); + const tab = builtInBrowserService.createAutomationTab(readString(args.url) || undefined); + return { state: builtInBrowserService.getAutomationState(), tab }; + } + case "browser_snapshot": + await this.ensureBrowserOpen(); + return await captureSnapshotWithHandoff( + builtInBrowserService.getAutomationWebContents(readString(args.tabId)), + readString(args.tabId), + { + maxElements: clampInteger(readNumber(args.maxElements) ?? defaultSnapshotMaxElements, 1, 300), + maxText: clampInteger(readNumber(args.maxText) ?? defaultSnapshotMaxText, 0, 20000) + } + ); + case "browser_click": + await this.ensureBrowserOpen(); + return await runTargetAction( + builtInBrowserService.getAutomationWebContents(readString(args.tabId)), + "click", + readTarget(args.target) + ); + case "browser_type": + await this.ensureBrowserOpen(); + return await runTargetAction( + builtInBrowserService.getAutomationWebContents(readString(args.tabId)), + "type", + readTarget(args.target), + { + replaceExisting: args.replaceExisting !== false, + text: typeof args.text === "string" ? args.text : "" + } + ); + case "browser_select": + await this.ensureBrowserOpen(); + return await runTargetAction( + builtInBrowserService.getAutomationWebContents(readString(args.tabId)), + "select", + readTarget(args.target), + { + exact: args.exact === true, + label: readString(args.label), + value: readString(args.value) + } + ); + case "browser_press_key": + await this.ensureBrowserOpen(); + return await pressKey( + builtInBrowserService.getAutomationWebContents(readString(args.tabId)), + requiredString(args.key, "browser_press_key requires key."), + isRecord(args.target) ? readTarget(args.target) : undefined + ); + case "browser_scroll": + await this.ensureBrowserOpen(); + return await runTargetAction( + builtInBrowserService.getAutomationWebContents(readString(args.tabId)), + "scroll", + isRecord(args.target) ? readTarget(args.target) : undefined, + { + deltaX: readNumber(args.deltaX) ?? 0, + deltaY: readNumber(args.deltaY) ?? 700 + } + ); + case "browser_wait_for": + await this.ensureBrowserOpen(); + return await waitForPageCondition( + builtInBrowserService.getAutomationWebContents(readString(args.tabId)), + { + selector: readString(args.selector), + text: readString(args.text), + timeoutMs: clampInteger(readNumber(args.timeoutMs) ?? defaultWaitTimeoutMs, 100, 120000), + urlIncludes: readString(args.urlIncludes), + urlMatches: readString(args.urlMatches) + } + ); + default: + throw new Error(`Unknown browser automation tool: ${name}`); + } + } + + private async ensureBrowserOpen(): Promise { + await builtInBrowserService.openHidden(await loadAppConfig()); + return builtInBrowserService.getAutomationState(); + } + + private async ensureBrowserVisible(): Promise { + await builtInBrowserService.open(await loadAppConfig()); + return builtInBrowserService.getAutomationState(); + } + + private async openSession(args: Record): Promise { + let state = await this.ensureBrowserOpen(); + const url = readString(args.url); + const requestedTabId = readString(args.tabId); + const previousActiveTabId = state.activeTabId; + let tabId = requestedTabId; + let createdTab = false; + + if (tabId && !state.tabs.some((tab) => tab.id === tabId)) { + throw new Error(`Browser tab was not found: ${tabId}`); + } + + if (!tabId && url) { + const tab = builtInBrowserService.createAutomationTab(); + tabId = tab.id; + createdTab = true; + } else if (!tabId) { + tabId = state.activeTabId || builtInBrowserService.createAutomationTab().id; + createdTab = !state.activeTabId; + } else if (state.activeTabId !== tabId) { + builtInBrowserService.selectAutomationTab(tabId); + } + + if (!tabId) { + throw new Error("Unable to resolve browser tab for automation session."); + } + + const waitUntil = normalizeWaitUntil(readString(args.waitUntil), url ? "interactive" : "none"); + const timeoutMs = clampInteger(readNumber(args.timeoutMs) ?? defaultWaitTimeoutMs, 100, 120000); + const webContents = builtInBrowserService.getAutomationWebContents(tabId); + const readiness = await waitForReadiness( + webContents, + waitUntil, + timeoutMs, + url ? () => builtInBrowserService.navigateAutomationTab(url, tabId) : undefined, + url + ); + state = builtInBrowserService.getAutomationState(); + const tab = requiredTabState(state, tabId); + const ref: BrowserSessionRef = { + sessionId: randomUUID(), + tabId, + ...(readString(args.userId) ? { userId: readString(args.userId) } : {}) + }; + const attached: AttachedSession = { + attachedAt: Date.now(), + leaseId: randomUUID(), + observeOnly: args.observeOnly === true, + ref + }; + this.sessions.set(ref.sessionId, attached); + const handoff = await maybeRequestHandoffAfterNavigation(webContents, readiness, { + requestedUrl: url, + session: ref, + tabId, + waitUntil + }); + + return { + session: ref, + attachedAt: attached.attachedAt, + createdTab, + ...(handoff ? humanHelpResultFields(handoff) : {}), + matched: readiness.matched, + matchedEvent: readiness.matchedEvent, + ...(readiness.navigationError ? { navigationError: readiness.navigationError } : {}), + previousActiveTabId, + readinessUrl: readiness.readinessUrl, + tabId, + timedOut: readiness.timedOut, + title: tab.title, + url: tab.url, + waitUntil, + windowId: builtInBrowserService.getAutomationWindowId() + }; + } + + private closeSession(args: Record): unknown { + const session = this.requireSession(args); + this.sessions.delete(session.ref.sessionId); + for (const [subscriptionId, subscription] of this.subscriptions) { + if (subscription.ref?.sessionId === session.ref.sessionId) { + subscription.unsubscribe(); + this.subscriptions.delete(subscriptionId); + } + } + return { success: true }; + } + + private async createTab(args: Record): Promise { + const state = await this.ensureBrowserOpen(); + const session = this.resolveOptionalSession(args); + this.assertCanMutate(session); + const previousActiveTabId = state.activeTabId; + const tab = builtInBrowserService.createAutomationTab(readString(args.url) || undefined); + if (args.activate === false && previousActiveTabId) { + builtInBrowserService.selectAutomationTab(previousActiveTabId); + } + const nextState = builtInBrowserService.getAutomationState(); + return summarizeTab(requiredTabState(nextState, tab.id), nextState.activeTabId); + } + + private requireSession(args: Record): AttachedSession { + const session = this.resolveOptionalSession(args); + if (!session) { + throw new Error("A valid browser automation session is required."); + } + return session; + } + + private resolveOptionalSession(args: Record): AttachedSession | undefined { + const ref = readSessionRef(args.session); + if (!ref) { + return undefined; + } + const existing = this.sessions.get(ref.sessionId); + if (existing) { + if (existing.ref.tabId !== ref.tabId) { + throw new Error("Browser automation session tabId does not match the attached session."); + } + return existing; + } + const state = builtInBrowserService.getAutomationState(); + if (!state.tabs.some((tab) => tab.id === ref.tabId)) { + throw new Error(`Browser automation session tab was not found: ${ref.tabId}`); + } + const restored: AttachedSession = { + attachedAt: Date.now(), + leaseId: randomUUID(), + observeOnly: false, + ref + }; + this.sessions.set(ref.sessionId, restored); + return restored; + } + + private assertCanMutate(session?: AttachedSession): void { + if (session?.observeOnly) { + throw new Error("This browser automation session is observeOnly and cannot mutate browser state."); + } + } + + private removeSessionsForTab(tabId: string): void { + for (const [sessionId, session] of this.sessions) { + if (session.ref.tabId === tabId) { + this.sessions.delete(sessionId); + } + } + for (const [subscriptionId, subscription] of this.subscriptions) { + if (subscription.ref?.tabId === tabId) { + subscription.unsubscribe(); + this.subscriptions.delete(subscriptionId); + } + } + } + + private subscribeEvents(args: Record): unknown { + const session = this.requireSession(args); + const channels = normalizeEventChannels(readStringArray(args.channels)); + const subscription: EventSubscription = { + channels, + dropped: false, + events: [], + ref: session.ref, + redactTextInput: args.redactTextInput !== false, + sampleMouseMove: args.sampleMouseMove === true, + startedAt: Date.now(), + subscriptionId: randomUUID(), + unsubscribe: () => undefined + }; + const listener = (event: BrowserAutomationEvent) => { + if (!matchesSubscriptionEvent(subscription, event)) { + return; + } + subscription.events.push(event); + if (subscription.events.length > maxSubscriptionEvents) { + subscription.events.splice(0, subscription.events.length - maxSubscriptionEvents); + subscription.dropped = true; + } + }; + subscription.unsubscribe = builtInBrowserService.subscribeAutomationEvents(listener, { + replayRecentMs: automationEventReplayMs + }); + this.subscriptions.set(subscription.subscriptionId, subscription); + + return { + subscription: subscriptionDescriptor(subscription) + }; + } + + private readEvents(args: Record): unknown { + const subscription = this.requireSubscription(args); + const limit = clampInteger(readNumber(args.limit) ?? 100, 1, 200); + const cursorSeq = readCursorSeq(args.cursor); + const events = eventsAfterCursor(subscription, cursorSeq).slice(0, limit); + return { + dropped: subscription.dropped || cursorDropped(subscription, cursorSeq), + events, + nextCursor: makeEventCursor(subscription, events.at(-1)?.seq ?? cursorSeq), + subscriptionId: subscription.subscriptionId + }; + } + + private async awaitEvents(args: Record): Promise { + const subscription = this.requireSubscription(args); + const timeoutMs = clampInteger(readNumber(args.timeoutMs) ?? defaultEventAwaitTimeoutMs, 100, 120000); + const maxEvents = clampInteger(readNumber(args.maxEvents) ?? 10, 1, 50); + const coalesceMs = clampInteger(readNumber(args.coalesceMs) ?? 100, 0, 2000); + const cursorSeq = readCursorSeq(args.cursor); + const deadline = Date.now() + timeoutMs; + let matched: BrowserAutomationEvent[] = []; + + while (Date.now() <= deadline) { + matched = eventsAfterCursor(subscription, cursorSeq) + .filter((event) => matchesAwaitFilter(event, args)) + .slice(0, maxEvents); + if (matched.length > 0) { + if (coalesceMs > 0) { + await sleep(coalesceMs); + matched = eventsAfterCursor(subscription, cursorSeq) + .filter((event) => matchesAwaitFilter(event, args)) + .slice(0, maxEvents); + } + break; + } + await sleep(100); + } + + return { + dropped: subscription.dropped || cursorDropped(subscription, cursorSeq), + event: matched[0], + events: matched, + matched: matched.length > 0, + nextCursor: makeEventCursor(subscription, matched.at(-1)?.seq ?? cursorSeq), + subscriptionId: subscription.subscriptionId, + timedOut: matched.length === 0 + }; + } + + private unsubscribeEvents(args: Record): unknown { + const subscription = this.requireSubscription(args); + subscription.unsubscribe(); + this.subscriptions.delete(subscription.subscriptionId); + return { + ok: true, + subscriptionId: subscription.subscriptionId + }; + } + + private requireSubscription(args: Record): EventSubscription { + const subscriptionId = requiredString(args.subscriptionId, "Browser event subscriptionId is required."); + const subscription = this.subscriptions.get(subscriptionId); + if (!subscription) { + throw new Error(`Browser event subscription was not found: ${subscriptionId}`); + } + return subscription; + } + + private requestHandoff(args: Record): unknown { + const session = this.resolveOptionalSession(args); + const sessionId = session?.ref.sessionId || readString(args.browserSessionId); + const tabId = readString(args.tabId) || session?.ref.tabId; + const handoff = builtInBrowserService.requestAutomationHandoff({ + kind: readHandoffKind(args.kind), + message: readString(args.message), + reason: requiredString(args.reason, "browser_handoff_request requires reason."), + sessionId, + tabId + }); + return { + ...humanHelpResultFields(handoff), + state: browserWindowState() + }; + } + + private async waitForHandoff(args: Record): Promise { + const session = this.resolveOptionalSession(args); + const handoffId = readString(args.handoffId); + const tabId = readString(args.tabId) || session?.ref.tabId; + const timeoutMs = clampInteger(readNumber(args.timeoutMs) ?? 300000, 100, 600000); + + const current = builtInBrowserService.getAutomationState().automationHandoff; + if (!handoffMatches(current, { handoffId, tabId })) { + const recentResolution = findRecentHandoffResolution({ handoffId, tabId }); + if (recentResolution) { + return handoffResolutionResult(recentResolution, undefined, false); + } + return { + handoff: current, + matched: false, + reason: current ? "A browser handoff is pending, but it does not match the requested handoffId/tabId." : "No browser handoff is currently pending.", + state: browserWindowState(), + timedOut: false + }; + } + + return await new Promise((resolve) => { + let settled = false; + const finish = (result: Record) => { + if (settled) { + return; + } + settled = true; + clearTimeout(timer); + unsubscribe(); + resolve(result); + }; + const unsubscribe = builtInBrowserService.subscribeAutomationEvents((event) => { + if (event.kind !== "handoff.completed" && event.kind !== "handoff.dismissed") { + return; + } + if (handoffId && event.handoffId !== handoffId) { + return; + } + if (tabId && event.tabId !== tabId) { + return; + } + finish(handoffResolutionResult(event, current, false)); + }); + const timer = setTimeout(() => { + finish({ + handoff: current, + matched: false, + state: browserWindowState(), + timedOut: true + }); + }, timeoutMs); + }); + } +} + +export const browserAutomationMcpService = new BrowserAutomationMcpService(); + +function browserWindowState(state = builtInBrowserService.getAutomationState()): Record { + return { + activeTabId: state.activeTabId, + tabs: state.tabs.map((tab) => summarizeTab(tab, state.activeTabId)), + windowId: builtInBrowserService.getAutomationWindowId() + }; +} + +function resolveChromeLoginImportDomains(args: Record, fallbackTabId?: string): string[] { + const explicit = uniqueStrings([ + ...readStringArray(args.domains), + ...(readString(args.domain) ? [readString(args.domain) as string] : []) + ].map(normalizeChromeLoginImportDomain).filter((domain): domain is string => Boolean(domain))); + if (explicit.length > 0) { + return explicit; + } + + const state = builtInBrowserService.getAutomationState(); + const tabId = readString(args.tabId) || fallbackTabId || state.activeTabId; + const tab = state.tabs.find((candidate) => candidate.id === tabId) || state.tabs.find((candidate) => candidate.id === state.activeTabId); + const domain = normalizeChromeLoginImportDomain(tab?.url); + if (!domain) { + throw new Error("browser_chrome_login_import requires domains or an active http(s) tab."); + } + return [domain]; +} + +function normalizeChromeLoginImportDomain(value: unknown): string | undefined { + const raw = readString(value)?.toLowerCase(); + if (!raw) { + return undefined; + } + try { + const url = new URL(raw.includes("://") ? raw : `https://${raw}`); + return url.hostname.replace(/^\*\./, "").replace(/^\./, ""); + } catch { + const domain = raw.replace(/^\*\./, "").replace(/^\./, "").split("/")[0]; + return domain && !domain.includes(" ") ? domain : undefined; + } +} + +function summarizeTab(tab: BuiltInBrowserTabState, activeTabId?: string): Record { + return { + canGoBack: tab.canGoBack, + canGoForward: tab.canGoForward, + displayUrl: tab.url, + isActive: tab.id === activeTabId, + isLoading: tab.isLoading, + tabId: tab.id, + title: tab.title, + url: tab.url, + windowId: builtInBrowserService.getAutomationWindowId() + }; +} + +function requiredTabState(state: BuiltInBrowserState, tabId: string): BuiltInBrowserTabState { + const tab = state.tabs.find((candidate) => candidate.id === tabId); + if (!tab) { + throw new Error(`Browser tab was not found: ${tabId}`); + } + return tab; +} + +function readSessionRef(value: unknown): BrowserSessionRef | undefined { + if (!isRecord(value)) { + return undefined; + } + const sessionId = readString(value.sessionId); + const tabId = readString(value.tabId); + if (!sessionId || !tabId) { + return undefined; + } + return { + sessionId, + tabId, + ...(readString(value.frameId) ? { frameId: readString(value.frameId) } : {}), + ...(readString(value.userId) ? { userId: readString(value.userId) } : {}) + }; +} + +async function pageActionResult( + webContents: WebContents, + session: BrowserSessionRef, + action: string, + result: unknown +): Promise> { + const handoff = await maybeRequestPageHandoff(webContents, { + session, + tabId: session.tabId + }); + return { + action, + ...(handoff ? humanHelpResultFields(handoff) : {}), + result, + session, + title: await webContents.getTitle(), + url: webContents.getURL() + }; +} + +async function captureAxSnapshot( + webContents: WebContents, + session: BrowserSessionRef, + options: { limit: number; rootAxNodeId?: string; scope: string } +): Promise { + const snapshot = await captureSnapshot(webContents, { + maxElements: options.limit, + maxText: options.scope === "outline" ? 0 : defaultSnapshotMaxText + }); + const record = isRecord(snapshot) ? snapshot : {}; + const title = readString(record.title) || await webContents.getTitle(); + const url = readString(record.url) || webContents.getURL(); + const elements = Array.isArray(record.elements) ? record.elements.filter(isRecord) : []; + const elementNodes = elements.map((element, index) => axNodeFromSnapshotElement(element, index)); + const rootNode = { + axNodeId: "document", + childAxNodeIds: elementNodes.map((node) => String(node.axNodeId)), + ignored: false, + name: title || url, + role: "document", + url + }; + let nodes: Array> = [rootNode, ...elementNodes]; + if (options.rootAxNodeId && options.rootAxNodeId !== "document") { + nodes = nodes.filter((node) => node.axNodeId === options.rootAxNodeId || node.ref === options.rootAxNodeId); + } + const result: AxSnapshotResult = { + nodes: nodes.slice(0, options.limit), + scope: options.scope, + session, + title, + url + }; + const handoff = await maybeRequestPageHandoff(webContents, { + session, + snapshot, + tabId: session.tabId + }); + return handoff ? { ...result, ...humanHelpResultFields(handoff) } : result; +} + +async function queryAxSnapshot( + webContents: WebContents, + session: BrowserSessionRef, + options: { limit: number; name?: string; role?: string; rootAxNodeId?: string; text?: string } +): Promise> { + const snapshot = await captureAxSnapshot(webContents, session, { + limit: 300, + rootAxNodeId: options.rootAxNodeId, + scope: "outline" + }); + const role = options.role?.toLowerCase(); + const name = options.name?.toLowerCase(); + const text = options.text?.toLowerCase(); + const matches = snapshot.nodes + .filter((node) => node.role !== "document") + .filter((node) => { + if (role && String(node.role || "").toLowerCase() !== role) { + return false; + } + if (name && !String(node.name || "").toLowerCase().includes(name)) { + return false; + } + if (text) { + const haystack = [ + node.name, + node.value, + node.description, + node.text, + node.placeholder + ].join(" ").toLowerCase(); + if (!haystack.includes(text)) { + return false; + } + } + return true; + }) + .slice(0, options.limit); + return { + ...(snapshot.handoff ? humanHelpResultFields(snapshot.handoff) : {}), + matches, + session, + title: snapshot.title, + url: snapshot.url + }; +} + +function axNodeFromSnapshotElement(element: Record, index: number): Record { + const ref = readString(element.ref) || `element-${index}`; + const text = readString(element.text); + const name = readString(element.name) || text || readString(element.placeholder) || ref; + const description = text && text !== name ? text : undefined; + return { + axNodeId: ref, + backendNodeId: readNumber(element.backendNodeId), + checked: element.checked === true ? true : undefined, + description, + disabled: element.disabled === true ? true : undefined, + href: readString(element.href), + ignored: false, + index, + name, + placeholder: readString(element.placeholder), + rect: isRecord(element.rect) ? element.rect : undefined, + ref, + role: readString(element.role) || readString(element.tag) || "generic", + tag: readString(element.tag), + text: description, + value: readString(element.value) + }; +} + +async function waitForReadiness( + webContents: WebContents, + waitUntil: string, + timeoutMs: number, + requestNavigation?: () => Promise, + expectedUrl?: string +): Promise { + if (waitUntil === "none") { + if (requestNavigation) { + await requestNavigation(); + } + return { matched: true, matchedEvent: "none", readinessUrl: safeWebContentsUrl(webContents), timedOut: false }; + } + if (webContents.isDestroyed()) { + return { matched: false, matchedEvent: "destroyed", timedOut: false }; + } + if (requestNavigation) { + const readiness = waitForNextReadinessEvent(webContents, waitUntil, timeoutMs, { + expectedUrl: normalizeNavigationUrlForMatch(expectedUrl), + previousUrl: safeWebContentsUrl(webContents) + }); + try { + await requestNavigation(); + } catch (error) { + return navigationFailureResult("navigation_request_failed", { + errorDescription: formatError(error), + url: normalizeNavigationUrlForMatch(expectedUrl) || safeWebContentsUrl(webContents) + }); + } + return await readiness; + } + + const deadline = Date.now() + timeoutMs; + while (Date.now() <= deadline) { + if (webContents.isDestroyed()) { + return { matched: false, matchedEvent: "destroyed", timedOut: false }; + } + + if (!webContents.isLoading()) { + const readinessUrl = safeWebContentsUrl(webContents); + if (isChromiumErrorPage(readinessUrl)) { + return navigationFailureResult("chrome-error", { + errorDescription: "Chromium displayed an internal error page.", + url: readinessUrl + }); + } + if (waitUntil === "interactive") { + const interactive = await interactiveReadinessResult(webContents, readinessUrl); + if (interactive) { + return interactive; + } + } + if (waitUntil === "domcontentloaded") { + return { matched: true, matchedEvent: "domcontentloaded", readinessUrl, timedOut: false }; + } + if (waitUntil === "load") { + return { matched: true, matchedEvent: "load", readinessUrl, timedOut: false }; + } + if (waitUntil === "network_idle") { + const idle = await waitForNoLoadingStart(webContents, Math.min(500, Math.max(0, deadline - Date.now()))); + if (idle && !webContents.isDestroyed() && !webContents.isLoading()) { + return { matched: true, matchedEvent: "network_idle", readinessUrl: safeWebContentsUrl(webContents), timedOut: false }; + } + } + } + + const event = await waitForReadinessEvent(webContents, Math.max(0, deadline - Date.now())); + if (event.type === "timeout") { + break; + } + if (event.type === "destroyed") { + return { matched: false, matchedEvent: "destroyed", timedOut: false }; + } + if (event.type === "did-fail-load" && event.errorCode !== -3) { + return navigationFailureResult("did-fail-load", event); + } + const eventUrl = event.url || safeWebContentsUrl(webContents); + if (isChromiumErrorPage(eventUrl)) { + return navigationFailureResult("chrome-error", { + errorDescription: "Chromium displayed an internal error page.", + url: eventUrl + }); + } + if (waitUntil === "interactive" && isInteractiveReadinessEvent(event)) { + const interactive = await interactiveReadinessResult(webContents, eventUrl); + if (interactive) { + return interactive; + } + } + if (waitUntil === "domcontentloaded" && event.type === "dom-ready") { + return { matched: true, matchedEvent: "domcontentloaded", readinessUrl: eventUrl, timedOut: false }; + } + if (waitUntil === "load" && (event.type === "did-finish-load" || event.type === "did-stop-loading") && !webContents.isDestroyed() && !webContents.isLoading()) { + return { matched: true, matchedEvent: "load", readinessUrl: eventUrl, timedOut: false }; + } + if (waitUntil === "network_idle" && (event.type === "did-finish-load" || event.type === "did-stop-loading") && !webContents.isDestroyed() && !webContents.isLoading()) { + const idle = await waitForNoLoadingStart(webContents, Math.min(500, Math.max(0, deadline - Date.now()))); + if (idle && !webContents.isDestroyed() && !webContents.isLoading()) { + return { matched: true, matchedEvent: "network_idle", readinessUrl: safeWebContentsUrl(webContents), timedOut: false }; + } + } + } + if (waitUntil === "interactive" || waitUntil === "network_idle") { + const fallback = await interactiveReadinessResult( + webContents, + undefined, + waitUntil === "network_idle" ? "interactive_after_network_idle_timeout" : "interactive" + ); + if (fallback) { + return fallback; + } + } + return { + matched: false, + matchedEvent: webContents.isDestroyed() ? "destroyed" : undefined, + timedOut: !webContents.isDestroyed() + }; +} + +async function waitForNextReadinessEvent( + webContents: WebContents, + waitUntil: string, + timeoutMs: number, + context: NavigationReadinessContext +): Promise { + const deadline = Date.now() + timeoutMs; + let navigationStarted = false; + let lastNavigationUrl: string | undefined; + + while (Date.now() <= deadline) { + if (webContents.isDestroyed()) { + return { matched: false, matchedEvent: "destroyed", timedOut: false }; + } + + const event = await waitForReadinessEvent(webContents, Math.max(0, deadline - Date.now())); + if (event.type === "timeout") { + break; + } + if (event.type === "destroyed") { + return { matched: false, matchedEvent: "destroyed", timedOut: false }; + } + + const eventUrl = event.url || safeWebContentsUrl(webContents); + if (isNavigationStartEvent(event)) { + if (event.isMainFrame !== false && isRelevantNavigationUrl(eventUrl, context)) { + navigationStarted = true; + lastNavigationUrl = eventUrl; + } + continue; + } + + if (event.type === "did-fail-load") { + if (event.errorCode === -3) { + continue; + } + if (event.isMainFrame !== false && (navigationStarted || isRelevantNavigationUrl(eventUrl, context))) { + return navigationFailureResult("did-fail-load", event); + } + continue; + } + + if (!navigationStarted) { + continue; + } + + if (isChromiumErrorPage(eventUrl)) { + return navigationFailureResult("chrome-error", { + errorDescription: "Chromium displayed an internal error page.", + url: eventUrl + }); + } + if (waitUntil === "interactive" && isInteractiveReadinessEvent(event)) { + const interactive = await interactiveReadinessResult(webContents, eventUrl || lastNavigationUrl); + if (interactive) { + return interactive; + } + } + if (waitUntil === "domcontentloaded" && event.type === "dom-ready") { + return { matched: true, matchedEvent: "domcontentloaded", readinessUrl: eventUrl || lastNavigationUrl, timedOut: false }; + } + if (waitUntil === "load" && (event.type === "did-finish-load" || event.type === "did-stop-loading") && !webContents.isDestroyed() && !webContents.isLoading()) { + return { matched: true, matchedEvent: "load", readinessUrl: eventUrl || lastNavigationUrl, timedOut: false }; + } + if (waitUntil === "network_idle" && (event.type === "did-finish-load" || event.type === "did-stop-loading") && !webContents.isDestroyed() && !webContents.isLoading()) { + const idle = await waitForNoLoadingStart(webContents, Math.min(500, Math.max(0, deadline - Date.now()))); + if (idle && !webContents.isDestroyed() && !webContents.isLoading()) { + return { matched: true, matchedEvent: "network_idle", readinessUrl: safeWebContentsUrl(webContents) || lastNavigationUrl, timedOut: false }; + } + } + } + if (waitUntil === "interactive" || waitUntil === "network_idle") { + const fallback = await interactiveReadinessResult( + webContents, + lastNavigationUrl, + waitUntil === "network_idle" ? "interactive_after_network_idle_timeout" : "interactive" + ); + if (fallback && (navigationStarted || isRelevantNavigationUrl(fallback.readinessUrl, context))) { + return fallback; + } + } + return { + matched: false, + matchedEvent: webContents.isDestroyed() ? "destroyed" : undefined, + timedOut: !webContents.isDestroyed() + }; +} + +function waitForReadinessEvent(webContents: WebContents, timeoutMs: number): Promise { + return new Promise((resolve) => { + let settled = false; + const finish = (event: ReadinessEvent) => { + if (settled) { + return; + } + settled = true; + clearTimeout(timer); + webContents.off("did-navigate", onDidNavigate); + webContents.off("did-redirect-navigation", onDidRedirectNavigation); + webContents.off("did-fail-load", onDidFailLoad); + webContents.off("did-finish-load", onDidFinishLoad); + webContents.off("did-start-navigation", onDidStartNavigation); + webContents.off("did-start-loading", onDidStartLoading); + webContents.off("did-stop-loading", onDidStopLoading); + webContents.off("dom-ready", onDomReady); + webContents.off("destroyed", onDestroyed); + resolve(event); + }; + const onDidFailLoad = ( + _event: ElectronEvent, + errorCode: number, + errorDescription: string, + validatedUrl: string, + isMainFrame?: boolean + ) => finish({ + errorCode, + errorDescription, + isMainFrame, + type: "did-fail-load", + url: validatedUrl || safeWebContentsUrl(webContents) + }); + const onDidFinishLoad = () => finish({ type: "did-finish-load", url: safeWebContentsUrl(webContents) }); + const onDidNavigate = (_event: ElectronEvent, url: string) => finish({ isMainFrame: true, type: "did-navigate", url }); + const onDidRedirectNavigation = (_event: ElectronEvent, url: string, isInPlace?: boolean, isMainFrame?: boolean) => { + finish({ isMainFrame, type: isInPlace ? "did-navigate-in-page" : "did-redirect-navigation", url }); + }; + const onDidStartNavigation = (_event: ElectronEvent, url: string, isInPlace?: boolean, isMainFrame?: boolean) => { + finish({ isMainFrame, type: isInPlace ? "did-navigate-in-page" : "did-start-navigation", url }); + }; + const onDidStartLoading = () => finish({ type: "did-start-loading", url: safeWebContentsUrl(webContents) }); + const onDidStopLoading = () => finish({ type: "did-stop-loading", url: safeWebContentsUrl(webContents) }); + const onDomReady = () => finish({ type: "dom-ready", url: safeWebContentsUrl(webContents) }); + const onDestroyed = () => finish({ type: "destroyed" }); + const timer = setTimeout(() => finish({ type: "timeout" }), Math.max(0, timeoutMs)); + + webContents.once("did-navigate", onDidNavigate); + webContents.once("did-redirect-navigation", onDidRedirectNavigation); + webContents.once("did-fail-load", onDidFailLoad); + webContents.once("did-finish-load", onDidFinishLoad); + webContents.once("did-start-navigation", onDidStartNavigation); + webContents.once("did-start-loading", onDidStartLoading); + webContents.once("did-stop-loading", onDidStopLoading); + webContents.once("dom-ready", onDomReady); + webContents.once("destroyed", onDestroyed); + }); +} + +function waitForNoLoadingStart(webContents: WebContents, timeoutMs: number): Promise { + return new Promise((resolve) => { + let settled = false; + const finish = (idle: boolean) => { + if (settled) { + return; + } + settled = true; + clearTimeout(timer); + webContents.off("did-start-loading", onDidStartLoading); + webContents.off("destroyed", onDestroyed); + resolve(idle); + }; + const onDidStartLoading = () => finish(false); + const onDestroyed = () => finish(false); + const timer = setTimeout(() => finish(true), Math.max(0, timeoutMs)); + webContents.once("did-start-loading", onDidStartLoading); + webContents.once("destroyed", onDestroyed); + }); +} + +async function interactiveReadinessResult( + webContents: WebContents, + fallbackUrl?: string, + matchedEvent = "interactive" +): Promise { + if (webContents.isDestroyed()) { + return undefined; + } + const readinessUrl = safeWebContentsUrl(webContents) || fallbackUrl; + if (!isUsableInteractiveUrl(readinessUrl)) { + return undefined; + } + const readyState = await documentReadyState(webContents); + if (readyState === "interactive" || readyState === "complete" || !webContents.isLoading()) { + return { matched: true, matchedEvent, readinessUrl, timedOut: false }; + } + return undefined; +} + +async function documentReadyState(webContents: WebContents): Promise { + try { + const value = await executeJavaScriptWithTimeout( + webContents, + "document.readyState", + 1000, + "Document readiness check" + ); + return typeof value === "string" ? value : undefined; + } catch { + return undefined; + } +} + +function isInteractiveReadinessEvent(event: ReadinessEvent): boolean { + return event.type === "dom-ready" || + event.type === "did-finish-load" || + event.type === "did-stop-loading"; +} + +function isUsableInteractiveUrl(value: string | undefined): value is string { + return Boolean(value) && !isBlankPageUrl(value) && !isChromiumErrorPage(value); +} + +function navigationFailureResult(matchedEvent: string, event: Pick): ReadinessResult { + return { + matched: false, + matchedEvent, + navigationError: { + ...(event.errorCode !== undefined ? { errorCode: event.errorCode } : {}), + ...(event.errorDescription ? { errorDescription: event.errorDescription } : {}), + ...(event.url ? { url: event.url } : {}) + }, + readinessUrl: event.url, + timedOut: false + }; +} + +function isNavigationStartEvent(event: ReadinessEvent): boolean { + return event.type === "did-start-navigation" || + event.type === "did-redirect-navigation" || + event.type === "did-navigate"; +} + +function isRelevantNavigationUrl(url: string | undefined, context: NavigationReadinessContext): boolean { + if (!url) { + return false; + } + const normalizedUrl = normalizeUrlForComparison(url); + if (!normalizedUrl) { + return false; + } + if (context.expectedUrl && urlsMatchForNavigation(normalizedUrl, context.expectedUrl)) { + return true; + } + if (isChromiumErrorPage(normalizedUrl)) { + return true; + } + if (isBlankPageUrl(normalizedUrl)) { + return isBlankPageUrl(context.expectedUrl); + } + if (!context.previousUrl) { + return true; + } + return !urlsMatchForNavigation(normalizedUrl, context.previousUrl); +} + +function normalizeNavigationUrlForMatch(value: string | undefined): string | undefined { + const trimmed = value?.trim(); + if (!trimmed) { + return undefined; + } + if (isBlankPageUrl(trimmed) || isChromiumErrorPage(trimmed)) { + return trimmed; + } + if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) { + return normalizeUrlForComparison(trimmed); + } + if (/^(localhost|127\.0\.0\.1|\[::1\])(?::\d+)?(?:\/|$)/i.test(trimmed) || trimmed.includes(".")) { + return normalizeUrlForComparison(`https://${trimmed}`); + } + return normalizeUrlForComparison(`https://www.google.com/search?q=${encodeURIComponent(trimmed)}`); +} + +function normalizeUrlForComparison(value: string | undefined): string | undefined { + const trimmed = value?.trim(); + if (!trimmed) { + return undefined; + } + try { + const url = new URL(trimmed); + url.hash = ""; + return url.toString(); + } catch { + return trimmed; + } +} + +function urlsMatchForNavigation(left: string | undefined, right: string | undefined): boolean { + if (!left || !right) { + return false; + } + const normalizedLeft = normalizeUrlForComparison(left); + const normalizedRight = normalizeUrlForComparison(right); + if (!normalizedLeft || !normalizedRight) { + return false; + } + if (normalizedLeft === normalizedRight) { + return true; + } + try { + const leftUrl = new URL(normalizedLeft); + const rightUrl = new URL(normalizedRight); + return leftUrl.protocol === rightUrl.protocol && + leftUrl.hostname === rightUrl.hostname && + leftUrl.pathname === rightUrl.pathname && + leftUrl.search === rightUrl.search; + } catch { + return normalizedLeft === normalizedRight; + } +} + +function isBlankPageUrl(value: string | undefined): boolean { + return value === "about:blank"; +} + +function isChromiumErrorPage(value: string | undefined): boolean { + return value === "chrome-error://chromewebdata/"; +} + +function safeWebContentsUrl(webContents: WebContents): string | undefined { + if (webContents.isDestroyed()) { + return undefined; + } + try { + return webContents.getURL(); + } catch { + return undefined; + } +} + +function normalizeWaitUntil(value: string | undefined, fallback: string): string { + const normalized = (value || fallback).toLowerCase().replace(/[-\s]/g, "_"); + if (normalized === "auto" || normalized === "interactive" || normalized === "ready") { + return "interactive"; + } + if (normalized === "dom_ready" || normalized === "domcontentloaded") { + return "domcontentloaded"; + } + if (normalized === "networkidle" || normalized === "network_idle") { + return "network_idle"; + } + if (normalized === "none" || normalized === "load") { + return normalized; + } + return fallback; +} + +function readHandoffKind(value: unknown): BuiltInBrowserAutomationHandoffKind { + const kind = readString(value); + if ( + kind === "blocked" || + kind === "human_verification" || + kind === "login_required" || + kind === "other" || + kind === "verification_code" + ) { + return kind; + } + return "other"; +} + +function normalizeEventChannels(values: string[]): Set { + const normalized = values + .map((value) => value.toLowerCase().trim()) + .filter(Boolean); + if (normalized.length === 0) { + return new Set(["tab", "navigation", "dom", "runtime", "dialog", "download", "input", "focus", "selection", "handoff"]); + } + return new Set(normalized); +} + +function matchesSubscriptionEvent(subscription: EventSubscription, event: BrowserAutomationEvent): boolean { + const channel = eventChannel(event.kind); + if (!subscription.channels.has(channel) && !subscription.channels.has(event.kind) && !subscription.channels.has("*")) { + return false; + } + if (!subscription.ref) { + return true; + } + if (!event.tabId || event.tabId === subscription.ref.tabId) { + return true; + } + return channel === "tab"; +} + +function eventChannel(kind: string): string { + if (kind.startsWith("tab.")) return "tab"; + if (kind.startsWith("page.navigation") || kind.startsWith("page.loading")) return "navigation"; + if (kind === "page.dom_ready") return "dom"; + if (kind.startsWith("runtime.")) return "runtime"; + if (kind.startsWith("dialog.")) return "dialog"; + if (kind.startsWith("download.")) return "download"; + if (kind.startsWith("handoff.")) return "handoff"; + if (kind.startsWith("input.")) return "input"; + if (kind.startsWith("focus.")) return "focus"; + if (kind.startsWith("selection.")) return "selection"; + return kind.split(".")[0] || kind; +} + +function subscriptionDescriptor(subscription: EventSubscription): Record { + return { + channels: [...subscription.channels], + cursor: makeEventCursor(subscription), + redactTextInput: subscription.redactTextInput, + sampleMouseMove: subscription.sampleMouseMove, + session: subscription.ref, + startedAt: subscription.startedAt, + subscriptionId: subscription.subscriptionId + }; +} + +function findRecentHandoffResolution(filter: { handoffId?: string; tabId?: string }): BrowserAutomationEvent | undefined { + const events = builtInBrowserService.getAutomationEvents({ replayRecentMs: automationEventReplayMs }); + for (let index = events.length - 1; index >= 0; index -= 1) { + const event = events[index]; + if (event.kind !== "handoff.completed" && event.kind !== "handoff.dismissed") { + continue; + } + if (filter.handoffId && event.handoffId !== filter.handoffId) { + continue; + } + if (filter.tabId && event.tabId !== filter.tabId) { + continue; + } + return event; + } + return undefined; +} + +function handoffResolutionResult( + event: BrowserAutomationEvent, + handoff: BuiltInBrowserAutomationHandoff | undefined, + timedOut: boolean +): Record { + return { + event, + handoff, + matched: !timedOut, + state: browserWindowState(), + status: event.handoffStatus || (event.kind === "handoff.completed" ? "completed" : "dismissed"), + timedOut, + title: event.title, + url: event.url + }; +} + +function eventsAfterCursor(subscription: EventSubscription, cursorSeq?: number): BrowserAutomationEvent[] { + const seq = cursorSeq ?? 0; + return subscription.events.filter((event) => event.seq > seq); +} + +function cursorDropped(subscription: EventSubscription, cursorSeq?: number): boolean { + if (cursorSeq === undefined || subscription.events.length === 0) { + return subscription.dropped; + } + return subscription.dropped && cursorSeq < subscription.events[0].seq - 1; +} + +function makeEventCursor(subscription: EventSubscription, seq?: number): Record { + const lastEvent = subscription.events.findLast((event) => seq === undefined || event.seq <= seq) || subscription.events.at(-1); + return { + dropped: subscription.dropped, + seq: seq ?? lastEvent?.seq ?? 0, + subscriptionId: subscription.subscriptionId, + ts: lastEvent?.ts ?? subscription.startedAt + }; +} + +function readCursorSeq(value: unknown): number | undefined { + if (!isRecord(value)) { + return undefined; + } + return readNumber(value.seq); +} + +function matchesAwaitFilter(event: BrowserAutomationEvent, args: Record): boolean { + const match = isRecord(args.match) ? args.match : args; + const kinds = readStringArray(match.kinds); + const kind = readString(match.kind); + if (kind && event.kind !== kind) { + return false; + } + if (kinds.length > 0 && !kinds.includes(event.kind)) { + return false; + } + const tabId = readString(match.tabId); + if (tabId && event.tabId !== tabId) { + return false; + } + const windowId = readString(match.windowId); + if (windowId && event.windowId !== windowId) { + return false; + } + return stringMatchesPattern(event.url, readString(match.urlPattern)) + && stringMatchesPattern(event.title, readString(match.titlePattern)) + && stringMatchesPattern(event.summary, readString(match.summaryPattern)); +} + +function stringMatchesPattern(value: string | undefined, pattern: string | undefined): boolean { + if (!pattern) { + return true; + } + const haystack = value || ""; + try { + return new RegExp(pattern).test(haystack); + } catch { + return haystack.toLowerCase().includes(pattern.toLowerCase()); + } +} + +async function handleJavaScriptDialog( + webContents: WebContents, + accept: boolean, + promptText: string | undefined, + session: BrowserSessionRef +): Promise> { + let attachedHere = false; + try { + if (!webContents.debugger.isAttached()) { + webContents.debugger.attach("1.3"); + attachedHere = true; + } + await withTimeout(webContents.debugger.sendCommand("Page.enable"), defaultJavascriptTimeoutMs, "Timed out enabling browser dialog handling."); + await withTimeout( + webContents.debugger.sendCommand("Page.handleJavaScriptDialog", { + accept, + ...(promptText !== undefined ? { promptText } : {}) + }), + defaultJavascriptTimeoutMs, + "Timed out handling browser dialog." + ); + return { + accepted: accept, + ok: true, + promptText, + session, + title: await webContents.getTitle(), + url: webContents.getURL() + }; + } catch (error) { + throw new Error(`Failed to handle browser dialog: ${formatError(error)}`); + } finally { + if (attachedHere && webContents.debugger.isAttached()) { + try { + webContents.debugger.detach(); + } catch { + // Ignore detach errors after the dialog is resolved. + } + } + } +} + +async function executeJavaScriptWithTimeout( + webContents: WebContents, + script: string, + timeoutMs: number, + label: string +): Promise { + if (webContents.isDestroyed()) { + throw new Error(`${label} failed because the browser tab is destroyed.`); + } + return await withTimeout( + webContents.executeJavaScript(script, true) as Promise, + timeoutMs, + `${label} timed out after ${timeoutMs}ms.` + ); +} + +async function withTimeout(promise: Promise, timeoutMs: number, message: string): Promise { + let timer: ReturnType | undefined; + try { + return await Promise.race([ + promise, + new Promise((_resolve, reject) => { + timer = setTimeout(() => reject(new Error(message)), Math.max(0, timeoutMs)); + }) + ]); + } finally { + if (timer) { + clearTimeout(timer); + } + } +} + +async function captureSnapshot(webContents: WebContents, options: { maxElements: number; maxText: number }): Promise { + return await executeJavaScriptWithTimeout( + webContents, + `(${snapshotScript})(${JSON.stringify(options)})`, + defaultJavascriptTimeoutMs, + "Browser snapshot" + ) as unknown; +} + +async function captureSnapshotWithHandoff( + webContents: WebContents, + tabId: string | undefined, + options: { maxElements: number; maxText: number } +): Promise { + const snapshot = await captureSnapshot(webContents, options); + const handoff = await maybeRequestPageHandoff(webContents, { + snapshot, + tabId + }); + if (!handoff || !isRecord(snapshot)) { + return snapshot; + } + return { + ...snapshot, + ...humanHelpResultFields(handoff) + }; +} + +async function maybeRequestHandoffAfterNavigation( + webContents: WebContents, + readiness: ReadinessResult, + options: { + requestedUrl?: string; + session?: BrowserSessionRef; + tabId?: string; + waitUntil?: string; + } +): Promise { + if (webContents.isDestroyed()) { + return undefined; + } + + if (!readiness.navigationError) { + const pageHandoff = await maybeRequestPageHandoff(webContents, { + session: options.session, + tabId: options.tabId + }); + if (pageHandoff) { + return pageHandoff; + } + } + + return maybeRequestNavigationHandoff(webContents, readiness, options); +} + +function maybeRequestNavigationHandoff( + webContents: WebContents, + readiness: ReadinessResult, + options: { + requestedUrl?: string; + session?: BrowserSessionRef; + tabId?: string; + waitUntil?: string; + } +): BuiltInBrowserAutomationHandoff | undefined { + if (readiness.matched || webContents.isDestroyed()) { + return undefined; + } + + const tabId = options.tabId || options.session?.tabId; + const existing = existingHandoffForTab(tabId); + if (existing) { + return existing; + } + + const currentUrl = safeWebContentsUrl(webContents); + const target = options.requestedUrl || readiness.readinessUrl || currentUrl || "the requested page"; + const error = readiness.navigationError; + const reason = error + ? `Browser navigation was blocked or failed while opening ${target}${error.errorCode !== undefined ? ` (error ${error.errorCode})` : ""}${error.errorDescription ? `: ${error.errorDescription}` : "."}` + : `Browser automation did not reach ${options.waitUntil || "the requested readiness state"} while opening ${target}.`; + + return builtInBrowserService.requestAutomationHandoff({ + kind: "blocked", + message: "Please inspect this browser page and complete or retry the blocked step, then click Done.", + reason, + sessionId: options.session?.sessionId, + tabId + }); +} + +async function maybeRequestPageHandoff( + webContents: WebContents, + options: { + session?: BrowserSessionRef; + snapshot?: unknown; + tabId?: string; + } = {} +): Promise { + if (webContents.isDestroyed()) { + return undefined; + } + + let snapshot = options.snapshot; + if (snapshot === undefined) { + try { + snapshot = await captureSnapshot(webContents, { + maxElements: 20, + maxText: 1200 + }); + } catch { + snapshot = undefined; + } + } + + const detection = detectPageHandoff(snapshot, { + title: await webContents.getTitle(), + url: webContents.getURL() + }); + if (!detection) { + return undefined; + } + + const tabId = options.tabId || options.session?.tabId; + const existing = existingHandoffForTab(tabId); + if (existing) { + return existing; + } + + return builtInBrowserService.requestAutomationHandoff({ + kind: detection.kind, + message: detection.message, + reason: detection.reason, + sessionId: options.session?.sessionId, + tabId + }); +} + +function existingHandoffForTab(tabId: string | undefined): BuiltInBrowserAutomationHandoff | undefined { + const existing = builtInBrowserService.getAutomationState().automationHandoff; + if (existing && (!tabId || existing.tabId === tabId)) { + return existing; + } + return undefined; +} + +function handoffMatches( + handoff: BuiltInBrowserAutomationHandoff | undefined, + filter: { handoffId?: string; tabId?: string } +): boolean { + if (!handoff) { + return false; + } + if (filter.handoffId && handoff.id !== filter.handoffId) { + return false; + } + if (filter.tabId && handoff.tabId !== filter.tabId) { + return false; + } + return true; +} + +function detectPageHandoff(snapshot: unknown, fallback: { title?: string; url?: string }): BrowserHandoffDetection | undefined { + const record = isRecord(snapshot) ? snapshot : {}; + const title = readString(record.title) || fallback.title || ""; + const url = readString(record.url) || fallback.url || ""; + const signal = pageHandoffSignal(record, title, url); + if (isChromiumErrorPage(url)) { + return { + kind: "blocked", + message: "Please inspect this browser error page and retry or complete the blocked step, then click Done.", + reason: "Chromium displayed an internal error page for this navigation." + }; + } + if (!signal) { + return undefined; + } + + const verificationCode = /verification code|security code|one[-\s]?time code|2[-\s]?step|two[-\s]?step|authenticator|check your (?:phone|email)|enter the code/.test(signal); + if (verificationCode) { + return { + kind: "verification_code", + message: "Please enter the verification code in this browser window, then click Done.", + reason: `The page appears to require a verification code: ${title || url || "current page"}.` + }; + } + + const cloudflare = signal.includes("cloudflare") || /\bray id\s*:/.test(signal); + const botVerification = /performing security verification|security verification|verif(?:y|ies|ying)[^.\n]{0,80}(?:not a bot|human)|not a bot|not a robot|human verification|are you human|checking your browser|security check/.test(signal); + const challenge = /captcha|hcaptcha|recaptcha|turnstile|cf-challenge|cf-turnstile/.test(signal); + const justAMoment = title.trim().toLowerCase() === "just a moment..." || signal.includes("just a moment..."); + if (challenge || (cloudflare && (botVerification || justAMoment))) { + return { + kind: "human_verification", + message: "Please complete the security verification in this browser window, then click Done.", + reason: `The page appears to be a human verification or bot-protection challenge: ${title || url || "current page"}.` + }; + } + + const loginUrl = /(?:^|[/.?=&_-])(?:login|signin|sign-in|accounts)(?:[/.?=&_-]|$)/.test(url.toLowerCase()) || + /accounts\.google\.com|mail\.google\.com/.test(url.toLowerCase()); + const loginTitle = /\bsign in\b|\blog in\b|\blogin\b|google accounts|account login/.test(title.toLowerCase()); + const credentialFields = /email or phone|email address|username|password|forgot (?:email|password)|create account|\bnext\b/.test(signal); + if ((loginUrl || loginTitle) && credentialFields) { + return { + kind: "login_required", + message: "Please sign in in this browser window, then click Done.", + reason: `The page appears to require a human login: ${title || url || "current page"}.` + }; + } + + return undefined; +} + +function humanHelpResultFields(handoff: BuiltInBrowserAutomationHandoff): Record { + return { + handoff, + handoffRequired: true, + humanHelp: { + aliasTool: "askHumanHelp", + instruction: "Browser automation needs human help. The browser window has been shown with a top toolbar instruction. Call browser_handoff_wait to receive the user's Done/Hide result before continuing automation.", + handoffId: handoff.id, + message: handoff.message, + reason: handoff.reason, + required: true, + status: "requested", + tool: "browser_handoff_wait" + }, + humanHelpRequired: true, + nextAction: "human_help" + }; +} + +function pageHandoffSignal(record: Record, title: string, url: string): string { + const parts = [title, url, rawString(record.text)]; + const activeElement = isRecord(record.activeElement) ? record.activeElement : undefined; + if (activeElement) { + parts.push(rawString(activeElement.name), rawString(activeElement.text), rawString(activeElement.role)); + } + const elements = Array.isArray(record.elements) ? record.elements : []; + for (const element of elements.slice(0, 40)) { + if (!isRecord(element)) { + continue; + } + parts.push( + rawString(element.name), + rawString(element.text), + rawString(element.role), + rawString(element.tag), + rawString(element.href) + ); + } + return parts + .filter((part): part is string => Boolean(part)) + .join("\n") + .replace(/\s+/g, " ") + .toLowerCase(); +} + +async function runTargetAction( + webContents: WebContents, + action: "click" | "focus" | "scroll" | "select" | "type", + target?: BrowserTarget, + value: Record = {} +): Promise { + const result = await executeJavaScriptWithTimeout( + webContents, + `(${targetActionScript})(${JSON.stringify({ action, target, value })})`, + defaultJavascriptTimeoutMs, + `Browser target action ${action}` + ) as { error?: string; ok?: boolean }; + if (!result?.ok) { + throw new Error(result?.error || `Browser target action failed: ${action}`); + } + return result; +} + +async function pressKey(webContents: WebContents, key: string, target?: BrowserTarget): Promise { + if (target) { + await runTargetAction(webContents, "focus", target); + } + webContents.sendInputEvent({ keyCode: key, type: "keyDown" }); + webContents.sendInputEvent({ keyCode: key, type: "keyUp" }); + return { + key, + ok: true, + title: await webContents.getTitle(), + url: webContents.getURL() + }; +} + +async function waitForPageCondition( + webContents: WebContents, + condition: { + selector?: string; + text?: string; + timeoutMs: number; + urlIncludes?: string; + urlMatches?: string; + } +): Promise { + const deadline = Date.now() + condition.timeoutMs; + let last: unknown; + while (Date.now() <= deadline) { + try { + last = await executeJavaScriptWithTimeout( + webContents, + `(${waitForConditionScript})(${JSON.stringify(condition)})`, + Math.max(100, Math.min(1000, deadline - Date.now())), + "Browser wait condition check" + ) as unknown; + if (isRecord(last) && last.matched === true) { + return last; + } + } catch (error) { + last = { + error: formatError(error), + matched: false + }; + } + await sleep(150); + } + return { + last, + matched: false, + timedOut: true, + title: await webContents.getTitle(), + url: webContents.getURL() + }; +} + +const snapshotScript = function(options: { maxElements: number; maxText: number }) { + const maxElements = Math.max(1, Math.min(300, Math.floor(options.maxElements || 80))); + const maxText = Math.max(0, Math.min(20000, Math.floor(options.maxText || 3000))); + const refAttribute = "data-ccr-browser-ref"; + const elementSelector = [ + "a[href]", + "button", + "input", + "textarea", + "select", + "summary", + "[role]", + "[contenteditable='true']", + "[contenteditable='plaintext-only']", + "[tabindex]:not([tabindex='-1'])" + ].join(","); + + function visible(el: Element) { + const style = window.getComputedStyle(el); + const rect = el.getBoundingClientRect(); + return style.display !== "none" && + style.visibility !== "hidden" && + Number(style.opacity || "1") > 0 && + rect.width > 0 && + rect.height > 0; + } + + function cssEscape(value: string) { + return window.CSS && typeof window.CSS.escape === "function" + ? window.CSS.escape(value) + : value.replace(/[^a-zA-Z0-9_-]/g, "\\$&"); + } + + function uniqueIdSelector(id: string) { + if (!id) return ""; + const selector = `#${cssEscape(id)}`; + try { + return document.querySelectorAll(selector).length === 1 ? selector : ""; + } catch { + return ""; + } + } + + function refFor(el: Element) { + const win = window as Window & { __ccrBrowserRefSeq?: number }; + const existing = el.getAttribute(refAttribute); + if (existing) { + return `[${refAttribute}="${cssEscape(existing)}"]`; + } + for (let attempt = 0; attempt < 1000; attempt += 1) { + win.__ccrBrowserRefSeq = (win.__ccrBrowserRefSeq || 0) + 1; + const nextRef = `ccr-${win.__ccrBrowserRefSeq}`; + const selector = `[${refAttribute}="${cssEscape(nextRef)}"]`; + if (!document.querySelector(selector)) { + el.setAttribute(refAttribute, nextRef); + return selector; + } + } + + const idSelector = uniqueIdSelector((el as HTMLElement).id || ""); + if (idSelector) return idSelector; + const parts: string[] = []; + let current: Element | null = el; + while (current && current.nodeType === 1 && current !== document.documentElement && parts.length < 8) { + const tag = current.tagName.toLowerCase(); + const parent: HTMLElement | null = current.parentElement; + if (!parent) { + parts.unshift(tag); + break; + } + const parentIdSelector = uniqueIdSelector(parent.id || ""); + const sameTag = Array.from(parent.children).filter((child): child is Element => child instanceof Element && child.tagName === current!.tagName); + const index = sameTag.indexOf(current) + 1; + parts.unshift(`${tag}:nth-of-type(${Math.max(index, 1)})`); + if (parentIdSelector) { + parts.unshift(parentIdSelector); + break; + } + current = parent; + } + return parts.join(" > "); + } + + function text(el: Element) { + return ((el as HTMLElement).innerText || el.textContent || "").replace(/\s+/g, " ").trim(); + } + + function labelText(el: Element) { + const labelledBy = el.getAttribute("aria-labelledby"); + if (labelledBy) { + const value = labelledBy + .split(/\s+/) + .map((id) => document.getElementById(id)?.innerText || "") + .join(" ") + .replace(/\s+/g, " ") + .trim(); + if (value) return value; + } + const id = (el as HTMLInputElement).id; + if (id) { + const label = document.querySelector(`label[for="${cssEscape(id)}"]`); + if (label) return text(label); + } + const wrappingLabel = el.closest("label"); + return wrappingLabel ? text(wrappingLabel) : ""; + } + + function unsafeField(el: Element) { + const input = el as HTMLInputElement; + const haystack = [ + input.type, + input.name, + input.id, + input.autocomplete, + el.getAttribute("aria-label"), + labelText(el) + ].join(" ").toLowerCase(); + return /\b(password|secret|token|api[-_ ]?key|bearer|credential)\b/.test(haystack); + } + + function valueOf(el: Element) { + if (!(el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement)) { + return ""; + } + if (unsafeField(el)) { + return ""; + } + return el.value || ""; + } + + function roleOf(el: Element) { + const explicit = el.getAttribute("role"); + if (explicit) return explicit; + const tag = el.tagName.toLowerCase(); + const input = el as HTMLInputElement; + if (tag === "a") return "link"; + if (tag === "button") return "button"; + if (tag === "textarea") return "textbox"; + if (tag === "select") return "combobox"; + if (tag === "summary") return "button"; + if (tag === "input") { + if (["button", "submit", "reset"].includes(input.type)) return "button"; + if (input.type === "checkbox") return "checkbox"; + if (input.type === "radio") return "radio"; + if (input.type === "range") return "slider"; + return "textbox"; + } + if (el.getAttribute("contenteditable")) return "textbox"; + return ""; + } + + function nameOf(el: Element) { + const input = el as HTMLInputElement; + return ( + el.getAttribute("aria-label") || + labelText(el) || + el.getAttribute("alt") || + el.getAttribute("title") || + input.placeholder || + (input.type && ["button", "submit", "reset"].includes(input.type) ? input.value : "") || + text(el) + ).replace(/\s+/g, " ").trim(); + } + + const elements = Array.from(document.querySelectorAll(elementSelector)) + .filter(visible) + .slice(0, maxElements) + .map((el, index) => { + const rect = el.getBoundingClientRect(); + return { + checked: el instanceof HTMLInputElement && ["checkbox", "radio"].includes(el.type) ? el.checked : undefined, + disabled: (el as HTMLButtonElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement).disabled || undefined, + href: el instanceof HTMLAnchorElement ? el.href : undefined, + index, + name: nameOf(el).slice(0, 240), + placeholder: (el as HTMLInputElement).placeholder || undefined, + rect: { + height: Math.round(rect.height), + width: Math.round(rect.width), + x: Math.round(rect.x), + y: Math.round(rect.y) + }, + ref: refFor(el), + role: roleOf(el), + tag: el.tagName.toLowerCase(), + text: text(el).slice(0, 180), + value: valueOf(el) || undefined + }; + }); + + return { + activeElement: document.activeElement ? { + name: nameOf(document.activeElement).slice(0, 240), + ref: refFor(document.activeElement), + role: roleOf(document.activeElement), + tag: document.activeElement.tagName.toLowerCase() + } : undefined, + elements, + text: (document.body?.innerText || "").replace(/\s+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim().slice(0, maxText), + title: document.title, + url: location.href + }; +}; + +const targetActionScript = function(request: { + action: "click" | "focus" | "scroll" | "select" | "type"; + target?: BrowserTarget; + value?: Record; +}) { + function visible(el: Element) { + const style = window.getComputedStyle(el); + const rect = el.getBoundingClientRect(); + return style.display !== "none" && style.visibility !== "hidden" && rect.width > 0 && rect.height > 0; + } + function text(el: Element) { + return ((el as HTMLElement).innerText || el.textContent || "").replace(/\s+/g, " ").trim(); + } + function cssEscape(value: string) { + return window.CSS && typeof window.CSS.escape === "function" + ? window.CSS.escape(value) + : value.replace(/[^a-zA-Z0-9_-]/g, "\\$&"); + } + function labelText(el: Element) { + const id = (el as HTMLInputElement).id; + if (id) { + const label = document.querySelector(`label[for="${cssEscape(id)}"]`); + if (label) return text(label); + } + const wrappingLabel = el.closest("label"); + return wrappingLabel ? text(wrappingLabel) : ""; + } + function roleOf(el: Element) { + const explicit = el.getAttribute("role"); + if (explicit) return explicit.toLowerCase(); + const tag = el.tagName.toLowerCase(); + const input = el as HTMLInputElement; + if (tag === "a") return "link"; + if (tag === "button") return "button"; + if (tag === "textarea") return "textbox"; + if (tag === "select") return "combobox"; + if (tag === "summary") return "button"; + if (tag === "input") { + if (["button", "submit", "reset"].includes(input.type)) return "button"; + if (input.type === "checkbox") return "checkbox"; + if (input.type === "radio") return "radio"; + return "textbox"; + } + if (el.getAttribute("contenteditable")) return "textbox"; + return ""; + } + function nameOf(el: Element) { + const input = el as HTMLInputElement; + return ( + el.getAttribute("aria-label") || + labelText(el) || + el.getAttribute("alt") || + el.getAttribute("title") || + input.placeholder || + input.value || + text(el) + ).replace(/\s+/g, " ").trim(); + } + function summarize(el: Element) { + const rect = el.getBoundingClientRect(); + return { + name: nameOf(el).slice(0, 240), + rect: { + height: Math.round(rect.height), + width: Math.round(rect.width), + x: Math.round(rect.x), + y: Math.round(rect.y) + }, + role: roleOf(el), + tag: el.tagName.toLowerCase(), + text: text(el).slice(0, 180) + }; + } + function selectorElement(selector: string) { + try { + return document.querySelector(selector); + } catch { + return null; + } + } + function matchesText(value: string, needle: string, exact?: boolean) { + const left = value.trim().toLowerCase(); + const right = needle.trim().toLowerCase(); + return exact ? left === right : left.includes(right); + } + function findTarget(target?: BrowserTarget) { + if (!target && request.action !== "scroll") return null; + const directSelector = target?.selector || target?.ref || target?.axNodeId; + if (directSelector) { + return selectorElement(directSelector); + } + const selector = [ + "a[href]", + "button", + "input", + "textarea", + "select", + "summary", + "[role]", + "[contenteditable='true']", + "[contenteditable='plaintext-only']", + "[tabindex]:not([tabindex='-1'])" + ].join(","); + const candidates = Array.from(document.querySelectorAll(selector)).filter(visible); + const role = target?.role?.trim().toLowerCase(); + const needle = target?.text?.trim(); + const matches = candidates.filter((el) => { + if (role && roleOf(el) !== role) return false; + if (!needle) return true; + const haystack = [ + nameOf(el), + text(el), + (el as HTMLInputElement).placeholder || "", + (el as HTMLInputElement).value || "" + ].join(" "); + return matchesText(haystack, needle, target?.exact); + }); + return matches[Math.max(0, Math.floor(target?.index || 0))] || null; + } + function dispatchValueEvents(el: Element) { + el.dispatchEvent(new Event("input", { bubbles: true })); + el.dispatchEvent(new Event("change", { bubbles: true })); + } + + const el = findTarget(request.target); + if (!el && request.action !== "scroll") { + return { error: "No matching browser element found.", ok: false }; + } + if (el) { + el.scrollIntoView({ block: "center", inline: "center" }); + if (el instanceof HTMLElement) { + el.focus({ preventScroll: true }); + } + } + + if (request.action === "focus") { + return { element: el ? summarize(el) : undefined, ok: true }; + } + + if (request.action === "click") { + if (!(el instanceof HTMLElement)) { + return { error: "Matched element is not clickable.", ok: false }; + } + el.click(); + return { element: summarize(el), ok: true }; + } + + if (request.action === "type") { + const nextText = typeof request.value?.text === "string" ? request.value.text : ""; + const replaceExisting = request.value?.replaceExisting !== false; + if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) { + el.value = replaceExisting ? nextText : `${el.value}${nextText}`; + dispatchValueEvents(el); + return { element: summarize(el), ok: true, value: el.type === "password" ? "" : el.value }; + } + if (el instanceof HTMLElement && el.isContentEditable) { + if (replaceExisting) { + el.textContent = nextText; + } else { + el.textContent = `${el.textContent || ""}${nextText}`; + } + dispatchValueEvents(el); + return { element: summarize(el), ok: true }; + } + return { error: "Matched element cannot receive typed text.", ok: false }; + } + + if (request.action === "select") { + if (!(el instanceof HTMLSelectElement)) { + return { error: "Matched element is not a select.", ok: false }; + } + const requestedValue = typeof request.value?.value === "string" ? request.value.value : ""; + const requestedLabel = typeof request.value?.label === "string" ? request.value.label : ""; + const exact = request.value?.exact === true; + const option = Array.from(el.options).find((candidate) => { + if (requestedValue && candidate.value === requestedValue) return true; + if (!requestedLabel) return false; + return matchesText(candidate.textContent || "", requestedLabel, exact); + }); + if (!option) { + return { error: "No matching select option found.", ok: false }; + } + el.value = option.value; + dispatchValueEvents(el); + return { element: summarize(el), label: option.textContent || "", ok: true, value: option.value }; + } + + if (request.action === "scroll") { + const deltaX = typeof request.value?.deltaX === "number" ? request.value.deltaX : 0; + const deltaY = typeof request.value?.deltaY === "number" ? request.value.deltaY : 700; + if (el instanceof HTMLElement) { + el.scrollBy({ behavior: "auto", left: deltaX, top: deltaY }); + return { element: summarize(el), ok: true }; + } + window.scrollBy({ behavior: "auto", left: deltaX, top: deltaY }); + return { ok: true, scrollX: window.scrollX, scrollY: window.scrollY }; + } + + return { error: "Unsupported browser target action.", ok: false }; +}; + +const waitForConditionScript = function(condition: { + selector?: string; + text?: string; + urlIncludes?: string; + urlMatches?: string; +}) { + const checks: Array<{ matched: boolean; type: string }> = []; + if (condition.urlIncludes) { + checks.push({ matched: location.href.includes(condition.urlIncludes), type: "urlIncludes" }); + } + if (condition.urlMatches) { + let matched = false; + try { + matched = new RegExp(condition.urlMatches).test(location.href); + } catch { + matched = false; + } + checks.push({ matched, type: "urlMatches" }); + } + if (condition.selector) { + let matched = false; + try { + const el = document.querySelector(condition.selector); + if (el) { + const style = window.getComputedStyle(el); + const rect = el.getBoundingClientRect(); + matched = style.display !== "none" && style.visibility !== "hidden" && rect.width > 0 && rect.height > 0; + } + } catch { + matched = false; + } + checks.push({ matched, type: "selector" }); + } + if (condition.text) { + const pageText = (document.body?.innerText || "").toLowerCase(); + checks.push({ matched: pageText.includes(condition.text.toLowerCase()), type: "text" }); + } + return { + checks, + matched: checks.length > 0 && checks.every((check) => check.matched), + title: document.title, + url: location.href + }; +}; + +function readTarget(value: unknown): BrowserTarget { + if (!isRecord(value)) { + throw new Error("A browser target object is required."); + } + return { + axNodeId: readString(value.axNodeId), + backendNodeId: readNumber(value.backendNodeId), + exact: value.exact === true, + index: clampInteger(readNumber(value.index) ?? 0, 0, 10000), + ref: readString(value.ref) || readString(value.axNodeId), + role: readString(value.role), + selector: readString(value.selector), + text: readString(value.text) + }; +} + +function requiredString(value: unknown, message: string): string { + const text = readString(value); + if (!text) { + throw new Error(message); + } + return text; +} + +function readString(value: unknown): string | undefined { + return typeof value === "string" && value.trim() ? value.trim() : undefined; +} + +function readStringArray(value: unknown): string[] { + return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string" && item.trim().length > 0) : []; +} + +function uniqueStrings(values: string[]): string[] { + return [...new Set(values)]; +} + +function readNumber(value: unknown): number | undefined { + return typeof value === "number" && Number.isFinite(value) ? value : undefined; +} + +function clampInteger(value: number, min: number, max: number): number { + return Math.min(max, Math.max(min, Math.trunc(value))); +} + +function objectSchema(properties: Record, required: string[] = []): JsonValue { + return { + additionalProperties: false, + properties, + required, + type: "object" + }; +} + +type CompactResultOptions = { + maxArrayItems: number; + maxDepth: number; + maxObjectKeys: number; + maxStringChars: number; +}; + +function formatToolResult(toolName: string, result: unknown): string { + const compacted = compactToolResult(toolName, result); + const serialized = stringifyToolResult(compacted); + if (serialized.length <= maxToolResultChars) { + return serialized; + } + + const tighter = compactJsonValue(compacted, { + maxArrayItems: 50, + maxDepth: 6, + maxObjectKeys: 80, + maxStringChars: 600 + }); + const tightSerialized = stringifyToolResult({ + guidance: largeResultGuidance(toolName), + maxChars: maxToolResultChars, + originalChars: serialized.length, + result: tighter, + tool: toolName, + truncated: true + }); + if (tightSerialized.length <= maxToolResultChars) { + return tightSerialized; + } + + return stringifyToolResult({ + guidance: largeResultGuidance(toolName), + maxChars: maxToolResultChars, + originalChars: serialized.length, + preview: truncateString(tightSerialized, 12_000), + tool: toolName, + truncated: true + }); +} + +function compactToolResult(toolName: string, result: unknown): unknown { + if (toolName === "browser_snapshot") { + return compactSnapshotResult(result); + } + if (toolName === "browser_ax_snapshot") { + return compactAxSnapshotResult(result); + } + if (toolName === "browser_ax_query") { + return compactArrayResult(result, "matches", 80, 600); + } + if (toolName === "browser_events_read" || toolName === "browser_events_await") { + return compactArrayResult(result, "events", 80, 800); + } + return compactJsonValue(result, defaultCompactResultOptions()); +} + +function compactSnapshotResult(result: unknown): unknown { + if (!isRecord(result)) { + return compactJsonValue(result, defaultCompactResultOptions()); + } + + const elements = Array.isArray(result.elements) ? result.elements : []; + const text = rawString(result.text); + const compacted: Record = {}; + for (const [key, value] of Object.entries(result)) { + if (key === "elements" || key === "text") { + continue; + } + compacted[key] = compactJsonValue(value, { + maxArrayItems: 40, + maxDepth: 5, + maxObjectKeys: 60, + maxStringChars: 600 + }); + } + + if (text !== undefined) { + compacted.text = truncateString(text, maxBrowserResultTextChars); + } + compacted.elements = elements + .slice(0, maxSnapshotResultElements) + .map((element) => compactJsonValue(element, { + maxArrayItems: 20, + maxDepth: 4, + maxObjectKeys: 40, + maxStringChars: 500 + })); + compacted.elementCount = elements.length; + + const truncated: Record = {}; + if (text !== undefined && text.length > maxBrowserResultTextChars) { + truncated.textChars = text.length - maxBrowserResultTextChars; + } + if (elements.length > maxSnapshotResultElements) { + truncated.elements = elements.length - maxSnapshotResultElements; + } + if (Object.keys(truncated).length > 0) { + compacted.truncated = truncated; + } + return compacted; +} + +function compactAxSnapshotResult(result: unknown): unknown { + if (!isRecord(result)) { + return compactJsonValue(result, defaultCompactResultOptions()); + } + + const nodes = Array.isArray(result.nodes) ? result.nodes : []; + const compacted: Record = {}; + for (const [key, value] of Object.entries(result)) { + if (key === "nodes") { + continue; + } + compacted[key] = compactJsonValue(value, { + maxArrayItems: 40, + maxDepth: 5, + maxObjectKeys: 60, + maxStringChars: 400 + }); + } + compacted.nodes = nodes + .slice(0, maxAxSnapshotResultNodes) + .map((node) => compactJsonValue(node, { + maxArrayItems: 80, + maxDepth: 5, + maxObjectKeys: 40, + maxStringChars: 260 + })); + compacted.nodeCount = nodes.length; + if (nodes.length > maxAxSnapshotResultNodes) { + compacted.truncated = { + nodes: nodes.length - maxAxSnapshotResultNodes + }; + } + return compacted; +} + +function compactArrayResult(result: unknown, key: string, maxItems: number, maxStringChars: number): unknown { + if (!isRecord(result)) { + return compactJsonValue(result, defaultCompactResultOptions()); + } + + const items = Array.isArray(result[key]) ? result[key] : []; + const compacted: Record = {}; + for (const [entryKey, value] of Object.entries(result)) { + if (entryKey === key) { + continue; + } + compacted[entryKey] = compactJsonValue(value, { + maxArrayItems: 40, + maxDepth: 5, + maxObjectKeys: 60, + maxStringChars + }); + } + compacted[key] = items + .slice(0, maxItems) + .map((item) => compactJsonValue(item, { + maxArrayItems: 40, + maxDepth: 5, + maxObjectKeys: 60, + maxStringChars + })); + compacted[`${key}Count`] = items.length; + if (items.length > maxItems) { + compacted.truncated = { + [key]: items.length - maxItems + }; + } + return compacted; +} + +function compactJsonValue( + value: unknown, + options: CompactResultOptions, + depth = 0, + seen: WeakSet = new WeakSet() +): unknown { + if (value === null || typeof value === "boolean" || typeof value === "number") { + return value; + } + if (typeof value === "string") { + return truncateString(value, options.maxStringChars); + } + if (typeof value === "bigint") { + return value.toString(); + } + if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") { + return undefined; + } + if (typeof value !== "object") { + return String(value); + } + if (seen.has(value)) { + return "[Circular]"; + } + if (depth >= options.maxDepth) { + return { + reason: "Maximum result depth reached.", + truncated: true + }; + } + + seen.add(value); + if (Array.isArray(value)) { + const limit = Math.max(0, options.maxArrayItems); + const items = value + .slice(0, limit) + .map((item) => compactJsonValue(item, options, depth + 1, seen)); + if (value.length > limit) { + items.push({ + omittedItems: value.length - limit, + truncated: true + }); + } + seen.delete(value); + return items; + } + + const entries = Object.entries(value); + const compacted: Record = {}; + for (const [key, entryValue] of entries.slice(0, options.maxObjectKeys)) { + compacted[key] = compactJsonValue(entryValue, options, depth + 1, seen); + } + if (entries.length > options.maxObjectKeys) { + compacted.truncatedKeys = entries.length - options.maxObjectKeys; + } + seen.delete(value); + return compacted; +} + +function defaultCompactResultOptions(): CompactResultOptions { + return { + maxArrayItems: maxToolResultArrayItems, + maxDepth: 8, + maxObjectKeys: maxToolResultObjectKeys, + maxStringChars: maxToolResultStringChars + }; +} + +function stringifyToolResult(value: unknown): string { + return JSON.stringify(value, null, 2) || "null"; +} + +function rawString(value: unknown): string | undefined { + return typeof value === "string" ? value : undefined; +} + +function truncateString(value: string, maxChars: number): string { + if (value.length <= maxChars) { + return value; + } + let suffix = "\n[truncated]"; + for (let index = 0; index < 2; index += 1) { + const keep = Math.max(0, maxChars - suffix.length); + suffix = `\n[truncated ${value.length - keep} chars]`; + } + return `${value.slice(0, Math.max(0, maxChars - suffix.length))}${suffix}`; +} + +function largeResultGuidance(toolName: string): string { + if (toolName === "browser_snapshot") { + return "Result was compacted. Re-run browser_snapshot with lower maxElements/maxText, or use browser_ax_query to fetch targeted elements."; + } + if (toolName === "browser_ax_snapshot") { + return "Result was compacted. Re-run browser_ax_snapshot with lower limit or scope=outline, or use browser_ax_query with role/name/text filters."; + } + if (toolName === "browser_ax_query") { + return "Result was compacted. Re-run browser_ax_query with a lower limit or narrower role/name/text filters."; + } + if (toolName === "browser_events_read" || toolName === "browser_events_await") { + return "Result was compacted. Read events with a lower limit/maxEvents or a more specific cursor/filter."; + } + return "Result was compacted before returning to the MCP client. Use narrower arguments or lower limits for more detail."; +} + +function textResult(text: string): ToolCallResult { + return { + content: [{ text, type: "text" }] + }; +} + +function jsonRpcResult(id: null | number | string, result: JsonValue): JsonRpcResponse { + return { + id, + jsonrpc: "2.0", + result + }; +} + +function jsonRpcError(id: null | number | string, code: number, message: string): JsonRpcResponse { + return { + error: { + code, + message + }, + id, + jsonrpc: "2.0" + }; +} + +function sendJson(response: ServerResponse, status: number, payload: unknown): void { + const body = `${JSON.stringify(payload)}\n`; + response.writeHead(status, { + "content-length": Buffer.byteLength(body), + "content-type": "application/json; charset=utf-8" + }); + response.end(body); +} + +function readRequestBody(request: IncomingMessage, maxBytes: number): Promise { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + let total = 0; + request.on("data", (chunk: Buffer | string) => { + const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + total += buffer.byteLength; + if (total > maxBytes) { + reject(new Error("MCP request body is too large.")); + request.destroy(); + return; + } + chunks.push(buffer); + }); + request.on("end", () => resolve(Buffer.concat(chunks))); + request.on("error", reject); + }); +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function formatError(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} diff --git a/packages/electron/src/main/browser-preload.ts b/packages/electron/src/main/browser-preload.ts index 2e1930f..9d571ff 100644 --- a/packages/electron/src/main/browser-preload.ts +++ b/packages/electron/src/main/browser-preload.ts @@ -1,16 +1,19 @@ import { contextBridge, ipcRenderer, type IpcRendererEvent } from "electron"; import { IPC_CHANNELS } from "@ccr/core/contracts/ipc-channels"; -import type { BuiltInBrowserState } from "@ccr/core/contracts/app"; +import type { BuiltInBrowserState, ChromeLoginImportJob, ChromeLoginImportRequest } from "@ccr/core/contracts/app"; contextBridge.exposeInMainWorld("ccrBrowser", { back: (tabId?: string) => ipcRenderer.invoke(IPC_CHANNELS.browserBack, tabId) as Promise, closeTab: (tabId: string) => ipcRenderer.invoke(IPC_CHANNELS.browserCloseTab, tabId) as Promise, forward: (tabId?: string) => ipcRenderer.invoke(IPC_CHANNELS.browserForward, tabId) as Promise, + getChromeLoginImport: (id: string) => ipcRenderer.invoke(IPC_CHANNELS.browserGetChromeLoginImport, id) as Promise, getState: () => ipcRenderer.invoke(IPC_CHANNELS.browserGetState) as Promise, navigate: (url: string, tabId?: string) => ipcRenderer.invoke(IPC_CHANNELS.browserNavigate, url, tabId) as Promise, newTab: (url?: string) => ipcRenderer.invoke(IPC_CHANNELS.browserNewTab, url) as Promise, reload: (tabId?: string) => ipcRenderer.invoke(IPC_CHANNELS.browserReload, tabId) as Promise, + resolveAutomationHandoff: (status: "completed" | "dismissed") => ipcRenderer.invoke(IPC_CHANNELS.browserResolveAutomationHandoff, status) as Promise, selectTab: (tabId: string) => ipcRenderer.invoke(IPC_CHANNELS.browserSelectTab, tabId) as Promise, + startChromeLoginImport: (request: ChromeLoginImportRequest) => ipcRenderer.invoke(IPC_CHANNELS.browserStartChromeLoginImport, request) as Promise, onStateChanged: (callback: (state: BuiltInBrowserState) => void) => { const handler = (_event: IpcRendererEvent, state: BuiltInBrowserState) => callback(state); ipcRenderer.on(IPC_CHANNELS.browserStateChanged, handler); diff --git a/packages/electron/src/main/built-in-browser.ts b/packages/electron/src/main/built-in-browser.ts index a8ae1b0..c941016 100644 --- a/packages/electron/src/main/built-in-browser.ts +++ b/packages/electron/src/main/built-in-browser.ts @@ -1,4 +1,5 @@ import { randomUUID } from "node:crypto"; +import { EventEmitter } from "node:events"; import { BrowserWindow, clipboard, @@ -10,28 +11,64 @@ import { WebContentsView, type ContextMenuParams, type IpcMainInvokeEvent, - type MenuItemConstructorOptions + type MenuItemConstructorOptions, + type WebContents } from "electron"; import path from "node:path"; import { pathToFileURL } from "node:url"; -import type { AppConfig, BuiltInBrowserState, BuiltInBrowserTabState, GatewayPluginAppConfig, InstalledBrowserApp } from "@ccr/core/contracts/app"; +import type { + AppConfig, + BuiltInBrowserAutomationHandoff, + BuiltInBrowserAutomationHandoffKind, + BuiltInBrowserState, + BuiltInBrowserTabState, + ChromeLoginImportRequest, + GatewayPluginAppConfig, + InstalledBrowserApp +} from "@ccr/core/contracts/app"; import { IPC_CHANNELS } from "@ccr/core/contracts/ipc-channels"; import { APP_NAME } from "@ccr/core/config/constants"; import { pluginService } from "@ccr/core/plugins/service"; -import { proxyService } from "@ccr/core/proxy/service"; +import { chromeLoginImportService } from "./chrome-login-import"; type BrowserTab = BuiltInBrowserTabState & { view: WebContentsView; }; -const browserChromeHeight = 82; +export type BrowserAutomationEvent = { + errorCode?: number; + errorDescription?: string; + handoffId?: string; + handoffStatus?: "completed" | "dismissed"; + kind: string; + seq: number; + summary?: string; + tabId?: string; + title?: string; + ts: number; + url?: string; + windowId?: string; +}; + +export type BrowserAutomationEventListener = (event: BrowserAutomationEvent) => void; + +const browserChromeBaseHeight = 82; +const browserHandoffToolbarHeight = 44; const browserHomeUrl = "about:blank"; const browserPartition = "persist:ccr-built-in-browser"; +const browserAutomationWindowId = "ccr-built-in-browser"; +const maxAutomationEventHistory = 512; +const maxAutomationEventAgeMs = 60_000; const titleBarHeight = 46; class BuiltInBrowserService { private activeTabId?: string; private apps: InstalledBrowserApp[] = []; + private automationHandoff?: BuiltInBrowserAutomationHandoff; + private automationEventSeq = 0; + private readonly automationEvents = new EventEmitter(); + private readonly automationEventHistory: BrowserAutomationEvent[] = []; + private hideWindowAfterAutomationHandoff = false; private proxyConfigKey = ""; private tabOrder: string[] = []; private tabs = new Map(); @@ -44,10 +81,7 @@ class BuiltInBrowserService { async open(config: AppConfig): Promise { await this.syncProxy(config); - const window = this.window && !this.window.isDestroyed() ? this.window : this.createWindow(); - if (this.tabs.size === 0) { - this.createTab(browserHomeUrl); - } + const window = this.ensureWindow(); if (window.isMinimized()) { window.restore(); } @@ -57,28 +91,18 @@ class BuiltInBrowserService { this.sendState(); } + async openHidden(config: AppConfig): Promise { + await this.syncProxy(config); + this.ensureWindow(); + this.layoutActiveView(); + this.sendState(); + } + async syncProxy(config: AppConfig): Promise { this.syncApps(config); const browserSession = session.fromPartition(browserPartition); - if (config.proxy.enabled) { - await proxyService.refreshUpstreamProxyFromCurrentSystem(); - } - const proxyStatus = proxyService.getStatus(); - const shouldUseProxy = Boolean( - config.proxy.enabled && - proxyStatus.state === "running" && - proxyStatus.endpoint - ); - const proxyConfig = shouldUseProxy - ? { - mode: "fixed_servers" as const, - proxyBypassRules: "<-loopback>", - proxyRules: electronProxyRules(proxyStatus.endpoint) - } - : { - mode: "direct" as const - }; + const proxyConfig = { mode: "system" as const }; const nextKey = JSON.stringify(proxyConfig); if (nextKey === this.proxyConfigKey) { return; @@ -100,9 +124,211 @@ class BuiltInBrowserService { async clearProxy(): Promise { const browserSession = session.fromPartition(browserPartition); - await browserSession.setProxy({ mode: "direct" }); + const proxyConfig = { mode: "system" as const }; + await browserSession.setProxy(proxyConfig); await browserSession.forceReloadProxyConfig(); - this.proxyConfigKey = JSON.stringify({ mode: "direct" }); + this.proxyConfigKey = JSON.stringify(proxyConfig); + } + + getAutomationState(): BuiltInBrowserState { + return this.getState(); + } + + getAutomationWindowId(): string { + return browserAutomationWindowId; + } + + getAutomationEvents(options: { replayRecentMs?: number } = {}): BrowserAutomationEvent[] { + const replayRecentMs = Math.max(0, Math.floor(options.replayRecentMs ?? 0)); + const cutoff = replayRecentMs > 0 ? Date.now() - replayRecentMs : 0; + this.pruneAutomationEventHistory(); + return this.automationEventHistory + .filter((event) => event.ts >= cutoff) + .map((event) => ({ ...event })); + } + + requestAutomationHandoff(request: { + kind?: BuiltInBrowserAutomationHandoffKind; + message?: string; + reason?: string; + sessionId?: string; + tabId?: string; + }): BuiltInBrowserAutomationHandoff { + const existingWindow = this.window && !this.window.isDestroyed() ? this.window : undefined; + if (!this.automationHandoff) { + this.hideWindowAfterAutomationHandoff = !existingWindow || !existingWindow.isVisible() || existingWindow.isMinimized(); + } + const tab = this.getTab(request.tabId); + if (request.tabId && tab?.id) { + this.selectTab(request.tabId); + } + const targetTab = tab || this.getTab(); + const handoff: BuiltInBrowserAutomationHandoff = { + id: randomUUID(), + kind: request.kind || "other", + message: request.message?.trim() || defaultAutomationHandoffMessage(request.kind), + ...(request.reason?.trim() ? { reason: request.reason.trim() } : {}), + requestedAt: Date.now(), + ...(request.sessionId?.trim() ? { sessionId: request.sessionId.trim() } : {}), + status: "pending", + tabId: targetTab?.id || request.tabId || this.activeTabId + }; + this.automationHandoff = handoff; + this.layoutActiveView(); + this.showAutomationWindow(); + this.sendState(); + this.emitAutomationEvent({ + handoffId: handoff.id, + kind: "handoff.requested", + summary: handoff.message, + tabId: handoff.tabId, + title: targetTab?.title, + url: targetTab?.url + }); + return handoff; + } + + resolveAutomationHandoff(status: "completed" | "dismissed" = "completed"): BuiltInBrowserState { + const handoff = this.automationHandoff; + const shouldHideWindow = this.hideWindowAfterAutomationHandoff; + this.automationHandoff = undefined; + this.hideWindowAfterAutomationHandoff = false; + this.layoutActiveView(); + this.sendState(); + if (handoff) { + const tab = this.getTab(handoff.tabId); + if (shouldHideWindow) { + this.hideAutomationWindow(); + } + this.emitAutomationEvent({ + handoffId: handoff.id, + handoffStatus: status, + kind: status === "completed" ? "handoff.completed" : "handoff.dismissed", + summary: status === "completed" ? "User completed the requested browser handoff." : "User dismissed the requested browser handoff.", + tabId: handoff.tabId, + title: tab?.title, + url: tab?.url + }); + } + return this.getState(); + } + + subscribeAutomationEvents( + listener: BrowserAutomationEventListener, + options: { replayRecentMs?: number } = {} + ): () => void { + this.automationEvents.on("event", listener); + const replayRecentMs = Math.max(0, Math.floor(options.replayRecentMs ?? 0)); + if (replayRecentMs > 0) { + const cutoff = Date.now() - replayRecentMs; + this.pruneAutomationEventHistory(); + for (const event of this.automationEventHistory) { + if (event.ts >= cutoff) { + listener(event); + } + } + } + return () => this.automationEvents.off("event", listener); + } + + createAutomationTab(url = browserHomeUrl): BuiltInBrowserTabState { + const tab = this.createTab(url); + const { view: _view, ...state } = tab; + return state; + } + + selectAutomationTab(tabId: string): BuiltInBrowserState { + this.selectTab(tabId); + return this.getState(); + } + + closeAutomationTab(tabId: string): BuiltInBrowserState { + this.closeTab(tabId); + return this.getState(); + } + + async navigateAutomationTab(url: string, tabId?: string): Promise { + const tab = this.getTab(tabId); + if (!tab) { + throw new Error("Browser tab was not found."); + } + + const nextUrl = normalizeBrowserUrl(url); + tab.url = nextUrl; + if (tab.id === this.activeTabId) { + this.layoutActiveView(); + } + this.sendState(); + void tab.view.webContents.loadURL(nextUrl).catch((error) => { + this.emitAutomationEvent({ + kind: "page.load_failed", + summary: `Navigation request failed: ${formatError(error)}`, + tabId: tab.id, + title: tab.title, + url: nextUrl + }); + }); + this.emitAutomationEvent({ + kind: "page.navigation_requested", + summary: `Navigation requested: ${nextUrl}`, + tabId: tab.id, + title: tab.title, + url: nextUrl + }); + return this.getState(); + } + + goBackAutomationTab(tabId?: string): BuiltInBrowserState { + const tab = this.getTab(tabId); + tab?.view.webContents.navigationHistory.goBack(); + if (tab) { + this.emitAutomationEvent({ + kind: "tab.go_back", + summary: `Go back in tab ${tab.id}.`, + tabId: tab.id, + title: tab.title, + url: tab.url + }); + } + return this.getState(); + } + + goForwardAutomationTab(tabId?: string): BuiltInBrowserState { + const tab = this.getTab(tabId); + tab?.view.webContents.navigationHistory.goForward(); + if (tab) { + this.emitAutomationEvent({ + kind: "tab.go_forward", + summary: `Go forward in tab ${tab.id}.`, + tabId: tab.id, + title: tab.title, + url: tab.url + }); + } + return this.getState(); + } + + reloadAutomationTab(tabId?: string): BuiltInBrowserState { + const tab = this.getTab(tabId); + tab?.view.webContents.reload(); + if (tab) { + this.emitAutomationEvent({ + kind: "tab.reload", + summary: `Reload tab ${tab.id}.`, + tabId: tab.id, + title: tab.title, + url: tab.url + }); + } + return this.getState(); + } + + getAutomationWebContents(tabId?: string): WebContents { + const tab = this.getTab(tabId); + if (!tab || tab.view.webContents.isDestroyed()) { + throw new Error("Browser tab is unavailable."); + } + return tab.view.webContents; } private registerIpcHandlers(): void { @@ -110,6 +336,14 @@ class BuiltInBrowserService { this.assertBrowserSender(event); return this.getState(); }); + ipcMain.handle(IPC_CHANNELS.browserStartChromeLoginImport, async (event, request: ChromeLoginImportRequest) => { + this.assertBrowserSender(event); + return await chromeLoginImportService.createJob(request); + }); + ipcMain.handle(IPC_CHANNELS.browserGetChromeLoginImport, (event, id: string) => { + this.assertBrowserSender(event); + return chromeLoginImportService.getJob(typeof id === "string" ? id : ""); + }); ipcMain.handle(IPC_CHANNELS.browserNewTab, (event, url?: string) => { this.assertBrowserSender(event); this.createTab(url); @@ -145,6 +379,34 @@ class BuiltInBrowserService { this.getTab(tabId)?.view.webContents.reload(); return this.getState(); }); + ipcMain.handle(IPC_CHANNELS.browserResolveAutomationHandoff, (event, status?: string) => { + this.assertBrowserSender(event); + return this.resolveAutomationHandoff(status === "dismissed" ? "dismissed" : "completed"); + }); + } + + private ensureWindow(): BrowserWindow { + const window = this.window && !this.window.isDestroyed() ? this.window : this.createWindow(); + if (this.tabs.size === 0) { + this.createTab(browserHomeUrl); + } + return window; + } + + private showAutomationWindow(): void { + const window = this.ensureWindow(); + if (window.isMinimized()) { + window.restore(); + } + window.show(); + window.focus(); + } + + private hideAutomationWindow(): void { + const window = this.window; + if (window && !window.isDestroyed()) { + window.hide(); + } } private createWindow(): BrowserWindow { @@ -187,15 +449,12 @@ class BuiltInBrowserService { this.window = undefined; } }); - window.once("ready-to-show", () => { - if (!window.isDestroyed()) { - window.show(); - } - }); window.webContents.setWindowOpenHandler(() => ({ action: "deny" })); window.webContents.on("did-finish-load", () => this.sendState()); - void window.loadURL(this.resolveRendererUrl("pages/browser/index.html")); + void window.loadURL(this.resolveRendererUrl("pages/browser/index.html")).catch((error) => { + console.warn(`[browser] Failed to load browser chrome: ${formatError(error)}`); + }); return window; } @@ -225,8 +484,23 @@ class BuiltInBrowserService { this.window?.contentView.addChildView(view); view.setVisible(false); this.selectTab(tab.id); - void view.webContents.loadURL(tab.url); + void view.webContents.loadURL(tab.url).catch((error) => { + this.emitAutomationEvent({ + kind: "page.load_failed", + summary: `Initial tab load failed: ${formatError(error)}`, + tabId: tab.id, + title: tab.title, + url: tab.url + }); + }); this.sendState(); + this.emitAutomationEvent({ + kind: "tab.created", + summary: `Created tab ${tab.id}.`, + tabId: tab.id, + title: tab.title, + url: tab.url + }); return tab; } @@ -234,7 +508,14 @@ class BuiltInBrowserService { const { webContents } = tab.view; webContents.setWindowOpenHandler(({ url }) => { if (isHttpUrl(url)) { - this.createTab(url); + const opened = this.createTab(url); + this.emitAutomationEvent({ + kind: "tab.opened", + summary: `Opened new tab from window.open: ${url}`, + tabId: opened.id, + title: opened.title, + url: opened.url + }); } return { action: "deny" }; }); @@ -244,14 +525,35 @@ class BuiltInBrowserService { webContents.on("page-title-updated", (_event, title) => { tab.title = title || titleFromUrl(tab.url); this.sendState(); + this.emitAutomationEvent({ + kind: "page.title", + summary: `Title updated: ${tab.title}`, + tabId: tab.id, + title: tab.title, + url: tab.url + }); }); webContents.on("did-start-loading", () => { tab.isLoading = true; this.updateTabNavigationState(tab); + this.emitAutomationEvent({ + kind: "page.loading_started", + summary: `Loading started: ${tab.url}`, + tabId: tab.id, + title: tab.title, + url: tab.url + }); }); webContents.on("did-stop-loading", () => { tab.isLoading = false; this.updateTabNavigationState(tab); + this.emitAutomationEvent({ + kind: "page.loading_stopped", + summary: `Loading stopped: ${tab.url}`, + tabId: tab.id, + title: tab.title, + url: tab.url + }); }); webContents.on("did-navigate", (_event, url) => { tab.url = url; @@ -260,6 +562,13 @@ class BuiltInBrowserService { this.layoutActiveView(); } this.updateTabNavigationState(tab); + this.emitAutomationEvent({ + kind: "page.navigation", + summary: `Navigated to ${url}`, + tabId: tab.id, + title: tab.title, + url + }); }); webContents.on("did-navigate-in-page", (_event, url) => { tab.url = url; @@ -267,8 +576,15 @@ class BuiltInBrowserService { this.layoutActiveView(); } this.updateTabNavigationState(tab); + this.emitAutomationEvent({ + kind: "page.navigation_in_page", + summary: `In-page navigation to ${url}`, + tabId: tab.id, + title: tab.title, + url + }); }); - webContents.on("did-fail-load", (_event, errorCode, _errorDescription, validatedUrl) => { + webContents.on("did-fail-load", (_event, errorCode, errorDescription, validatedUrl) => { if (errorCode !== -3) { tab.isLoading = false; tab.url = validatedUrl || tab.url; @@ -276,6 +592,35 @@ class BuiltInBrowserService { this.layoutActiveView(); } this.updateTabNavigationState(tab); + this.emitAutomationEvent({ + errorCode, + errorDescription, + kind: "page.load_failed", + summary: `Load failed (${errorCode}): ${errorDescription}`, + tabId: tab.id, + title: tab.title, + url: tab.url + }); + } + }); + webContents.on("dom-ready", () => { + this.emitAutomationEvent({ + kind: "page.dom_ready", + summary: `DOM ready: ${tab.url}`, + tabId: tab.id, + title: tab.title, + url: tab.url + }); + }); + webContents.on("console-message", (_event, level, message) => { + if (level >= 2) { + this.emitAutomationEvent({ + kind: "runtime.console", + summary: message, + tabId: tab.id, + title: tab.title, + url: tab.url + }); } }); webContents.on("destroyed", () => { @@ -286,6 +631,13 @@ class BuiltInBrowserService { this.activeTabId = this.tabOrder[0]; } this.sendState(); + this.emitAutomationEvent({ + kind: "tab.destroyed", + summary: `Destroyed tab ${tab.id}.`, + tabId: tab.id, + title: tab.title, + url: tab.url + }); } }); } @@ -403,6 +755,13 @@ class BuiltInBrowserService { selected.view.webContents.focus(); } this.sendState(); + this.emitAutomationEvent({ + kind: "tab.activated", + summary: `Activated tab ${tabId}.`, + tabId, + title: selected.title, + url: selected.url + }); } private closeTab(tabId: string): void { @@ -415,6 +774,13 @@ class BuiltInBrowserService { this.tabs.delete(tabId); this.tabOrder = this.tabOrder.filter((id) => id !== tabId); tab.view.webContents.close({ waitForBeforeUnload: false }); + this.emitAutomationEvent({ + kind: "tab.closed", + summary: `Closed tab ${tabId}.`, + tabId, + title: tab.title, + url: tab.url + }); if (this.tabOrder.length === 0) { this.createTab(browserHomeUrl); @@ -440,8 +806,23 @@ class BuiltInBrowserService { if (tab.id === this.activeTabId) { this.layoutActiveView(); } - void tab.view.webContents.loadURL(nextUrl); + void tab.view.webContents.loadURL(nextUrl).catch((error) => { + this.emitAutomationEvent({ + kind: "page.load_failed", + summary: `Navigation request failed: ${formatError(error)}`, + tabId: tab.id, + title: tab.title, + url: nextUrl + }); + }); this.sendState(); + this.emitAutomationEvent({ + kind: "page.navigation_requested", + summary: `Navigation requested: ${nextUrl}`, + tabId: tab.id, + title: tab.title, + url: nextUrl + }); } private getTab(tabId?: string): BrowserTab | undefined { @@ -469,17 +850,22 @@ class BuiltInBrowserService { const { height, width } = window.getContentBounds(); activeTab.view.setVisible(true); activeTab.view.setBounds({ - height: Math.max(0, height - browserChromeHeight), + height: Math.max(0, height - this.browserChromeHeight()), width, x: 0, - y: browserChromeHeight + y: this.browserChromeHeight() }); } + private browserChromeHeight(): number { + return browserChromeBaseHeight + (this.automationHandoff ? browserHandoffToolbarHeight : 0); + } + private getState(): BuiltInBrowserState { return { activeTabId: this.activeTabId, apps: this.apps.map((app) => ({ ...app })), + ...(this.automationHandoff ? { automationHandoff: { ...this.automationHandoff } } : {}), tabs: this.tabOrder .map((id) => this.tabs.get(id)) .filter((tab): tab is BrowserTab => Boolean(tab)) @@ -512,6 +898,27 @@ class BuiltInBrowserService { this.activeTabId = undefined; } + private emitAutomationEvent(event: Omit): void { + const nextEvent: BrowserAutomationEvent = { + ...event, + seq: ++this.automationEventSeq, + ts: Date.now(), + windowId: browserAutomationWindowId + }; + this.automationEventHistory.push(nextEvent); + this.pruneAutomationEventHistory(nextEvent.ts); + this.automationEvents.emit("event", nextEvent); + } + + private pruneAutomationEventHistory(now = Date.now()): void { + while (this.automationEventHistory.length > 0 && now - this.automationEventHistory[0].ts > maxAutomationEventAgeMs) { + this.automationEventHistory.shift(); + } + if (this.automationEventHistory.length > maxAutomationEventHistory) { + this.automationEventHistory.splice(0, this.automationEventHistory.length - maxAutomationEventHistory); + } + } + private resolveRendererUrl(relativeHtmlPath: string): string { return pathToFileURL(path.join(__dirname, "../renderer", relativeHtmlPath)).toString(); } @@ -619,9 +1026,22 @@ function titleFromUrl(value: string): string { } } -function electronProxyRules(endpoint: string): string { - const parsed = new URL(endpoint); - const host = parsed.hostname.includes(":") ? `[${parsed.hostname}]` : parsed.hostname; - const port = parsed.port || (parsed.protocol === "https:" ? "443" : "80"); - return `http=${host}:${port};https=${host}:${port}`; +function defaultAutomationHandoffMessage(kind?: BuiltInBrowserAutomationHandoffKind): string { + if (kind === "login_required") { + return "Please sign in on this page, then click Done."; + } + if (kind === "verification_code") { + return "Please enter the verification code, then click Done."; + } + if (kind === "human_verification") { + return "Please complete the human verification, then click Done."; + } + if (kind === "blocked") { + return "Please resolve the blocker on this page, then click Done."; + } + return "Please complete the requested browser step, then click Done."; +} + +function formatError(error: unknown): string { + return error instanceof Error ? error.message : String(error); } diff --git a/packages/electron/src/main/chrome-login-import.ts b/packages/electron/src/main/chrome-login-import.ts new file mode 100644 index 0000000..850dd17 --- /dev/null +++ b/packages/electron/src/main/chrome-login-import.ts @@ -0,0 +1,593 @@ +import { randomUUID } from "node:crypto"; +import http, { type IncomingMessage, type Server, type ServerResponse } from "node:http"; +import { BrowserWindow, session, shell } from "electron"; +import type { + ChromeLoginImportCookie, + ChromeLoginImportJob, + ChromeLoginImportLocalStorage, + ChromeLoginImportRequest, + ChromeLoginImportResult, + ChromeLoginImportTarget +} from "@ccr/core/contracts/app"; + +type ServerInfo = { + server: Server; + url: string; +}; + +type StoredChromeLoginImportJob = ChromeLoginImportJob; + +type CookieSetDetails = Parameters[0]; + +const browserPartition = "persist:ccr-built-in-browser"; +const webSearchPartition = "persist:ccr-browser-web-search-mcp"; +const importJobTtlMs = 5 * 60_000; +const maxImportRequestBytes = 16 * 1024 * 1024; +const maxStoredErrors = 20; +const localStorageWriteTimeoutMs = 15_000; + +class ChromeLoginImportService { + private readonly jobs = new Map(); + private serverInfo?: ServerInfo; + private serverStartPromise?: Promise; + + async createJob(request: ChromeLoginImportRequest): Promise { + const domains = uniqueStrings(request.domains.map(normalizeImportDomain).filter((item): item is string => Boolean(item))); + if (domains.length === 0) { + throw new Error("At least one Chrome import domain is required."); + } + + const target = normalizeImportTarget(request.target); + const serverInfo = await this.ensureServer(); + const id = randomUUID(); + const now = Date.now(); + const job: StoredChromeLoginImportJob = { + confirmUrl: `${serverInfo.url}/chrome-import/jobs/${id}/confirm`, + createdAt: now, + domains, + endpointUrl: serverInfo.url, + expiresAt: now + importJobTtlMs, + id, + importUrl: `${serverInfo.url}/chrome-import/jobs/${id}`, + status: "pending", + target + }; + this.jobs.set(id, job); + this.pruneJobs(); + if (request.openConfirmationPage) { + await this.openConfirmationPage(job.id); + } + return cloneJob(job); + } + + getJob(id: string): ChromeLoginImportJob | undefined { + const job = this.jobs.get(id); + if (!job) { + return undefined; + } + this.refreshExpiredJob(job); + return cloneJob(job); + } + + async openConfirmationPage(id: string): Promise { + const job = this.jobs.get(id); + if (!job) { + throw new Error("Chrome login import job was not found."); + } + this.refreshExpiredJob(job); + if (job.status !== "pending") { + throw new Error(`Chrome login import job is ${job.status}.`); + } + await shell.openExternal(job.confirmUrl); + return cloneJob(job); + } + + private async ensureServer(): Promise { + if (this.serverInfo) { + return this.serverInfo; + } + if (this.serverStartPromise) { + return this.serverStartPromise; + } + + this.serverStartPromise = new Promise((resolve, reject) => { + const server = http.createServer((request, response) => { + void this.handleRequest(request, response).catch((error) => { + sendJson(response, 500, { error: { message: formatError(error) } }); + }); + }); + server.once("error", reject); + server.listen(0, "127.0.0.1", () => { + server.off("error", reject); + const address = server.address(); + if (!address || typeof address === "string") { + server.close(); + reject(new Error("Chrome login import server failed to start.")); + return; + } + const info = { + server, + url: `http://127.0.0.1:${address.port}` + }; + this.serverInfo = info; + resolve(info); + }); + }).finally(() => { + this.serverStartPromise = undefined; + }); + return this.serverStartPromise; + } + + private async handleRequest(request: IncomingMessage, response: ServerResponse): Promise { + setCorsHeaders(response); + if (request.method === "OPTIONS") { + response.writeHead(204); + response.end(); + return; + } + + const path = request.url ? new URL(request.url, "http://127.0.0.1").pathname : "/"; + const match = path.match(/^\/chrome-import\/jobs\/([^/]+)(?:\/(confirm|cookies))?$/); + if (!match) { + sendJson(response, 404, { error: { message: "Chrome login import endpoint not found." } }); + return; + } + + const id = decodeURIComponent(match[1]); + const job = this.jobs.get(id); + if (!job) { + sendJson(response, 404, { error: { message: "Chrome login import job was not found." } }); + return; + } + this.refreshExpiredJob(job); + if (job.status === "expired") { + sendJson(response, 410, { error: { message: "Chrome login import job expired." }, job: cloneJob(job) }); + return; + } + + const action = match[2]; + if (request.method === "GET" && action === "confirm") { + sendHtml(response, 200, confirmationPageHtml(job)); + return; + } + + if (request.method === "GET" && !action) { + sendJson(response, 200, { job: cloneJob(job) }); + return; + } + + if (request.method === "POST" && action === "cookies") { + if (job.status !== "pending") { + sendJson(response, 409, { + error: { + message: `Chrome login import job is ${job.status}.` + }, + job: cloneJob(job) + }); + return; + } + const payload = await readJsonRequest(request); + const result = await this.importLoginState(job, payload); + sendJson(response, 200, { job: cloneJob(job), result }); + return; + } + + sendJson(response, 405, { error: { message: "Unsupported Chrome login import method." } }); + } + + private async importLoginState(job: StoredChromeLoginImportJob, payload: unknown): Promise { + const importPayload = readImportPayload(payload); + const partitions = targetPartitions(job.target); + const result: ChromeLoginImportResult = { + completedAt: Date.now(), + cookieImported: 0, + cookieSkipped: 0, + domains: [...job.domains], + imported: 0, + localStorageImported: 0, + localStorageSkipped: 0, + partitions, + skipped: 0 + }; + const errors: string[] = []; + + for (const cookie of importPayload.cookies) { + const normalized = normalizeChromeCookie(cookie, job.domains); + if (!normalized) { + result.cookieSkipped += 1; + continue; + } + + try { + await Promise.all(partitions.map((partition) => session.fromPartition(partition).cookies.set(normalized))); + result.cookieImported += 1; + } catch (error) { + result.cookieSkipped += 1; + if (errors.length < maxStoredErrors) { + errors.push(`${normalized.domain || new URL(normalized.url).hostname}:${normalized.name}: ${formatError(error)}`); + } + } + } + + await Promise.all(partitions.map((partition) => session.fromPartition(partition).cookies.flushStore())); + + for (const localStorageEntry of importPayload.localStorage) { + const normalized = normalizeLocalStorageEntry(localStorageEntry, job.domains); + if (!normalized) { + result.localStorageSkipped += 1; + continue; + } + + const itemCount = Object.keys(normalized.items).length; + if (itemCount === 0) { + result.localStorageSkipped += 1; + continue; + } + + try { + await Promise.all(partitions.map((partition) => writeLocalStorage(partition, normalized.origin, normalized.items))); + result.localStorageImported += itemCount; + } catch (error) { + result.localStorageSkipped += itemCount; + if (errors.length < maxStoredErrors) { + errors.push(`${normalized.origin}: localStorage: ${formatError(error)}`); + } + } + } + + result.imported = result.cookieImported + result.localStorageImported; + result.skipped = result.cookieSkipped + result.localStorageSkipped; + if (errors.length > 0) { + result.errors = errors; + } + job.result = result; + job.status = result.imported > 0 || result.skipped > 0 ? "completed" : "failed"; + return result; + } + + private refreshExpiredJob(job: StoredChromeLoginImportJob): void { + if (job.status === "pending" && Date.now() > job.expiresAt) { + job.status = "expired"; + } + } + + private pruneJobs(): void { + const cutoff = Date.now() - importJobTtlMs; + for (const [id, job] of this.jobs) { + this.refreshExpiredJob(job); + if (job.expiresAt < cutoff) { + this.jobs.delete(id); + } + } + } +} + +export const chromeLoginImportService = new ChromeLoginImportService(); + +function targetPartitions(target: ChromeLoginImportTarget): string[] { + return target === "browser-and-web-search" + ? [browserPartition, webSearchPartition] + : [browserPartition]; +} + +function normalizeChromeCookie(cookie: ChromeLoginImportCookie, allowedDomains: string[]): CookieSetDetails | undefined { + if (!isRecord(cookie) || typeof cookie.name !== "string" || typeof cookie.value !== "string") { + return undefined; + } + if (cookie.partitionKey !== undefined) { + return undefined; + } + + const rawDomain = readString(cookie.domain).toLowerCase(); + const cookieHost = normalizeCookieHost(rawDomain); + if (!cookieHost || !allowedDomains.some((domain) => cookieDomainMatches(cookieHost, domain))) { + return undefined; + } + + const path = normalizeCookiePath(cookie.path); + const expirationDate = typeof cookie.expirationDate === "number" && Number.isFinite(cookie.expirationDate) + ? cookie.expirationDate + : undefined; + if (expirationDate !== undefined && expirationDate <= Date.now() / 1000) { + return undefined; + } + + const secure = cookie.secure === true; + const details: CookieSetDetails = { + httpOnly: cookie.httpOnly === true, + name: cookie.name, + path, + secure, + url: cookieUrl(cookieHost, path, secure), + value: cookie.value + }; + if (cookie.hostOnly !== true) { + details.domain = rawDomain || cookieHost; + } + if (expirationDate !== undefined && cookie.session !== true) { + details.expirationDate = expirationDate; + } + const sameSite = normalizeSameSite(cookie.sameSite); + if (sameSite) { + details.sameSite = sameSite; + } + return details; +} + +function normalizeLocalStorageEntry( + entry: ChromeLoginImportLocalStorage, + allowedDomains: string[] +): ChromeLoginImportLocalStorage | undefined { + if (!isRecord(entry) || !isRecord(entry.items)) { + return undefined; + } + + let origin: URL; + try { + origin = new URL(readString(entry.origin)); + } catch { + return undefined; + } + + if (!["http:", "https:"].includes(origin.protocol)) { + return undefined; + } + if (!allowedDomains.some((domain) => cookieDomainMatches(origin.hostname.toLowerCase(), domain))) { + return undefined; + } + + const items: Record = {}; + for (const [key, value] of Object.entries(entry.items)) { + if (typeof key === "string" && typeof value === "string") { + items[key] = value; + } + } + return { + items, + origin: origin.origin + }; +} + +async function writeLocalStorage(partition: string, origin: string, items: Record): Promise { + const window = new BrowserWindow({ + height: 480, + paintWhenInitiallyHidden: true, + show: false, + skipTaskbar: true, + title: "CCR Chrome Login Import", + webPreferences: { + contextIsolation: true, + nodeIntegration: false, + partition, + sandbox: true, + webSecurity: true + }, + width: 640 + }); + try { + await withTimeout(window.webContents.loadURL(`${origin}/`), localStorageWriteTimeoutMs, "Timed out loading localStorage origin."); + const loadedOrigin = new URL(window.webContents.getURL()).origin; + if (loadedOrigin !== origin) { + throw new Error(`Origin redirected to ${loadedOrigin}.`); + } + const entries = Object.entries(items); + await window.webContents.executeJavaScript( + `(() => { + const entries = ${JSON.stringify(entries)}; + for (const [key, value] of entries) { + window.localStorage.setItem(key, value); + } + return entries.length; + })()`, + true + ); + } finally { + if (!window.isDestroyed()) { + window.destroy(); + } + } +} + +function normalizeImportTarget(value: unknown): ChromeLoginImportTarget { + return value === "browser-and-web-search" ? "browser-and-web-search" : "browser"; +} + +function normalizeImportDomain(value: unknown): string | undefined { + const raw = readString(value).toLowerCase(); + if (!raw) { + return undefined; + } + try { + const parsed = new URL(raw.includes("://") ? raw : `https://${raw}`); + return normalizeCookieHost(parsed.hostname); + } catch { + return normalizeCookieHost(raw.replace(/^\*\./, "").split("/")[0] || ""); + } +} + +function normalizeCookieHost(value: string): string | undefined { + const host = value.trim().replace(/^\./, "").toLowerCase(); + if (!host || host.includes("*") || host.includes("/") || host.includes(" ")) { + return undefined; + } + return host; +} + +function normalizeCookiePath(value: unknown): string { + const path = readString(value); + return path.startsWith("/") ? path : "/"; +} + +function cookieUrl(host: string, path: string, secure: boolean): string { + const normalizedPath = path.startsWith("/") ? path : "/"; + return `${secure ? "https" : "http"}://${host}${normalizedPath}`; +} + +function cookieDomainMatches(cookieHost: string, allowedDomain: string): boolean { + return cookieHost === allowedDomain || cookieHost.endsWith(`.${allowedDomain}`); +} + +function normalizeSameSite(value: unknown): CookieSetDetails["sameSite"] | undefined { + return value === "lax" || value === "strict" || value === "no_restriction" || value === "unspecified" + ? value + : undefined; +} + +function readImportPayload(payload: unknown): { + cookies: ChromeLoginImportCookie[]; + localStorage: ChromeLoginImportLocalStorage[]; +} { + if (!isRecord(payload)) { + throw new Error("Chrome login import payload must be an object."); + } + const cookies = Array.isArray(payload.cookies) + ? payload.cookies.filter(isRecord) as ChromeLoginImportCookie[] + : []; + const localStorage = Array.isArray(payload.localStorage) + ? payload.localStorage.filter(isRecord) as ChromeLoginImportLocalStorage[] + : []; + if (cookies.length === 0 && localStorage.length === 0) { + throw new Error("Chrome login import payload must include cookies or localStorage."); + } + return { cookies, localStorage }; +} + +function cloneJob(job: StoredChromeLoginImportJob): ChromeLoginImportJob { + return { + ...job, + domains: [...job.domains], + ...(job.result + ? { + result: { + ...job.result, + domains: [...job.result.domains], + ...(job.result.errors ? { errors: [...job.result.errors] } : {}), + partitions: [...job.result.partitions] + } + } + : {}) + }; +} + +async function readJsonRequest(request: IncomingMessage): Promise { + const chunks: Buffer[] = []; + let total = 0; + for await (const chunk of request) { + const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + total += buffer.byteLength; + if (total > maxImportRequestBytes) { + throw new Error("Chrome login import payload is too large."); + } + chunks.push(buffer); + } + return JSON.parse(Buffer.concat(chunks).toString("utf8")) as unknown; +} + +function sendJson(response: ServerResponse, statusCode: number, body: unknown): void { + if (!response.headersSent) { + response.writeHead(statusCode, { "content-type": "application/json" }); + } + response.end(`${JSON.stringify(body)}\n`); +} + +function sendHtml(response: ServerResponse, statusCode: number, body: string): void { + if (!response.headersSent) { + response.writeHead(statusCode, { + "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'; script-src 'none'; connect-src http://127.0.0.1:* http://localhost:*", + "content-type": "text/html; charset=utf-8" + }); + } + response.end(body); +} + +function confirmationPageHtml(job: StoredChromeLoginImportJob): string { + const domains = job.domains.map((domain) => `
  • ${escapeHtml(domain)}
  • `).join(""); + return ` + + + + + CCR Chrome Login Import + + + +
    +

    Import Chrome Login State into CCR

    +

    CCR is requesting permission to import cookies and localStorage for these domains into the in-app browser.

    +
      ${domains}
    + +
    Install or enable the CCR Login Import extension in Chrome to continue.
    +
    + +`; +} + +function setCorsHeaders(response: ServerResponse): void { + response.setHeader("access-control-allow-headers", "content-type, x-ccr-login-import"); + response.setHeader("access-control-allow-methods", "GET, POST, OPTIONS"); + response.setHeader("access-control-allow-origin", "*"); +} + +function uniqueStrings(values: string[]): string[] { + return [...new Set(values)]; +} + +function readString(value: unknown): string { + return typeof value === "string" ? value.trim() : ""; +} + +function isRecord(value: unknown): value is Record { + return Boolean(value) && typeof value === "object" && !Array.isArray(value); +} + +function escapeHtml(value: string): string { + return value.replace(/[&<>"']/g, (char) => { + switch (char) { + case "&": + return "&"; + case "<": + return "<"; + case ">": + return ">"; + case "\"": + return """; + default: + return "'"; + } + }); +} + +function withTimeout(promise: Promise, timeoutMs: number, message: string): Promise { + let timeout: NodeJS.Timeout | undefined; + const timeoutPromise = new Promise((_resolve, reject) => { + timeout = setTimeout(() => reject(new Error(message)), timeoutMs); + }); + return Promise.race([promise, timeoutPromise]).finally(() => { + if (timeout) { + clearTimeout(timeout); + } + }); +} + +function formatError(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} diff --git a/packages/electron/src/main/electron-web-search-mcp.ts b/packages/electron/src/main/electron-web-search-mcp.ts index 0badf3b..e59ec3a 100644 --- a/packages/electron/src/main/electron-web-search-mcp.ts +++ b/packages/electron/src/main/electron-web-search-mcp.ts @@ -70,6 +70,7 @@ type BrowserSearchRequest = BrowserSearchInput & { type BrowserSearchResult = { content?: string; + diagnostics?: string[]; snippet?: string; title: string; url: string; @@ -597,11 +598,15 @@ class HiddenBrowserSearchPool { return { ...result, ...(content ? { content } : {}), + ...(!content ? { diagnostics: appendSearchResultDiagnostic(result.diagnostics, "No extractable page content found.") } : {}), ...(snippet ? { snippet } : {}), ...(page.title && page.title.length > result.title.length ? { title: page.title.slice(0, 240) } : {}) }; - } catch { - return result; + } catch (error) { + return { + ...result, + diagnostics: appendSearchResultDiagnostic(result.diagnostics, `Page extraction failed: ${formatError(error)}`) + }; } finally { if (worker) { this.release(worker); @@ -635,7 +640,16 @@ async function pollSearchResults(webContents: WebContents, request: BrowserSearc const deadline = Date.now() + request.timeoutMs; let last: BrowserSearchPageResult = { results: [] }; while (Date.now() < deadline) { - last = await webContents.executeJavaScript(searchResultExtractionScript(request.engine, request.count), true) as BrowserSearchPageResult; + try { + last = await withTimeout( + webContents.executeJavaScript(searchResultExtractionScript(request.engine, request.count), true) as Promise, + Math.max(100, Math.min(1000, deadline - Date.now())), + "Browser search result extraction timed out." + ); + } catch { + await sleep(150); + continue; + } if (last.blocked || (last.results?.length ?? 0) >= Math.min(request.count, 3)) { return last; } @@ -823,18 +837,46 @@ function searchResultExtractionScript(engine: BrowserSearchEngine, count: number const host = url.hostname.toLowerCase(); const path = url.pathname.toLowerCase(); if (host === location.hostname.toLowerCase() && (path === "/" || path === "/search" || path.startsWith("/search/"))) return true; - if (host.includes("google.") && /\\/(preferences|advanced_search|intl|policies|support|sorry)/.test(path)) return true; - if (host.endsWith("bing.com") && /\\/(search|account|profile|images|videos|maps|news)/.test(path)) return true; - if (host.endsWith("duckduckgo.com") && path === "/") return true; + if (host.includes("google.") && /\\/(preferences|advanced_search|intl|policies|support|sorry|search|aclk|imgres)/.test(path)) return true; + if (host.endsWith("bing.com") && /\\/(search|account|profile|images|videos|maps|news|aclick)/.test(path)) return true; + if (host.endsWith("bing.com") && path.startsWith("/ck/")) return true; + if (host.endsWith("duckduckgo.com") && (path === "/" || path.startsWith("/settings") || path.startsWith("/y.js"))) return true; return false; } function resultContainer(anchor, title) { + const root = anchor && anchor.closest("li.b_algo, li.b_ad, div.g, div[data-sokoban-container], div[data-testid='result'], article, .result, .result__body, .web-result"); + if (root) return root; let node = anchor; for (let depth = 0; node && depth < 8; depth += 1, node = node.parentElement) { + if (node.matches && node.matches("nav, header, footer, form, aside")) return undefined; + if (node.matches && node.matches("main, #search, #b_results, #rso")) break; const text = compact(node.textContent || ""); if (text.length > title.length + 40) return node; } - return anchor.closest("li.b_algo, article, div.g, div[data-sokoban-container], div[data-testid='result'], .result, li, div"); + return undefined; + } + function isLikelyOrganicResult(anchor, container) { + if (!anchor || !container) return false; + if (anchor.closest("nav, header, footer, form, aside")) return false; + if (anchor.closest("li.b_algo, div.g, div[data-sokoban-container], div[data-testid='result'], article, .result, .result__body, .web-result")) return true; + const heading = anchor.closest("h1, h2, h3, [role='heading']"); + const searchRegion = anchor.closest("main, #search, #b_results, #rso"); + return Boolean(heading && searchRegion); + } + function isAdOrUtilityResult(anchor, container) { + let node = anchor; + for (let depth = 0; node && depth < 6; depth += 1, node = node.parentElement) { + const attributes = [ + node.getAttribute && node.getAttribute("aria-label"), + node.getAttribute && node.getAttribute("data-text-ad"), + node.getAttribute && node.getAttribute("data-testid"), + node.className + ].filter(Boolean).join(" "); + if (/\\b(?:ad|ads|sponsored|promo|shopping)\\b|广告|赞助|推廣/i.test(attributes)) return true; + } + const firstText = compact(container && container.textContent).slice(0, 180); + if (/^(?:ad|ads|sponsored|promo|shopping)\\b|^(?:广告|赞助|推廣)/i.test(firstText)) return true; + return false; } function cleanSnippetCandidate(value, title, url) { const host = (() => { try { return new URL(url).hostname.replace(/^www\\./, ""); } catch (_) { return ""; } })(); @@ -881,6 +923,7 @@ function searchResultExtractionScript(engine: BrowserSearchEngine, count: number const title = titleFor(anchor, titleOverride); if (!title || title.length < 2) return; const container = resultContainer(anchor, title); + if (!isLikelyOrganicResult(anchor, container) || isAdOrUtilityResult(anchor, container)) return; seen.add(url); results.push({ snippet: snippetFor(anchor, container, title, url), @@ -908,19 +951,22 @@ function searchResultExtractionScript(engine: BrowserSearchEngine, count: number collect(".result__a"); collect("article h2 a[href]"); } + function collectGenericHeadings() { + collect("main h1 a[href], main h2 a[href], main h3 a[href], #search h3, #b_results h2 a[href], #rso h3, article h2 a[href], article h3 a[href]"); + } if (engine === "google") collectGoogle(); if (engine === "bing") collectBing(); if (engine === "duckduckgo") collectDuckDuckGo(); - collect("main a[href], #search a[href], #b_results a[href], article a[href], a[href]"); + collectGenericHeadings(); return { results, title: document.title || "" }; function detectBlocked() { const text = compact(document.body && document.body.innerText).toLowerCase(); if (!text) return ""; - if (text.includes("unusual traffic") || text.includes("detected unusual") || text.includes("not a robot") || text.includes("captcha")) { + if (text.includes("unusual traffic") || text.includes("detected unusual") || text.includes("not a robot") || text.includes("captcha") || text.includes("验证码") || text.includes("人机验证")) { return "Search engine returned an anti-bot or CAPTCHA page."; } - if (text.includes("before you continue to google") || text.includes("consent.google")) { + if (text.includes("before you continue to google") || text.includes("consent.google") || text.includes("cookie consent")) { return "Google returned a consent page instead of search results."; } return ""; @@ -996,11 +1042,16 @@ function formatSearchResponse( `${index + 1}. ${result.title}`, `URL: ${result.url}`, result.snippet ? `Snippet: ${result.snippet}` : "", - result.content ? `Extracted content: ${result.content}` : "" + result.content ? `Extracted content: ${result.content}` : "", + result.diagnostics?.length ? `Diagnostics: ${result.diagnostics.join("; ")}` : "" ].filter(Boolean).join("\n")) ].filter(Boolean).join("\n\n"); } +function appendSearchResultDiagnostic(existing: string[] | undefined, message: string): string[] { + return uniqueStrings([...(existing ?? []), message]); +} + function uniqueSearchResults(results: BrowserSearchResult[]): BrowserSearchResult[] { const seen = new Set(); const unique: BrowserSearchResult[] = []; diff --git a/packages/electron/src/main/ipc.ts b/packages/electron/src/main/ipc.ts index 53afe4c..a639fbc 100644 --- a/packages/electron/src/main/ipc.ts +++ b/packages/electron/src/main/ipc.ts @@ -1,7 +1,6 @@ import { app, BrowserWindow, dialog, ipcMain, nativeImage, shell, type OpenDialogOptions, type Rectangle, type SaveDialogOptions } from "electron"; import { randomUUID } from "node:crypto"; import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs"; -import net from "node:net"; import path from "node:path"; import { deflateSync, inflateSync } from "node:zlib"; import { loadPersistedAppSetting, replacePersistedAppSetting } from "@ccr/core/config/app-config-store"; @@ -133,7 +132,6 @@ ipcMain.handle(IPC_CHANNELS.appListMcpServerTools, async (_event, serverName: st }); ipcMain.handle(IPC_CHANNELS.appOpenBuiltInBrowser, async () => { const config = await loadAppConfig(); - await ensureBuiltInBrowserProxyReady(config); await builtInBrowserService.open(config); }); ipcMain.handle(IPC_CHANNELS.appCloseTray, () => { @@ -361,157 +359,6 @@ function logProfileApplyResult(result: ProfileApplyResult): void { } } -type ProxyConnectProbeResult = { - detail?: string; - ok: boolean; -}; - -const browserProxyConnectProbeTarget = "claude.ai:443"; -const proxyConnectProbeTimeoutMs = 3000; - -async function ensureBuiltInBrowserProxyReady(config: AppConfig): Promise { - if (!config.proxy.enabled) { - return; - } - - let proxyStatus = proxyService.getStatus(); - if (proxyStatus.state === "running" && proxyStatus.endpoint) { - const probe = await probeProxyConnect(proxyStatus.endpoint); - if (probe.ok) { - return; - } - console.warn(`[browser] Proxy CONNECT probe failed at ${proxyStatus.endpoint}; restarting proxy: ${probe.detail || "unknown error"}`); - } - - const status = await gatewayService.start(config); - if (status.state === "error") { - throw new Error(status.lastError || "Failed to start proxy mode."); - } - - proxyStatus = proxyService.getStatus(); - if (proxyStatus.state !== "running" || !proxyStatus.endpoint) { - throw new Error(proxyStatus.lastError || "Proxy mode is not running."); - } - - const probe = await probeProxyConnect(proxyStatus.endpoint); - if (probe.ok) { - return; - } - - console.warn( - `[browser] Shared proxy endpoint ${proxyStatus.endpoint} still does not accept CONNECT after restart; starting dedicated proxy endpoint: ${probe.detail || "unknown error"}` - ); - const dedicatedProxyConfig = await createBuiltInBrowserProxyConfig(config); - const dedicatedProxyStatus = await proxyService.start(dedicatedProxyConfig); - if (dedicatedProxyStatus.state !== "running" || !dedicatedProxyStatus.endpoint) { - throw new Error(dedicatedProxyStatus.lastError || "Failed to start the dedicated proxy endpoint for the built-in browser."); - } - - const dedicatedProbe = await probeProxyConnect(dedicatedProxyStatus.endpoint); - if (!dedicatedProbe.ok) { - const detail = dedicatedProbe.detail ? `: ${dedicatedProbe.detail}` : ""; - throw new Error(`Proxy mode is running at ${dedicatedProxyStatus.endpoint}, but HTTPS CONNECT is not available${detail}.`); - } -} - -async function createBuiltInBrowserProxyConfig(config: AppConfig): Promise { - return { - ...config, - proxy: { - ...config.proxy, - host: "127.0.0.1", - port: await findAvailableLoopbackPort(), - systemProxy: false - } - }; -} - -function findAvailableLoopbackPort(): Promise { - return new Promise((resolve, reject) => { - const server = net.createServer(); - server.once("error", reject); - server.listen(0, "127.0.0.1", () => { - const address = server.address(); - server.close((error) => { - if (error) { - reject(error); - return; - } - if (!address || typeof address === "string") { - reject(new Error("Failed to allocate a local proxy port.")); - return; - } - resolve(address.port); - }); - }); - }); -} - -function probeProxyConnect(endpoint: string): Promise { - return new Promise((resolve) => { - let parsed: URL; - try { - parsed = new URL(endpoint); - } catch { - resolve({ detail: `Invalid proxy endpoint: ${endpoint}`, ok: false }); - return; - } - - const port = Number(parsed.port || (parsed.protocol === "https:" ? 443 : 80)); - if (!Number.isInteger(port) || port <= 0 || port > 65535) { - resolve({ detail: `Invalid proxy port in endpoint: ${endpoint}`, ok: false }); - return; - } - - const host = parsed.hostname.replace(/^\[|\]$/g, ""); - const socket = net.connect({ host, port }); - let response = ""; - let settled = false; - - const finish = (result: ProxyConnectProbeResult) => { - if (settled) { - return; - } - settled = true; - socket.destroy(); - resolve(result); - }; - const parseResponse = (): ProxyConnectProbeResult | undefined => { - const firstLine = response.split(/\r?\n/, 1)[0]?.trim(); - if (!firstLine) { - return undefined; - } - if (/^HTTP\/1\.[01]\s+200\b/i.test(firstLine)) { - return { ok: true }; - } - if (/^HTTP\/1\.[01]\s+\d{3}\b/i.test(firstLine)) { - return { detail: firstLine, ok: false }; - } - return { detail: `Unexpected response: ${firstLine}`, ok: false }; - }; - - socket.setTimeout(proxyConnectProbeTimeoutMs, () => { - finish({ detail: `Timed out after ${proxyConnectProbeTimeoutMs}ms`, ok: false }); - }); - socket.once("error", (error) => { - finish({ detail: formatError(error), ok: false }); - }); - socket.once("connect", () => { - socket.write(`CONNECT ${browserProxyConnectProbeTarget} HTTP/1.1\r\nHost: ${browserProxyConnectProbeTarget}\r\n\r\n`); - }); - socket.on("data", (chunk) => { - response += chunk.toString("latin1"); - const result = parseResponse(); - if (result) { - finish(result); - } - }); - socket.once("close", () => { - finish(parseResponse() ?? { detail: "Connection closed without a CONNECT response", ok: false }); - }); - }); -} - async function exportAppData(window: BrowserWindow | null): Promise { const exportedAt = new Date().toISOString(); const result = window diff --git a/packages/electron/src/main/main-app.ts b/packages/electron/src/main/main-app.ts index f71892a..9f3ab9a 100644 --- a/packages/electron/src/main/main-app.ts +++ b/packages/electron/src/main/main-app.ts @@ -11,6 +11,7 @@ import { syncLaunchAtLogin } from "./launch-at-login"; import { proxyService } from "@ccr/core/proxy/service"; import trayController from "./tray-controller"; import { appUpdateService } from "./update-service"; +import { browserAutomationMcpService } from "./browser-automation-mcp"; import { browserWebSearchMcpService } from "./electron-web-search-mcp"; import windowsManager from "./windows"; @@ -29,6 +30,7 @@ if (!gotTheLock) { } function startPrimaryInstance(): void { + gatewayService.setBrowserAutomationMcpIntegration(browserAutomationMcpService); gatewayService.setBrowserWebSearchMcpIntegration(browserWebSearchMcpService); deepLinkService.register(); deepLinkService.handleArgv(process.argv); diff --git a/packages/ui/src/pages/browser/main.tsx b/packages/ui/src/pages/browser/main.tsx index 578481e..11b5e90 100644 --- a/packages/ui/src/pages/browser/main.tsx +++ b/packages/ui/src/pages/browser/main.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo, useState, type FormEvent } from "react"; import { createRoot } from "react-dom/client"; -import { ArrowLeft, ArrowRight, Globe2, LoaderCircle, Plus, RotateCw, Search, X } from "lucide-react"; -import type { BuiltInBrowserState } from "@ccr/core/contracts/app"; +import { ArrowLeft, ArrowRight, Check, KeyRound, LoaderCircle, Plus, RotateCw, Search, UserRound, X } from "lucide-react"; +import type { BuiltInBrowserState, ChromeLoginImportJob, ChromeLoginImportRequest } from "@ccr/core/contracts/app"; declare global { interface Window { @@ -9,11 +9,14 @@ declare global { back: (tabId?: string) => Promise; closeTab: (tabId: string) => Promise; forward: (tabId?: string) => Promise; + getChromeLoginImport: (id: string) => Promise; getState: () => Promise; navigate: (url: string, tabId?: string) => Promise; newTab: (url?: string) => Promise; reload: (tabId?: string) => Promise; + resolveAutomationHandoff: (status: "completed" | "dismissed") => Promise; selectTab: (tabId: string) => Promise; + startChromeLoginImport: (request: ChromeLoginImportRequest) => Promise; onStateChanged: (callback: (state: BuiltInBrowserState) => void) => () => void; }; } @@ -29,10 +32,12 @@ function BrowserChrome() { const [state, setState] = useState(emptyState); const [addressDraft, setAddressDraft] = useState(""); const [homeDraft, setHomeDraft] = useState(""); + const [chromeImportJob, setChromeImportJob] = useState(); const activeTab = useMemo( () => state.tabs.find((tab) => tab.id === state.activeTabId), [state.activeTabId, state.tabs] ); + const handoff = state.automationHandoff; const homeVisible = activeTab?.url === browserHomeUrl; useEffect(() => { @@ -57,6 +62,25 @@ function BrowserChrome() { setHomeDraft(""); }, [activeTab?.id]); + useEffect(() => { + if (!chromeImportJob || chromeImportJob.status !== "pending") { + return; + } + const interval = window.setInterval(() => { + void window.ccrBrowser?.getChromeLoginImport(chromeImportJob.id).then((job) => { + if (!job) { + setChromeImportJob(undefined); + return; + } + setChromeImportJob(job); + if (job.status === "completed" && activeTab?.id && activeTabMatchesImport(activeTab.url, job.domains)) { + void run(window.ccrBrowser?.reload(activeTab.id)); + } + }); + }, 2000); + return () => window.clearInterval(interval); + }, [activeTab?.id, activeTab?.url, chromeImportJob]); + async function run(action: Promise | undefined): Promise { if (!action) { return; @@ -79,8 +103,53 @@ function BrowserChrome() { void run(window.ccrBrowser?.navigate(url, activeTab?.id)); } + async function startChromeLoginImport() { + const defaultDomain = activeTabDomain(activeTab?.url); + const rawDomains = window.prompt("Chrome domain(s) to import. Separate multiple domains with commas.", defaultDomain); + if (!rawDomains) { + return; + } + const domains = parseImportDomains(rawDomains); + if (domains.length === 0) { + return; + } + const job = await window.ccrBrowser?.startChromeLoginImport({ domains, openConfirmationPage: true }); + if (!job) { + return; + } + setChromeImportJob(job); + } + + function chromeImportTitle(): string { + if (!chromeImportJob) { + return "Import login from Chrome"; + } + if (chromeImportJob.status === "completed") { + return `Chrome import complete: ${chromeImportJob.result?.imported ?? 0} imported, ${chromeImportJob.result?.skipped ?? 0} skipped`; + } + if (chromeImportJob.status === "expired") { + return "Chrome import expired"; + } + if (chromeImportJob.status === "failed") { + return "Chrome import failed"; + } + return "Chrome import pending. Confirm it in the browser window."; + } + + async function handleChromeImportButton() { + if (chromeImportJob?.status === "pending") { + try { + await navigator.clipboard.writeText(chromeImportJob.confirmUrl); + } catch { + window.prompt("Open this Chrome import confirmation URL.", chromeImportJob.confirmUrl); + } + return; + } + await startChromeLoginImport(); + } + return ( -
    +
    @@ -149,8 +218,45 @@ function BrowserChrome() { spellCheck={false} value={addressDraft} /> + + {handoff ? ( +
    +
    + + {handoff.message} + {handoff.reason ? {handoff.reason} : null} +
    +
    + + +
    +
    + ) : null} + {homeVisible ? (
    @@ -189,6 +295,42 @@ function BrowserChrome() { const root = createRoot(document.getElementById("root") as HTMLElement); root.render(); +function activeTabDomain(url: string | undefined): string { + if (!url || url === browserHomeUrl) { + return ""; + } + try { + return new URL(url).hostname; + } catch { + return ""; + } +} + +function activeTabMatchesImport(url: string | undefined, domains: string[]): boolean { + const host = activeTabDomain(url).toLowerCase(); + return Boolean(host && domains.some((domain) => host === domain || host.endsWith(`.${domain}`))); +} + +function parseImportDomains(value: string): string[] { + return [...new Set(value + .split(/[,\n]/) + .map((item) => normalizeImportDomain(item)) + .filter((item): item is string => Boolean(item)))]; +} + +function normalizeImportDomain(value: string): string | undefined { + const raw = value.trim().toLowerCase(); + if (!raw) { + return undefined; + } + try { + return new URL(raw.includes("://") ? raw : `https://${raw}`).hostname; + } catch { + const domain = raw.replace(/^\*\./, "").split("/")[0]; + return domain && !domain.includes(" ") ? domain : undefined; + } +} + const style = document.createElement("style"); style.textContent = ` :root { @@ -231,6 +373,10 @@ style.textContent = ` width: 100%; } + .browser-shell.has-handoff { + grid-template-rows: 38px 44px 44px minmax(0, 1fr); + } + .tabs-row { -webkit-app-region: drag; align-items: end; @@ -325,7 +471,7 @@ style.textContent = ` border-bottom: 1px solid color-mix(in srgb, CanvasText 12%, transparent); display: grid; gap: 4px; - grid-template-columns: 32px 32px 32px minmax(0, 1fr); + grid-template-columns: 32px 32px 32px minmax(0, 1fr) 32px; padding: 6px 10px; } @@ -339,6 +485,11 @@ style.textContent = ` opacity: 0.4; } + .icon-button.active-import { + background: color-mix(in srgb, #0f766e 16%, transparent); + color: color-mix(in srgb, #0f766e 82%, CanvasText); + } + input { background: color-mix(in srgb, CanvasText 4%, Canvas); border: 1px solid color-mix(in srgb, CanvasText 12%, transparent); @@ -355,6 +506,79 @@ style.textContent = ` border-color: color-mix(in srgb, #2563eb 70%, CanvasText 30%); } + .automation-handoff { + -webkit-app-region: drag; + align-items: center; + background: color-mix(in srgb, #f59e0b 16%, Canvas); + border-bottom: 1px solid color-mix(in srgb, #92400e 24%, transparent); + display: grid; + gap: 10px; + grid-template-columns: minmax(0, 1fr) auto; + min-width: 0; + padding: 6px 10px; + } + + .handoff-copy { + align-items: center; + color: color-mix(in srgb, #78350f 76%, CanvasText); + display: flex; + gap: 8px; + min-width: 0; + } + + .handoff-message, + .handoff-reason { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .handoff-message { + font-size: 13px; + font-weight: 700; + } + + .handoff-reason { + color: color-mix(in srgb, CanvasText 58%, transparent); + font-size: 12px; + } + + .handoff-actions { + -webkit-app-region: no-drag; + align-items: center; + display: flex; + flex: 0 0 auto; + gap: 6px; + } + + .handoff-button { + align-items: center; + background: color-mix(in srgb, CanvasText 5%, Canvas); + border: 1px solid color-mix(in srgb, CanvasText 12%, transparent); + border-radius: 7px; + display: inline-flex; + gap: 5px; + height: 30px; + justify-content: center; + min-width: 0; + padding: 0 9px; + white-space: nowrap; + } + + .handoff-button:hover { + background: color-mix(in srgb, CanvasText 9%, Canvas); + } + + .handoff-button.primary { + background: #166534; + border-color: #166534; + color: white; + } + + .handoff-button.primary:hover { + background: #14532d; + } + .home-page { align-items: flex-start; background: @@ -487,6 +711,24 @@ style.textContent = ` .installed-apps { grid-template-columns: 1fr; } + + .automation-handoff { + gap: 6px; + grid-template-columns: minmax(0, 1fr) auto; + } + + .handoff-reason { + display: none; + } + + .handoff-button span { + display: none; + } + + .handoff-button { + padding: 0 8px; + width: 32px; + } } @keyframes spin { diff --git a/packages/ui/src/pages/home/components/settings.tsx b/packages/ui/src/pages/home/components/settings.tsx index 0b12d40..2b6657f 100644 --- a/packages/ui/src/pages/home/components/settings.tsx +++ b/packages/ui/src/pages/home/components/settings.tsx @@ -4,6 +4,7 @@ import { botGatewaySavedConfigFromDraft, botGatewaySavedConfigLabel, BotGatewayQrLoginStartResult, BotGatewayQrLoginWaitResult, BotGatewayQrWindowOpenResult, BotGatewaySavedConfig, Button, CircleAlert, closestCenter, cn, CSS, Database, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Field, formatAppError, formatProviderAccountMeterValue, formatSystemOption, Gauge, + Globe, createBotGatewayConfigDraft, createMcpServerDraft, createMcpServerDraftFromConfig, createMcpServerDraftFromUnknown, createRouteModelOptions, DndContext, DragEndEvent, GatewayMcpServerConfig, GatewayProviderConfig, Input, isBotGatewayConfigDraftSubmittable, KeyboardSensor, KeyRound, KeyValueRowsControl, languageDisplayName, Layers3, LoaderCircle, mcpServerConfigFromDraft, mcpServerEndpointSummary, mcpServerTransportOptions, mcpStdioMessageModeOptions, McpServerDraft, normalizeBotGatewayAuthType, normalizeBotGatewayPlatform, normalizeToolHubConfig, Palette, Pencil, Plus, ProfileConfig, profileAgentLabel, PanelLeftOpen, Power, ProviderAccountMeter, ProviderAccountSnapshot, ReactNode, ResolvedLanguage, ResolvedTheme, Select, SelectControl, @@ -512,6 +513,13 @@ function ToolHubSettingsPage({
    {toolHub.enabled ? ( <> + onChange({ browserAutomation })} + />
    {t("MCP servers")}
    -
    {String(toolHub.mcpServers.length)}
    ## Embeddable Button Component diff --git a/docs/src/content/docs/zh/configuration/provider-deeplink.md b/docs/src/content/docs/zh/configuration/provider-deeplink.md index 703ad8e..ac471c7 100644 --- a/docs/src/content/docs/zh/configuration/provider-deeplink.md +++ b/docs/src/content/docs/zh/configuration/provider-deeplink.md @@ -78,6 +78,14 @@ lead: 快速添加常见模型供应商,确认无误后即可保存,减少 TeamoRouterAnthropic / Chat / Responses + + + code0.aiAnthropic / Chat / Responses + + + + claudeapiAnthropic Messages +
    ## 嵌入式按钮组件 diff --git a/docs/src/styles/global.css b/docs/src/styles/global.css index 215d8c2..094438b 100644 --- a/docs/src/styles/global.css +++ b/docs/src/styles/global.css @@ -1318,6 +1318,18 @@ h1 { --provider-brand-3: #f4f4f5; } +.doc-markdown a.provider-import-button.provider-code0 { + --provider-brand: #101214; + --provider-brand-2: #267dff; + --provider-brand-3: #d8e8ff; +} + +.doc-markdown a.provider-import-button.provider-claudeapi { + --provider-brand: #0d1b2a; + --provider-brand-2: #2f8f83; + --provider-brand-3: #d7fff8; +} + .doc-markdown a.provider-import-button.provider-deepseek { --provider-brand: #173aa8; --provider-brand-2: #4e69ff; diff --git a/packages/core/src/providers/presets/claudeapi/index.ts b/packages/core/src/providers/presets/claudeapi/index.ts new file mode 100644 index 0000000..85a7b65 --- /dev/null +++ b/packages/core/src/providers/presets/claudeapi/index.ts @@ -0,0 +1,15 @@ +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; + +export const claudeApiProviderPreset: ProviderPreset = { + account: defaultProviderAccountConfig, + aliases: ["claudeapi", "claudeapi.com", "www.claudeapi.com"], + endpoints: [ + { + baseUrl: "https://gw.claudeapi.com", + protocols: ["anthropic_messages"] + } + ], + id: "claudeapi", + name: "claudeapi", + websiteUrl: "https://www.claudeapi.com?source=claudecoderouter" +}; diff --git a/packages/core/src/providers/presets/code0/index.ts b/packages/core/src/providers/presets/code0/index.ts new file mode 100644 index 0000000..b87be18 --- /dev/null +++ b/packages/core/src/providers/presets/code0/index.ts @@ -0,0 +1,15 @@ +import { defaultProviderAccountConfig, type ProviderPreset } from "@ccr/core/providers/presets/types"; + +export const code0ProviderPreset: ProviderPreset = { + account: defaultProviderAccountConfig, + aliases: ["code0", "code0.ai", "code 0"], + endpoints: [ + { + baseUrl: "https://console.code0.ai", + protocols: ["anthropic_messages", "openai_chat_completions", "openai_responses"] + } + ], + id: "code0", + name: "code0.ai", + websiteUrl: "https://code0.ai?source=claudecoderouter" +}; diff --git a/packages/core/src/providers/presets/index.ts b/packages/core/src/providers/presets/index.ts index 760c2b1..85c1c86 100644 --- a/packages/core/src/providers/presets/index.ts +++ b/packages/core/src/providers/presets/index.ts @@ -1,5 +1,7 @@ import { anthropicProviderPreset } from "@ccr/core/providers/presets/anthropic/index"; import { bailianProviderPreset } from "@ccr/core/providers/presets/bailian/index"; +import { claudeApiProviderPreset } from "@ccr/core/providers/presets/claudeapi/index"; +import { code0ProviderPreset } from "@ccr/core/providers/presets/code0/index"; import { deepSeekProviderPreset } from "@ccr/core/providers/presets/deepseek/index"; import { geminiProviderPreset } from "@ccr/core/providers/presets/gemini/index"; import { kimiCodingProviderPreset } from "@ccr/core/providers/presets/kimi-coding/index"; @@ -42,7 +44,9 @@ export const providerPresets: ProviderPreset[] = [ bailianProviderPreset, siliconFlowProviderPreset, runApiProviderPreset, - teamoRouterProviderPreset + teamoRouterProviderPreset, + code0ProviderPreset, + claudeApiProviderPreset ]; export function getProviderPresets(): ProviderPreset[] { diff --git a/packages/ui/src/assets/provider-icons/claudeapi.png b/packages/ui/src/assets/provider-icons/claudeapi.png new file mode 100644 index 0000000000000000000000000000000000000000..776ced8c7f16c712ffe861794957f6b31bcd56ff GIT binary patch literal 17658 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

    K!}f5Qjf?D;}Dsfk#(;d=GK6jJ3(o(vej z!#1O5^FK+1P9yexdlJ`t)^eyS7lA}%>8RCG`hm{UDZRj~BF%?Y%s*fM0#Yf@RuS=@ zw{IN0Pt`MT-)z#8vj3Zy@0u>|;F8;?qh$xH&KeW$ESUIarC$|qEt7o1*cd#5{fc@g zlFBex?Q9>(MgANPsDfPewvK-CJaEUt#4FM=TH>|En?DYGi!pGp?6?E&0*I!j{gC?> zZ9fQOXP;@)H1HkpZU!J^2$76iO8d|nT!_+j2A~DN{qpa-01dnzmoMS}E6rWBD;*|` zefy+@IPW&oye9l#6TYOM>L6YV`nLFt$8~}Kf!i|?kSkA%i`2S2az@GDdJ4ePmA(dI z!{$$`+X=3G9F&Zbst+BkahL$t$vN=C2dXwQ7{(UF9SE2B3wXEkrLLo#lCNKwbF4|O z^xOBGAwT|g=}!ZhC5#(!cH3K!^o(mUYYA@vcDAPvde8k?xQl0#~Sc@bAx49>nfmkH1}zhK|TTkq;qz&f7dA{iQ5mv_c;?wDw@Mm z`!l}p{RjIf2-otJj*q!;J>b+x8C-Sb{Z<-1{%OwmL=qY%LxbWs_D_&_>m9FwfS6o7 zO?WwQ&lZ==Oie`I3V5Lq&I2hH=cH1#7mc%Ht~cE`zav?zrA=EOGxqe4Q~Icqog@XNlubW&q(@yvB!)gK zI|8oHTqZ;YGxI;0%I&K*4DbYiQ%Bke_b<1%8cZC@>g?j?8KR2arS#mYivKM;c0auj z;M6)NLF(-IEv3gtw!g=zjUXHG2dZKK4F1{lk9M~2madBqGyq42hQ3G-OkG2!U5*!g zJt0VAG)Ce76r7M@dgcGw@l#VYZ;n?XrdJVj_%2ZuTK3P`O#R_jvVwG;7@p#|(f|-J zK|;{2wl%QEx%eedG~nz3fHI46HuY9EDQ`DDoJmRah`WW9Fkykg_u(xSh^K4(XyOpj zShaxGlx~jx`x!1j8z{3~*W^-95BNL%TnYMJ30E=2*oXD86>!$)?Gj50hiW&$_reeC zsP2EbkVqOFegx7UsRdj`(jq&naFW|(#k7uDTlkj<#IOp(4^$Itcxh&gqGkdk%jIyD zvB*K#Sd(q{^`^j=F@WUr|GU48@&Ef(5UmlBd**D`UHfb%rgq?CCRl39+Dc_FUWNTX DcEeu; literal 0 HcmV?d00001 diff --git a/packages/ui/src/assets/provider-icons/code0.png b/packages/ui/src/assets/provider-icons/code0.png new file mode 100644 index 0000000000000000000000000000000000000000..32d81c1e5b44077d6fcb83e675350cbfee6f84a2 GIT binary patch literal 12992 zcmeHNdr(tX8b1MSgeBrYmGBs#+uc&zQ3)sz2|=nh_^8|6I!IhZAdsLbO_)?(K}pet zN>g#mOCAMB*5d98f;=J;LI5E?LV#8why-XB0tFHvJQou7+?3A#xlU(0vv=oa=FWHT zot%5m_xpa|>zsS#!{ao9m7^5^fN(hI;2!|k)C7;Z&9A{bJ0>sOhr*H@MCSp(E=LYr ztK}PJ0Jwp}2Ln$e7Eisz#g@^ChkADB;w)M{t8gJtcHzz6zU(=^>(YJ?z>9guVZYpM zYhKHl+`ydJn~F~I*0H3hi5G>9u>;5}0I<4P^J~+|wwpdC>3~TL2Z#X11I!-~N5FW1 z@c`oi<_B0Dz~aCw#R0#M%z$)q>TdbntC^ephpy(ArkexmuX>yS2-EFmwWmos<=G=Z z(ABDc3wa=j`do!cG~qCH%o2dRi=Dr(zQHQPfzro=^|7zv5gWR-pv|U;E;*2_vH~(O zx4oDnd)gnvQLAd+UUC7dE8_(4Y|TmHa_328iS^>qm;B&=sLZ8)vtkGU{9!Q>Wic^f zpq_4rSWaNEK5uG(-438cGFV4INS`rzpIqK#NtZvJPD~;>ikl4!^U+L+@44-IeY}|B zL=cE%&mzOa~sa^7uWw@E(5+59b0cuo7i-~^ed73Ej zB8gdY>``P2b3}cgb7#E$99YYQt2xeYh)IDZ+H8yHWYjJ8eV6^`kZxCzdbFQAV}zG# zxwG@$FUYWbsObU2&F;zORcZKlt8ehjS~AGXmEnOGpWy`w-zS;88F(J#nAFE4N^sED z$JYE<{aG3(2#D?Wr^9562HBy=0acZTg-@>+@a^th&mWkbi>wvJ>F7Cp8<0YTb#CvX zua}K?6e)l?*4U$xH?a++ue zSedj;et_`h)gSt*FaM?fhbRSB&nG3E_wPuIqLWEFRlr5V%WrUi`vVNn0>i*2oqO*# z0ETD8Dhot4fi=<=ViGkt2pf9u6(JS_zghkUWyL?Xv1KwG*0lbQ@z%cHcKB%_kLtvr z)DrN#n%)Ni*$rlE8Dxz%K~ZBD=CBm!`(A#+$u1djsK4maiqW9%>tlJI6s#AG6J|!I zTifhbxgH{hb+v^rt8v`${bh}gxegVhwR>dEcZB?j&vyFW zrtuUNR}-2c9*VQ(K%<@0{?L=&RZ?=+X0l~;@Of}*SxVyi@fou#71AAjvA&$gHJ5Dlxr*?j#^O5u!eTF*8rP_$%qPpI{R``J8 zJ+Npgj3djlQ3mF23fM^%7G!5<*LA&S;CX7q(8qn)6;AfA!;GFnhMub#dz`Kx^t^2jZW!Y0J}#RVd#<4dY`q#rnw zH%^e!>1_WxHN5KH+jpwoe%M}47FplwV+fD(xgJB6b)QUT7xSo&42m))v2QA($(2DF z8voq2_mu8x#8*5;21A(PL`dD+vE24PgO^B)b1mg*wbfbPl!rwG*;rLrMO~OnTj3ei z=_Km7a(-nPW>ea13YcL-t4Q6Ib6JyQ4W2KR#5#DMuE8&jI8$%a*@K*g%o|y*W-V}d z{8H=AQk##7d{OixvAA(Ag&Wf7@pUQ{EZ42k^5Dc#u%#8MJR;bwWeVfiPFu!|$Hp3N z=^8)sJJP$VH$d-OeCA$FJM$$;qT;SK3rQE1+%;Z9R`b3zUU-0Fl-#BJd)qw~SZ@nu z(_Kya+G712*2h97pr((-)*UduVtmE?6^jE{9KhnhzvDn^Ed##?0P!w^~6P8;yV2SWNqNgGUn^n|MEcRe=rvzkn`A86QRSIJ%^M{VRkH OhYuY;SovOf+P?sKzeTD5 literal 0 HcmV?d00001 diff --git a/packages/ui/src/pages/home/shared/options.ts b/packages/ui/src/pages/home/shared/options.ts index 2415bb1..18ad4c8 100644 --- a/packages/ui/src/pages/home/shared/options.ts +++ b/packages/ui/src/pages/home/shared/options.ts @@ -43,6 +43,8 @@ import type { } from "@ccr/core/contracts/app"; import anthropicProviderIconUrl from "@/assets/provider-icons/anthropic.png"; import bailianProviderIconUrl from "@/assets/provider-icons/bailian.ico"; +import claudeapiProviderIconUrl from "@/assets/provider-icons/claudeapi.png"; +import code0ProviderIconUrl from "@/assets/provider-icons/code0.png"; import deepseekProviderIconUrl from "@/assets/provider-icons/deepseek.ico"; import geminiProviderIconUrl from "@/assets/provider-icons/gemini.svg"; import mistralProviderIconUrl from "@/assets/provider-icons/mistral.webp"; @@ -319,6 +321,8 @@ export const mcpStdioMessageModeOptions: Array<{ label: string; value: GatewayMc export const providerPresetIconUrls: Record = { anthropic: anthropicProviderIconUrl, bailian: bailianProviderIconUrl, + claudeapi: claudeapiProviderIconUrl, + code0: code0ProviderIconUrl, deepseek: deepseekProviderIconUrl, gemini: geminiProviderIconUrl, "kimi-coding": moonshotProviderIconUrl, From c8cbc09b96c97e768b6c8ee94d87cd4c553b4d1b Mon Sep 17 00:00:00 2001 From: musistudio Date: Tue, 7 Jul 2026 20:29:32 +0800 Subject: [PATCH 39/62] add new api probe --- packages/core/src/contracts/app.ts | 8 +- packages/core/src/gateway/service.ts | 19 ++- .../core/src/providers/account-service.ts | 134 ++++++++++++++++++ packages/core/src/providers/new-api.ts | 92 ++++++++++++ packages/core/src/providers/probe.ts | 35 ++++- .../src/pages/home/components/providers.tsx | 67 ++++++--- packages/ui/src/pages/home/shared/i18n.tsx | 2 + .../ui/src/pages/home/shared/providers.ts | 53 ++++++- tests/main/gateway-virtual-models.test.mjs | 51 +++++++ tests/main/provider-probe.test.mjs | 67 +++++++++ tests/renderer/providers.test.ts | 58 ++++++++ 11 files changed, 559 insertions(+), 27 deletions(-) create mode 100644 packages/core/src/providers/new-api.ts diff --git a/packages/core/src/contracts/app.ts b/packages/core/src/contracts/app.ts index 685a35e..0dbfa56 100644 --- a/packages/core/src/contracts/app.ts +++ b/packages/core/src/contracts/app.ts @@ -160,6 +160,7 @@ export type ProviderAccountStatus = "ok" | "warning" | "critical" | "error" | "u export type ProviderAccountMeterKind = "balance" | "subscription" | "quota" | "time_window" | "tokens" | "requests"; export type ProviderAccountMeterUnit = "USD" | "CNY" | "hours" | "minutes" | "tokens" | "requests" | string; export type ProviderAccountMeterWindow = "5h" | "daily" | "weekly" | "monthly" | string; +export type ProviderAccountHttpJsonParser = "kimi-code-usages" | "new-api-key-usage" | "new-api-user-self"; export type ProviderAccountConfig = { connectors?: ProviderAccountConnectorConfig[]; @@ -193,7 +194,7 @@ export type ProviderAccountHttpJsonConnectorConfig = ProviderAccountConnectorBas headers?: Record; mapping: ProviderAccountMappingConfig; method?: "GET" | "POST"; - parser?: "kimi-code-usages"; + parser?: ProviderAccountHttpJsonParser; type: "http-json"; }; @@ -405,6 +406,8 @@ export type GatewayProviderCapability = { type: GatewayProviderProtocol; }; +export type GatewayProviderDetectedProvider = "new-api"; + export type GatewayProviderProbeRequest = { apiKey?: string; baseUrl: string; @@ -445,6 +448,7 @@ export type ProviderIconDetectionResult = { export type GatewayProviderProbeProtocolResult = { baseUrl?: string; + detectedProvider?: GatewayProviderDetectedProvider; endpoint: string; message: string; protocol: GatewayProviderProtocol; @@ -453,7 +457,9 @@ export type GatewayProviderProbeProtocolResult = { }; export type GatewayProviderProbeResult = { + account?: ProviderAccountConfig; capabilities?: GatewayProviderCapability[]; + detectedProvider?: GatewayProviderDetectedProvider; detectedProtocol?: GatewayProviderProtocol; modelDisplayNames?: Record; modelSource?: "anthropic" | "gemini" | "openai"; diff --git a/packages/core/src/gateway/service.ts b/packages/core/src/gateway/service.ts index ef274df..5936e7a 100644 --- a/packages/core/src/gateway/service.ts +++ b/packages/core/src/gateway/service.ts @@ -391,7 +391,7 @@ class GatewayService { } if (shouldRunGateway) { - await writeCoreGatewayConfig(config, this.rawTraceSyncToken, this.browserWebSearchMcpIntegration); + await writeCoreGatewayConfig(config, this.rawTraceSyncToken, this.coreAuthToken, this.browserWebSearchMcpIntegration); await stopPreviousManagedCoreGateway(config, this.status.coreEndpoint); if (await isCoreGatewayHealthy(this.status.coreEndpoint)) { throw new Error(`Core gateway endpoint is already in use: ${this.status.coreEndpoint}`); @@ -1150,6 +1150,7 @@ export const gatewayService = new GatewayService(); async function writeCoreGatewayConfig( config: AppConfig, rawTraceSyncToken: string, + coreAuthToken: string, browserWebSearchMcpIntegration?: BrowserWebSearchMcpIntegration ): Promise { assertLoopbackCoreHost(config.gateway.coreHost); @@ -1165,7 +1166,7 @@ async function writeCoreGatewayConfig( ...pluginService.getVirtualModelProfiles() ])), config); const coreEndpoint = endpoint(config.gateway.coreHost, config.gateway.corePort); - const builtinToolArtifacts = await fusionBuiltinToolArtifacts(virtualModelProfiles, coreEndpoint, browserWebSearchMcpIntegration); + const builtinToolArtifacts = await fusionBuiltinToolArtifacts(virtualModelProfiles, coreEndpoint, coreAuthToken, browserWebSearchMcpIntegration); const providers = [ ...config.Providers .flatMap((provider) => toCoreGatewayProviders(withCodexOauthProviderBaseUrl(provider, codexOauthProviderNames))) @@ -1459,6 +1460,7 @@ function hasOwn(value: Record, key: string): boolean { async function fusionBuiltinToolArtifacts( profiles: unknown[], coreEndpoint: string, + coreAuthToken: string, browserWebSearchMcpIntegration?: BrowserWebSearchMcpIntegration ): Promise<{ mcpServers: GatewayMcpServerConfig[]; providers: CoreGatewayProvider[] }> { const providers: CoreGatewayProvider[] = []; @@ -1481,12 +1483,14 @@ async function fusionBuiltinToolArtifacts( const toolServerKey = `vision:${visionConfig.toolName}`; if (!toolServerKeys.has(toolServerKey)) { toolServerKeys.add(toolServerKey); + const useGatewayVisionRuntime = !visionConfig.baseUrl; mcpServers.push(fusionBuiltinMcpServer({ entry, env: { FUSION_BUILTIN_TOOL_KIND: "vision", FUSION_TOOL_NAME: visionConfig.toolName, - ...(visionConfig.baseUrl ? { VISION_BASE_URL: visionConfig.baseUrl } : { VISION_GATEWAY_BASE_URL: `${coreEndpoint}/v1` }), + ...(useGatewayVisionRuntime ? { VISION_GATEWAY_BASE_URL: `${coreEndpoint}/v1` } : { VISION_BASE_URL: visionConfig.baseUrl || "" }), + ...(useGatewayVisionRuntime && coreAuthToken ? { VISION_GATEWAY_API_KEY: coreAuthToken } : {}), ...(resolvedVision.model ? { VISION_MODEL: resolvedVision.model } : {}), ...(visionConfig.baseUrl && visionConfig.apiKey ? { VISION_API_KEY: visionConfig.apiKey } : {}), ...(visionConfig.timeoutMs ? { VISION_TIMEOUT_MS: String(visionConfig.timeoutMs) } : {}) @@ -1534,6 +1538,15 @@ async function fusionBuiltinToolArtifacts( return { mcpServers, providers }; } +export async function fusionBuiltinToolArtifactsForTest( + profiles: unknown[], + coreEndpoint: string, + coreAuthToken: string, + browserWebSearchMcpIntegration?: BrowserWebSearchMcpIntegration +): Promise<{ mcpServers: GatewayMcpServerConfig[]; providers: unknown[] }> { + return fusionBuiltinToolArtifacts(profiles, coreEndpoint, coreAuthToken, browserWebSearchMcpIntegration); +} + function fusionBuiltinMcpServer({ entry, env, diff --git a/packages/core/src/providers/account-service.ts b/packages/core/src/providers/account-service.ts index 31df114..adeb61d 100644 --- a/packages/core/src/providers/account-service.ts +++ b/packages/core/src/providers/account-service.ts @@ -146,6 +146,26 @@ export async function testProviderAccountConnector(request: ProviderAccountTestR status: statusFromMeters(meters, [], 1) }; } + if (connector.parser === "new-api-key-usage") { + const meters = newApiKeyUsageMeters(payload); + return { + meters, + message: meters.length === 0 ? newApiKeyUsageFallbackMessage(payload) : readMappedString(connector.mapping.message, payload), + paths: flattenJsonPaths(payload), + payload, + status: statusFromMeters(meters, [], 1) + }; + } + if (connector.parser === "new-api-user-self") { + const meters = newApiUserSelfMeters(payload); + return { + meters, + message: meters.length === 0 ? readMappedString(connector.mapping.message, payload) ?? "No user balance data available." : readMappedString(connector.mapping.message, payload), + paths: flattenJsonPaths(payload), + payload, + status: statusFromMeters(meters, [], 1) + }; + } const meters = mappedMetersFromPayload(connector, payload); @@ -158,6 +178,18 @@ export async function testProviderAccountConnector(request: ProviderAccountTestR }; } +export function newApiKeyUsageMetersForTest(payload: unknown): ProviderAccountMeter[] { + return newApiKeyUsageMeters(payload); +} + +export function newApiKeyUsageFallbackMessageForTest(payload: unknown): string { + return newApiKeyUsageFallbackMessage(payload); +} + +export function newApiUserSelfMetersForTest(payload: unknown): ProviderAccountMeter[] { + return newApiUserSelfMeters(payload); +} + export async function resetCodexRateLimitCredit(request: ProviderAccountResetRequest): Promise { const providerName = request.provider?.trim(); const creditId = request.creditId?.trim(); @@ -586,6 +618,24 @@ async function resolveHttpJsonConnector( source: "http-json" }; } + if (connector.parser === "new-api-key-usage") { + const meters = newApiKeyUsageMeters(payload); + return { + errors: [], + message: meters.length === 0 ? newApiKeyUsageFallbackMessage(payload) : readMappedString(connector.mapping.message, payload), + meters, + source: "http-json" + }; + } + if (connector.parser === "new-api-user-self") { + const meters = newApiUserSelfMeters(payload); + return { + errors: [], + message: meters.length === 0 ? readMappedString(connector.mapping.message, payload) ?? "No user balance data available." : readMappedString(connector.mapping.message, payload), + meters, + source: "http-json" + }; + } const meters = mappedMetersFromPayload(connector, payload); return { @@ -758,6 +808,90 @@ function normalizeRemoteSnapshot( }; } +function newApiKeyUsageMeters(payload: unknown): ProviderAccountMeter[] { + const meter = newApiKeyUsageMeter(payload); + return meter ? [meter] : []; +} + +function newApiKeyUsageMeter(payload: unknown): ProviderAccountMeter | undefined { + const data = newApiKeyUsageData(payload); + if (!data) { + return undefined; + } + + const unlimited = readBoolean(readJsonRecordValue(data, "unlimited_quota")) === true; + const remaining = normalizeNumber(readJsonRecordValue(data, "total_available")); + const limit = normalizeNumber(readJsonRecordValue(data, "total_granted")); + const used = normalizeNumber(readJsonRecordValue(data, "total_used")); + if (unlimited || (limit === undefined && remaining === undefined && used === undefined)) { + return undefined; + } + + return { + id: "new_api_key_quota", + kind: "quota", + label: "API key quota", + limit, + remaining, + source: "http-json", + unit: "quota", + used + }; +} + +function newApiKeyUsageFallbackMessage(payload: unknown): string { + const data = newApiKeyUsageData(payload); + if (data && readBoolean(readJsonRecordValue(data, "unlimited_quota")) === true) { + return "API key has no dedicated quota limit. Configure a New API user balance connector with an access token and New-Api-User to read account balance."; + } + return readMappedString("$.message", payload) ?? "No API key quota data available."; +} + +function newApiUserSelfMeters(payload: unknown): ProviderAccountMeter[] { + const meter = newApiUserSelfMeter(payload); + return meter ? [meter] : []; +} + +function newApiUserSelfMeter(payload: unknown): ProviderAccountMeter | undefined { + const data = newApiUserSelfData(payload); + if (!data) { + return undefined; + } + + const remaining = normalizeNumber(readJsonRecordValue(data, "quota")); + const used = normalizeNumber(readJsonRecordValue(data, "used_quota")); + if (remaining === undefined && used === undefined) { + return undefined; + } + + return { + id: "new_api_user_balance", + kind: "balance", + label: "User balance", + limit: remaining !== undefined && used !== undefined ? remaining + used : undefined, + remaining, + source: "http-json", + unit: "quota", + used + }; +} + +function newApiKeyUsageData(payload: unknown): Record | undefined { + if (!isRecord(payload)) { + return undefined; + } + const data = readJsonRecordValue(payload, "data"); + return isRecord(data) ? data : payload; +} + +function newApiUserSelfData(payload: unknown): Record | undefined { + if (!isRecord(payload)) { + return undefined; + } + const data = readJsonRecordValue(payload, "data"); + return isRecord(data) ? data : payload; +} + function kimiCodeUsageMeters(payload: unknown): ProviderAccountMeter[] { if (!isRecord(payload)) { return []; diff --git a/packages/core/src/providers/new-api.ts b/packages/core/src/providers/new-api.ts new file mode 100644 index 0000000..44c2609 --- /dev/null +++ b/packages/core/src/providers/new-api.ts @@ -0,0 +1,92 @@ +import type { ProviderAccountConfig, ProviderAccountHttpJsonConnectorConfig } from "@ccr/core/contracts/app"; +import { compactProviderUrl, providerUrlWithDefaultScheme } from "@ccr/core/providers/url"; + +export type DetectedProviderKind = "new-api"; + +const newApiHeaderNames = ["x-new-api-version", "x-oneapi-request-id"]; + +export function detectedProviderFromHeaders(headers: Record): DetectedProviderKind | undefined { + return hasNewApiHeaders(headers) ? "new-api" : undefined; +} + +export function hasNewApiHeaders(headers: Record): boolean { + const normalized = new Set(Object.keys(headers).map((key) => key.toLowerCase())); + return newApiHeaderNames.some((header) => normalized.has(header)); +} + +export function newApiKeyUsageAccountConfig(baseUrl: string): ProviderAccountConfig { + return { + connectors: [ + { + auth: "provider-api-key", + endpoint: newApiKeyUsageEndpoint(baseUrl), + mapping: { + message: "$.message", + meters: [ + { + id: "new_api_key_quota", + kind: "quota", + label: "API key quota", + limit: "$.data.total_granted", + remaining: "$.data.total_available", + unit: "quota", + used: "$.data.total_used", + } + ] + }, + method: "GET", + parser: "new-api-key-usage", + type: "http-json" + } + ], + enabled: true + }; +} + +export function newApiKeyUsageEndpoint(baseUrl: string): string { + const root = newApiRootBaseUrl(baseUrl); + return `${root}/api/usage/token/`; +} + +export function newApiUserSelfConnectorConfig(baseUrl: string): ProviderAccountHttpJsonConnectorConfig { + return { + auth: "none", + endpoint: newApiUserSelfEndpoint(baseUrl), + headers: { + Authorization: "Bearer ", + "New-Api-User": "" + }, + mapping: { + meters: [ + { + id: "new_api_user_balance", + kind: "balance", + label: "User balance", + remaining: "$.data.quota", + unit: "quota", + used: "$.data.used_quota" + } + ] + }, + method: "GET", + parser: "new-api-user-self", + type: "http-json" + }; +} + +export function newApiUserSelfEndpoint(baseUrl: string): string { + const root = newApiRootBaseUrl(baseUrl); + return `${root}/api/user/self`; +} + +export function newApiRootBaseUrl(baseUrl: string): string { + try { + const url = new URL(providerUrlWithDefaultScheme(baseUrl.trim())); + url.pathname = url.pathname.replace(/\/+(v1|api)$/i, "").replace(/\/+$/, "") || "/"; + url.search = ""; + url.hash = ""; + return compactProviderUrl(url); + } catch { + return baseUrl.trim().replace(/[?#].*$/, "").replace(/\/+$/, "").replace(/\/(v1|api)$/i, ""); + } +} diff --git a/packages/core/src/providers/probe.ts b/packages/core/src/providers/probe.ts index e5b24d4..0a82346 100644 --- a/packages/core/src/providers/probe.ts +++ b/packages/core/src/providers/probe.ts @@ -19,6 +19,11 @@ import { providerBaseUrlForProtocol, type ParsedProviderBaseUrl } from "@ccr/core/providers/url"; +import { + detectedProviderFromHeaders, + newApiKeyUsageAccountConfig, + type DetectedProviderKind +} from "@ccr/core/providers/new-api"; type ModelSource = NonNullable; @@ -27,6 +32,8 @@ type ParsedProviderUrl = ParsedProviderBaseUrl & { }; type FetchJsonResult = { + detectedProvider?: DetectedProviderKind; + headers?: Record; payload?: unknown; status?: number; text: string; @@ -226,16 +233,21 @@ async function resolveGatewayProviderProbe(request: GatewayProviderProbeRequest) : typedModels; const protocolResults = await probeProtocols(parsed, request.apiKey, models, protocols, mode); const detectedProtocol = detectProtocol(parsed, protocolResults, modelProbe.source, protocols); + const normalizedBaseUrl = detectedProtocol + ? resolveProbeBaseUrl(parsed, detectedProtocol, protocolResults, modelProbe) + : parsed.normalizedInputBaseUrl; + const detectedProvider = detectProvider(protocolResults); + const account = detectedProvider === "new-api" ? newApiKeyUsageAccountConfig(normalizedBaseUrl) : undefined; return { + ...(account ? { account } : {}), capabilities: capabilitiesFromProtocolResults(protocolResults), + ...(detectedProvider ? { detectedProvider } : {}), detectedProtocol, modelDisplayNames: modelProbe.modelDisplayNames, modelSource: modelProbe.source, models: modelProbe.models, - normalizedBaseUrl: detectedProtocol - ? resolveProbeBaseUrl(parsed, detectedProtocol, protocolResults, modelProbe) - : parsed.normalizedInputBaseUrl, + normalizedBaseUrl, protocols: protocolResults }; } @@ -491,6 +503,7 @@ async function probeProtocolSupport( const supported = isProviderProtocolEndpointSupportedForProbe(result.status, message, protocol, parsed.hints); const probeResult = { baseUrl: candidate.baseUrl, + ...(result.detectedProvider ? { detectedProvider: result.detectedProvider } : {}), endpoint: candidate.endpoint, message, protocol, @@ -539,6 +552,7 @@ async function probeProtocolConnectivity( const supported = isProtocolSupported(result.status, message, protocol); const probeResult = { baseUrl: candidate.baseUrl, + ...(result.detectedProvider ? { detectedProvider: result.detectedProvider } : {}), endpoint: candidate.endpoint, message, protocol, @@ -663,7 +677,10 @@ async function requestJson(url: string, init: RequestInit): Promise item.detectedProvider)?.detectedProvider; +} + +function responseHeadersRecord(headers: Headers): Record { + const record: Record = {}; + headers.forEach((value, key) => { + record[key] = value; + }); + return record; +} + function parseProviderUrl(value: string): ParsedProviderUrl { const parsed = parseProviderBaseUrl(value); const url = new URL(parsed.normalizedInputBaseUrl); diff --git a/packages/ui/src/pages/home/components/providers.tsx b/packages/ui/src/pages/home/components/providers.tsx index 040617c..5f1753a 100644 --- a/packages/ui/src/pages/home/components/providers.tsx +++ b/packages/ui/src/pages/home/components/providers.tsx @@ -9,7 +9,7 @@ import { Layers3, LoaderCircle, localAgentProviderIconUrls, mergeProviderModelLists, modelCatalogItemMatchesQuery, motion, Pencil, Plus, PopoverContent, primaryProviderAccountMeter, primaryProviderPresetEndpoint, providerAccountConnectorApiKeySafetyIssue, providerAccountConnectorExample, ProviderAccountDraftMode, providerAccountModeOptions, ProviderAccountSnapshot, - providerAccountSnapshotCredentialLabel, providerAccountSnapshotLabel, ProviderAccountTestPath, + providerAccountConnectorsTextWithNewApiUserBalanceTemplate, providerAccountSnapshotCredentialLabel, providerAccountSnapshotLabel, ProviderAccountTestPath, ProviderAccountTestResult, providerBaseUrl, providerCapabilitiesSummary, ProviderCredentialDraft, ProviderDeepLinkPayload, ProviderDeepLinkRequest, providerDraftSafetyIssue, providerCredentialDraftPatchFromJson, providerHttpJsonConnectorFromDraft, ProviderConnectivityCheckReport, providerDeepLinkDisplayIcon, providerListItemKey, providerMatchesQuery, ProviderPreset, providerPresetIconUrls, providerProbeHasSupportedProtocol, providerModelDisplayName, providerModelDisplayTitle, providerSelectableProtocolsFromProbe, providerUsageFieldPatch, ProviderUsageFieldTarget, providerUsageMethodOptions, Search, SelectControl, @@ -2085,7 +2085,11 @@ function ProviderUsageSettings({ const [testLoading, setTestLoading] = useState(false); const [testResult, setTestResult] = useState(); const [testError, setTestError] = useState(""); + const [newApiUserId, setNewApiUserId] = useState(""); const modeOptions = translateOptions(providerAccountModeOptions, t); + const showNewApiUserBalanceTemplate = probe?.detectedProvider === "new-api" || + draft.accountConnectorsText.includes("new-api-key-usage") || + draft.accountConnectorsText.includes("new-api-user-self"); useEffect(() => { setTestResult(undefined); @@ -2135,6 +2139,16 @@ function ProviderUsageSettings({ onChange(providerUsageFieldPatch(target, path)); } + function insertNewApiUserBalanceTemplate() { + onChange({ + accountConnectorsText: providerAccountConnectorsTextWithNewApiUserBalanceTemplate( + draft.accountConnectorsText, + probe?.normalizedBaseUrl || draft.baseUrl, + newApiUserId + ) + }); + } + return (
    @@ -2253,23 +2267,40 @@ function ProviderUsageSettings({ ) : null} {draft.accountMode === "raw" ? ( - -