@forsion/extend 0.1.0 → 0.2.0
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/CHANGELOG.md +8 -0
- package/README.md +5 -5
- package/SIGNATURE +6 -6
- package/dist/desktop.mjs +193 -1
- package/manifest.json +3 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## 0.2.0 — 2026-09-27
|
|
4
|
+
|
|
5
|
+
- Forsion Connect(Coding Space 项目发布、应用商店上架申请、预览态 AI 代理)从 Forsion 本体搬入本包,行为与之前一致。预览页里的 `window.forsion`(`/forsion-connect.js`)与 `/__forsion/*` 云端代理也随之由本包提供。
|
|
6
|
+
- 需要较新的 Forsion 桌面端才会启用 Connect;旧桌面端上本包只提供云端账号功能,不影响使用。
|
|
7
|
+
|
|
8
|
+
Forsion Connect (Coding Space project publishing, app store listing requests, and the preview-mode AI proxy) now ships in this package, moved over from Forsion itself with its behavior unchanged. The `window.forsion` SDK served to preview pages (`/forsion-connect.js`) and the `/__forsion/*` cloud proxy come from this package as well.
|
|
9
|
+
Connect is enabled on newer Forsion Desktop builds only; on older desktops this package keeps providing the cloud-account features and nothing else changes.
|
|
10
|
+
|
|
3
11
|
## 0.1.0 — 2026-09-27
|
|
4
12
|
|
|
5
13
|
- 首个版本。Forsion 云端账号相关的桌面功能首次从 Forsion 本体搬出,作为内置插件随桌面端发布、自动更新:个人中心与会员页入口、额度视图与重置卡、后台额度转换,以及反馈中心提交。
|
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# Forsion Extend
|
|
2
2
|
|
|
3
|
-
Forsion 桌面端里**跟云端账号打交道的那一半**:个人中心 /
|
|
3
|
+
Forsion 桌面端里**跟云端账号打交道的那一半**:个人中心 / 会员页入口、额度视图与重置卡、后台额度转换、反馈中心提交,插件以当前账号身份调 Forsion 云端 API 的通用接缝 `cloud:fetch`,以及(0.2 起)Forsion Connect —— Coding Space 项目发布、应用商店上架申请、预览态 AI 代理(预览页的 `window.forsion` SDK 与 `/__forsion/*` 云端代理)。它是一个 Forsion 捆绑包(manifest.json + 渲染半身 + 主进程半身),编译产物以 npm 包 `@forsion/extend` 发布。
|
|
4
4
|
|
|
5
|
-
The **cloud-account half of Forsion Desktop**: the account center and membership page shortcuts, the quota view and reset cards, background quota conversion, feedback submission,
|
|
5
|
+
The **cloud-account half of Forsion Desktop**: the account center and membership page shortcuts, the quota view and reset cards, background quota conversion, feedback submission, `cloud:fetch` (the shared seam plugins use to call the Forsion cloud API as the signed-in account), and, since 0.2, Forsion Connect: Coding Space project publishing, app store listing requests, and the preview-mode AI proxy (the `window.forsion` SDK served to preview pages plus the `/__forsion/*` cloud proxy). It is a Forsion bundle (manifest.json + a renderer half + a main-process half), published as the npm package `@forsion/extend`.
|
|
6
6
|
|
|
7
7
|
## Forsion 桌面端怎么装载它 / How Forsion Desktop loads it
|
|
8
8
|
|
|
9
|
-
Forsion 桌面端把本包当**内置捆绑包**:随桌面安装包带一份,启动时播种进 `<home>/plugins/forsion-extend/`,之后由 npm 更新器在后台下载新版、下次启动换上。带主进程半身的包比普通捆绑包多一道闸:播种 / 换版前,桌面端用钉在自己代码里的 ed25519 公钥核对包内 `SIGNATURE`(每个打包文件的 sha256 + 签名),验不过的那份不换、不载;验过后 `import()` `dist/desktop.mjs`,调用 `registerCloud(host)` 注册 8
|
|
9
|
+
Forsion 桌面端把本包当**内置捆绑包**:随桌面安装包带一份,启动时播种进 `<home>/plugins/forsion-extend/`,之后由 npm 更新器在后台下载新版、下次启动换上。带主进程半身的包比普通捆绑包多一道闸:播种 / 换版前,桌面端用钉在自己代码里的 ed25519 公钥核对包内 `SIGNATURE`(每个打包文件的 sha256 + 签名),验不过的那份不换、不载;验过后 `import()` `dist/desktop.mjs`,调用 `registerCloud(host)` 注册 8 个云端账号 IPC 通道;桌面端够新(提供 Connect 所需的宿主接缝)时再注册 7 个 `connect:*` 通道与预览挂钩,旧桌面端上这一块自动跳过、账号功能照常。
|
|
10
10
|
|
|
11
|
-
Forsion Desktop treats this package as a **built-in bundle**: a copy ships inside the desktop installer, is seeded into `<home>/plugins/forsion-extend/` on startup, and the npm updater downloads newer versions in the background and switches to them on the next launch. A bundle with a main-process half has one extra gate: before seeding or switching, the desktop checks the in-package `SIGNATURE` (sha256 of every packed file plus an ed25519 signature) against the public key pinned in its own code; a copy that fails is neither installed nor loaded. Once verified, the desktop `import()`s `dist/desktop.mjs` and calls `registerCloud(host)`, which registers the 8 IPC channels.
|
|
11
|
+
Forsion Desktop treats this package as a **built-in bundle**: a copy ships inside the desktop installer, is seeded into `<home>/plugins/forsion-extend/` on startup, and the npm updater downloads newer versions in the background and switches to them on the next launch. A bundle with a main-process half has one extra gate: before seeding or switching, the desktop checks the in-package `SIGNATURE` (sha256 of every packed file plus an ed25519 signature) against the public key pinned in its own code; a copy that fails is neither installed nor loaded. Once verified, the desktop `import()`s `dist/desktop.mjs` and calls `registerCloud(host)`, which registers the 8 cloud-account IPC channels and, when the desktop is new enough to provide the host seam Connect needs, the 7 `connect:*` channels plus the preview hooks; on older desktops that part is skipped and the account features work as before.
|
|
12
12
|
|
|
13
13
|
| 包内文件 / file | 用途 / role |
|
|
14
14
|
|---|---|
|
|
15
15
|
| `manifest.json` | 捆绑包清单:`id` = `forsion-extend`,`main` = `dist/main.js` / bundle manifest |
|
|
16
|
-
| `dist/main.js` | 渲染半身(iife,宿主 `new Function('ctx', code)`
|
|
16
|
+
| `dist/main.js` | 渲染半身(iife,宿主 `new Function('ctx', code)` 装载;目前为空)/ renderer half, currently empty |
|
|
17
17
|
| `dist/desktop.mjs` | 主进程半身(ESM,验签后 `import()`)/ main-process half, imported after signature check |
|
|
18
18
|
| `SIGNATURE` | 包内签名 / in-package signature |
|
|
19
19
|
| `CHANGELOG.md` | 显示在桌面「设置 → 插件」详情页 / shown in Settings → Plugins |
|
package/SIGNATURE
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"alg": "ed25519",
|
|
3
3
|
"files": {
|
|
4
|
-
"CHANGELOG.md": "
|
|
4
|
+
"CHANGELOG.md": "da1f256d4f7c18fe0b5e120f9985ca1205b783197f7f54ccdeec08e9a6451e93",
|
|
5
5
|
"LICENSE": "5594544ee08011ed5785ee26410f1f2ef005f66e9283833e8d435e34c10de1ed",
|
|
6
|
-
"README.md": "
|
|
7
|
-
"dist/desktop.mjs": "
|
|
6
|
+
"README.md": "041eee1d470243f33f31bb1a3bc89c124b3b2dd493794aa4e4b865e07fcee7f3",
|
|
7
|
+
"dist/desktop.mjs": "0c3aefeab0d2f8f341b75a2c58afc0d038cfdd93dee8e23f4c5c6be66ecf4427",
|
|
8
8
|
"dist/main.js": "8669f048a0e9a5d47dc657306109ab437da6683ad925fbd6a451aef72542a787",
|
|
9
|
-
"manifest.json": "
|
|
10
|
-
"package.json": "
|
|
9
|
+
"manifest.json": "8eeb1fe22f305a45fb8a8d8be4c5ea36a679f7dcfee07c4ae9732275c776bcbb",
|
|
10
|
+
"package.json": "51f549f7dab0cb60bd84399a7b6fc544745f10ab1f903dd8ee967e536f503629"
|
|
11
11
|
},
|
|
12
|
-
"sig": "
|
|
12
|
+
"sig": "gLd7ckUY+zTvo5WwIrwOu9F+rQOA+t1OWVHqydjWm4axD50O/NoUvGKpc/pvlifuThhJm+4gXofkzoVh1cZUBw=="
|
|
13
13
|
}
|
package/dist/desktop.mjs
CHANGED
|
@@ -1 +1,193 @@
|
|
|
1
|
-
function
|
|
1
|
+
function v(r,a){if(typeof a!="string"||!a.startsWith("/"))return null;let n=String(r||"").replace(/\/+$/,"").replace(/\/api$/,"");if(!n)return null;let t,e;try{t=new URL(n),e=new URL(n+"/api"+a)}catch{return null}return t.protocol!=="http:"&&t.protocol!=="https:"||e.origin!==t.origin||!e.pathname.startsWith("/api/")?null:e.toString()}async function g(r,a,n,t,e=6e4){let o=await fetch(r.base+n,{method:a,headers:{"Content-Type":"application/json","X-Project-Source":"connect",...r.token?{Authorization:`Bearer ${r.token}`}:{}},body:t===void 0?void 0:JSON.stringify(t),signal:AbortSignal.timeout(e)}),l=await o.json().catch(()=>({}));return{status:o.status,json:l}}import{readdirSync as $,lstatSync as N,readFileSync as w,writeFileSync as R,realpathSync as T}from"node:fs";import{join as k,extname as M,sep as U}from"node:path";var _=300,S=5*1024*1024,E=20*1024*1024,F=50*1024*1024,H=new Set(["node_modules"]),L=new Set([".ts",".tsx",".jsx",".mts",".cts"]);function j(r,a){let n=[],t=0,e=(o,l)=>{for(let s of $(o)){if(s.startsWith("."))continue;let i=k(o,s),u;try{u=N(i)}catch{continue}if(u.isSymbolicLink())continue;let f=l?`${l}/${s}`:s;if(u.isDirectory()){H.has(s)||e(i,f);continue}if(!u.isFile()||s.endsWith(".d.ts"))continue;if(n.length>=_)throw new Error(`\u6587\u4EF6\u6570\u8D85\u8FC7 ${_}\uFF0C\u8BF7\u7CBE\u7B80\u9879\u76EE\uFF08node_modules \u4E0E\u9690\u85CF\u6587\u4EF6\u5DF2\u81EA\u52A8\u8DF3\u8FC7\uFF09`);let d=M(s).toLowerCase(),p,y=a.mimeOf(d)??"application/octet-stream";if(L.has(d)){let c=a.transpile(w(i,"utf8"),d,f);p=Buffer.from(c??"","utf8"),y="text/javascript"}else p=w(i);if(p.length>S)throw new Error(`\u5355\u6587\u4EF6\u8D85\u8FC7 5MB\uFF1A${f}`);if(t+=p.length,t>E)throw new Error("\u9879\u76EE\u603B\u5927\u5C0F\u8D85\u8FC7 20MB\uFF0C\u65E0\u6CD5\u53D1\u5E03");n.push({path:f,content_b64:p.toString("base64"),content_type:y,size:p.length})}};if(e(r,""),!n.length)throw new Error("\u9879\u76EE\u4E3A\u7A7A\uFF0C\u6CA1\u6709\u53EF\u53D1\u5E03\u7684\u6587\u4EF6");return{files:n,totalBytes:t}}var O=".forsion-connect.json";function P(r){try{return JSON.parse(w(k(r,O),"utf8"))}catch{return{}}}function x(r,a){try{R(k(r,O),JSON.stringify(a,null,2)+`
|
|
2
|
+
`,"utf8")}catch{}}function I(r){let a={"/__forsion/chat":{method:"POST",path:"/api/chat/completions",auth:!0},"/__forsion/images":{method:"POST",path:"/api/images/generations",auth:!0},"/__forsion/user":{method:"GET",path:"/api/brain/users/me",auth:!0},"/__forsion/models":{method:"GET",path:"/api/models",auth:!1},"/__forsion/config":{method:"GET",path:"/api/connect/config",auth:!1},"/__forsion/agent":{method:"POST",path:"/api/agent/runs",auth:!0,agentRun:!0}};function n(t){if(a[t])return a[t];let e="/__forsion/agent-events/";if(t.startsWith(e)){let o=t.slice(e.length);if(/^[A-Za-z0-9-]{1,64}$/.test(o))return{method:"GET",path:`/api/agent/runs/${o}/events`,auth:!0,timeoutMs:9e5}}}return async(t,e)=>{let[o,l]=(t.url||"").split("?"),s=(c,m)=>{e.headersSent||(e.statusCode=c,e.setHeader("Content-Type","application/json")),e.end(JSON.stringify({detail:m}))},i=n(o);if(!i)return s(404,"unknown connect endpoint");if((t.method||"GET")!==i.method)return s(405,"method not allowed");let u="",f="";try{({base:u,token:f}=await r())}catch{}if(!u)return s(502,"\u672A\u914D\u7F6E Forsion \u4E91\u7AEF\u5730\u5740");if(i.auth&&!f)return s(401,"\u8BF7\u5148\u5728 Forsion Desktop \u4E2D\u767B\u5F55 Forsion \u8D26\u53F7");let d;if(i.method==="POST"){let c=[],m=0;try{for await(let h of t){if(m+=h.length,m>F)return t.destroy(),s(413,"\u8BF7\u6C42\u4F53\u8FC7\u5927");c.push(h)}}catch(h){return s(400,`\u8BF7\u6C42\u4F53\u8BFB\u53D6\u5931\u8D25\uFF1A${h?.message||String(h)}`)}d=Buffer.concat(c)}if(i.agentRun){let c;try{c=JSON.parse((d||Buffer.alloc(0)).toString("utf8")||"{}")}catch{return s(400,"agent \u8BF7\u6C42\u4F53\u5FC5\u987B\u662F JSON")}d=Buffer.from(JSON.stringify({session_id:typeof c.session_id=="string"?c.session_id:"",message:typeof c.message=="string"?c.message:"",app_id:"connect",agent_config:{}}))}let p=new AbortController,y=setTimeout(()=>p.abort(new Error("proxy timeout")),i.timeoutMs??3e5);t.on("close",()=>p.abort());try{let c=await fetch(u+i.path+(l?`?${l}`:""),{method:i.method,headers:{"Content-Type":"application/json","X-Project-Source":"connect",...f?{Authorization:`Bearer ${f}`}:{}},body:d&&d.length?new Uint8Array(d):void 0,signal:p.signal});if(e.statusCode=c.status,e.setHeader("Content-Type",c.headers.get("content-type")||"application/json"),e.setHeader("Cache-Control","no-store"),c.body)for await(let m of c.body)e.write(m);e.end()}catch(c){let m=`\u4E91\u7AEF\u8BF7\u6C42\u4E2D\u65AD\uFF1A${c?.message||String(c)}`;if(e.headersSent){try{e.write(`data: ${JSON.stringify({error:m})}
|
|
3
|
+
|
|
4
|
+
`)}catch{}e.end()}else s(502,m)}finally{clearTimeout(y)}}}var A=`
|
|
5
|
+
(function () {
|
|
6
|
+
'use strict';
|
|
7
|
+
if (window.forsion) return;
|
|
8
|
+
|
|
9
|
+
function jsonOrThrow(r) {
|
|
10
|
+
if (r.ok) return r.json();
|
|
11
|
+
return r.json().catch(function () { return {}; }).then(function (b) {
|
|
12
|
+
throw new Error((b && b.detail) || ('HTTP ' + r.status));
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// \u5BF9\u8BDD\u6A21\u578B\u4E0D\u5728\u5BA2\u6237\u7AEF\u89E3\u6790:chat \u4E0D\u6307\u540D\u5C31\u4E0D\u4F20,agent \u4E00\u5F8B\u4E0D\u4F20,\u670D\u52A1\u7AEF\u6309 Connect \u7B56\u7565\u73B0\u586B\u3002
|
|
17
|
+
var modelCache = {};
|
|
18
|
+
function listModels(type) {
|
|
19
|
+
var t = type === 'image' ? 'image_gen' : (type === 'asr' ? 'asr' : 'llm');
|
|
20
|
+
if (!modelCache[t]) {
|
|
21
|
+
modelCache[t] = fetch('/__forsion/models?type=' + t).then(jsonOrThrow).then(function (arr) {
|
|
22
|
+
return (Array.isArray(arr) ? arr : []).map(function (m) { return { id: m.id, name: m.name || m.id, type: t }; });
|
|
23
|
+
});
|
|
24
|
+
modelCache[t].catch(function () { modelCache[t] = null; });
|
|
25
|
+
}
|
|
26
|
+
return modelCache[t];
|
|
27
|
+
}
|
|
28
|
+
function pickImageModel(explicit) {
|
|
29
|
+
if (explicit) return Promise.resolve(explicit);
|
|
30
|
+
return listModels('image').then(function (ms) {
|
|
31
|
+
if (ms[0]) return ms[0].id;
|
|
32
|
+
throw new Error('\u5E73\u53F0\u672A\u914D\u7F6E\u53EF\u7528\u7684\u751F\u56FE\u6A21\u578B');
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getUser() {
|
|
37
|
+
return fetch('/__forsion/user').then(function (r) {
|
|
38
|
+
if (r.status === 401 || r.status === 403) return null;
|
|
39
|
+
if (!r.ok) return null;
|
|
40
|
+
return r.json().then(function (u) {
|
|
41
|
+
return u ? {
|
|
42
|
+
username: u.username || u.nickname || '',
|
|
43
|
+
nickname: u.nickname || '',
|
|
44
|
+
avatar: u.avatar || u.avatarUrl || u.avatar_url || null,
|
|
45
|
+
tier: u.membershipTier || u.membership_tier || 'free'
|
|
46
|
+
} : null;
|
|
47
|
+
});
|
|
48
|
+
}).catch(function () { return null; });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function chat(opts) {
|
|
52
|
+
opts = opts || {};
|
|
53
|
+
var messages = Array.isArray(opts.messages) ? opts.messages.slice()
|
|
54
|
+
: opts.prompt ? [{ role: 'user', content: String(opts.prompt) }] : null;
|
|
55
|
+
if (!messages || !messages.length) throw new Error('chat \u9700\u8981 messages \u6216 prompt');
|
|
56
|
+
if (opts.system) messages.unshift({ role: 'system', content: String(opts.system) });
|
|
57
|
+
// \u9875\u9762\u6CA1\u6307\u540D\u6A21\u578B\u5C31\u4E0D\u4F20:\u670D\u52A1\u7AEF\u6309 Connect \u7B56\u7565\u73B0\u67E5\u73B0\u586B\u9ED8\u8BA4(admin \u6539\u9ED8\u8BA4\u5373\u65F6\u751F\u6548,
|
|
58
|
+
// \u4E0D\u53D7\u672C\u9875\u52A0\u8F7D\u65F6\u7F13\u5B58\u7684 config \u5F71\u54CD);\u6307\u540D\u6A21\u578B\u5219\u539F\u6837\u900F\u4F20,\u8D8A\u767D\u540D\u5355\u7531\u670D\u52A1\u7AEF 403\u3002
|
|
59
|
+
var model = opts.model || null;
|
|
60
|
+
var body = { messages: messages, stream: true };
|
|
61
|
+
if (model) body.model_id = model;
|
|
62
|
+
if (typeof opts.temperature === 'number') body.temperature = opts.temperature;
|
|
63
|
+
if (typeof opts.maxTokens === 'number') body.max_tokens = opts.maxTokens;
|
|
64
|
+
var r = await fetch('/__forsion/chat', {
|
|
65
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
|
|
66
|
+
});
|
|
67
|
+
if (!r.ok) {
|
|
68
|
+
var eb = await r.json().catch(function () { return {}; });
|
|
69
|
+
throw new Error((eb && eb.detail) || ('HTTP ' + r.status));
|
|
70
|
+
}
|
|
71
|
+
var reader = r.body.getReader();
|
|
72
|
+
var dec = new TextDecoder();
|
|
73
|
+
var buf = '', text = '', sawDone = false;
|
|
74
|
+
for (;;) {
|
|
75
|
+
var step = await reader.read();
|
|
76
|
+
if (step.done) break;
|
|
77
|
+
buf += dec.decode(step.value, { stream: true });
|
|
78
|
+
var lines = buf.split('\\n');
|
|
79
|
+
buf = lines.pop();
|
|
80
|
+
for (var i = 0; i < lines.length; i++) {
|
|
81
|
+
var line = lines[i].trim();
|
|
82
|
+
if (!line || line.indexOf('data:') !== 0) continue;
|
|
83
|
+
var payload = line.slice(5).trim();
|
|
84
|
+
if (payload === '[DONE]') { sawDone = true; continue; }
|
|
85
|
+
try {
|
|
86
|
+
var j = JSON.parse(payload);
|
|
87
|
+
if (j.error) throw new Error(typeof j.error === 'string' ? j.error : (j.error.message || 'AI \u9519\u8BEF'));
|
|
88
|
+
if (j.model) model = j.model; // \u670D\u52A1\u7AEF\u4EE3\u586B\u9ED8\u8BA4\u65F6,\u4ECE\u6D41\u5757\u91CC\u56DE\u8BFB\u5B9E\u9645\u6A21\u578B
|
|
89
|
+
var d = j.choices && j.choices[0] && (j.choices[0].delta && j.choices[0].delta.content ||
|
|
90
|
+
j.choices[0].message && j.choices[0].message.content) || '';
|
|
91
|
+
if (d) { text += d; if (typeof opts.onDelta === 'function') try { opts.onDelta(d); } catch (e) {} }
|
|
92
|
+
} catch (e) {
|
|
93
|
+
if (e instanceof SyntaxError) continue;
|
|
94
|
+
throw e;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// \u670D\u52A1\u7AEF\u6210\u529F\u5FC5\u53D1 data: [DONE];\u6CA1\u89C1\u5230 = \u6D41\u88AB\u4E2D\u65AD,\u5224\u5931\u8D25\u800C\u975E\u8FD4\u56DE\u622A\u65AD\u7684\u90E8\u5206\u7ED3\u679C\u3002
|
|
99
|
+
if (!sawDone) throw new Error('AI \u54CD\u5E94\u4E2D\u65AD\uFF08\u8FDE\u63A5\u672A\u6B63\u5E38\u7ED3\u675F\uFF09');
|
|
100
|
+
return { text: text, model: model };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// \u2500\u2500 Agent \u901A\u9053(\u5951\u7EA6\u4E0E\u53D1\u5E03\u6001\u58F3\u9875 doAgent \u4E00\u81F4):\u9700\u6C42\u9012\u7ED9\u4E91\u7AEF worker,\u4E0A\u4E0B\u6587/\u5DE5\u5177\u670D\u52A1\u7AEF\u6258\u7BA1 \u2500\u2500
|
|
104
|
+
function genSession() {
|
|
105
|
+
if (window.crypto && crypto.randomUUID) return crypto.randomUUID();
|
|
106
|
+
var a = new Uint8Array(16); crypto.getRandomValues(a);
|
|
107
|
+
return Array.prototype.map.call(a, function (b) { return ('0' + b.toString(16)).slice(-2); }).join('');
|
|
108
|
+
}
|
|
109
|
+
async function agentRun(opts) {
|
|
110
|
+
opts = opts || {};
|
|
111
|
+
var input = opts.input != null ? String(opts.input) : '';
|
|
112
|
+
if (!input.trim()) throw new Error('agent \u9700\u8981 input');
|
|
113
|
+
var session = typeof opts.session === 'string' && opts.session ? opts.session : genSession();
|
|
114
|
+
// \u4E0D\u53D1 model_id:\u6A21\u578B\u5B8C\u5168\u7531\u7F51\u5173\u6309 Connect \u7B56\u7565\u51B3\u5B9A(app_id/agent_config/model \u90FD\u7531\u4EE3\u7406+\u7F51\u5173\u9489\u6B7B)
|
|
115
|
+
var r = await fetch('/__forsion/agent', {
|
|
116
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
117
|
+
body: JSON.stringify({ session_id: session, message: input })
|
|
118
|
+
});
|
|
119
|
+
if (!r.ok) {
|
|
120
|
+
var eb = await r.json().catch(function () { return {}; });
|
|
121
|
+
throw new Error((eb && eb.detail) || ('HTTP ' + r.status));
|
|
122
|
+
}
|
|
123
|
+
var runId = (await r.json()).runId;
|
|
124
|
+
var er = await fetch('/__forsion/agent-events/' + encodeURIComponent(runId));
|
|
125
|
+
if (!er.ok) {
|
|
126
|
+
var eb2 = await er.json().catch(function () { return {}; });
|
|
127
|
+
throw new Error((eb2 && eb2.detail) || ('HTTP ' + er.status));
|
|
128
|
+
}
|
|
129
|
+
// \u5E27\u683C\u5F0F data: {seq,type,payload}:token.delta=\u6587\u672C\u589E\u91CF,done.content=\u6743\u5A01\u7EC8\u7A3F,error=\u5931\u8D25;
|
|
130
|
+
// \u4EE3\u7406\u4E2D\u65AD\u4F1A\u8865 data: {error} \u5E27(\u65E0 type),\u540C\u6837\u5224\u5931\u8D25\u3002
|
|
131
|
+
var reader = er.body.getReader();
|
|
132
|
+
var dec = new TextDecoder();
|
|
133
|
+
var buf = '', streamed = '', finalText = null, errMsg = null;
|
|
134
|
+
for (;;) {
|
|
135
|
+
var step = await reader.read();
|
|
136
|
+
if (step.done) break;
|
|
137
|
+
buf += dec.decode(step.value, { stream: true });
|
|
138
|
+
var lines = buf.split('\\n');
|
|
139
|
+
buf = lines.pop();
|
|
140
|
+
for (var i = 0; i < lines.length; i++) {
|
|
141
|
+
var line = lines[i].trim();
|
|
142
|
+
if (!line || line.indexOf('data:') !== 0) continue;
|
|
143
|
+
var ev;
|
|
144
|
+
try { ev = JSON.parse(line.slice(5)); } catch (e) { continue; }
|
|
145
|
+
if (ev.error && !ev.type) { errMsg = String(ev.error); continue; }
|
|
146
|
+
if (ev.type === 'token' && ev.payload && ev.payload.delta) {
|
|
147
|
+
streamed += ev.payload.delta;
|
|
148
|
+
if (typeof opts.onDelta === 'function') try { opts.onDelta(ev.payload.delta); } catch (e) {}
|
|
149
|
+
} else if (ev.type === 'done') {
|
|
150
|
+
// done.content \u662F\u6743\u5A01\u7EC8\u7A3F(\u54EA\u6015\u4E3A\u7A7A\u4E32\u4E5F\u7167\u4FE1);\u53EA\u6709\u8001 worker \u7F3A\u5B57\u6BB5\u624D\u56DE\u9000\u6D41\u7D2F\u8BA1
|
|
151
|
+
finalText = (ev.payload && typeof ev.payload.content === 'string') ? ev.payload.content : streamed;
|
|
152
|
+
} else if (ev.type === 'error') {
|
|
153
|
+
errMsg = (ev.payload && (ev.payload.detail || ev.payload.error)) || 'AI agent \u6267\u884C\u5931\u8D25';
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (errMsg) throw new Error(errMsg);
|
|
158
|
+
if (finalText == null) throw new Error('AI \u54CD\u5E94\u4E2D\u65AD\uFF08\u8FDE\u63A5\u672A\u6B63\u5E38\u7ED3\u675F\uFF09');
|
|
159
|
+
return { text: finalText, session: session };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function generateImage(opts) {
|
|
163
|
+
opts = opts || {};
|
|
164
|
+
if (!opts.prompt) throw new Error('generateImage \u9700\u8981 prompt');
|
|
165
|
+
var model = await pickImageModel(opts.model);
|
|
166
|
+
var body = {
|
|
167
|
+
model_id: model,
|
|
168
|
+
prompt: String(opts.prompt),
|
|
169
|
+
n: Math.max(1, Math.min(4, opts.n || 1)),
|
|
170
|
+
response_format: opts.responseFormat === 'b64' ? 'b64_json' : 'url'
|
|
171
|
+
};
|
|
172
|
+
if (opts.size) body.size = String(opts.size);
|
|
173
|
+
var j = await fetch('/__forsion/images', {
|
|
174
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
|
|
175
|
+
}).then(jsonOrThrow);
|
|
176
|
+
var data = (j && j.data) || [];
|
|
177
|
+
return { model: model, images: data.map(function (d) { return { url: d.url || null, b64: d.b64_json || null }; }) };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
window.forsion = {
|
|
181
|
+
mode: 'preview',
|
|
182
|
+
user: function () { return getUser(); },
|
|
183
|
+
login: function () {
|
|
184
|
+
return getUser().then(function (u) {
|
|
185
|
+
if (u) return u;
|
|
186
|
+
throw new Error('\u9884\u89C8\u6A21\u5F0F\u4F7F\u7528\u684C\u9762\u7AEF\u767B\u5F55\u6001\uFF1A\u8BF7\u5728 Forsion Desktop \u8BBE\u7F6E\u4E2D\u767B\u5F55 Forsion \u8D26\u53F7');
|
|
187
|
+
});
|
|
188
|
+
},
|
|
189
|
+
models: function (type) { return listModels(type); },
|
|
190
|
+
ai: { chat: chat, agent: agentRun, generateImage: generateImage }
|
|
191
|
+
};
|
|
192
|
+
})();
|
|
193
|
+
`,D=r=>({transpile:(a,n,t)=>r.transpileForServe(a,n,t),mimeOf:a=>r.mimeOf(a)});function b(r){let a=r;return typeof a.projectsRoot=="function"&&typeof a.transpileForServe=="function"&&typeof a.mimeOf=="function"&&typeof a.setPreviewHooks=="function"}function C(r){r.setPreviewHooks({sdkJs:A,proxy:I(()=>r.getCloud())}),r.handle("connect:meta",(a,n)=>typeof n=="string"&&n?P(n):{}),r.handle("connect:list",async()=>{let a=await r.getCloud();if(!a.token)return{ok:!1,code:"not_logged_in",detail:"\u8BF7\u5148\u767B\u5F55 Forsion \u8D26\u53F7"};try{let{status:n,json:t}=await g(a,"GET","/api/connect/apps");return n===401||n===403?{ok:!1,code:"not_logged_in",detail:"\u767B\u5F55\u5DF2\u8FC7\u671F,\u8BF7\u91CD\u65B0\u767B\u5F55"}:n!==200?{ok:!1,code:"error",detail:t?.detail||`HTTP ${n}`}:{ok:!0,base:a.base,...t}}catch(n){return{ok:!1,code:"error",detail:n?.message||String(n)}}}),r.handle("connect:publish",async(a,n)=>{if(!n||typeof n.dir!="string"||!n.dir||!n.name||!n.slug||!n.entry)return{ok:!1,code:"error",detail:"\u53C2\u6570\u4E0D\u5B8C\u6574"};let t;try{let o=T(r.projectsRoot());if(t=T(n.dir),t!==o&&!t.startsWith(o+U))return{ok:!1,code:"error",detail:"\u53EA\u80FD\u53D1\u5E03 Coding Space \u9879\u76EE\u76EE\u5F55"}}catch{return{ok:!1,code:"error",detail:"\u9879\u76EE\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u65E0\u6CD5\u8BBF\u95EE"}}let e=await r.getCloud();if(!e.token)return{ok:!1,code:"not_logged_in",detail:"\u8BF7\u5148\u767B\u5F55 Forsion \u8D26\u53F7"};try{let{files:o,totalBytes:l}=j(t,D(r));if(!o.some(u=>u.path===n.entry))return{ok:!1,code:"error",detail:`\u5165\u53E3\u6587\u4EF6\u4E0D\u5B58\u5728:${n.entry}`};let{status:s,json:i}=await g(e,"POST","/api/connect/apps",{slug:n.slug,name:n.name,entry:n.entry,files:o},3e5);return s===401?{ok:!1,code:"not_logged_in",detail:"\u767B\u5F55\u5DF2\u8FC7\u671F,\u8BF7\u91CD\u65B0\u767B\u5F55"}:s!==200?{ok:!1,code:i?.code||"error",detail:i?.detail||`HTTP ${s}`,used:i?.used,limit:i?.limit}:(x(t,{slug:String(i.slug)}),{ok:!0,slug:i.slug,handle:i.handle,url:`${e.base}${i.url}`,totalBytes:l})}catch(o){return{ok:!1,code:"error",detail:o?.message||String(o)}}}),r.handle("connect:unpublish",async(a,n)=>{if(!n||typeof n!="string")return{ok:!1,code:"error",detail:"\u53C2\u6570\u4E0D\u5B8C\u6574"};let t=await r.getCloud();if(!t.token)return{ok:!1,code:"not_logged_in",detail:"\u8BF7\u5148\u767B\u5F55 Forsion \u8D26\u53F7"};try{let{status:e,json:o}=await g(t,"DELETE",`/api/connect/apps/${encodeURIComponent(n)}`);return e===401||e===403?{ok:!1,code:"not_logged_in",detail:"\u767B\u5F55\u5DF2\u8FC7\u671F,\u8BF7\u91CD\u65B0\u767B\u5F55"}:e!==200&&e!==404?{ok:!1,code:"error",detail:o?.detail||`HTTP ${e}`}:{ok:!0}}catch(e){return{ok:!1,code:"error",detail:e?.message||String(e)}}}),r.handle("connect:listingApply",async(a,n)=>{if(!n||typeof n.slug!="string"||!n.slug||typeof n.summary!="string"||!n.summary.trim())return{ok:!1,code:"error",detail:"\u53C2\u6570\u4E0D\u5B8C\u6574"};let t=await r.getCloud();if(!t.token)return{ok:!1,code:"not_logged_in",detail:"\u8BF7\u5148\u767B\u5F55 Forsion \u8D26\u53F7"};try{let{status:e,json:o}=await g(t,"PUT",`/api/connect/apps/${encodeURIComponent(n.slug)}/listing`,{summary:n.summary});return e===401?{ok:!1,code:"not_logged_in",detail:"\u767B\u5F55\u5DF2\u8FC7\u671F,\u8BF7\u91CD\u65B0\u767B\u5F55"}:e!==200?{ok:!1,code:o?.code||"error",detail:o?.detail||`HTTP ${e}`}:{ok:!0,status:o.status}}catch(e){return{ok:!1,code:"error",detail:e?.message||String(e)}}}),r.handle("connect:listingWithdraw",async(a,n)=>{if(!n||typeof n!="string")return{ok:!1,code:"error",detail:"\u53C2\u6570\u4E0D\u5B8C\u6574"};let t=await r.getCloud();if(!t.token)return{ok:!1,code:"not_logged_in",detail:"\u8BF7\u5148\u767B\u5F55 Forsion \u8D26\u53F7"};try{let{status:e,json:o}=await g(t,"DELETE",`/api/connect/apps/${encodeURIComponent(n)}/listing`);return e===401?{ok:!1,code:"not_logged_in",detail:"\u767B\u5F55\u5DF2\u8FC7\u671F,\u8BF7\u91CD\u65B0\u767B\u5F55"}:e!==200&&e!==404?{ok:!1,code:"error",detail:o?.detail||`HTTP ${e}`}:{ok:!0}}catch(e){return{ok:!1,code:"error",detail:e?.message||String(e)}}}),r.handle("connect:store",async()=>{let a=await r.getCloud();try{let{status:n,json:t}=await g(a,"GET","/api/connect/store");return n!==200?{ok:!1,detail:t?.detail||`HTTP ${n}`}:{ok:!0,base:a.base,items:Array.isArray(t.items)?t.items:[]}}catch(n){return{ok:!1,detail:n?.message||String(n)}}}),r.log("[forsion-extend] connect handlers registered")}function V(r){r.handle("auth:openAccountCenter",async(n,t)=>{let{base:e,token:o}=await r.getCloud();if(!e)return{ok:!1};let l=t&&/^[a-z0-9-]+$/i.test(t)?`#${t}`:"",s=`${e}/account${o?`?token=${encodeURIComponent(o)}`:""}${l}`;return await r.openExternal(s),{ok:!0}}),r.handle("auth:openPayCenter",async()=>{let{base:n,token:t}=await r.getCloud();if(!n)return{ok:!1};let e=`${n}/pay?tab=membership${t?`&token=${encodeURIComponent(t)}`:""}&redirect=${encodeURIComponent(`${n}/account`)}`;return await r.openExternal(e),{ok:!0}});let a=()=>r.getCloud();r.handle("account:quota",async()=>{let n=await a();if(!n.token)return{status:401,json:null};try{return await g(n,"GET","/api/token-quota/my",void 0,15e3)}catch(t){return{status:0,json:{detail:String(t?.message||t)}}}}),r.handle("account:useResetCard",async(n,t)=>{let e=await a();if(!e.token)return{status:401,json:null};if(t!==void 0&&t!=="both"&&t!=="weekly")return{status:400,json:{error:"invalid_type",detail:`invalid reset card type: ${t}`}};let o=t??"both";try{return await g(e,"POST","/api/token-quota/reset-card/use",{type:o},15e3)}catch(l){return{status:0,json:{detail:String(l?.message||l)}}}}),r.handle("account:bgConvert",async(n,t)=>{let e=await a();if(!e.token)return{status:401,json:null};if(!Number.isInteger(t)||t<1||t>100)return{status:400,json:{error:"invalid_percent"}};try{return await g(e,"POST","/api/token-quota/background/convert",{percent:t},15e3)}catch(o){return{status:0,json:{detail:String(o?.message||o)}}}}),r.handle("account:bgAutoMain",async(n,t)=>{let e=await a();if(!e.token)return{status:401,json:null};if(typeof t!="boolean")return{status:400,json:{error:"invalid_request"}};try{return await g(e,"POST","/api/token-quota/background/auto-main",{enabled:t},15e3)}catch(o){return{status:0,json:{detail:String(o?.message||o)}}}}),r.handle("feedback:submit",async(n,t)=>{let{base:e,token:o}=await r.getCloud();if(!e||!o)return{ok:!1,error:"not-logged-in"};let l=(t?.description||"").trim();if(!l)return{ok:!1,error:"empty"};if(l.length>1e4)return{ok:!1,error:"description-too-long"};let s=[],i=!1;if(t?.sessionLogJson){let u=Buffer.from(t.sessionLogJson,"utf8");if(u.length>5*1024*1024)return{ok:!1,error:"attachment-too-large"};s.push({filename:t.sessionLogName||"tangu-session.json",mime_type:"application/json",size:u.length,data_base64:u.toString("base64")})}try{let u=await fetch(`${e}/api/feedback`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({description:l,attachments:s}),signal:AbortSignal.timeout(2e4)});if(!u.ok){let d=`HTTP ${u.status}`;try{d=(await u.json())?.detail||d}catch{}return{ok:!1,error:d}}return{ok:!0,id:(await u.json().catch(()=>({})))?.id??null,attachmentSkipped:i}}catch(u){return{ok:!1,error:u?.message||String(u)}}}),r.handle("cloud:fetch",async(n,t)=>{if(!r.isTrustedSender(n))return{status:0,error:"untrusted"};let e=t??{},o=typeof e.path=="string"?e.path:"",l=Math.min(12e4,Math.max(1e3,Number(e.timeoutMs)||15e3)),s=typeof e.method=="string"?e.method.toUpperCase():"GET";if(!/^(GET|POST|PUT|PATCH|DELETE)$/.test(s))return{status:0,error:"bad_method"};if(!o.startsWith("/"))return{status:0,error:"bad_path"};let{base:i,token:u}=await r.getCloud();if(!String(i||""))return{status:0,error:"no_cloud_url"};let f=v(i,o);if(!f)return{status:0,error:"bad_path"};if(!u)return{status:401,error:"not_signed_in"};let d=e.body!==void 0&&s!=="GET"&&s!=="DELETE",p=new AbortController,y=setTimeout(()=>p.abort(),l);try{let c=await fetch(f,{method:s,headers:{Authorization:`Bearer ${u}`,...d?{"Content-Type":"application/json"}:{}},body:d?JSON.stringify(e.body):void 0,signal:p.signal});return{status:c.status,json:await c.json().catch(()=>null)}}catch(c){return{status:0,error:String(c?.message||c)}}finally{clearTimeout(y)}}),r.log("[forsion-extend] cloud account handlers registered"),b(r)?C(r):r.log("[forsion-extend] host has no Connect seam (projectsRoot / transpileForServe / mimeOf / setPreviewHooks); Connect stays with the host")}export{_ as CONNECT_MAX_FILES,S as CONNECT_MAX_FILE_BYTES,E as CONNECT_MAX_TOTAL_BYTES,A as FORSION_CONNECT_LOCAL_SDK,g as cloudJson,j as collectProjectFiles,b as hasConnectHost,I as makePreviewProxy,P as readConnectMeta,V as registerCloud,C as registerConnect,x as writeConnectMeta};
|
package/manifest.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"id": "forsion-extend",
|
|
3
3
|
"name": "Forsion 扩展",
|
|
4
4
|
"nameEn": "Forsion Extend",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.2.0",
|
|
6
6
|
"apiVersion": 1,
|
|
7
7
|
"minAppVersion": "2.11.4",
|
|
8
8
|
"main": "dist/main.js",
|
|
9
|
-
"description": "Forsion
|
|
10
|
-
"descriptionEn": "The cloud-account features of Forsion Desktop: account center and membership page shortcuts, quota view and reset cards, background quota conversion, feedback submission,
|
|
9
|
+
"description": "Forsion 云端账号相关的桌面功能:个人中心与会员页入口、额度视图与重置卡、后台额度转换、反馈中心提交,插件以当前账号身份调用 Forsion 云端 API 的通用接口,以及 Forsion Connect(Coding Space 项目发布、应用商店上架申请、预览态 AI 代理)。随 Forsion 桌面端内置,自动更新。",
|
|
10
|
+
"descriptionEn": "The cloud-account features of Forsion Desktop: account center and membership page shortcuts, quota view and reset cards, background quota conversion, feedback submission, the shared interface plugins use to call the Forsion cloud API as the signed-in account, and Forsion Connect (Coding Space project publishing, app store listing requests, and the preview-mode AI proxy). Built into Forsion Desktop and updated automatically.",
|
|
11
11
|
"author": "Forsion"
|
|
12
12
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forsion/extend",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Forsion Extend: the cloud-account half of Forsion Desktop, shipped as a built-in bundle (signed, updated through npm).",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Forsion Extend: the cloud-account half of Forsion Desktop plus Forsion Connect (Coding Space publishing and the preview-mode AI proxy), shipped as a built-in bundle (signed, updated through npm).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
7
7
|
"repository": {
|