@aiti-ai/aiti-model-check 0.0.0-stage → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +28 -0
- package/LICENSE +21 -0
- package/client/src/entry.tsx +37 -0
- package/client/src/host-shim.ts +95 -0
- package/client/src/jsx-runtime.ts +2 -0
- package/client/src/model-check-app.tsx +1164 -0
- package/client/src/money.ts +41 -0
- package/core.cjs +1666 -0
- package/deploy-commit.mjs +52 -0
- package/dev-pack.mjs +246 -0
- package/dist/aiti-model-check-client.mjs +2527 -0
- package/ext-icons-deploy.mjs +24 -0
- package/icon.svg +1 -0
- package/icons/alert.svg +1 -0
- package/icons/check-circle.svg +1 -0
- package/icons/edit.svg +1 -0
- package/icons/eye.svg +1 -0
- package/icons/radio-dot.svg +1 -0
- package/icons/radio.svg +1 -0
- package/icons/refresh.svg +1 -0
- package/icons/search.svg +1 -0
- package/icons/wand.svg +1 -0
- package/icons/x-circle.svg +1 -0
- package/icons/x.svg +1 -0
- package/index.ts +935 -0
- package/package.json +30 -4
- package/reload-instances.mjs +60 -0
- package/skills/index.json +1 -0
- package/skills/model-check/SKILL.md +38 -0
- package/skills/model-config/SKILL.md +36 -0
- package/test/ocsession-learn.test.mjs +55 -0
- package/test/perkey.test.mjs +57 -0
- package/test/probeByProvider.test.mjs +65 -0
- package/test/recover.test.mjs +202 -0
- package/tools/build-client.mjs +27 -0
- package/tools/deploy.mjs +80 -0
- package/vendor/effective-models.cjs +176 -0
- package/vite.config.ts +41 -0
- package/README.md +0 -3
package/AGENTS.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# aiti-model-check 扩展(部署目录直改指南)
|
|
2
|
+
|
|
3
|
+
## 结构
|
|
4
|
+
- 运行时文件平铺在本目录根(index.ts / core.cjs …,宿主 loader 按相对路径加载——**禁手改运行时平铺文件**,改 src/ 再 build)
|
|
5
|
+
- `src/` 源码(client/src + 服务端 ts/cjs)· `dist/` 构建产物(禁手改)· `tools/` 构建/部署脚本
|
|
6
|
+
- npm 包形态的扩展:本目录 package.json 含 version 与 aitiConfigs 声明
|
|
7
|
+
|
|
8
|
+
## 小改现场流程(改文案/样式/小逻辑)
|
|
9
|
+
1. 改 `src/` 下对应源码
|
|
10
|
+
2. `node tools/build.mjs`(client 有构建产物的扩展;纯服务端改动跳过)
|
|
11
|
+
3. `curl -X POST http://127.0.0.1:3001/api/extensions/reload`(dev;prod 3002;外部经 nginx 带 basePath:/dev、/mac、/mdev、/win、/wdev → `/xxx/api/extensions/reload`)
|
|
12
|
+
4. 浏览器刷新页面验证
|
|
13
|
+
|
|
14
|
+
## 大改回源码仓
|
|
15
|
+
- 源码仓:`extensions/aiti-model-check/`(git 管理,deploy 同步回本目录)
|
|
16
|
+
- 改完在源码仓跑 `node tools/deploy.mjs`(或仓库根 `node scripts/deploy-all.mjs --only aiti-model-check`)
|
|
17
|
+
|
|
18
|
+
## 数据目录(数据分层:config / cache)
|
|
19
|
+
- `ctx.configDir` 根级 = 配置与关键小状态(aiti-configs 备份范围;小、珍贵、人可读)
|
|
20
|
+
- `ctx.cacheDir`(= `<configDir>/cache/`)= 可删缓存:db/venv/日志/大文件;rm -rf 后扩展下次访问自动重建
|
|
21
|
+
- `ctx.dataDir` 已移除(访问即抛)——新代码禁用
|
|
22
|
+
- 本扩展白名单(永不为 cache 的配置文件)见 package.json `aitiConfigs.configFiles`
|
|
23
|
+
|
|
24
|
+
## 红线
|
|
25
|
+
- 禁轮询(setInterval 拉取);需要等待用事件/回调/SSE(桥的 SSE、aiti-event-bus topics)
|
|
26
|
+
- 禁重启生产服务(pi-web 3002 等 restart 须用户明确同意)
|
|
27
|
+
- 禁手改 dist/ 与运行时平铺文件;跨扩展通信走 ctx.services(禁 globalThis.__ext 直穿)
|
|
28
|
+
- 服务端:SQLite 一律 WAL + 分批小事务(批间 setImmediate 让步);禁 execFileSync 调 >1s 外部程序;>8MB 文件禁同步读
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 aiti-ai contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* aiti-model-check · client 入口(JSX + TypeScript + vite 构建)。
|
|
3
|
+
*
|
|
4
|
+
* register(ctx) 装配:
|
|
5
|
+
* - pages slot 常驻 dialog(状态/余额/历史;meta.open 供 extman 直接打开)
|
|
6
|
+
* - toolbar 📡 按钮(feature 门控:behavior.showToolbar,默认关,动态注册/注销)
|
|
7
|
+
* - window.__aitiModelCheckApp = App(aiti-model-wizard「检查」tab 内嵌渲染)
|
|
8
|
+
*
|
|
9
|
+
* 构建:node tools/build-client.mjs (或 npm run build:client)
|
|
10
|
+
* 部署:node tools/deploy.mjs (自动先构建再拷贝)
|
|
11
|
+
*/
|
|
12
|
+
import { __setCtx } from "./host-shim";
|
|
13
|
+
import { declareDockZones, sidebarZoneOf } from "../../../_shared/ext-float-dock";
|
|
14
|
+
import { App, ModelDialog, ModelCheckDockFace, ModelCheckDockRight, setMcCtx, syncToolbar, initRecoverWatcher, MC_KEY } from "./model-check-app";
|
|
15
|
+
|
|
16
|
+
export default function register(ctx: Record<string, any>) {
|
|
17
|
+
// 重要:必须在任何 JSX 组件渲染前调用(host-shim 模块级持 ctx)
|
|
18
|
+
__setCtx(ctx);
|
|
19
|
+
setMcCtx(ctx);
|
|
20
|
+
|
|
21
|
+
ctx.registerItems?.("pages", [{ id: "aiti-model-check-dialog", render: ModelDialog, meta: { open: () => { try { window.__aitiModelCheckOpen && window.__aitiModelCheckOpen(); } catch {} } } }]);
|
|
22
|
+
// zones 全集注册(v3.1):TabBar ⇱ 菜单在 face 未挂载时的数据源;zone 用规范值 sidebar:<key>
|
|
23
|
+
declareDockZones(MC_KEY, [sidebarZoneOf(MC_KEY), "right-panel"], sidebarZoneOf(MC_KEY));
|
|
24
|
+
ctx.registerItems?.("rightFileDock", [{ id: "panel", render: ModelCheckDockRight, meta: { title: "模型可用性" } }]);
|
|
25
|
+
// aiti-float dock v3(P3 批):左栏可选(保守无 default)
|
|
26
|
+
ctx.registerItems?.("sidebarExplorerDock", [{ id: "panel", render: ModelCheckDockFace, meta: { title: "模型检查", priority: 3 } }]);
|
|
27
|
+
syncToolbar();
|
|
28
|
+
if (typeof ctx.onFeatureChange === "function") ctx.onFeatureChange(syncToolbar);
|
|
29
|
+
// 限流到期提醒(recover):页面级监听 mc:recover → 浏览器通知(面板不在前台/未打开也能收)
|
|
30
|
+
// 注:依赖 index.ts ui.clientImmediately=true(页面加载即加载本 bundle,不依赖面板物化)
|
|
31
|
+
try { initRecoverWatcher(); } catch { /* noop */ }
|
|
32
|
+
// 导出 App 供其它扩展嵌入渲染(aiti-model-wizard「检查」tab 同窗口内嵌;同一宿主 React)
|
|
33
|
+
try { window.__aitiModelCheckApp = App; } catch {}
|
|
34
|
+
// DI 方案 P2:内嵌 App 组件导出(services 双轨;组件值同样是合法服务 impl)
|
|
35
|
+
try { ctx.registerExtService?.("aiti-model-check.app", App); } catch {}
|
|
36
|
+
try { console.log("[aiti-model-check] client 已挂载(toolbar 📡 → pages 面板 + window.__aitiModelCheckApp 内嵌导出)"); } catch {}
|
|
37
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* host-shim —— JSX client bundle 的宿主桥(共享模板通用版)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要它:浏览器不认识 JSX,扩展 client 要写 JSX 必须经过 esbuild 预编译
|
|
5
|
+
* (vite lib mode)。esbuild 的 JSX 自动运行时(jsx: "automatic")会生成
|
|
6
|
+
* `jsx(type, props)` 调用,默认指向 React。但 pi-web 的扩展 client 不是 React——
|
|
7
|
+
* 宿主经 register(ctx) 传入的是自己的渲染原语(h / useState / useEffect / apiUrl…)。
|
|
8
|
+
*
|
|
9
|
+
* 所以这里用 vite 的 resolve.alias 把 react / react/jsx-runtime 重定向到本 shim:
|
|
10
|
+
* 组件里 import react 的 hooks → 本 shim 的 createElement/useState/… → 委托 ctx。
|
|
11
|
+
* 产物里零 React 引用,宿主保持单渲染实例,npm 生态组件(只要用标准 react hooks)
|
|
12
|
+
* 也能直接打包进来用。
|
|
13
|
+
*
|
|
14
|
+
* 使用:entry.tsx 的 register(ctx) 里第一件事调 `__setCtx(ctx)`(任何 JSX 渲染前)。
|
|
15
|
+
* 需要的字段(apiUrl/t/useRef/useMemo…)按需在这里加一行委托即可。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// 模块级 ctx(entry.tsx 的 register 时设置;组件渲染时读取)
|
|
19
|
+
let _ctx: Record<string, unknown> | null = null;
|
|
20
|
+
export function __setCtx(ctx: Record<string, unknown>): void { _ctx = ctx; }
|
|
21
|
+
|
|
22
|
+
/** DI 方案 P2:跨扩展客户端服务读取(ctx.getExtService 的 shim 封装;提供方懒加载/缺席 → undefined,消费方必须降级) */
|
|
23
|
+
export function extSvc<T = unknown>(key: string): T | undefined {
|
|
24
|
+
return (_ctx as any)?.getExtService?.(key) as T | undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** DI 方案 P2:注册跨扩展客户端服务(ctx.registerExtService 的 shim 封装;返回 disposer,旧宿主返回 null) */
|
|
28
|
+
export function regSvc(key: string, impl: unknown): (() => void) | null {
|
|
29
|
+
const c = _ctx as any;
|
|
30
|
+
return typeof c?.registerExtService === "function" ? c.registerExtService(key, impl) : null;
|
|
31
|
+
}
|
|
32
|
+
function c<T = (...args: never[]) => unknown>(name: string): T {
|
|
33
|
+
return (_ctx?.[name] as T) ?? (() => undefined as never);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------- react 兼容导出(esbuild alias react → 本 shim) ----------
|
|
37
|
+
// React 的 Fragment 类型是 Symbol.for("react.fragment")——传给宿主 h 即生成真 Fragment。
|
|
38
|
+
export const Fragment = (typeof Symbol !== "undefined" && Symbol.for) ? Symbol.for("react.fragment") : 0xacb;
|
|
39
|
+
export function createElement(...args: unknown[]): unknown {
|
|
40
|
+
return c("h").call(null, ...args);
|
|
41
|
+
}
|
|
42
|
+
// 自动运行时 jsx(type, props, key)——key 是独立第三参;宿主 h() = React.createElement
|
|
43
|
+
// 的第三参是 **children**。直接透传会把 key 当子元素(元素 key 丢失 + key 值覆盖
|
|
44
|
+
// props.children 成为真实内容)。因此必须把 key 合并回 props。
|
|
45
|
+
function mergeKey(args: unknown[]): unknown[] {
|
|
46
|
+
const [type, props, key, ...rest] = args;
|
|
47
|
+
if (props && typeof props === "object") {
|
|
48
|
+
// key 一律合并进 props,createElement 恒收 (type, config) 两参:
|
|
49
|
+
// 有 key → config.key 由 React 读为元素 key;key 为 undefined/null → 也必须剥掉第三参——
|
|
50
|
+
// 否则 createElement(type, config, undefined) 按 legacy 语义(第三参=children)用 undefined
|
|
51
|
+
// 覆盖 props.children,元素内容整体丢失(实证:aiti-editor ⋯ 菜单 key=undefined 的项全部空白)。
|
|
52
|
+
const merged: Record<string, unknown> = { ...(props as Record<string, unknown>) };
|
|
53
|
+
if (key !== undefined && key !== null) merged.key = key;
|
|
54
|
+
return [type, merged, ...rest];
|
|
55
|
+
}
|
|
56
|
+
return args;
|
|
57
|
+
}
|
|
58
|
+
export const jsx = (...args: unknown[]) => createElement(...mergeKey(args));
|
|
59
|
+
export const jsxs = (...args: unknown[]) => {
|
|
60
|
+
// React 自动运行时语义:jsxs(静态多子元素)创建时会对每个子元素调用
|
|
61
|
+
// validateChildKeys → 标记 _store.validated = 1("已校验,勿再告警")。宿主 h()
|
|
62
|
+
// 是 React.createElement,但它只创建元素、不做这个标记——不补的话,
|
|
63
|
+
// `<div><A/><B/></div>` 这类“静态兄弟元素”(JSX 里无需写 key,React 自动运行时
|
|
64
|
+
// 本应抑制该警告)会被 React 误报 “Each child in a list should have a unique key”
|
|
65
|
+
// (dev-only,按 owner 去重一次)。值必须是 1(2 是另一特殊标记,仍会告警)。
|
|
66
|
+
const props = (args as [unknown, { children?: unknown } | undefined, ...unknown[]])[1];
|
|
67
|
+
const children = props?.children;
|
|
68
|
+
if (Array.isArray(children)) {
|
|
69
|
+
for (const c of children) {
|
|
70
|
+
if (c && typeof c === "object" && (c as { _store?: { validated?: number } })._store) {
|
|
71
|
+
(c as { _store: { validated: number } })._store.validated = 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return createElement(...mergeKey(args));
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// ---------- React hooks 委托(按需增删) ----------
|
|
79
|
+
export const useState = <T>(init: T | (() => T)) => c<(...a: unknown[]) => [T, (v: T | ((prev: T) => T)) => void]>("useState")(init);
|
|
80
|
+
export const useEffect = (fn: () => unknown, deps?: unknown[]) => c("useEffect")(fn, deps);
|
|
81
|
+
export const useRef = <T>(v: T) => c<(...a: unknown[]) => { current: T }>("useRef")(v);
|
|
82
|
+
export const useCallback = <T extends (...args: never[]) => unknown>(fn: T, deps?: unknown[]) => c<(...a: unknown[]) => T>("useCallback")(fn, deps);
|
|
83
|
+
export const useMemo = <T>(fn: () => T, deps?: unknown[]) => c<(...a: unknown[]) => T>("useMemo")(fn, deps);
|
|
84
|
+
|
|
85
|
+
// ---------- 宿主工具委托(按需增删) ----------
|
|
86
|
+
/** apiUrl(p):把相对路径拼成当前 basePath 下的完整 API URL(多实例 /dev 前缀安全)。 */
|
|
87
|
+
export function apiUrl(p: string): string {
|
|
88
|
+
const fn = _ctx?.apiUrl as ((p: string) => string) | undefined;
|
|
89
|
+
return typeof fn === "function" ? fn(p) : p;
|
|
90
|
+
}
|
|
91
|
+
/** t(key, params?):扩展 i18n 翻译(index.ts ui.i18n 里的词条)。 */
|
|
92
|
+
export function t(key: string, params?: Record<string, unknown>): string {
|
|
93
|
+
const fn = _ctx?.t as ((key: string, params?: Record<string, unknown>) => string) | undefined;
|
|
94
|
+
return typeof fn === "function" ? fn(key, params) : key;
|
|
95
|
+
}
|