@panda-agent/panda-app-sdk 0.1.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/README.md +100 -0
- package/dist/browser/index.d.ts +115 -0
- package/dist/browser/index.js +212 -0
- package/dist/node/index.d.ts +115 -0
- package/dist/node/index.js +216 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# @panda-agent/panda-app-sdk
|
|
2
|
+
|
|
3
|
+
Panda 智能应用 SDK — 在 iframe 或 Utility Process 中调用 Panda Desktop 提供的应用平台 API。
|
|
4
|
+
|
|
5
|
+
SDK 为纯客户端 RPC 封装,**不包含** Panda Core 运行时。应用须在 Panda Desktop 中安装并运行。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @panda-agent/panda-app-sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 入口
|
|
14
|
+
|
|
15
|
+
| 子路径 | 运行环境 | 用途 |
|
|
16
|
+
|--------|----------|------|
|
|
17
|
+
| `@panda-agent/panda-app-sdk/browser` | iframe(Session / Standalone UI) | `PandaApp.connectIframe()` |
|
|
18
|
+
| `@panda-agent/panda-app-sdk/node` | Utility Process(Standalone 后端 / Headless) | `PandaApp.connectUtilityProcess()` |
|
|
19
|
+
|
|
20
|
+
## 快速开始
|
|
21
|
+
|
|
22
|
+
### Session / Standalone 前端
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { PandaApp } from '@panda-agent/panda-app-sdk/browser';
|
|
26
|
+
|
|
27
|
+
const app = PandaApp.connectIframe();
|
|
28
|
+
const conv = await app.conversation.createForApp({ title: 'My task', ephemeral: true });
|
|
29
|
+
await app.conversation.submitMessage(conv.conversationId, 'Hello');
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
使用 Vite / webpack 将 browser 入口打进前端 bundle。
|
|
33
|
+
|
|
34
|
+
### Standalone / Headless 后端(`package/main.mjs`)
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { PandaApp, APP_UTILITY_READY } from '@panda-agent/panda-app-sdk/node';
|
|
38
|
+
|
|
39
|
+
const app = PandaApp.connectUtilityProcess();
|
|
40
|
+
|
|
41
|
+
process.parentPort.postMessage({
|
|
42
|
+
type: APP_UTILITY_READY,
|
|
43
|
+
appId: process.env.PANDA_APP_ID ?? 'unknown',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// 使用 app.conversation / app.tools 等 API
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`APP_UTILITY_READY` 为可选握手消息(宿主仅记录日志,不阻塞启动)。
|
|
50
|
+
|
|
51
|
+
建议用 esbuild 将 node 入口打成单文件 `main.mjs` 再随应用 zip 分发。
|
|
52
|
+
|
|
53
|
+
## 应用类型对照
|
|
54
|
+
|
|
55
|
+
| presentation | browser SDK | node SDK |
|
|
56
|
+
|--------------|-------------|----------|
|
|
57
|
+
| `session` | 必须 | 不需要 |
|
|
58
|
+
| `standalone` | UI 需要 | `runtime.processEntry` 需要 |
|
|
59
|
+
| `headless` | 不需要 | 必须 |
|
|
60
|
+
|
|
61
|
+
## 高级用法
|
|
62
|
+
|
|
63
|
+
需要裸 `invoke`(例如共享验收脚本)时:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { createIframeBridgeTransport, createAppSdk } from '@panda-agent/panda-app-sdk/browser';
|
|
67
|
+
|
|
68
|
+
const transport = createIframeBridgeTransport();
|
|
69
|
+
const app = createAppSdk(transport);
|
|
70
|
+
const invoke = (path: string, args?: unknown[]) => transport.invoke(path, args);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 开发与构建(monorepo 内)
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
cd packages/app-sdk
|
|
77
|
+
npm install
|
|
78
|
+
npm run build # 产出 dist/browser、dist/node
|
|
79
|
+
npm run vendor-fixtures # 复制到 panda-desktop/fixtures/*/package/vendor/
|
|
80
|
+
npm test
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
修改 SDK 后需重新 `build` 并 `vendor-fixtures`,桌面 demo 才会更新。
|
|
84
|
+
|
|
85
|
+
## 发布到 npm
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cd packages/app-sdk
|
|
89
|
+
npm run release:publish:dry-run # 干跑
|
|
90
|
+
npm run release:publish # 发布到 @panda-agent(需 npm 组织权限)
|
|
91
|
+
npm run release:publish:tester # 先发 tester tag
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
发布前会校验 `package.json` version 与 Core `PANDA_APP_SDK_VERSION` 一致。本包 `.npmrc` 指向 `registry.npmjs.org`(勿使用仓库根目录的 npmmirror 配置)。
|
|
95
|
+
|
|
96
|
+
## 版本
|
|
97
|
+
|
|
98
|
+
`runtime.getHost().appSdkVersion` 与 npm 包版本应对齐。发布时同步更新 Core 中 `PANDA_APP_SDK_VERSION`(`app-platform-runtime-types.ts`)。
|
|
99
|
+
|
|
100
|
+
建议 Panda Desktop 版本不低于 SDK 发布说明中的最低宿主版本。
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export { PANDA_APP_BRIDGE_EVENT, PANDA_APP_BRIDGE_INVOKE, PANDA_APP_BRIDGE_INVOKE_RESULT, } from './protocol.js';
|
|
2
|
+
export { createAppSdk } from './create-app-sdk.js';
|
|
3
|
+
export { createIframeBridgeTransport } from './transport/iframe.js';
|
|
4
|
+
export type * from './types.js';
|
|
5
|
+
export declare const PandaApp: {
|
|
6
|
+
connectIframe(): {
|
|
7
|
+
auth: {
|
|
8
|
+
getCurrentUser: () => Promise<import("./types.js").AppUser | null>;
|
|
9
|
+
};
|
|
10
|
+
model: {
|
|
11
|
+
list: (options?: {
|
|
12
|
+
enabledOnly?: boolean;
|
|
13
|
+
visibleOnly?: boolean;
|
|
14
|
+
scene?: string;
|
|
15
|
+
}) => Promise<import("./types.js").ModelListItem[]>;
|
|
16
|
+
resolveForScene: (request?: {
|
|
17
|
+
scene?: string;
|
|
18
|
+
preferredModelId?: string;
|
|
19
|
+
}) => Promise<import("./types.js").ModelListItem | null>;
|
|
20
|
+
};
|
|
21
|
+
workspace: {
|
|
22
|
+
list: (request: {
|
|
23
|
+
limit: number;
|
|
24
|
+
includeEphemeral?: boolean;
|
|
25
|
+
}) => Promise<import("./types.js").WorkspaceListItem[]>;
|
|
26
|
+
ensureLocal: (request: {
|
|
27
|
+
path: string;
|
|
28
|
+
touch?: boolean;
|
|
29
|
+
}) => Promise<import("./types.js").WorkspaceListItem>;
|
|
30
|
+
};
|
|
31
|
+
conversation: {
|
|
32
|
+
createForApp: (options?: import("./types.js").CreateForAppOptions) => Promise<import("./types.js").CreateForAppResult>;
|
|
33
|
+
disposeForApp: (conversationId: string) => Promise<unknown>;
|
|
34
|
+
submitMessage: (conversationId: string, prompt: string) => Promise<unknown>;
|
|
35
|
+
getMessages: (conversationId: string) => Promise<unknown>;
|
|
36
|
+
subscribe: (options: import("./types.js").ConversationSubscribeOptions & {
|
|
37
|
+
onEvent?: import("./types.js").AppEventHandler;
|
|
38
|
+
}) => Promise<{
|
|
39
|
+
subscriptionId: string;
|
|
40
|
+
unsubscribe: () => Promise<void>;
|
|
41
|
+
}>;
|
|
42
|
+
awaitTurn: (options: {
|
|
43
|
+
conversationId: string;
|
|
44
|
+
turnId?: string;
|
|
45
|
+
timeoutMs?: number;
|
|
46
|
+
}) => Promise<unknown>;
|
|
47
|
+
};
|
|
48
|
+
session: {
|
|
49
|
+
mount: (conversationId: string, appId: string, replaceConfirmed?: boolean) => Promise<unknown>;
|
|
50
|
+
unmount: (conversationId: string) => Promise<unknown>;
|
|
51
|
+
getMount: (conversationId: string) => Promise<unknown>;
|
|
52
|
+
};
|
|
53
|
+
ingress: {
|
|
54
|
+
submit: (conversationId: string, prompt: string) => Promise<{
|
|
55
|
+
conversationId: string;
|
|
56
|
+
status: string;
|
|
57
|
+
}>;
|
|
58
|
+
};
|
|
59
|
+
tools: {
|
|
60
|
+
list: (request: {
|
|
61
|
+
workspace?: import("./types.js").WorkspaceRef;
|
|
62
|
+
workspaceId?: string;
|
|
63
|
+
}) => Promise<Array<{
|
|
64
|
+
name: string;
|
|
65
|
+
displayName: string;
|
|
66
|
+
description: string;
|
|
67
|
+
kind: string;
|
|
68
|
+
}>>;
|
|
69
|
+
execute: (request: {
|
|
70
|
+
toolName: string;
|
|
71
|
+
args?: Record<string, unknown>;
|
|
72
|
+
workspace?: import("./types.js").WorkspaceRef;
|
|
73
|
+
workspaceId?: string;
|
|
74
|
+
}) => Promise<{
|
|
75
|
+
toolName: string;
|
|
76
|
+
workspaceId: string;
|
|
77
|
+
ok: boolean;
|
|
78
|
+
llmContent?: string;
|
|
79
|
+
error?: {
|
|
80
|
+
message: string;
|
|
81
|
+
type?: string;
|
|
82
|
+
};
|
|
83
|
+
displayData?: unknown;
|
|
84
|
+
}>;
|
|
85
|
+
};
|
|
86
|
+
runtime: {
|
|
87
|
+
now: () => Promise<{
|
|
88
|
+
timestamp: number;
|
|
89
|
+
iso: string;
|
|
90
|
+
timeZone: string;
|
|
91
|
+
utcOffsetMinutes: number;
|
|
92
|
+
}>;
|
|
93
|
+
getHost: () => Promise<{
|
|
94
|
+
platform: string;
|
|
95
|
+
arch: string;
|
|
96
|
+
osName: string;
|
|
97
|
+
hostProduct: string;
|
|
98
|
+
shell: string;
|
|
99
|
+
shellKind: string;
|
|
100
|
+
homeDir: string;
|
|
101
|
+
pandaVersion: string;
|
|
102
|
+
appSdkVersion: string;
|
|
103
|
+
}>;
|
|
104
|
+
};
|
|
105
|
+
dialog: {
|
|
106
|
+
showOpen: (request?: import("./types.js").DialogShowOpenRequest) => Promise<import("./types.js").DialogShowOpenResult>;
|
|
107
|
+
readPickedFile: (request: import("./types.js").DialogReadPickedFileRequest) => Promise<import("./types.js").DialogReadPickedFileResult>;
|
|
108
|
+
releasePick: (pickToken: string) => Promise<unknown>;
|
|
109
|
+
showSave: (request?: import("./types.js").DialogShowSaveRequest) => Promise<import("./types.js").DialogShowSaveResult>;
|
|
110
|
+
writeSaveFile: (request: import("./types.js").DialogWriteSaveFileRequest) => Promise<import("./types.js").DialogWriteSaveFileResult>;
|
|
111
|
+
pickAndReadFile: (options?: import("./types.js").DialogPickAndReadFileOptions) => Promise<import("./types.js").DialogReadPickedFileResult | null>;
|
|
112
|
+
saveFile: (options: import("./types.js").DialogSaveFileOptions) => Promise<string | null>;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// src/protocol.ts
|
|
2
|
+
var PANDA_APP_BRIDGE_INVOKE = "panda-app-bridge:invoke";
|
|
3
|
+
var PANDA_APP_BRIDGE_INVOKE_RESULT = "panda-app-bridge:invoke-result";
|
|
4
|
+
var PANDA_APP_BRIDGE_EVENT = "panda-app-bridge:event";
|
|
5
|
+
|
|
6
|
+
// src/create-app-sdk.ts
|
|
7
|
+
function createAppSdk(transport) {
|
|
8
|
+
return {
|
|
9
|
+
auth: {
|
|
10
|
+
getCurrentUser: () => transport.invoke("auth.getCurrentUser", [])
|
|
11
|
+
},
|
|
12
|
+
model: {
|
|
13
|
+
list: (options) => transport.invoke("model.list", [options ?? {}]),
|
|
14
|
+
resolveForScene: (request) => transport.invoke("model.resolveForScene", [request ?? {}])
|
|
15
|
+
},
|
|
16
|
+
workspace: {
|
|
17
|
+
list: (request) => transport.invoke("workspace.list", [request]),
|
|
18
|
+
ensureLocal: (request) => transport.invoke("workspace.ensureLocal", [request])
|
|
19
|
+
},
|
|
20
|
+
conversation: {
|
|
21
|
+
createForApp: (options) => transport.invoke("conversation.createForApp", [options ?? {}]),
|
|
22
|
+
disposeForApp: (conversationId) => transport.invoke("conversation.disposeForApp", [conversationId]),
|
|
23
|
+
submitMessage: (conversationId, prompt) => transport.invoke("conversation.submitMessage", [conversationId, prompt]),
|
|
24
|
+
getMessages: (conversationId) => transport.invoke("conversation.getMessages", [conversationId]),
|
|
25
|
+
subscribe: async (options) => {
|
|
26
|
+
const result = await transport.invoke("conversation.subscribe", [options]);
|
|
27
|
+
let offEvent;
|
|
28
|
+
if (options.onEvent) {
|
|
29
|
+
offEvent = transport.subscribe(result.subscriptionId, options.onEvent);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
subscriptionId: result.subscriptionId,
|
|
33
|
+
unsubscribe: async () => {
|
|
34
|
+
offEvent?.();
|
|
35
|
+
await transport.invoke("conversation.unsubscribe", [result.subscriptionId]);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
awaitTurn: (options) => transport.invoke("conversation.awaitTurn", [options])
|
|
40
|
+
},
|
|
41
|
+
session: {
|
|
42
|
+
mount: (conversationId, appId, replaceConfirmed) => transport.invoke("mountSession", [conversationId, appId, replaceConfirmed === true]),
|
|
43
|
+
unmount: (conversationId) => transport.invoke("unmountSession", [conversationId]),
|
|
44
|
+
getMount: (conversationId) => transport.invoke("getSessionMount", [conversationId])
|
|
45
|
+
},
|
|
46
|
+
ingress: {
|
|
47
|
+
submit: (conversationId, prompt) => transport.invoke("ingress.submit", [conversationId, prompt])
|
|
48
|
+
},
|
|
49
|
+
tools: {
|
|
50
|
+
list: (request) => transport.invoke("tools.list", [request]),
|
|
51
|
+
execute: (request) => transport.invoke("tools.execute", [request])
|
|
52
|
+
},
|
|
53
|
+
runtime: {
|
|
54
|
+
now: () => transport.invoke("runtime.now", []),
|
|
55
|
+
getHost: () => transport.invoke("runtime.getHost", [])
|
|
56
|
+
},
|
|
57
|
+
dialog: {
|
|
58
|
+
showOpen: (request) => transport.invoke("dialog.showOpen", [request ?? {}]),
|
|
59
|
+
readPickedFile: (request) => transport.invoke("dialog.readPickedFile", [request]),
|
|
60
|
+
releasePick: (pickToken) => transport.invoke("dialog.releasePick", [pickToken]),
|
|
61
|
+
showSave: (request) => transport.invoke("dialog.showSave", [request ?? {}]),
|
|
62
|
+
writeSaveFile: (request) => transport.invoke("dialog.writeSaveFile", [request]),
|
|
63
|
+
pickAndReadFile: async (options) => {
|
|
64
|
+
const { encoding, ...openRequest } = options ?? {};
|
|
65
|
+
const opened = await transport.invoke("dialog.showOpen", [openRequest]);
|
|
66
|
+
if (opened.canceled || opened.filePaths.length === 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const filePath = opened.filePaths[0];
|
|
70
|
+
try {
|
|
71
|
+
return await transport.invoke("dialog.readPickedFile", [
|
|
72
|
+
{ pickToken: opened.pickToken, filePath, encoding }
|
|
73
|
+
]);
|
|
74
|
+
} finally {
|
|
75
|
+
await transport.invoke("dialog.releasePick", [opened.pickToken]).catch(() => void 0);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
saveFile: async (options) => {
|
|
79
|
+
const { content, encoding, ...saveRequest } = options;
|
|
80
|
+
const saved = await transport.invoke("dialog.showSave", [saveRequest]);
|
|
81
|
+
if (saved.canceled) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
await transport.invoke("dialog.writeSaveFile", [
|
|
85
|
+
{
|
|
86
|
+
saveToken: saved.saveToken,
|
|
87
|
+
filePath: saved.filePath,
|
|
88
|
+
content,
|
|
89
|
+
encoding
|
|
90
|
+
}
|
|
91
|
+
]);
|
|
92
|
+
return saved.filePath;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/request-id.browser.ts
|
|
99
|
+
function newRequestId() {
|
|
100
|
+
return crypto.randomUUID();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/transport/shared/pending-invoke.ts
|
|
104
|
+
function createPendingInvoke(newRequestId2) {
|
|
105
|
+
const pending = /* @__PURE__ */ new Map();
|
|
106
|
+
function invoke(postMessage, invokeType, path, args) {
|
|
107
|
+
const requestId = newRequestId2();
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
pending.set(requestId, { resolve, reject });
|
|
110
|
+
postMessage({ type: invokeType, requestId, path, args });
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function handleInvokeResult(message) {
|
|
114
|
+
if (!message.requestId) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const entry = pending.get(message.requestId);
|
|
118
|
+
if (!entry) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
pending.delete(message.requestId);
|
|
122
|
+
if (message.ok) {
|
|
123
|
+
entry.resolve(message.result);
|
|
124
|
+
} else {
|
|
125
|
+
entry.reject(new Error(message.error ?? "invoke failed"));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { invoke, handleInvokeResult };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/transport/shared/subscription-events.ts
|
|
132
|
+
function createSubscriptionEvents() {
|
|
133
|
+
const handlersBySubscriptionId = /* @__PURE__ */ new Map();
|
|
134
|
+
function subscribe(subscriptionId, handler) {
|
|
135
|
+
const handlers = handlersBySubscriptionId.get(subscriptionId) ?? /* @__PURE__ */ new Set();
|
|
136
|
+
handlers.add(handler);
|
|
137
|
+
handlersBySubscriptionId.set(subscriptionId, handlers);
|
|
138
|
+
return () => {
|
|
139
|
+
const current = handlersBySubscriptionId.get(subscriptionId);
|
|
140
|
+
if (!current) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
current.delete(handler);
|
|
144
|
+
if (current.size === 0) {
|
|
145
|
+
handlersBySubscriptionId.delete(subscriptionId);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function dispatch(subscriptionId, event) {
|
|
150
|
+
const handlers = handlersBySubscriptionId.get(subscriptionId);
|
|
151
|
+
if (!handlers) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
for (const handler of handlers) {
|
|
155
|
+
handler(event);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function remove(subscriptionId) {
|
|
159
|
+
handlersBySubscriptionId.delete(subscriptionId);
|
|
160
|
+
}
|
|
161
|
+
return { subscribe, dispatch, remove };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// src/transport/iframe.ts
|
|
165
|
+
function createIframeBridgeTransport() {
|
|
166
|
+
if (typeof window === "undefined") {
|
|
167
|
+
throw new Error("[app-sdk] iframe bridge requires browser context");
|
|
168
|
+
}
|
|
169
|
+
const { invoke: createInvoke, handleInvokeResult } = createPendingInvoke(newRequestId);
|
|
170
|
+
const subscriptions = createSubscriptionEvents();
|
|
171
|
+
window.addEventListener("message", (event) => {
|
|
172
|
+
const data = event.data;
|
|
173
|
+
if (!data || typeof data !== "object" || !data.type) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (data.type === PANDA_APP_BRIDGE_EVENT && data.subscriptionId && data.event) {
|
|
177
|
+
subscriptions.dispatch(data.subscriptionId, data.event);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (data.type === PANDA_APP_BRIDGE_INVOKE_RESULT) {
|
|
181
|
+
handleInvokeResult(data);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
invoke(path, args) {
|
|
186
|
+
return createInvoke(
|
|
187
|
+
(message) => {
|
|
188
|
+
window.parent.postMessage(message, "*");
|
|
189
|
+
},
|
|
190
|
+
PANDA_APP_BRIDGE_INVOKE,
|
|
191
|
+
path,
|
|
192
|
+
args
|
|
193
|
+
);
|
|
194
|
+
},
|
|
195
|
+
subscribe: subscriptions.subscribe
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/browser.ts
|
|
200
|
+
var PandaApp = {
|
|
201
|
+
connectIframe() {
|
|
202
|
+
return createAppSdk(createIframeBridgeTransport());
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
export {
|
|
206
|
+
PANDA_APP_BRIDGE_EVENT,
|
|
207
|
+
PANDA_APP_BRIDGE_INVOKE,
|
|
208
|
+
PANDA_APP_BRIDGE_INVOKE_RESULT,
|
|
209
|
+
PandaApp,
|
|
210
|
+
createAppSdk,
|
|
211
|
+
createIframeBridgeTransport
|
|
212
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export { APP_UTILITY_EVENT, APP_UTILITY_INVOKE, APP_UTILITY_INVOKE_RESULT, APP_UTILITY_READY, } from './protocol.js';
|
|
2
|
+
export { createAppSdk } from './create-app-sdk.js';
|
|
3
|
+
export { createUtilityProcessTransport } from './transport/utility-process.js';
|
|
4
|
+
export type * from './types.js';
|
|
5
|
+
export declare const PandaApp: {
|
|
6
|
+
connectUtilityProcess(): {
|
|
7
|
+
auth: {
|
|
8
|
+
getCurrentUser: () => Promise<import("./types.js").AppUser | null>;
|
|
9
|
+
};
|
|
10
|
+
model: {
|
|
11
|
+
list: (options?: {
|
|
12
|
+
enabledOnly?: boolean;
|
|
13
|
+
visibleOnly?: boolean;
|
|
14
|
+
scene?: string;
|
|
15
|
+
}) => Promise<import("./types.js").ModelListItem[]>;
|
|
16
|
+
resolveForScene: (request?: {
|
|
17
|
+
scene?: string;
|
|
18
|
+
preferredModelId?: string;
|
|
19
|
+
}) => Promise<import("./types.js").ModelListItem | null>;
|
|
20
|
+
};
|
|
21
|
+
workspace: {
|
|
22
|
+
list: (request: {
|
|
23
|
+
limit: number;
|
|
24
|
+
includeEphemeral?: boolean;
|
|
25
|
+
}) => Promise<import("./types.js").WorkspaceListItem[]>;
|
|
26
|
+
ensureLocal: (request: {
|
|
27
|
+
path: string;
|
|
28
|
+
touch?: boolean;
|
|
29
|
+
}) => Promise<import("./types.js").WorkspaceListItem>;
|
|
30
|
+
};
|
|
31
|
+
conversation: {
|
|
32
|
+
createForApp: (options?: import("./types.js").CreateForAppOptions) => Promise<import("./types.js").CreateForAppResult>;
|
|
33
|
+
disposeForApp: (conversationId: string) => Promise<unknown>;
|
|
34
|
+
submitMessage: (conversationId: string, prompt: string) => Promise<unknown>;
|
|
35
|
+
getMessages: (conversationId: string) => Promise<unknown>;
|
|
36
|
+
subscribe: (options: import("./types.js").ConversationSubscribeOptions & {
|
|
37
|
+
onEvent?: import("./types.js").AppEventHandler;
|
|
38
|
+
}) => Promise<{
|
|
39
|
+
subscriptionId: string;
|
|
40
|
+
unsubscribe: () => Promise<void>;
|
|
41
|
+
}>;
|
|
42
|
+
awaitTurn: (options: {
|
|
43
|
+
conversationId: string;
|
|
44
|
+
turnId?: string;
|
|
45
|
+
timeoutMs?: number;
|
|
46
|
+
}) => Promise<unknown>;
|
|
47
|
+
};
|
|
48
|
+
session: {
|
|
49
|
+
mount: (conversationId: string, appId: string, replaceConfirmed?: boolean) => Promise<unknown>;
|
|
50
|
+
unmount: (conversationId: string) => Promise<unknown>;
|
|
51
|
+
getMount: (conversationId: string) => Promise<unknown>;
|
|
52
|
+
};
|
|
53
|
+
ingress: {
|
|
54
|
+
submit: (conversationId: string, prompt: string) => Promise<{
|
|
55
|
+
conversationId: string;
|
|
56
|
+
status: string;
|
|
57
|
+
}>;
|
|
58
|
+
};
|
|
59
|
+
tools: {
|
|
60
|
+
list: (request: {
|
|
61
|
+
workspace?: import("./types.js").WorkspaceRef;
|
|
62
|
+
workspaceId?: string;
|
|
63
|
+
}) => Promise<Array<{
|
|
64
|
+
name: string;
|
|
65
|
+
displayName: string;
|
|
66
|
+
description: string;
|
|
67
|
+
kind: string;
|
|
68
|
+
}>>;
|
|
69
|
+
execute: (request: {
|
|
70
|
+
toolName: string;
|
|
71
|
+
args?: Record<string, unknown>;
|
|
72
|
+
workspace?: import("./types.js").WorkspaceRef;
|
|
73
|
+
workspaceId?: string;
|
|
74
|
+
}) => Promise<{
|
|
75
|
+
toolName: string;
|
|
76
|
+
workspaceId: string;
|
|
77
|
+
ok: boolean;
|
|
78
|
+
llmContent?: string;
|
|
79
|
+
error?: {
|
|
80
|
+
message: string;
|
|
81
|
+
type?: string;
|
|
82
|
+
};
|
|
83
|
+
displayData?: unknown;
|
|
84
|
+
}>;
|
|
85
|
+
};
|
|
86
|
+
runtime: {
|
|
87
|
+
now: () => Promise<{
|
|
88
|
+
timestamp: number;
|
|
89
|
+
iso: string;
|
|
90
|
+
timeZone: string;
|
|
91
|
+
utcOffsetMinutes: number;
|
|
92
|
+
}>;
|
|
93
|
+
getHost: () => Promise<{
|
|
94
|
+
platform: string;
|
|
95
|
+
arch: string;
|
|
96
|
+
osName: string;
|
|
97
|
+
hostProduct: string;
|
|
98
|
+
shell: string;
|
|
99
|
+
shellKind: string;
|
|
100
|
+
homeDir: string;
|
|
101
|
+
pandaVersion: string;
|
|
102
|
+
appSdkVersion: string;
|
|
103
|
+
}>;
|
|
104
|
+
};
|
|
105
|
+
dialog: {
|
|
106
|
+
showOpen: (request?: import("./types.js").DialogShowOpenRequest) => Promise<import("./types.js").DialogShowOpenResult>;
|
|
107
|
+
readPickedFile: (request: import("./types.js").DialogReadPickedFileRequest) => Promise<import("./types.js").DialogReadPickedFileResult>;
|
|
108
|
+
releasePick: (pickToken: string) => Promise<unknown>;
|
|
109
|
+
showSave: (request?: import("./types.js").DialogShowSaveRequest) => Promise<import("./types.js").DialogShowSaveResult>;
|
|
110
|
+
writeSaveFile: (request: import("./types.js").DialogWriteSaveFileRequest) => Promise<import("./types.js").DialogWriteSaveFileResult>;
|
|
111
|
+
pickAndReadFile: (options?: import("./types.js").DialogPickAndReadFileOptions) => Promise<import("./types.js").DialogReadPickedFileResult | null>;
|
|
112
|
+
saveFile: (options: import("./types.js").DialogSaveFileOptions) => Promise<string | null>;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// src/protocol.ts
|
|
2
|
+
var APP_UTILITY_INVOKE = "app-utility:invoke";
|
|
3
|
+
var APP_UTILITY_INVOKE_RESULT = "app-utility:invoke-result";
|
|
4
|
+
var APP_UTILITY_READY = "app-utility:ready";
|
|
5
|
+
var APP_UTILITY_EVENT = "app-utility:event";
|
|
6
|
+
|
|
7
|
+
// src/create-app-sdk.ts
|
|
8
|
+
function createAppSdk(transport) {
|
|
9
|
+
return {
|
|
10
|
+
auth: {
|
|
11
|
+
getCurrentUser: () => transport.invoke("auth.getCurrentUser", [])
|
|
12
|
+
},
|
|
13
|
+
model: {
|
|
14
|
+
list: (options) => transport.invoke("model.list", [options ?? {}]),
|
|
15
|
+
resolveForScene: (request) => transport.invoke("model.resolveForScene", [request ?? {}])
|
|
16
|
+
},
|
|
17
|
+
workspace: {
|
|
18
|
+
list: (request) => transport.invoke("workspace.list", [request]),
|
|
19
|
+
ensureLocal: (request) => transport.invoke("workspace.ensureLocal", [request])
|
|
20
|
+
},
|
|
21
|
+
conversation: {
|
|
22
|
+
createForApp: (options) => transport.invoke("conversation.createForApp", [options ?? {}]),
|
|
23
|
+
disposeForApp: (conversationId) => transport.invoke("conversation.disposeForApp", [conversationId]),
|
|
24
|
+
submitMessage: (conversationId, prompt) => transport.invoke("conversation.submitMessage", [conversationId, prompt]),
|
|
25
|
+
getMessages: (conversationId) => transport.invoke("conversation.getMessages", [conversationId]),
|
|
26
|
+
subscribe: async (options) => {
|
|
27
|
+
const result = await transport.invoke("conversation.subscribe", [options]);
|
|
28
|
+
let offEvent;
|
|
29
|
+
if (options.onEvent) {
|
|
30
|
+
offEvent = transport.subscribe(result.subscriptionId, options.onEvent);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
subscriptionId: result.subscriptionId,
|
|
34
|
+
unsubscribe: async () => {
|
|
35
|
+
offEvent?.();
|
|
36
|
+
await transport.invoke("conversation.unsubscribe", [result.subscriptionId]);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
awaitTurn: (options) => transport.invoke("conversation.awaitTurn", [options])
|
|
41
|
+
},
|
|
42
|
+
session: {
|
|
43
|
+
mount: (conversationId, appId, replaceConfirmed) => transport.invoke("mountSession", [conversationId, appId, replaceConfirmed === true]),
|
|
44
|
+
unmount: (conversationId) => transport.invoke("unmountSession", [conversationId]),
|
|
45
|
+
getMount: (conversationId) => transport.invoke("getSessionMount", [conversationId])
|
|
46
|
+
},
|
|
47
|
+
ingress: {
|
|
48
|
+
submit: (conversationId, prompt) => transport.invoke("ingress.submit", [conversationId, prompt])
|
|
49
|
+
},
|
|
50
|
+
tools: {
|
|
51
|
+
list: (request) => transport.invoke("tools.list", [request]),
|
|
52
|
+
execute: (request) => transport.invoke("tools.execute", [request])
|
|
53
|
+
},
|
|
54
|
+
runtime: {
|
|
55
|
+
now: () => transport.invoke("runtime.now", []),
|
|
56
|
+
getHost: () => transport.invoke("runtime.getHost", [])
|
|
57
|
+
},
|
|
58
|
+
dialog: {
|
|
59
|
+
showOpen: (request) => transport.invoke("dialog.showOpen", [request ?? {}]),
|
|
60
|
+
readPickedFile: (request) => transport.invoke("dialog.readPickedFile", [request]),
|
|
61
|
+
releasePick: (pickToken) => transport.invoke("dialog.releasePick", [pickToken]),
|
|
62
|
+
showSave: (request) => transport.invoke("dialog.showSave", [request ?? {}]),
|
|
63
|
+
writeSaveFile: (request) => transport.invoke("dialog.writeSaveFile", [request]),
|
|
64
|
+
pickAndReadFile: async (options) => {
|
|
65
|
+
const { encoding, ...openRequest } = options ?? {};
|
|
66
|
+
const opened = await transport.invoke("dialog.showOpen", [openRequest]);
|
|
67
|
+
if (opened.canceled || opened.filePaths.length === 0) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const filePath = opened.filePaths[0];
|
|
71
|
+
try {
|
|
72
|
+
return await transport.invoke("dialog.readPickedFile", [
|
|
73
|
+
{ pickToken: opened.pickToken, filePath, encoding }
|
|
74
|
+
]);
|
|
75
|
+
} finally {
|
|
76
|
+
await transport.invoke("dialog.releasePick", [opened.pickToken]).catch(() => void 0);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
saveFile: async (options) => {
|
|
80
|
+
const { content, encoding, ...saveRequest } = options;
|
|
81
|
+
const saved = await transport.invoke("dialog.showSave", [saveRequest]);
|
|
82
|
+
if (saved.canceled) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
await transport.invoke("dialog.writeSaveFile", [
|
|
86
|
+
{
|
|
87
|
+
saveToken: saved.saveToken,
|
|
88
|
+
filePath: saved.filePath,
|
|
89
|
+
content,
|
|
90
|
+
encoding
|
|
91
|
+
}
|
|
92
|
+
]);
|
|
93
|
+
return saved.filePath;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/request-id.node.ts
|
|
100
|
+
import { randomUUID } from "node:crypto";
|
|
101
|
+
function newRequestId() {
|
|
102
|
+
return randomUUID();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/transport/shared/pending-invoke.ts
|
|
106
|
+
function createPendingInvoke(newRequestId2) {
|
|
107
|
+
const pending = /* @__PURE__ */ new Map();
|
|
108
|
+
function invoke(postMessage, invokeType, path, args) {
|
|
109
|
+
const requestId = newRequestId2();
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
pending.set(requestId, { resolve, reject });
|
|
112
|
+
postMessage({ type: invokeType, requestId, path, args });
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function handleInvokeResult(message) {
|
|
116
|
+
if (!message.requestId) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const entry = pending.get(message.requestId);
|
|
120
|
+
if (!entry) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
pending.delete(message.requestId);
|
|
124
|
+
if (message.ok) {
|
|
125
|
+
entry.resolve(message.result);
|
|
126
|
+
} else {
|
|
127
|
+
entry.reject(new Error(message.error ?? "invoke failed"));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { invoke, handleInvokeResult };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/transport/shared/subscription-events.ts
|
|
134
|
+
function createSubscriptionEvents() {
|
|
135
|
+
const handlersBySubscriptionId = /* @__PURE__ */ new Map();
|
|
136
|
+
function subscribe(subscriptionId, handler) {
|
|
137
|
+
const handlers = handlersBySubscriptionId.get(subscriptionId) ?? /* @__PURE__ */ new Set();
|
|
138
|
+
handlers.add(handler);
|
|
139
|
+
handlersBySubscriptionId.set(subscriptionId, handlers);
|
|
140
|
+
return () => {
|
|
141
|
+
const current = handlersBySubscriptionId.get(subscriptionId);
|
|
142
|
+
if (!current) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
current.delete(handler);
|
|
146
|
+
if (current.size === 0) {
|
|
147
|
+
handlersBySubscriptionId.delete(subscriptionId);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function dispatch(subscriptionId, event) {
|
|
152
|
+
const handlers = handlersBySubscriptionId.get(subscriptionId);
|
|
153
|
+
if (!handlers) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
for (const handler of handlers) {
|
|
157
|
+
handler(event);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function remove(subscriptionId) {
|
|
161
|
+
handlersBySubscriptionId.delete(subscriptionId);
|
|
162
|
+
}
|
|
163
|
+
return { subscribe, dispatch, remove };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/transport/utility-process.ts
|
|
167
|
+
function createUtilityProcessTransport() {
|
|
168
|
+
const parentPort = process.parentPort;
|
|
169
|
+
if (!parentPort) {
|
|
170
|
+
throw new Error("[app-sdk] parentPort unavailable");
|
|
171
|
+
}
|
|
172
|
+
const { invoke: createInvoke, handleInvokeResult } = createPendingInvoke(newRequestId);
|
|
173
|
+
const subscriptions = createSubscriptionEvents();
|
|
174
|
+
parentPort.on("message", (event) => {
|
|
175
|
+
const msg = event.data;
|
|
176
|
+
if (!msg || typeof msg !== "object" || !msg.type) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (msg.type === APP_UTILITY_EVENT && msg.subscriptionId && msg.event) {
|
|
180
|
+
subscriptions.dispatch(msg.subscriptionId, msg.event);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (msg.type === APP_UTILITY_INVOKE_RESULT) {
|
|
184
|
+
handleInvokeResult(msg);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
invoke(path, args) {
|
|
189
|
+
return createInvoke(
|
|
190
|
+
(message) => {
|
|
191
|
+
parentPort.postMessage(message);
|
|
192
|
+
},
|
|
193
|
+
APP_UTILITY_INVOKE,
|
|
194
|
+
path,
|
|
195
|
+
args
|
|
196
|
+
);
|
|
197
|
+
},
|
|
198
|
+
subscribe: subscriptions.subscribe
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/node.ts
|
|
203
|
+
var PandaApp = {
|
|
204
|
+
connectUtilityProcess() {
|
|
205
|
+
return createAppSdk(createUtilityProcessTransport());
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
export {
|
|
209
|
+
APP_UTILITY_EVENT,
|
|
210
|
+
APP_UTILITY_INVOKE,
|
|
211
|
+
APP_UTILITY_INVOKE_RESULT,
|
|
212
|
+
APP_UTILITY_READY,
|
|
213
|
+
PandaApp,
|
|
214
|
+
createAppSdk,
|
|
215
|
+
createUtilityProcessTransport
|
|
216
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@panda-agent/panda-app-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Panda 智能应用 SDK — iframe / Utility Process 客户端",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
"./browser": {
|
|
15
|
+
"types": "./dist/browser/index.d.ts",
|
|
16
|
+
"default": "./dist/browser/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./node": {
|
|
19
|
+
"types": "./dist/node/index.d.ts",
|
|
20
|
+
"default": "./dist/node/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "node scripts/build.mjs",
|
|
28
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
29
|
+
"test": "npm run build && vitest run",
|
|
30
|
+
"verify-dist": "node scripts/verify-dist.mjs",
|
|
31
|
+
"vendor-fixtures": "node scripts/vendor-fixtures.mjs",
|
|
32
|
+
"prepack": "npm run build && npm run verify-dist",
|
|
33
|
+
"release:pack": "npm pack",
|
|
34
|
+
"release:publish": "node ./scripts/publish-app-sdk.mjs --tag latest",
|
|
35
|
+
"release:publish:dry-run": "node ./scripts/publish-app-sdk.mjs --tag latest --dry-run",
|
|
36
|
+
"release:publish:tester": "node ./scripts/publish-app-sdk.mjs --tag tester",
|
|
37
|
+
"release:publish:tester:dry-run": "node ./scripts/publish-app-sdk.mjs --tag tester --dry-run",
|
|
38
|
+
"prepublishOnly": "npm run typecheck && npm run test"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^22.0.0",
|
|
42
|
+
"esbuild": "^0.27.2",
|
|
43
|
+
"typescript": "~5.8.3",
|
|
44
|
+
"vitest": "^4.0.17"
|
|
45
|
+
}
|
|
46
|
+
}
|