virlen-remote 0.5.0 → 0.6.1
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 +362 -169
- package/README.md +389 -201
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/protocol/agents.d.ts +14 -0
- package/dist/protocol/agents.d.ts.map +1 -0
- package/dist/protocol/agents.js +14 -0
- package/dist/protocol/agents.js.map +1 -0
- package/dist/protocol/api.d.ts +356 -1
- package/dist/protocol/api.d.ts.map +1 -1
- package/dist/protocol/api.js.map +1 -1
- package/dist/protocol/compress.d.ts +55 -0
- package/dist/protocol/compress.d.ts.map +1 -0
- package/dist/protocol/compress.js +60 -0
- package/dist/protocol/compress.js.map +1 -0
- package/dist/protocol/files.d.ts +261 -0
- package/dist/protocol/files.d.ts.map +1 -0
- package/dist/protocol/files.js +506 -0
- package/dist/protocol/files.js.map +1 -0
- package/dist/protocol/host.d.ts +24 -1
- package/dist/protocol/host.d.ts.map +1 -1
- package/dist/protocol/host.js +9 -1
- package/dist/protocol/host.js.map +1 -1
- package/dist/protocol/message-files.d.ts +79 -0
- package/dist/protocol/message-files.d.ts.map +1 -0
- package/dist/protocol/message-files.js +118 -0
- package/dist/protocol/message-files.js.map +1 -0
- package/dist/protocol/tool-args.d.ts +95 -0
- package/dist/protocol/tool-args.d.ts.map +1 -0
- package/dist/protocol/tool-args.js +439 -0
- package/dist/protocol/tool-args.js.map +1 -0
- package/dist/testing/mock-host.d.ts +44 -1
- package/dist/testing/mock-host.d.ts.map +1 -1
- package/dist/testing/mock-host.js +439 -10
- package/dist/testing/mock-host.js.map +1 -1
- package/dist/transport/rtc.d.ts +17 -0
- package/dist/transport/rtc.d.ts.map +1 -1
- package/dist/transport/rtc.js +49 -12
- package/dist/transport/rtc.js.map +1 -1
- package/dist/transport/signaling.d.ts +12 -1
- package/dist/transport/signaling.d.ts.map +1 -1
- package/dist/transport/signaling.js +17 -3
- package/dist/transport/signaling.js.map +1 -1
- package/package.json +69 -69
- package/src/index.ts +270 -184
- package/src/protocol/agents.ts +13 -0
- package/src/protocol/api.ts +960 -615
- package/src/protocol/compress.ts +65 -0
- package/src/protocol/files.ts +544 -0
- package/src/protocol/host.ts +205 -165
- package/src/protocol/message-actions.ts +60 -60
- package/src/protocol/message-files.ts +161 -0
- package/src/protocol/pairing.ts +254 -254
- package/src/protocol/tool-args.ts +478 -0
- package/src/testing/mock-host.ts +1285 -740
- package/src/transport/link-kind.ts +222 -222
- package/src/transport/rtc.ts +381 -344
- package/src/transport/signaling.ts +331 -317
- package/src/transport/transfer-tier.ts +64 -64
package/README.md
CHANGED
|
@@ -1,201 +1,389 @@
|
|
|
1
|
-
# virlen-remote
|
|
2
|
-
|
|
3
|
-
Virlen「手机控制电脑」功能的**协议栈共享包** —— 电脑端(virlen-app)与手机端(virlen-mobile)
|
|
4
|
-
共用同一份实现,避免两端各写一份契约然后慢慢漂移。
|
|
5
|
-
|
|
6
|
-
- **零运行时依赖**:WebRTC 用浏览器原生 `RTCPeerConnection`,信令用 `fetch` + `EventSource`,
|
|
7
|
-
Node 侧只用内存传输做测试。
|
|
8
|
-
- **ESM only**(`"type": "module"`):Node ≥ 18,或任意现代打包器(Vite / webpack / esbuild / Rollup)。
|
|
9
|
-
- 自带类型声明(`.d.ts` + `.d.ts.map`,随包附源码便于跳转)。
|
|
10
|
-
- **源码与产物里都没有任何 TURN 凭证** —— ICE 默认值由服务端下发,见下文。
|
|
11
|
-
|
|
12
|
-
## 安装
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
npm i virlen-remote
|
|
16
|
-
# 或 pnpm add virlen-remote / yarn add virlen-remote
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
```ts
|
|
20
|
-
import { Endpoint, RtcTransport } from 'virlen-remote'
|
|
21
|
-
import { createMockHostDataSource } from 'virlen-remote/testing' // 仅测试/联调用
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## 它包含什么
|
|
25
|
-
|
|
26
|
-
| 分层 | 内容 |
|
|
27
|
-
|---|---|
|
|
28
|
-
| `protocol/` | 帧编解码与分片重组、RPC(`Endpoint` / 幂等 / 超时 / 重放)、错误码、能力协商、方法表与 DTO、设备身份与授权凭证、配对载荷、流式下行(整帧 /
|
|
29
|
-
| `transport/` | `Transport` 抽象 + 三种实现(`RtcTransport` / `MemoryTransport` / `BroadcastTransport`)、`SseSignalingClient`(信令)、ICE 配置解析 |
|
|
30
|
-
| `testing/`(子路径) | `createMockHostDataSource()`:可直接挂到 `registerHostHandlers` 的 mock 宿主,供两端联调与单测使用 |
|
|
31
|
-
|
|
32
|
-
## 快速开始
|
|
33
|
-
|
|
34
|
-
### 电脑端(host,发起 offer)
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
import {
|
|
38
|
-
Endpoint,
|
|
39
|
-
RtcTransport,
|
|
40
|
-
SseSignalingClient,
|
|
41
|
-
registerHostHandlers,
|
|
42
|
-
newDeviceKey,
|
|
43
|
-
roomFor,
|
|
44
|
-
resolveIceServers,
|
|
45
|
-
} from 'virlen-remote'
|
|
46
|
-
|
|
47
|
-
// 设备 key 首次生成后**必须持久化**:它既是「这台电脑是谁」,也是房间号的来源
|
|
48
|
-
const deviceKey = newDeviceKey('host')
|
|
49
|
-
const signalUrl = 'https://your-server/api/rtc/'
|
|
50
|
-
const room = roomFor(deviceKey)
|
|
51
|
-
|
|
52
|
-
// ICE 默认值从信令服务取(服务端配置;本机可自定义覆盖)
|
|
53
|
-
const ice = await resolveIceServers({ baseUrl: signalUrl, storage: localStorage })
|
|
54
|
-
|
|
55
|
-
const signaling = new SseSignalingClient({
|
|
56
|
-
baseUrl: signalUrl,
|
|
57
|
-
room,
|
|
58
|
-
role: 'host',
|
|
59
|
-
deviceKey,
|
|
60
|
-
clientName: '我的电脑',
|
|
61
|
-
})
|
|
62
|
-
const transport = new RtcTransport({ role: 'host', signaling, iceServers: ice.servers })
|
|
63
|
-
const endpoint = new Endpoint({ transport })
|
|
64
|
-
|
|
65
|
-
registerHostHandlers(endpoint, myDataSource) // 实现 HostDataSource 端口即可
|
|
66
|
-
void transport.start()
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
### 手机端(guest,应答方)
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
import {
|
|
73
|
-
Endpoint,
|
|
74
|
-
RtcTransport,
|
|
75
|
-
SseSignalingClient,
|
|
76
|
-
createCaller,
|
|
77
|
-
type HostApi,
|
|
78
|
-
} from 'virlen-remote'
|
|
79
|
-
|
|
80
|
-
const signaling = new SseSignalingClient({
|
|
81
|
-
baseUrl: signalUrl,
|
|
82
|
-
room, // 与电脑端同一个房间(由电脑 key 派生)
|
|
83
|
-
role: 'guest',
|
|
84
|
-
deviceKey: mobileKey,
|
|
85
|
-
requireHostOnline: true, // 房间里没有电脑时立刻失败,而不是空等超时
|
|
86
|
-
})
|
|
87
|
-
const transport = new RtcTransport({ role: 'guest', signaling, iceServers: ice.servers })
|
|
88
|
-
await transport.start()
|
|
89
|
-
await transport.whenReady()
|
|
90
|
-
|
|
91
|
-
const caller = createCaller<HostApi>(new Endpoint({ transport }))
|
|
92
|
-
const hello = await caller.call('host.hello', {
|
|
93
|
-
protocolVersion: 1,
|
|
94
|
-
client: { platform: 'ios-web', appVersion: '0.1.0' },
|
|
95
|
-
capabilities: ['session.list', 'session.send'],
|
|
96
|
-
token, // 首次配对是一次性票据;之后是上一轮拿到的授权凭证
|
|
97
|
-
mobileKey,
|
|
98
|
-
mobileName: 'iPhone · 3f9a',
|
|
99
|
-
})
|
|
100
|
-
// 首次配对就在这里拿到长期凭证:
|
|
101
|
-
// hello.grant → { token: 'gt-…', issuedAt, expiresAt }
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### 配对(二维码 / 配对串)
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
import {
|
|
108
|
-
buildPairingPayload,
|
|
109
|
-
buildPairingUrl,
|
|
110
|
-
parsePairingPayload,
|
|
111
|
-
roomOfPayload,
|
|
112
|
-
} from 'virlen-remote'
|
|
113
|
-
|
|
114
|
-
const payload = buildPairingPayload({ host: deviceKey, name: '我的电脑', ticket, signal: signalUrl })
|
|
115
|
-
const qrText = buildPairingUrl(payload) // 画进二维码:'https://virlen.cn/mobile?t=vrp1:xxxx'
|
|
116
|
-
const back = parsePairingPayload(qrText) // 手机端解析(URL / 混淆串 / 旧明文 JSON 都吃;认不出返回 null)
|
|
117
|
-
if (back) await connect(roomOfPayload(back), back.host, back.ticket)
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
> **配对链接**:二维码内容 = `https://virlen.cn/mobile?t=<配对数据>`,于是**系统相机 / 微信 / 任意浏览器**
|
|
121
|
-
> 扫码就能直接打开手机端并自动配对。手机端从 `?t=` 取回数据后同样交给 `parsePairingPayload`。
|
|
122
|
-
> 地址常量见 `PAIRING_URL_BASE`(自建 / 换域名给 `buildPairingUrl` 传第二个参数覆盖)。
|
|
123
|
-
>
|
|
124
|
-
> **关于混淆**:配对数据是 `vrp1:` + `Base64URL(JSON ⊕ 固定盐)`,只是让二维码文字 /
|
|
125
|
-
> 界面上的排查文本**不是一眼可读**,**不是加密** —— 固定盐在源码里,拿到源码即可解;它也
|
|
126
|
-
> 挡不住有人对着屏幕拍照。真正的安全边界是「一次性 ticket + 电脑端确认弹窗 + 授权凭证」。
|
|
127
|
-
> `parsePairingPayload` **同时接受** URL、混淆串与旧版明文 JSON(向后兼容)。前缀常量见 `PAIRING_OBFUSCATION_PREFIX`。
|
|
128
|
-
|
|
129
|
-
##
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
1
|
+
# virlen-remote
|
|
2
|
+
|
|
3
|
+
Virlen「手机控制电脑」功能的**协议栈共享包** —— 电脑端(virlen-app)与手机端(virlen-mobile)
|
|
4
|
+
共用同一份实现,避免两端各写一份契约然后慢慢漂移。
|
|
5
|
+
|
|
6
|
+
- **零运行时依赖**:WebRTC 用浏览器原生 `RTCPeerConnection`,信令用 `fetch` + `EventSource`,
|
|
7
|
+
Node 侧只用内存传输做测试。
|
|
8
|
+
- **ESM only**(`"type": "module"`):Node ≥ 18,或任意现代打包器(Vite / webpack / esbuild / Rollup)。
|
|
9
|
+
- 自带类型声明(`.d.ts` + `.d.ts.map`,随包附源码便于跳转)。
|
|
10
|
+
- **源码与产物里都没有任何 TURN 凭证** —— ICE 默认值由服务端下发,见下文。
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm i virlen-remote
|
|
16
|
+
# 或 pnpm add virlen-remote / yarn add virlen-remote
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { Endpoint, RtcTransport } from 'virlen-remote'
|
|
21
|
+
import { createMockHostDataSource } from 'virlen-remote/testing' // 仅测试/联调用
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 它包含什么
|
|
25
|
+
|
|
26
|
+
| 分层 | 内容 |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `protocol/` | 帧编解码与分片重组、RPC(`Endpoint` / 幂等 / 超时 / 重放)、错误码、能力协商、方法表与 DTO、设备身份与授权凭证、配对载荷、流式下行(整帧 / 增量)、工作目录文件(分类 / 限额 / base64 / 路径工具) |
|
|
29
|
+
| `transport/` | `Transport` 抽象 + 三种实现(`RtcTransport` / `MemoryTransport` / `BroadcastTransport`)、`SseSignalingClient`(信令)、ICE 配置解析 |
|
|
30
|
+
| `testing/`(子路径) | `createMockHostDataSource()`:可直接挂到 `registerHostHandlers` 的 mock 宿主,供两端联调与单测使用 |
|
|
31
|
+
|
|
32
|
+
## 快速开始
|
|
33
|
+
|
|
34
|
+
### 电脑端(host,发起 offer)
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import {
|
|
38
|
+
Endpoint,
|
|
39
|
+
RtcTransport,
|
|
40
|
+
SseSignalingClient,
|
|
41
|
+
registerHostHandlers,
|
|
42
|
+
newDeviceKey,
|
|
43
|
+
roomFor,
|
|
44
|
+
resolveIceServers,
|
|
45
|
+
} from 'virlen-remote'
|
|
46
|
+
|
|
47
|
+
// 设备 key 首次生成后**必须持久化**:它既是「这台电脑是谁」,也是房间号的来源
|
|
48
|
+
const deviceKey = newDeviceKey('host')
|
|
49
|
+
const signalUrl = 'https://your-server/api/rtc/'
|
|
50
|
+
const room = roomFor(deviceKey)
|
|
51
|
+
|
|
52
|
+
// ICE 默认值从信令服务取(服务端配置;本机可自定义覆盖)
|
|
53
|
+
const ice = await resolveIceServers({ baseUrl: signalUrl, storage: localStorage })
|
|
54
|
+
|
|
55
|
+
const signaling = new SseSignalingClient({
|
|
56
|
+
baseUrl: signalUrl,
|
|
57
|
+
room,
|
|
58
|
+
role: 'host',
|
|
59
|
+
deviceKey,
|
|
60
|
+
clientName: '我的电脑',
|
|
61
|
+
})
|
|
62
|
+
const transport = new RtcTransport({ role: 'host', signaling, iceServers: ice.servers })
|
|
63
|
+
const endpoint = new Endpoint({ transport })
|
|
64
|
+
|
|
65
|
+
registerHostHandlers(endpoint, myDataSource) // 实现 HostDataSource 端口即可
|
|
66
|
+
void transport.start()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 手机端(guest,应答方)
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import {
|
|
73
|
+
Endpoint,
|
|
74
|
+
RtcTransport,
|
|
75
|
+
SseSignalingClient,
|
|
76
|
+
createCaller,
|
|
77
|
+
type HostApi,
|
|
78
|
+
} from 'virlen-remote'
|
|
79
|
+
|
|
80
|
+
const signaling = new SseSignalingClient({
|
|
81
|
+
baseUrl: signalUrl,
|
|
82
|
+
room, // 与电脑端同一个房间(由电脑 key 派生)
|
|
83
|
+
role: 'guest',
|
|
84
|
+
deviceKey: mobileKey,
|
|
85
|
+
requireHostOnline: true, // 房间里没有电脑时立刻失败,而不是空等超时
|
|
86
|
+
})
|
|
87
|
+
const transport = new RtcTransport({ role: 'guest', signaling, iceServers: ice.servers })
|
|
88
|
+
await transport.start()
|
|
89
|
+
await transport.whenReady()
|
|
90
|
+
|
|
91
|
+
const caller = createCaller<HostApi>(new Endpoint({ transport }))
|
|
92
|
+
const hello = await caller.call('host.hello', {
|
|
93
|
+
protocolVersion: 1,
|
|
94
|
+
client: { platform: 'ios-web', appVersion: '0.1.0' },
|
|
95
|
+
capabilities: ['session.list', 'session.send'],
|
|
96
|
+
token, // 首次配对是一次性票据;之后是上一轮拿到的授权凭证
|
|
97
|
+
mobileKey,
|
|
98
|
+
mobileName: 'iPhone · 3f9a',
|
|
99
|
+
})
|
|
100
|
+
// 首次配对就在这里拿到长期凭证:
|
|
101
|
+
// hello.grant → { token: 'gt-…', issuedAt, expiresAt }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 配对(二维码 / 配对串)
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import {
|
|
108
|
+
buildPairingPayload,
|
|
109
|
+
buildPairingUrl,
|
|
110
|
+
parsePairingPayload,
|
|
111
|
+
roomOfPayload,
|
|
112
|
+
} from 'virlen-remote'
|
|
113
|
+
|
|
114
|
+
const payload = buildPairingPayload({ host: deviceKey, name: '我的电脑', ticket, signal: signalUrl })
|
|
115
|
+
const qrText = buildPairingUrl(payload) // 画进二维码:'https://virlen.cn/mobile?t=vrp1:xxxx'
|
|
116
|
+
const back = parsePairingPayload(qrText) // 手机端解析(URL / 混淆串 / 旧明文 JSON 都吃;认不出返回 null)
|
|
117
|
+
if (back) await connect(roomOfPayload(back), back.host, back.ticket)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
> **配对链接**:二维码内容 = `https://virlen.cn/mobile?t=<配对数据>`,于是**系统相机 / 微信 / 任意浏览器**
|
|
121
|
+
> 扫码就能直接打开手机端并自动配对。手机端从 `?t=` 取回数据后同样交给 `parsePairingPayload`。
|
|
122
|
+
> 地址常量见 `PAIRING_URL_BASE`(自建 / 换域名给 `buildPairingUrl` 传第二个参数覆盖)。
|
|
123
|
+
>
|
|
124
|
+
> **关于混淆**:配对数据是 `vrp1:` + `Base64URL(JSON ⊕ 固定盐)`,只是让二维码文字 /
|
|
125
|
+
> 界面上的排查文本**不是一眼可读**,**不是加密** —— 固定盐在源码里,拿到源码即可解;它也
|
|
126
|
+
> 挡不住有人对着屏幕拍照。真正的安全边界是「一次性 ticket + 电脑端确认弹窗 + 授权凭证」。
|
|
127
|
+
> `parsePairingPayload` **同时接受** URL、混淆串与旧版明文 JSON(向后兼容)。前缀常量见 `PAIRING_OBFUSCATION_PREFIX`。
|
|
128
|
+
|
|
129
|
+
## 上下文压缩方式(§22)
|
|
130
|
+
|
|
131
|
+
`host.session.compress` 支持**两种压缩方式**,由 `CompressParams.mode` 指定:
|
|
132
|
+
|
|
133
|
+
| `mode` | 做法 | 产物 |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `'ai'`(缺省) | 一次非流式模型调用(走对话页的压缩提示词) | 模型写的摘要,最省 token,**慢且要花钱** |
|
|
136
|
+
| `'raw'` | 纯本地渲染,毫秒级、零消耗 | 整段历史重排成一段文本(用户 / 助手正文**一字不删**,只丢深度思考并省略超长工具参数与输出) |
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { COMPRESS_MODE_CAPABILITY, COMPRESS_MODES, DEFAULT_COMPRESS_MODE, compressModeOf } from 'virlen-remote'
|
|
140
|
+
|
|
141
|
+
// 手机端:先看电脑端认不认这个参数,再决定给不给选择器
|
|
142
|
+
const canPick = hello.capabilities.includes(COMPRESS_MODE_CAPABILITY)
|
|
143
|
+
await caller.call('host.session.compress', { sessionId, confirm: true, mode: canPick ? 'raw' : undefined })
|
|
144
|
+
|
|
145
|
+
// 电脑端:校验手机传来的取值("没传"与"不认识"必须分开处理)
|
|
146
|
+
const mode = params.mode == null ? DEFAULT_COMPRESS_MODE : compressModeOf(params.mode)
|
|
147
|
+
if (!mode) throw new BridgeError('E_BAD_REQUEST', `未知的压缩方式: ${params.mode}`)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
⚠️ **`mode` 只在电脑端声明 `COMPRESS_MODE_CAPABILITY`(`'session.compress.mode'`)时才能传**:
|
|
151
|
+
它是个普通字段,**旧电脑端会静默忽略它**,然后按电脑侧设置里的方式压缩 —— 用户侧的表现是
|
|
152
|
+
「我点了正文压缩,结果还是 AI 摘要(还花了钱)」,且没有任何报错可查。
|
|
153
|
+
能力名是**功能标记**而不是权限:压缩本身的授权仍是 `session.compress`(破坏性、需 `confirm: true`)。
|
|
154
|
+
|
|
155
|
+
⚠️ **`raw` 的产物比 `ai` 长得多**(正文一字不删,数万字符是常态):消费方渲染摘要时不要假定
|
|
156
|
+
「摘要一定短」;产物形态与方式的对应关系还经 `ui_data.compressMode` 落库,供界面说明用。
|
|
157
|
+
|
|
158
|
+
## 工作目录文件(§37)
|
|
159
|
+
|
|
160
|
+
手机端能浏览 / 预览 / 下载 / **上传** / **编辑**电脑上**当前会话工作目录**里的文件。
|
|
161
|
+
|
|
162
|
+
| 方法 | 作用 |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `host.file.list` | 列目录(非递归;只给名字 / 是否目录 / 大小 / 修改时刻) |
|
|
165
|
+
| `host.file.read` | 分块读(单次 ≤ `FILE_CHUNK_BYTES` = 256KB);预览与下载走**同一条**路;应答带 `mtimeMs`(编辑保存的版本凭据) |
|
|
166
|
+
| `host.file.write.begin` | 开始上传 / **覆写**(校验大小 / 名字 / 目标目录;上传**在开始时就定名**) |
|
|
167
|
+
| `host.file.write.chunk` | 写一块(偏移必须等于已接收字节数 —— 乱序即拒) |
|
|
168
|
+
| `host.file.write.finish` | 收尾:临时文件 `rename` 落盘(上传 = 挑空位改名,覆写 = **替换**目标) |
|
|
169
|
+
| `host.file.write.abort` | 放弃(删临时文件;幂等,且**不过非中继门槛**) |
|
|
170
|
+
|
|
171
|
+
四个能力名(ACL 四档,可分别开关;默认全开):`file.browse` / `file.download` / `file.upload` /
|
|
172
|
+
`file.edit`(最后一档只管「改写已有文件」,详见下面《编辑保存》)。
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import {
|
|
176
|
+
FILE_CHUNK_BYTES, FILE_UPLOAD_MAX_BYTES,
|
|
177
|
+
bytesToBase64, base64ToBytes,
|
|
178
|
+
previewKindOf, previewLimitOf, mimeTypeOf,
|
|
179
|
+
fileTransferDeniedReason,
|
|
180
|
+
} from 'virlen-remote'
|
|
181
|
+
|
|
182
|
+
// 手机端:分块读(拼接与进度都在客户端)
|
|
183
|
+
let offset = 0
|
|
184
|
+
const parts: Uint8Array[] = []
|
|
185
|
+
for (;;) {
|
|
186
|
+
const chunk = await caller.call('host.file.read', { sessionId, path: 'src/index.ts', offset })
|
|
187
|
+
const bytes = base64ToBytes(chunk.data) // 而不是把整个文件塞进一次 RPC
|
|
188
|
+
parts.push(bytes)
|
|
189
|
+
offset += bytes.length
|
|
190
|
+
if (chunk.eof) break
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 电脑端:当前链路能不能传文件(**两端同一句话**)
|
|
194
|
+
const reason = fileTransferDeniedReason(linkKind) // 'relay' → 拒;'direct' / 'unknown' → null
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
要点:
|
|
198
|
+
|
|
199
|
+
- **非中继门槛**:只在**确认走了 TURN 中继**时拒(每个字节都要过服务器绕一圈,而文件动辄几 MB);
|
|
200
|
+
`direct` 与 `unknown` 都放行 —— `unknown` 是常态(同源 Broadcast 联调 / 非 WebRTC 链路),
|
|
201
|
+
拿不准就禁用等于让功能在联调里根本进不来。这不采用 `TransferTier` 的「拿不准就少发」取向,
|
|
202
|
+
因为那边的代价只是少一段正文,而这边是**功能直接消失**;
|
|
203
|
+
- **为何是 base64 分块**(而不是整文件 / 二进制帧):帧层载荷是 UTF-8 JSON,且一条逻辑消息要
|
|
204
|
+
攒齐全部分片才交付 —— 整文件塞一次会同时炸掉两端的组装缓冲;换二进制帧则要改帧格式与主版本。
|
|
205
|
+
代价是 33% 的 base64 开销,换来进度可见、可取消、单请求内存上界固定;
|
|
206
|
+
- **落盘是原子的**:`begin` 之后字节只写临时文件(`.virlen-part`),`finish` 才 `rename` 到目标名
|
|
207
|
+
—— 传输中断 / 用户取消不会在用户的项目里留下一个「打开是坏的」半截文件;
|
|
208
|
+
- **磁盘安全不在本包**:越权防线是消费方(电脑端)的 `resolveSafePath`(会话工作目录 + 黑白名单)。
|
|
209
|
+
本包只负责把手机传来的相对路径规整成**可控形状**(`normalizeRelPath` 会把逃出工作目录的
|
|
210
|
+
`..` 段就地吃掉)——规则写在这里是因为两端都要用同一份,而不是因为它是安全边界。
|
|
211
|
+
|
|
212
|
+
### 编辑保存(覆写已有文件)
|
|
213
|
+
|
|
214
|
+
手机端改一份文本 / 代码 / Markdown 源码时走**同一条分块通道**,但语义是覆写而不是新建:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import {
|
|
218
|
+
bytesToBase64, base64ToBytes,
|
|
219
|
+
decodeUtf8Strict, detectEolStyle, hasUtf8Bom, encodeEditedText,
|
|
220
|
+
} from 'virlen-remote'
|
|
221
|
+
|
|
222
|
+
// ① 打开:记下电脑侧给的版本凭据
|
|
223
|
+
const read = await caller.call('host.file.read', { sessionId, path: 'src/index.ts' })
|
|
224
|
+
const original = decodeUtf8Strict(base64ToBytes(read.data))!
|
|
225
|
+
|
|
226
|
+
// ② 保存:把编辑后的字节传上去,并声明「我打开时是哪一版」
|
|
227
|
+
const bytes = encodeEditedText(edited, {
|
|
228
|
+
eol: detectEolStyle(original),
|
|
229
|
+
bom: hasUtf8Bom(base64ToBytes(read.data)),
|
|
230
|
+
})
|
|
231
|
+
const begin = await caller.call('host.file.write.begin', {
|
|
232
|
+
sessionId, dir: 'src', name: 'index.ts', size: bytes.length,
|
|
233
|
+
overwrite: true,
|
|
234
|
+
expectMtimeMs: read.mtimeMs!, // **必须带**:不带就直接 E_BAD_REQUEST
|
|
235
|
+
expectSize: read.size,
|
|
236
|
+
})
|
|
237
|
+
// …chunk 循环与上传完全一样…
|
|
238
|
+
const done = await caller.call('host.file.write.finish', { uploadId: begin.uploadId })
|
|
239
|
+
// 回执里的 mtimeMs 是**新的**版本凭据 —— 连改两次时第二次的校验得用它
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
要点(每一条都对应一次真机上会疼的失败):
|
|
243
|
+
|
|
244
|
+
- **目标必须已存在,且不改名**:覆写是「原地改」而不是「另存为」,所以它与上传的
|
|
245
|
+
「同名自动加 ` - 副本`」是两条路;路径写错时给 `E_NOT_FOUND`,**绝不新建**(手机端一个笔误
|
|
246
|
+
不该在用户目录里凭空造出一个文件);
|
|
247
|
+
- **必须带 `expectMtimeMs`**(不给就 `E_BAD_REQUEST`):于是「盲写」这条路根本不存在 ——
|
|
248
|
+
从手机上打开文件到按保存之间可能过了几分钟,期间 AI / 用户 / 编辑器都可能已经写过它,
|
|
249
|
+
不校验版本就会把这些改动**静默吞掉**。版本不符回 `E_CONFLICT`,由界面让用户选
|
|
250
|
+
「重新载入 / 强制覆盖」(强制覆盖 = **先取一次当前版本再写**,仍是带版本地写);
|
|
251
|
+
- **换行与 BOM 的往返用共享包**(`detectEolStyle` / `applyEolStyle` / `hasUtf8Bom` /
|
|
252
|
+
`encodeEditedText`):手机上的 `<textarea>` 只有 LF(HTML 规范),而 Windows 上的源码大多是
|
|
253
|
+
CRLF —— 照 LF 存回去等于在「只改了三个字符」的改动里混进一次**全文行尾改写**;
|
|
254
|
+
- **非 UTF-8 不给编**(`decodeUtf8Strict` 返回 `null`):宽容解出来的乱码存回去就是毁文件
|
|
255
|
+
(原本在电脑上还能正常看的文件,之后连电脑上也读不回来了);
|
|
256
|
+
- **编辑上限 `FILE_EDIT_MAX_BYTES` = 256KB**(比预览的 1MB 严):编辑要把全文塞进 `textarea`
|
|
257
|
+
并整篇回传,超限只读(「请在电脑上改」);
|
|
258
|
+
- **能力名 `file.edit`**(第四档,默认开、与 `file.upload` 分开):上传最坏只是让项目里**多一个
|
|
259
|
+
文件**,而覆写是**把已有文件的内容换掉**(那可能是源码)——「只让手机传照片、不让手机改代码」
|
|
260
|
+
是一种应该被支持的授权。它同时是**功能标记**:旧电脑端会静默忽略 `overwrite`(一次覆盖保存
|
|
261
|
+
退化成「另存为 - 副本」,用户以为改了、原文件其实没动),所以手机端只在电脑端声明它时才
|
|
262
|
+
显示「编辑」入口。
|
|
263
|
+
|
|
264
|
+
### 消息里引用文件(`SendParams.files`)
|
|
265
|
+
|
|
266
|
+
手机端可以把**电脑上的文件**挂在要发的那条消息上 —— 与桌面输入框的「文件附件」同一条口径:
|
|
267
|
+
**只带路径,不搬运内容**,具体内容由 AI 用 `read_file` 按需读取。
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
import { MESSAGE_FILE_CAPABILITY, sanitizeFileRefs } from 'virlen-remote'
|
|
271
|
+
|
|
272
|
+
// 手机端:只在电脑端声明了这个能力位时才给「引用」入口(与 quotes 同一条教训)
|
|
273
|
+
if (capabilities.includes(MESSAGE_FILE_CAPABILITY)) {
|
|
274
|
+
await caller.call('host.session.send', {
|
|
275
|
+
sessionId, text: '帮我看看这个文件',
|
|
276
|
+
files: [{ path: 'E:/proj/src/a.ts', name: 'a.ts', size: 1024 }],
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 电脑侧:真实现与演示宿主都用这一份校验(两端唯一口径)
|
|
281
|
+
const checked = sanitizeFileRefs(params.files)
|
|
282
|
+
if (!checked.ok) throw new BridgeError('E_BAD_REQUEST', checked.reason)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
要点:
|
|
286
|
+
|
|
287
|
+
- **不搬运内容**:与「上传文件」是两件不同的事 —— 上传是把字节搬到电脑侧,这里是告诉模型
|
|
288
|
+
「用户附了这个文件」(电脑侧组装成 `{type:'file'}` 块,各 Provider 序列化成
|
|
289
|
+
`[User attached file] <path>`)。引用的永远是**当前**内容,不会拿一份过期副本去回答;
|
|
290
|
+
- **路径是电脑上的绝对路径**(与 `SessionSummaryDTO.workspace` / `host.file.list` 的 `absPath`
|
|
291
|
+
同一口径,手机上本来就看得见工作目录),手机上它只是一段可复制的文本;
|
|
292
|
+
- **形状非法 → 拒整条**(`E_BAD_REQUEST`),**不静默丢掉那一条**:§36 的教训是「假绿灯比报错
|
|
293
|
+
难查得多」—— 丢一条时手机端 chip 还在、用户以为带上了,而 AI 从未看到。超上限
|
|
294
|
+
(`MESSAGE_FILE_MAX` = 20)与路径过长(`MESSAGE_FILE_PATH_MAX`)同理;
|
|
295
|
+
- **`isDir` / `size` 只是展示元数据**(形状不对就丢字段);目录也能被引用(模型用 `list_files`
|
|
296
|
+
去看里面有什么);
|
|
297
|
+
- **结构化下行**:`MessageDTO.files` 与 `quotes` 并列,文件引用**不进 `text`** —— 电脑侧投影正文时
|
|
298
|
+
本就会把文件块展平成 `[文件] <名字>`(§7-⑦,与图片同一套降级规则),两条路同时走会显示两遍,
|
|
299
|
+
而那个展平占位符**只有名字没有路径**(同一目录下两个 `index.ts` 长得一样);
|
|
300
|
+
- **能力名 `message.file`**(**功能标记**,不是权限):文件引用本身就是 `session.send` 的一个参数,
|
|
301
|
+
越权防线仍是 `session.send`。写进能力表的用途是让手机端知道「本机电脑端认识 `files`」——
|
|
302
|
+
旧电脑端会把未知字段**静默丢掉**。
|
|
303
|
+
|
|
304
|
+
### 联调(`virlen-remote/testing`)
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import { createMockHostDataSource } from 'virlen-remote/testing'
|
|
308
|
+
|
|
309
|
+
// 自带一棵演示文件树:真 PNG(可预览)、未知类型的 build/app.bin(只能下载)、若干源码文件
|
|
310
|
+
const host = createMockHostDataSource({ fileLinkKind: 'relay' }) // 'relay' = 所有文件方法与真实电脑端一样拒
|
|
311
|
+
await host.listFiles({ sessionId: 'demo-1' })
|
|
312
|
+
host.readMockFile('demo-1', 'upload.bin') // 上传是否真的落到那个路径与那些字节
|
|
313
|
+
// 演示文件树**每格有自己的 mtime**(写盘即换):
|
|
314
|
+
host.writeMockFile('demo-1', 'README.md', '# AI 刚改的\n') // 模拟「电脑上有人改了它」→ 手机端保存拿到 E_CONFLICT
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## ICE / TURN 配置
|
|
318
|
+
|
|
319
|
+
**本包与两个客户端里都没有任何 TURN 凭证。** 默认值由**服务端**提供:
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
GET <信令基址>/ice
|
|
323
|
+
→ { "v": 1,
|
|
324
|
+
"iceServers": [ { "urls": "stun:turn.example.com:3478" },
|
|
325
|
+
{ "urls": "turn:turn.example.com:3478", "username": "…", "credential": "…" } ],
|
|
326
|
+
"mode": "static" | "rest" | "none",
|
|
327
|
+
"ttlSec": 3600, "expiresAt": 1730000000000 }
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
客户端用 `resolveIceServers()` 解析,优先级与降级:
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
自定义(用户手填)
|
|
334
|
+
> 服务端下发的本地缓存(默认 6 小时)
|
|
335
|
+
> 服务端现取
|
|
336
|
+
> 过期缓存(信令暂时不可达时,旧的也比没有强)
|
|
337
|
+
> 空(仅本机候选;局域网可用,跨网多半不行)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
import { resolveIceServers } from 'virlen-remote'
|
|
342
|
+
|
|
343
|
+
const ice = await resolveIceServers({
|
|
344
|
+
baseUrl: signalUrl,
|
|
345
|
+
customText: localStorage.getItem('virlen.rtc.ice'), // 用户自定义(空 = 用服务端默认)
|
|
346
|
+
storage: localStorage, // 缓存位置
|
|
347
|
+
})
|
|
348
|
+
ice.servers // → 交给 new RTCPeerConnection({ iceServers })
|
|
349
|
+
ice.source // → 'custom' | 'remote' | 'cache' | 'stale-cache' | 'none'
|
|
350
|
+
ice.detail // → 给用户看的中文说明(两端同一套口径)
|
|
351
|
+
ice.warning // → 降级原因(如「信令服务暂不可达,先用上次缓存」)
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
要点:
|
|
355
|
+
|
|
356
|
+
- **永不抛错**:任何一步失败都降级到下一优先级,并把来源如实报给上层 ——
|
|
357
|
+
「拿不到 TURN 配置」不该让用户连不上,只该让跨网连通率变差;
|
|
358
|
+
- 服务端可配**静态口令**,也可用 coturn `use-auth-secret` 下发**临时凭证**
|
|
359
|
+
(HMAC-SHA1 现算,密钥永不下发;被抄走也只有一小时额度);
|
|
360
|
+
- 凭证会随 `iceServers` 一起进内存,因此**不要**把它写进日志或埋点
|
|
361
|
+
(本包提供 `iceCount` / `iceSource` 这类只记元数据的写法)。
|
|
362
|
+
|
|
363
|
+
## 约定与边界
|
|
364
|
+
|
|
365
|
+
- **不用 DOM 类型做对外契约**:例如 ICE 条目是结构等价的 `IceServerInit`
|
|
366
|
+
而非 DOM 的 `RTCIceServer`,避免把「需要 DOM lib」扩散到所有消费方;
|
|
367
|
+
- `Transport.onError?()` 是**可选**方法:只有 `RtcTransport` 有致命错误通道(如被顶号 `E_REPLACED`);
|
|
368
|
+
- **布尔判别字段请显式比较**(`x.ok === false` 而不是 `!x.ok`):若消费方 tsconfig 关了
|
|
369
|
+
`strictNullChecks`,真值判断不收敛联合类型;
|
|
370
|
+
- **被顶号不要自动重连**:信令服务是「后来者优先」,自动重连会让两台设备互相顶号;
|
|
371
|
+
- 房间号由电脑设备 key 派生(`roomFor`):**知道 key 就能进房间**,授权关口是凭证校验。
|
|
372
|
+
|
|
373
|
+
## 维护者:构建与发布
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
pnpm install
|
|
377
|
+
pnpm typecheck && pnpm test # 类型检查 + 用例
|
|
378
|
+
pnpm build # 产出 dist/(ESM + .d.ts)
|
|
379
|
+
npm publish # prepublishOnly 会自动跑上面三步
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`pnpm build` 在 `tsc` 之后会把产物里的**相对导入补上 `.js` 后缀**:
|
|
383
|
+
源码里的免后缀写法对打包器友好,但**运行时 ESM 加载器不认**(`./protocol/frame` 会 404)。
|
|
384
|
+
本包是被 `import` 直接消费的,所以产物必须是合法 ESM。实现见 `scripts/build.mjs`
|
|
385
|
+
(零新增依赖,逐文件可 diff)。
|
|
386
|
+
|
|
387
|
+
## License
|
|
388
|
+
|
|
389
|
+
[MIT](./LICENSE)
|
package/dist/index.d.ts
CHANGED
|
@@ -25,11 +25,20 @@ export type { ChoiceAnswer } from './protocol/answer.js';
|
|
|
25
25
|
export { negotiate, intersectCapabilities } from './protocol/hello.js';
|
|
26
26
|
export { MESSAGE_QUOTE_CAPABILITY, MESSAGE_DELETE_CAPABILITY } from './protocol/message-actions.js';
|
|
27
27
|
export type { MessageQuote } from './protocol/message-actions.js';
|
|
28
|
+
export { MESSAGE_FILE_CAPABILITY, MESSAGE_FILE_MAX, MESSAGE_FILE_PATH_MAX, sanitizeFileRefs, } from './protocol/message-files.js';
|
|
29
|
+
export type { MessageFileRef, FileRefSanitizeResult } from './protocol/message-files.js';
|
|
30
|
+
export { SESSION_AGENT_CAPABILITY } from './protocol/agents.js';
|
|
31
|
+
export { COMPRESS_MODES, DEFAULT_COMPRESS_MODE, COMPRESS_MODE_CAPABILITY, compressModeOf, } from './protocol/compress.js';
|
|
32
|
+
export type { CompressMode } from './protocol/compress.js';
|
|
33
|
+
export { summarizeToolArgs, formatToolArgs, elideMiddle, TOOL_ARGS_MAX, TOOL_DETAIL_MAX, } from './protocol/tool-args.js';
|
|
34
|
+
export type { ToolArgsSummaryOptions, ToolArgsFormatOptions } from './protocol/tool-args.js';
|
|
35
|
+
export { FILE_BROWSE_CAPABILITY, FILE_DOWNLOAD_CAPABILITY, FILE_UPLOAD_CAPABILITY, FILE_EDIT_CAPABILITY, FILE_CHUNK_BYTES, FILE_UPLOAD_MAX_BYTES, FILE_EDIT_MAX_BYTES, FILE_TEXT_PREVIEW_MAX_BYTES, FILE_IMAGE_PREVIEW_MAX_BYTES, FILE_LIST_MAX_ENTRIES, FILE_NAME_MAX_LEN, UPLOAD_PART_SUFFIX, FILE_DIRECT_ONLY_MESSAGE, fileTransferDeniedReason, splitNameExt, previewKindOf, previewLimitOf, mimeTypeOf, isEditableKind, isEditableFileName, detectEolStyle, applyEolStyle, hasUtf8Bom, decodeUtf8Strict, encodeEditedText, bytesToBase64, base64ToBytes, formatFileSize, normalizeRelPath, joinRelPath, parentOfRelPath, baseNameOfPath, isSafeEntryName, duplicateNameCandidate, compareFileEntries, } from './protocol/files.js';
|
|
36
|
+
export type { FilePreviewKind, EolStyle } from './protocol/files.js';
|
|
28
37
|
export { Endpoint } from './protocol/endpoint.js';
|
|
29
38
|
export type { CallContext, EventContext, RpcHandler, EventSubscriber, CallOptions, EndpointOptions, } from './protocol/endpoint.js';
|
|
30
39
|
export { createCaller, createSubscriber, registerHandlers } from './protocol/api.js';
|
|
31
40
|
export { DEFAULT_CONTEXT_WINDOW_TOKENS, COMPRESS_MIN_RATIO } from './protocol/api.js';
|
|
32
|
-
export type { ParamsOf, ResultOf, TypedCaller, TypedSubscriber, HandlerImpl, HostApi, MobileApi, HostEvents, MobileEvents, SessionSummaryDTO, MessageDTO, RuntimeDTO, InteractionDTO, InteractionOutcome, StreamMode, ApprovalTier, SendParams, MsgPageParams, MsgPageDTO, AnswerParams, AnswerAction, AnswerResult, AnswerRejectReason, CreateSessionParams, RenameSessionParams, PinSessionParams, DeleteSessionParams, DeleteMessageParams, ModelProviderDTO, WorkspaceOptionDTO, ContextInfoDTO, SetModelParams, ContextParams, CompressParams, } from './protocol/api.js';
|
|
41
|
+
export type { ParamsOf, ResultOf, TypedCaller, TypedSubscriber, HandlerImpl, HostApi, MobileApi, HostEvents, MobileEvents, SessionSummaryDTO, MessageDTO, RuntimeDTO, RunningToolDTO, InteractionDTO, InteractionOutcome, StreamMode, ApprovalTier, SendParams, MsgPageParams, MsgPageDTO, AnswerParams, AnswerAction, AnswerResult, AnswerRejectReason, CreateSessionParams, RenameSessionParams, PinSessionParams, DeleteSessionParams, DeleteMessageParams, AgentOptionDTO, ModelProviderDTO, WorkspaceOptionDTO, ContextInfoDTO, SetModelParams, ContextParams, CompressParams, FileEntryDTO, FileListParams, FileListResult, FileReadParams, FileReadResult, FileWriteBeginParams, FileWriteBeginResult, FileWriteChunkParams, FileWriteChunkResult, FileWriteFinishParams, FileWriteFinishResult, FileWriteAbortParams, FileConflictPolicy, } from './protocol/api.js';
|
|
33
42
|
export type { HelloParams, HelloResult, ClientInfo, NegotiationInput, Negotiated } from './protocol/hello.js';
|
|
34
43
|
export { registerHostHandlers } from './protocol/host.js';
|
|
35
44
|
export type { HostDataSource, HostEmit, HostRegistration, RegisterHostOptions } from './protocol/host.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,yBAAyB,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACtG,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AACnG,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEnE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAC9D,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAG7C,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,SAAS,EACT,YAAY,EACZ,aAAa,EACb,WAAW,EACX,OAAO,EACP,eAAe,EACf,UAAU,EACV,cAAc,EACd,UAAU,EACV,UAAU,EACV,sBAAsB,GACvB,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAGtG,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,0BAA0B,EAC1B,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,aAAa,GACd,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAC3F,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AAGnE,OAAO,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAA;AAChG,YAAY,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|