dsh-model-usage 0.8.1 → 0.8.2
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 +32 -22
- package/README.md +262 -206
- package/docs/screenshots/usage-panel.png +0 -0
- package/package.json +15 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,35 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this package are documented in this file. Versions follow Semantic Versioning.
|
|
4
|
+
|
|
5
|
+
## 0.8.2
|
|
6
|
+
|
|
7
|
+
- Documentation: README restructured for the npm package page and the repository; usage panel
|
|
8
|
+
screenshot added under `docs/screenshots/` and shipped with the package.
|
|
9
|
+
- Package metadata: shorter description, expanded keyword set for npm search, `npm test` alias,
|
|
10
|
+
normalized JSON layout.
|
|
11
|
+
- `scripts/check.mjs` validates npm metadata, README image references and `files[]` glob semantics.
|
|
12
|
+
|
|
2
13
|
## 0.8.1
|
|
3
|
-
|
|
14
|
+
|
|
15
|
+
- Published with `publishConfig.access` set for public access.
|
|
4
16
|
|
|
5
17
|
## 0.8.0
|
|
6
18
|
|
|
7
|
-
|
|
19
|
+
Published to npm as `dsh-model-usage`.
|
|
8
20
|
|
|
9
|
-
- Host half:
|
|
10
|
-
`
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
a bilingual README, and `scripts/check.mjs` wired to `prepublishOnly`.
|
|
17
|
-
- `private` removed so the package can be published.
|
|
21
|
+
- Host half: `webServer` is the only hard dependency. `connection`, `credentials`, `llm`, `settings`,
|
|
22
|
+
`sessions`, `sessionProjections`, `sessionController`, `agentDefaultModel` and `deepseekAccount`
|
|
23
|
+
are resolved per call through `ctx.get()`.
|
|
24
|
+
- Browser half: locale is resolved lazily; without a locale service the panel renders the Chinese
|
|
25
|
+
source copy.
|
|
26
|
+
- Publish metadata: `engines`, `repository`, `homepage`, `bugs`, `keywords`; `prepublishOnly` runs the
|
|
27
|
+
verification scripts.
|
|
18
28
|
|
|
19
29
|
## 0.7.0
|
|
20
30
|
|
|
21
|
-
- The browser half no longer imports
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
- Host routes apply the
|
|
27
|
-
|
|
28
|
-
- Display metadata: `locale/zh.json`, `locale/en.json`, `icon.svg`,
|
|
29
|
-
|
|
31
|
+
- The browser half no longer imports Harness Client packages; the button and dialogs use the plugin's
|
|
32
|
+
own controls and the theme tokens exposed by the running build.
|
|
33
|
+
- Interface copy moved to the Client locale service (namespace `dsh-model-usage`, Chinese and
|
|
34
|
+
English), following the interface language.
|
|
35
|
+
- Styles render as part of the cell rather than `document.head`.
|
|
36
|
+
- Host routes apply the browser trust fence and answer 401/403; the browser addresses them with
|
|
37
|
+
document-relative URLs.
|
|
38
|
+
- Display metadata: `locale/zh.json`, `locale/en.json`, `icon.svg`, `dsh.manifestVersion: 1` and
|
|
39
|
+
`engines.dsh`.
|
|
30
40
|
- `modelSelection` is read through its wire view (`{ lastUsed, next }`).
|
|
31
41
|
|
|
32
42
|
## 0.6.0
|
|
33
43
|
|
|
34
|
-
- Usage for the current conversation's model, manual target override, background auto-refresh
|
|
35
|
-
read-only provider directory.
|
|
44
|
+
- Usage query for the current conversation's model, manual target override, background auto-refresh
|
|
45
|
+
and a read-only provider directory.
|
package/README.md
CHANGED
|
@@ -1,239 +1,295 @@
|
|
|
1
|
-
# dsh-model-usage
|
|
1
|
+
# dsh-model-usage
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Provider-account usage for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)
|
|
4
|
+
sessions. The plugin adds a **Usage** (`用量`) entry to the session header and reports the quota
|
|
5
|
+
windows, balance and request counters of the provider account behind the model the conversation is
|
|
6
|
+
using.
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
Queries run in the host process with the provider's own credential reference. The browser receives
|
|
9
|
+
normalized values and the credential reference name; credential values never leave the host.
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+

|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
dialogs (refresh settings, target picker, provider directory) go in the same folder.
|
|
13
|
+
*Usage panel for a Command Code subscription (Chinese UI): five-hour and weekly quota windows,
|
|
14
|
+
credit balance, spend, request count, and the model and credential reference used for the query.*
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
*Command Code 订阅的用量面板:5 小时 / 7 天窗口、信用余额、已花费、请求数,以及本次查询使用的
|
|
17
|
+
模型与凭据引用。*
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
entry to the right end of the session header (`conversation.session.header.utilities`) showing the
|
|
17
|
-
provider-account usage behind the model the current conversation runs on — read with that provider's
|
|
18
|
-
own credential reference, never with another provider's key.
|
|
19
|
+
Published on npm as [`dsh-model-usage`](https://www.npmjs.com/package/dsh-model-usage).
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
key from that provider's own credential reference, the numbers from that provider's usage API.
|
|
22
|
-
- **Manual target**: pick any routable provider model in the panel without changing what the
|
|
23
|
-
conversation runs on.
|
|
24
|
-
- **Warm in the background**: refreshes on an interval while the panel is closed.
|
|
25
|
-
- **Read-only directory**: lists the deployment's provider routes, their usage API kind, credential
|
|
26
|
-
reference name, and whether that reference is configured — never a value.
|
|
21
|
+
## Features
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
- **Session model.** The query target comes from the session's `modelSelection` projection, so the
|
|
24
|
+
panel follows model changes in the conversation.
|
|
25
|
+
- **Manual target.** Any routable provider model can be queried from the panel without changing the
|
|
26
|
+
model the conversation runs on.
|
|
27
|
+
- **Background refresh.** The mounted component refreshes on an interval, including while the panel
|
|
28
|
+
is closed.
|
|
29
|
+
- **Provider directory.** The deployment's provider routes are listed with their usage adapter,
|
|
30
|
+
credential reference name and configuration state. Values are never read out.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- DeepSeek Harness `>=0.2.1-rc.2` with the Web surface
|
|
35
|
+
- Node.js `>=18`
|
|
36
|
+
- No runtime dependencies. The browser half requires only the platform `react` module.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
33
39
|
|
|
34
40
|
```sh
|
|
35
|
-
# npm (this package is published as a Harness bundle)
|
|
36
41
|
dsh plugin --profile <profile> add dsh-model-usage
|
|
37
42
|
```
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
From an agent session, the bundle can also be installed by name with `install_bundle` (target
|
|
45
|
+
`dsh-model-usage`, or the repository path for a local checkout). Both paths install the package into
|
|
46
|
+
the profile, mount the bundle patch row `model-usage` and enable the bundle.
|
|
47
|
+
|
|
48
|
+
With profile HMR enabled, host-side changes apply immediately; otherwise restart the host
|
|
49
|
+
(`dsh web` or the desktop application). The browser half is served by `dsh-client-modules` and is
|
|
50
|
+
picked up by reloading the page.
|
|
51
|
+
|
|
52
|
+
## Usage sources
|
|
53
|
+
|
|
54
|
+
| Provider / base URL | Source | Reported values |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| `commandcode`, `*.commandcode.ai` | `GET /alpha/whoami?limits=1`, `/alpha/billing/credits`, `/alpha/billing/subscriptions`, `/alpha/usage/summary?since=` | Plan, five-hour and weekly windows, credit balance, spend, request count |
|
|
57
|
+
| `deepseek-official`, `deepseek`, `*.deepseek.com` | `GET https://api.deepseek.com/user/balance` | Balance per currency |
|
|
58
|
+
| `deepseek-account` | `ctx.deepseekAccount.getBalance()` | Wallet balances of the signed-in account, without an API key |
|
|
59
|
+
| `opencode-go`, `opencode.ai/zen/go` | `GET {origin}/zen/go/v1/usage` | Five-hour, weekly and monthly allowances per model |
|
|
60
|
+
| `openrouter`, `*.openrouter.ai` | `GET /api/v1/key`; `GET /api/v1/credits` when the key has no limit of its own | Used, limit, remaining and the account credit pool |
|
|
61
|
+
| `moonshotai`, `moonshotai-cn`, `kimi-coding`, `*.moonshot.cn`, `*.moonshot.ai` | `GET /v1/users/me/balance` | Available, cash and voucher balance |
|
|
62
|
+
| `siliconflow`, `*.siliconflow.cn`, `*.siliconflow.com` | `GET /v1/user/info` | Total, charged and granted balance |
|
|
63
|
+
| Other OpenAI-compatible routes with a `baseURL` | cc-switch New API convention: `GET {origin}/api/user/self` | Quota converted to USD; best effort |
|
|
64
|
+
|
|
65
|
+
The OpenCode Go endpoint is not publicly documented. Fields are normalized by their common names;
|
|
66
|
+
payloads that cannot be recognized remain available under **Raw response**. OpenCode Zen
|
|
67
|
+
(pay-as-you-go) has no usage endpoint (`/zen/v1/usage`, `/me` and `/credits` return 404), so
|
|
68
|
+
`opencode` routes report that usage queries are not supported. Routes without a `baseURL` and
|
|
69
|
+
without a known provider return an explicit error instead of an inferred endpoint.
|
|
70
|
+
|
|
71
|
+
Adding an adapter requires selecting the route in `kindOf()` and returning normalized data from a
|
|
72
|
+
`queryXxx()` function: `{ rows }` for label/value lines or `{ windows, models }` for meters. The
|
|
73
|
+
client renders both shapes with the generic card.
|
|
74
|
+
|
|
75
|
+
## Query target and credentials
|
|
76
|
+
|
|
77
|
+
1. The browser reads `next ?? lastUsed` from the session's `modelSelection` projection and sends
|
|
78
|
+
`{ provider, model, reasoningEffort, sessionId }` to `POST /model-usage/balance`.
|
|
79
|
+
2. If the request carries no target, the host falls back to the session projection of the process
|
|
80
|
+
and then to the deployment default (`agentDefaultModel.currentSelection()`).
|
|
81
|
+
3. The provider's settings location is resolved through `llm.listConfigurableProviders()` and read
|
|
82
|
+
with `settings.describe()`, which supplies `apiKeyEnv`, `baseURL`, `api` and custom headers.
|
|
83
|
+
4. `ctx.credentials.resolve(apiKeyEnv)` returns the key in use. An unconfigured reference falls back
|
|
84
|
+
to the environment variable of the same name; Command Code also falls back to
|
|
85
|
+
`~/.commandcode/auth.json`. Keys are resolved per request, so a key added in Settings → Models is
|
|
86
|
+
used by the next query without a restart.
|
|
87
|
+
5. The usage adapter is selected from the provider or base URL.
|
|
88
|
+
|
|
89
|
+
Providers without `apiKeyEnv` use the convention `${PROVIDER}_API_KEY`; `deepseek-official` uses
|
|
90
|
+
`DEEPSEEK_API_KEY`.
|
|
91
|
+
|
|
92
|
+
The panel header shows the active target. `Auto · <provider> / <model>` follows the conversation.
|
|
93
|
+
Selecting it opens the model picker with `Auto (follow the conversation)` and the routable provider
|
|
94
|
+
models from `sessionController.modelCatalog()`, annotated with their adapter or with
|
|
95
|
+
`usage query not supported`. A manual target changes only the panel's query, not the conversation
|
|
96
|
+
model, and the credential reference follows the selected provider. The choice is stored in
|
|
97
|
+
`dsh-model-usage:model` and can be reset to auto from the panel.
|
|
98
|
+
|
|
99
|
+
Usage is account-level: models behind the same key share the same quota, so selecting a target
|
|
100
|
+
selects the account to query. The panel lists the model and credential reference used.
|
|
101
|
+
|
|
102
|
+
## Refresh and status
|
|
103
|
+
|
|
104
|
+
The component queries when it mounts and when the session model changes. With auto-refresh enabled
|
|
105
|
+
it continues on an interval whether or not the panel is open.
|
|
106
|
+
|
|
107
|
+
- The status line reports freshness: `Updated just now`, `Updated 12 min ago`,
|
|
108
|
+
`Refreshing… · showing data from 3 min ago`, or `Not refreshed yet`. A hint is appended when the
|
|
109
|
+
data is older than the interval.
|
|
110
|
+
- Auto refresh is toggled from the status line or the refresh settings dialog.
|
|
111
|
+
- The interval accepts 1, 5, 15, 30 or 60 minutes, or any value from 1 to 1440. Defaults are enabled
|
|
112
|
+
and five minutes. The policy is stored in `dsh-model-usage:refresh`.
|
|
113
|
+
- Opening the panel with data inside the interval does not send a request; **Refresh** forces one.
|
|
114
|
+
- A failed query keeps the previous data and appends `Last refresh failed: …`. The full error state
|
|
115
|
+
with **Retry** appears only when no query has succeeded.
|
|
116
|
+
- The host applies a 30-second timeout per usage call; the browser applies a 45-second outer timeout.
|
|
117
|
+
|
|
118
|
+
## Security
|
|
119
|
+
|
|
120
|
+
- Credential values are read in the host process and are never returned to the browser or written to
|
|
121
|
+
logs. Responses contain normalized values plus the reference name and source.
|
|
122
|
+
- All three routes check the composition's browser trust decision (Host/Origin) through the
|
|
123
|
+
`connection` service and answer 401/403 when it is denied.
|
|
124
|
+
- The plugin uploads nothing. Usage requests go from the host to the provider API.
|
|
125
|
+
|
|
126
|
+
## Compatibility
|
|
127
|
+
|
|
128
|
+
- The bundle declares `dsh.bundle.patch`, `dsh.client` (`platform: web`), `manifestVersion: 1`,
|
|
129
|
+
`engines.dsh` and `engines.node`.
|
|
130
|
+
- `locale/zh.json` and `locale/en.json` provide the plugin title and description, and `icon.svg` is
|
|
131
|
+
the icon; both are exported and included in the package.
|
|
132
|
+
- The browser half registers `conversation.session.header.utilities`, requires only `react`, and
|
|
133
|
+
addresses the plugin routes with document-relative URLs, so a GUI mounted under a subpath works.
|
|
134
|
+
- The host half uses Node built-ins only. `webServer` is the single hard dependency; `connection`,
|
|
135
|
+
`credentials`, `llm`, `settings`, `sessions`, `sessionProjections`, `sessionController`,
|
|
136
|
+
`agentDefaultModel` and `deepseekAccount` are resolved through `ctx.get()` when used, so the
|
|
137
|
+
plugin mounts where those services are optional or activate later.
|
|
138
|
+
- Without a locale service, both dictionaries fall back to the Chinese source text.
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
npm run verify # check + smoke, also runs from prepublishOnly
|
|
144
|
+
npm test # alias for the same suite
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`scripts/check.mjs` validates the manifest and npm metadata, shipped files, README images, loader
|
|
148
|
+
id, baseline module requests, dictionaries, host imports and route registration.
|
|
149
|
+
`scripts/smoke.mjs` mounts the host half on a stub context and exercises the three routes, the error
|
|
150
|
+
envelopes and the trust fence.
|
|
151
|
+
|
|
152
|
+
Release:
|
|
153
|
+
|
|
154
|
+
1. Set `version` according to Semantic Versioning (visible behavior: minor; fixes: patch).
|
|
155
|
+
2. Update `CHANGELOG.md`.
|
|
156
|
+
3. Run `npm run verify`, then `npm publish`.
|
|
157
|
+
|
|
158
|
+
Accounts with 2FA publish with `npm publish --otp=<code>`, or authenticate with a Granular Access
|
|
159
|
+
Token that allows bypassing 2FA. Keep tokens in the user-level `~/.npmrc`, outside the repository.
|
|
44
160
|
|
|
45
|
-
|
|
46
|
-
no runtime dependencies: the host half is Node builtins only, the browser half requires nothing but
|
|
47
|
-
the frozen `react` module. Nothing is uploaded anywhere; the host process calls the provider API with
|
|
48
|
-
the key it already holds and returns only normalized numbers plus the credential *reference name*.
|
|
161
|
+
## License
|
|
49
162
|
|
|
50
|
-
|
|
163
|
+
MIT. DeepSeek Harness is also MIT-licensed. This is an independent community plugin, not affiliated
|
|
164
|
+
with or endorsed by DeepSeek.
|
|
51
165
|
|
|
52
166
|
## 中文
|
|
53
167
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
插件做四件事:
|
|
59
|
-
|
|
60
|
-
- **跟随对话查询**:模型取自会话的 `modelSelection` 投影(与模型选择器同一份事实),
|
|
61
|
-
密钥取自该供应商自己的凭据引用,用量来自该供应商的用量接口。
|
|
62
|
-
- **支持手动切换查询对象**:可查任意可路由的供应商模型,且不改变当前对话使用的模型。
|
|
63
|
-
- **后台自动刷新**:按间隔在后台保持数字新鲜,弹窗关着也照常刷新。
|
|
64
|
-
- **只读说明目录**:列出本部署声明的供应商路由、各自的用量接口类型、密钥引用名与是否已配置(永不返回值)。
|
|
65
|
-
|
|
66
|
-
## 原理
|
|
67
|
-
|
|
68
|
-
- **node 半**(`lib/index.js`):通过 `ctx.webServer.register()` 注册 loopback Web 路由:
|
|
69
|
-
- `POST /model-usage/balance`:按给定(或当前对话)的模型查询用量;
|
|
70
|
-
- `GET /model-usage/providers`:只读列出本部署声明的供应商路由与密钥是否就绪;
|
|
71
|
-
- `GET /model-usage/models`:可路由模型目录 + 每个供应商对应的用量接口类型(手动切换用)。
|
|
72
|
-
三条路由都会先向 `connection` 服务要一次浏览器信任裁决(Host/Origin 栅栏),被拒即 401/403;
|
|
73
|
-
密钥只在本机 host 进程内使用,浏览器只拿到归一化数据 + 凭据**引用名**(永不返回值)。
|
|
74
|
-
- **浏览器半**(`lib/client.js`):注册 `conversation.session.header.utilities` slot,
|
|
75
|
-
从会话的 `modelSelection` 投影读出当前模型(线格式 `{ lastUsed, next }`,`next` 已经含了
|
|
76
|
-
尚未使用的这次选择),随请求发给 host;在后台按间隔刷新用量(弹窗关闭也生效),
|
|
77
|
-
点开弹窗展示状态与用量。控件(按钮 / 弹窗)是本插件自写的,样式与文案分别来自主题 token
|
|
78
|
-
与 Client locale 服务。
|
|
79
|
-
|
|
80
|
-
### 模型与密钥是怎么确定的
|
|
81
|
-
|
|
82
|
-
1. 浏览器读会话投影 `modelSelection` 的 `next ?? lastUsed`(与模型选择器同一份事实),
|
|
83
|
-
POST `{ provider, model, reasoningEffort, sessionId }` 给 host。
|
|
84
|
-
payload 里没有 provider/model 时,host 依次回退到「本进程的会话投影」→「部署默认模型」
|
|
85
|
-
(`agentDefaultModel.currentSelection()`)。
|
|
86
|
-
2. host 用 `llm.listConfigurableProviders()` 找到该 provider 的配置地址
|
|
87
|
-
(`settingsNs` + `settingsPath`),再用 `settings.describe()` 读实时配置,得到
|
|
88
|
-
`apiKeyEnv`(凭据引用)、`baseURL`、`api`、自定义 headers。
|
|
89
|
-
3. 用 `ctx.credentials.resolve(apiKeyEnv)` 取到**当前对话正在用的那把 key**:
|
|
90
|
-
该引用没配置时回退到同名环境变量;Command Code 再回退 `~/.commandcode/auth.json`。
|
|
91
|
-
解析是逐次调用进行的,在 设置 → 模型 里新填的 key 下一次查询即可用,不需要重启。
|
|
92
|
-
4. 按 provider / baseURL 选择对应的用量接口(见下表)。
|
|
93
|
-
|
|
94
|
-
provider 没声明 `apiKeyEnv` 时按约定推导引用名 `${PROVIDER}_API_KEY`
|
|
95
|
-
(如 `commandcode` → `COMMANDCODE_API_KEY`,与 设置 → 模型 里存 key 的名字一致);
|
|
96
|
-
`deepseek-official` 固定用 `DEEPSEEK_API_KEY`。
|
|
97
|
-
|
|
98
|
-
### 手动切换查询对象
|
|
99
|
-
|
|
100
|
-
面板顶部有一行「查询对象」,显示这次查询用的是谁:
|
|
101
|
-
|
|
102
|
-
- 默认 `自动 · 供应商 / 模型`,跟随当前对话的模型选择(对话里换模型,插件跟着换)。
|
|
103
|
-
- 点它打开「选择模型」:可选 `自动(跟随当前对话)`,或**任意可路由的供应商模型**
|
|
104
|
-
(来自 `sessionController.modelCatalog()`,每条路由标注它对应的用量接口类型;不支持的会写明
|
|
105
|
-
`不支持用量查询`)。
|
|
106
|
-
- 选中后只改变**插件查询的对象**,不会切换当前对话使用的模型;密钥也随之变成那个供应商自己的
|
|
107
|
-
凭据引用。选择存在浏览器 localStorage(`dsh-model-usage:model`),弹窗里「改回自动」可恢复。
|
|
108
|
-
- 注意:用量是**账户级**的,同一把 key 下的模型共享同一份额度,所以切换模型实际是在切换
|
|
109
|
-
「用哪个供应商的 key 去查哪份额度」;卡片下方「模型 / 密钥」两行会如实显示这次用的是谁。
|
|
110
|
-
|
|
111
|
-
### 用量接口
|
|
112
|
-
|
|
113
|
-
| provider / baseURL | 用量来源 |
|
|
114
|
-
| --- | --- |
|
|
115
|
-
| `commandcode`(或 baseURL 落在 `*.commandcode.ai`) | Command Code 私有 API:`GET /alpha/whoami?limits=1`、`/alpha/billing/credits`、`/alpha/billing/subscriptions`、`/alpha/usage/summary?since=`,即官方 CLI `/usage` 的同一组端点;显示套餐、5 小时 / 7 天窗口用量条、信用余额、请求数 |
|
|
116
|
-
| `deepseek-official`、`deepseek`(或 baseURL 落在 `*.deepseek.com`) | `GET https://api.deepseek.com/user/balance`,按币种显示余额 |
|
|
117
|
-
| `deepseek-account` | `ctx.deepseekAccount.getBalance()`:读已登录 DeepSeek 账户的钱包余额(不涉及 API Key) |
|
|
118
|
-
| `opencode-go`(或 baseURL 落在 `opencode.ai/zen/go`) | `GET {origin}/zen/go/v1/usage`(OpenCode Go / Go Plus 订阅额度:5 小时 / 周 / 月,按模型各自的月额度计)。**该端点未公开文档化**,字段按常见写法归一化,识别不了的会原样显示在「原始返回」里 |
|
|
119
|
-
| `openrouter`(或 baseURL 落在 `*.openrouter.ai`) | `GET /api/v1/key`(已用 / 额度 / 剩余);key 没有独立额度时再查 `GET /api/v1/credits` 补充账户总额度与已用 |
|
|
120
|
-
| `moonshotai`、`moonshotai-cn`、`kimi-coding`(或 baseURL 落在 `*.moonshot.cn` / `*.moonshot.ai`) | `GET /v1/users/me/balance`(可用余额 / 现金余额 / 代金券) |
|
|
121
|
-
| `siliconflow`(或 baseURL 落在 `*.siliconflow.cn` / `*.siliconflow.com`) | `GET /v1/user/info`(总余额 / 充值余额 / 赠送余额) |
|
|
122
|
-
| 其它已配置 `baseURL` 的 OpenAI 兼容路由 | 尽力而为:按 cc-switch 的 New API 约定查 `GET {origin}/api/user/self`(`Authorization: Bearer <key>`、`User-Agent: cc-switch/1.0`),按 `quota / 500000` 归一化为 USD |
|
|
123
|
-
|
|
124
|
-
**OpenCode Zen(按量付费余额)没有用量接口**:`/zen/v1/usage`、`/me`、`/credits` 全是 404,官方只能在 console 网页看,
|
|
125
|
-
所以 `opencode`(baseURL `https://opencode.ai/zen`)在面板里会显示「不支持用量查询」——同一个域名下的 `/zen/go` 才走 Go 适配器。
|
|
126
|
-
|
|
127
|
-
既没有 `baseURL`、也不是已知供应商的路由会明确报错(“无法查询用量”),不会去猜接口。
|
|
128
|
-
|
|
129
|
-
新增一个适配器大约 20 行:在 `kindOf()` 里按 host / provider 认领,写一个 `queryXxx()` 归一到 `{ rows }` 或 `{ windows, models }`,
|
|
130
|
-
客户端会自动用通用卡片渲染(`rows` 出标签值行,`windows` 出用量条,`raw` 收在「原始返回」按钮里)。
|
|
168
|
+
本插件为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 会话提供
|
|
169
|
+
当前模型对应供应商账户的用量。它在会话头部增加「用量」入口,显示套餐窗口、余额与请求数;
|
|
170
|
+
查询在 host 进程内使用该供应商自己的凭据引用完成,浏览器只收到归一化数值与凭据引用名。
|
|
131
171
|
|
|
132
|
-
###
|
|
172
|
+
### 功能
|
|
173
|
+
|
|
174
|
+
- **跟随会话模型**:查询对象取自会话的 `modelSelection` 投影,对话中切换模型时面板随之切换。
|
|
175
|
+
- **手动查询对象**:可查询任意可路由的供应商模型,不改变当前对话使用的模型。
|
|
176
|
+
- **后台刷新**:已挂载的组件按间隔刷新,弹窗关闭时同样生效。
|
|
177
|
+
- **供应商目录**:列出本部署声明的供应商路由、用量适配器、凭据引用名与配置状态,不读取凭据值。
|
|
178
|
+
|
|
179
|
+
### 环境要求
|
|
180
|
+
|
|
181
|
+
- DeepSeek Harness `>=0.2.1-rc.2`,并启用 Web 界面
|
|
182
|
+
- Node.js `>=18`
|
|
183
|
+
- 无运行时依赖;浏览器半只使用平台提供的 `react` 模块
|
|
133
184
|
|
|
134
|
-
|
|
135
|
-
之后只要「自动刷新」是开着的就按间隔在后台自己刷新 —— 弹窗关着也照样刷新,打开时直接就是最近一次的结果。
|
|
136
|
-
|
|
137
|
-
- **状态行**(弹窗顶部)显示本次刷新的新鲜度:`刷新时间:刚刚` / `刷新时间:12 分钟前`,
|
|
138
|
-
查询进行中显示 `刷新中… · 当前显示 3 分钟前数据`,从未查询过显示 `尚未刷新`;
|
|
139
|
-
数据超过刷新间隔时会在状态行补一句提示。
|
|
140
|
-
- **自动刷新开关**:状态行右侧的 `自动刷新:开/关` chip,点一下即切换(设置弹窗里也有「开启/关闭」)。
|
|
141
|
-
关闭后不再有后台定时刷新,打开弹窗且数据过期时才查询。
|
|
142
|
-
- **刷新间隔**:`刷新设置` 里可选 1 / 5 / 15 / 30 / 60 分钟,也可以直接填 1–1440 之间的分钟数;
|
|
143
|
-
默认开启自动刷新、间隔 5 分钟。策略存在浏览器 localStorage(`dsh-model-usage:refresh`)。
|
|
144
|
-
- **上次刷新超过间隔时**:打开弹窗不会清空内容 —— 先显示上次查询到的用量,并标记 `刷新中…`,
|
|
145
|
-
用量接口返回后再替换内容,状态变为 `刷新时间:刚刚`。
|
|
146
|
-
- 数据在间隔内时打开弹窗**不会**发请求(直接用缓存),要强制刷新就点卡片里的「刷新」。
|
|
147
|
-
- 刷新失败(例如接口 5xx / 网络不通)时保留上次数据,并在卡片下方追加 `上次刷新失败:…`;
|
|
148
|
-
从未成功过才显示整屏错误 + 「重试」。标题按钮在后台刷新时带一个呼吸小圆点,
|
|
149
|
-
鼠标悬停显示刷新策略。
|
|
150
|
-
- host 侧每次用量接口调用有 30 秒超时,浏览器侧另有一道 45 秒的外层兜底,
|
|
151
|
-
避免一次卡住的请求把后续自动刷新一直挡在门外。
|
|
152
|
-
|
|
153
|
-
### 弹窗内容
|
|
154
|
-
|
|
155
|
-
- 顶部状态行 + 自动刷新 chip + `刷新设置`。
|
|
156
|
-
- 卡片:按上面的接口类型渲染(窗口用量条 / 余额 / 钱包)。
|
|
157
|
-
- 卡片下方「模型 · 密钥 · 用量页」:本次查询用的 provider/model(含推理档位)与**凭据引用名 + 来源**
|
|
158
|
-
(例如 `COMMANDCODE_API_KEY · 凭据 COMMANDCODE_API_KEY(store)`),确认用的是当前对话那把 key。
|
|
159
|
-
- 底部「说明」:列出本部署声明的供应商路由 —— 用量接口类型、密钥引用名、是否已配置、接口地址。
|
|
160
|
-
默认只列出**有用量接口或已配置密钥**的路由(有些部署自带的目录有数十个路由),
|
|
161
|
-
其余收在「显示其余 N 个未配置路由」按钮后面;每个弹窗都自带上限高度 + 内部滚动
|
|
162
|
-
(Modal 原语把高度留给调用方,不设上限时内容会顶出屏幕)。
|
|
163
|
-
|
|
164
|
-
### 与当前 Harness 的适配
|
|
165
|
-
|
|
166
|
-
- manifest:`dsh.bundle.patch`、`dsh.client`(`platform: web`)、`dsh.manifestVersion: 1`,
|
|
167
|
-
以及 `engines.dsh: ">=0.2.1-rc.2"`(本插件逐项核对过的版本,只挡更旧的宿主)。
|
|
168
|
-
- 展示元数据:`locale/zh.json` + `locale/en.json`(`meta.title` / `meta.description`)与
|
|
169
|
-
`icon.svg` 都已从 `exports` / `files` 导出,插件列表与 bundle 详情直接读取。
|
|
170
|
-
- 浏览器半只 require `react`(loader 的冻结模块表),不 require 任何 Harness Client 包;
|
|
171
|
-
数据面走本插件自己的 `webServer` 路由 + 同源 `fetch`(官方浏览器插件同款做法)。
|
|
172
|
-
- host 半只用 Node 原生模块(`node:fs` / `node:os`);只有 `webServer` 是硬依赖,
|
|
173
|
-
`connection`(信任栅栏)、`credentials`(取键)、`llm` / `settings` / `sessions` /
|
|
174
|
-
`sessionProjections` / `sessionController` / `agentDefaultModel` / `deepseekAccount`
|
|
175
|
-
都在每次使用时通过 `ctx.get()` 读取 —— 少挂服务时插件照常挂载并在面板里说明缺什么,
|
|
176
|
-
后激活的服务下一次查询就能用上。
|
|
177
|
-
- 浏览器半只有 `slots` 是硬依赖,`locale` 同样按需读取:没有 locale 服务时面板降级显示中文原文,
|
|
178
|
-
而不是整格消失。
|
|
179
|
-
|
|
180
|
-
## 安装
|
|
181
|
-
|
|
182
|
-
包已发布为 npm bundle,安装入口是"把 bundle 装进 profile":
|
|
185
|
+
### 安装
|
|
183
186
|
|
|
184
187
|
```sh
|
|
185
|
-
# CLI(推荐)
|
|
186
188
|
dsh plugin --profile <profile> add dsh-model-usage
|
|
187
189
|
```
|
|
188
190
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
191
|
+
也可在 agent 会话中通过 `install_bundle` 安装(target 为 `dsh-model-usage`,本地开发时传仓库路径)。
|
|
192
|
+
两种方式都会把包装入 profile、挂载 bundle patch 的 `model-usage` 行并启用。
|
|
193
|
+
|
|
194
|
+
启用 profile HMR 时 host 侧改动立即生效;否则重启宿主(`dsh web` 或桌面版)。浏览器半由
|
|
195
|
+
`dsh-client-modules` 提供,刷新页面后生效。
|
|
196
|
+
|
|
197
|
+
### 用量来源
|
|
198
|
+
|
|
199
|
+
| provider / baseURL | 来源 | 返回值 |
|
|
200
|
+
| --- | --- | --- |
|
|
201
|
+
| `commandcode`、`*.commandcode.ai` | `GET /alpha/whoami?limits=1`、`/alpha/billing/credits`、`/alpha/billing/subscriptions`、`/alpha/usage/summary?since=` | 套餐、5 小时 / 7 天窗口、信用余额、已花费、请求数 |
|
|
202
|
+
| `deepseek-official`、`deepseek`、`*.deepseek.com` | `GET https://api.deepseek.com/user/balance` | 按币种的余额 |
|
|
203
|
+
| `deepseek-account` | `ctx.deepseekAccount.getBalance()` | 已登录账户的钱包余额(不涉及 API Key) |
|
|
204
|
+
| `opencode-go`、`opencode.ai/zen/go` | `GET {origin}/zen/go/v1/usage` | 按模型的 5 小时 / 周 / 月额度 |
|
|
205
|
+
| `openrouter`、`*.openrouter.ai` | `GET /api/v1/key`;key 无独立额度时再查 `GET /api/v1/credits` | 已用、额度、剩余与账户额度池 |
|
|
206
|
+
| `moonshotai`、`moonshotai-cn`、`kimi-coding`、`*.moonshot.cn`、`*.moonshot.ai` | `GET /v1/users/me/balance` | 可用余额、现金余额、代金券 |
|
|
207
|
+
| `siliconflow`、`*.siliconflow.cn`、`*.siliconflow.com` | `GET /v1/user/info` | 总余额、充值余额、赠送余额 |
|
|
208
|
+
| 其它带 `baseURL` 的 OpenAI 兼容路由 | cc-switch New API 约定:`GET {origin}/api/user/self` | 按约定换算为 USD,尽力而为 |
|
|
209
|
+
|
|
210
|
+
OpenCode Go 的端点未公开文档化,字段按常见命名归一化;无法识别的返回保留在「原始返回」中。
|
|
211
|
+
OpenCode Zen(按量付费)没有用量接口(`/zen/v1/usage`、`/me`、`/credits` 均为 404),
|
|
212
|
+
`opencode` 路由会明确提示不支持用量查询。既无 `baseURL`、也非已知供应商的路由返回明确错误,
|
|
213
|
+
不会推断接口。
|
|
193
214
|
|
|
194
|
-
|
|
215
|
+
新增适配器只需在 `kindOf()` 中认领路由,并由 `queryXxx()` 返回归一化数据:`{ rows }` 渲染为
|
|
216
|
+
标签值行,`{ windows, models }` 渲染为用量条,客户端统一用通用卡片展示。
|
|
195
217
|
|
|
196
|
-
|
|
197
|
-
- 浏览器半(`lib/client.js`)改动后硬刷新页面即可(开发时 `pnpm run dev:web` 会推送重建)。
|
|
218
|
+
### 查询对象与凭据
|
|
198
219
|
|
|
199
|
-
|
|
220
|
+
1. 浏览器读取会话 `modelSelection` 投影的 `next ?? lastUsed`,向 `POST /model-usage/balance`
|
|
221
|
+
发送 `{ provider, model, reasoningEffort, sessionId }`。
|
|
222
|
+
2. 请求未携带查询对象时,host 依次回退到本进程的会话投影与部署默认模型
|
|
223
|
+
(`agentDefaultModel.currentSelection()`)。
|
|
224
|
+
3. host 通过 `llm.listConfigurableProviders()` 取得该 provider 的配置位置,并用
|
|
225
|
+
`settings.describe()` 读取实时配置,得到 `apiKeyEnv`、`baseURL`、`api` 与自定义 headers。
|
|
226
|
+
4. `ctx.credentials.resolve(apiKeyEnv)` 取得当前使用的 key。引用未配置时回退到同名环境变量;
|
|
227
|
+
Command Code 还会回退到 `~/.commandcode/auth.json`。解析在每次查询时进行,因此
|
|
228
|
+
「设置 → 模型」中新填的 key 无需重启即可用于下一次查询。
|
|
229
|
+
5. 按 provider 或 baseURL 选择用量适配器。
|
|
230
|
+
|
|
231
|
+
未声明 `apiKeyEnv` 的 provider 按 `${PROVIDER}_API_KEY` 约定推导引用名,`deepseek-official`
|
|
232
|
+
使用 `DEEPSEEK_API_KEY`。
|
|
233
|
+
|
|
234
|
+
面板顶部显示当前查询对象。`自动 · <provider> / <model>` 跟随对话;点击后可在模型选择器中选择
|
|
235
|
+
`自动(跟随当前对话)`,或选择 `sessionController.modelCatalog()` 中任意可路由模型(标注其适配器
|
|
236
|
+
或「不支持用量查询」)。手动选择只改变面板的查询对象,不改变对话模型,凭据引用随所选供应商切换;
|
|
237
|
+
选择保存在 `dsh-model-usage:model`,可在面板中改回自动。
|
|
238
|
+
|
|
239
|
+
用量是账户级数据:同一把 key 下的模型共享同一份额度,因此切换查询对象即切换要查询的账户。
|
|
240
|
+
面板底部列出本次查询使用的模型与凭据引用。
|
|
241
|
+
|
|
242
|
+
### 刷新与状态
|
|
243
|
+
|
|
244
|
+
组件在挂载时与会话模型变化时查询。开启自动刷新后,无论弹窗是否打开,都会按间隔继续查询。
|
|
245
|
+
|
|
246
|
+
- 状态行显示数据新鲜度:`刷新时间:刚刚`、`刷新时间:12 分钟前`、
|
|
247
|
+
`刷新中… · 当前显示 3 分钟前数据`、`尚未刷新`;数据超过刷新间隔时会追加提示。
|
|
248
|
+
- 自动刷新可在状态行或刷新设置中开关。
|
|
249
|
+
- 刷新间隔支持 1 / 5 / 15 / 30 / 60 分钟,也可填写 1–1440 之间的任意分钟数;默认开启、间隔
|
|
250
|
+
5 分钟。策略保存在 `dsh-model-usage:refresh`。
|
|
251
|
+
- 数据仍在间隔内时打开面板不会发起请求;「刷新」按钮可强制查询。
|
|
252
|
+
- 查询失败时保留上次数据并追加 `上次刷新失败:…`;仅在从未成功时显示整屏错误与「重试」。
|
|
253
|
+
- host 侧单次用量请求超时 30 秒,浏览器侧另有 45 秒外层超时。
|
|
254
|
+
|
|
255
|
+
### 安全
|
|
256
|
+
|
|
257
|
+
- 凭据值只在 host 进程内读取,不返回浏览器,也不写入日志;响应仅包含归一化数值、引用名与来源。
|
|
258
|
+
- 三条路由在应答前通过 `connection` 服务校验浏览器信任(Host/Origin),未通过时返回 401/403。
|
|
259
|
+
- 插件不上传任何数据,用量请求由 host 直接发往供应商接口。
|
|
260
|
+
|
|
261
|
+
### 兼容性
|
|
262
|
+
|
|
263
|
+
- bundle 声明 `dsh.bundle.patch`、`dsh.client`(`platform: web`)、`manifestVersion: 1`、
|
|
264
|
+
`engines.dsh` 与 `engines.node`。
|
|
265
|
+
- `locale/zh.json`、`locale/en.json` 提供插件标题与描述,`icon.svg` 为图标;两者都已导出并包含在包内。
|
|
266
|
+
- 浏览器半注册 `conversation.session.header.utilities`,只依赖 `react`,并使用文档相对路径访问
|
|
267
|
+
插件路由,因此 GUI 挂在子目录下同样可用。
|
|
268
|
+
- host 半只使用 Node 内置模块。`webServer` 是唯一硬依赖;`connection`、`credentials`、`llm`、
|
|
269
|
+
`settings`、`sessions`、`sessionProjections`、`sessionController`、`agentDefaultModel`、
|
|
270
|
+
`deepseekAccount` 均按需通过 `ctx.get()` 获取,服务缺失或稍后激活时插件仍可挂载。
|
|
271
|
+
- 没有 locale 服务时,词典回退为中文原文。
|
|
272
|
+
|
|
273
|
+
### 开发
|
|
200
274
|
|
|
201
275
|
```sh
|
|
202
|
-
npm run verify
|
|
203
|
-
npm
|
|
276
|
+
npm run verify # check + smoke,prepublishOnly 同样执行
|
|
277
|
+
npm test # 同一套检查的别名
|
|
204
278
|
```
|
|
205
279
|
|
|
206
|
-
`scripts/check.mjs`
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
## 说明
|
|
223
|
-
|
|
224
|
-
- 浏览器半**只从 loader 的模块表取 `react`**,不 require 任何 Harness Client 包:
|
|
225
|
-
面板里的按钮与弹窗是本插件自己的控件,只用当前主题列出的 `--dsw-alias-*` token 上色
|
|
226
|
-
(唯一例外是弹窗遮罩,主题没有对应的 scrim token)。
|
|
227
|
-
- 浏览器半的文案全部走 Client locale 服务(`ctx.locale`,命名空间 `dsh-model-usage`,
|
|
228
|
-
中英双语),跟随界面语言切换;host 返回的**接口诊断信息**(供应商报错原文)按原样显示。
|
|
229
|
-
- 样式表随 cell 渲染(`<style>` 是 cell 输出的一部分),slot 条目卸载时一起消失,
|
|
230
|
-
不往 `document.head` 之外留东西。
|
|
231
|
-
- 三条 host 路由在应答前都会先问 composition 的 connection 服务要一次浏览器信任裁决
|
|
232
|
-
(本机 Host/Origin 栅栏),与官方浏览器插件同款;被拒时直接回 401/403。
|
|
233
|
-
- 浏览器半用**文档相对路径**(`model-usage/...`)访问这三条路由,因此 GUI 挂在子目录下也能用。
|
|
234
|
-
- 用量只能查到**供应商账户级**的数据(余额 / 套餐窗口 / 请求数);同一把 key 下的模型共享这些额度。
|
|
235
|
-
- Command Code 模式的数字与官方 CLI 的 `/usage` 一致(同一组私有端点)。
|
|
236
|
-
- 密钥只在本机 host 进程内使用,不写入日志、不返回给浏览器。
|
|
237
|
-
- 本插件以 [MIT 许可](LICENSE)发布;DeepSeek Harness 本身同为 MIT,二者许可一致、可双向复用代码。
|
|
238
|
-
本项目是独立社区插件,与深度求索公司无隶属或背书关系。
|
|
239
|
-
- 浏览器半是**手写的闭包工厂 bundle**,改动后硬刷新浏览器生效;host 半改动需重启 `dsh web`。
|
|
280
|
+
`scripts/check.mjs` 校验清单与 npm 元数据、随包文件、README 图片、loader id、基线模块、
|
|
281
|
+
词典、host 导入与路由注册;`scripts/smoke.mjs` 在桩 ctx 上挂载 host 半,覆盖三条路由、
|
|
282
|
+
错误响应与信任栅栏。
|
|
283
|
+
|
|
284
|
+
发布流程:
|
|
285
|
+
|
|
286
|
+
1. 按语义化版本更新 `version`(可见行为变化为 minor,修复为 patch)。
|
|
287
|
+
2. 更新 `CHANGELOG.md`。
|
|
288
|
+
3. 运行 `npm run verify`,然后执行 `npm publish`。
|
|
289
|
+
|
|
290
|
+
启用 2FA 的账号可用 `npm publish --otp=<验证码>` 发布,或使用允许绕过 2FA 的 Granular Access
|
|
291
|
+
Token 认证。令牌只保存在用户级 `~/.npmrc`,不要提交到仓库。
|
|
292
|
+
|
|
293
|
+
### 许可
|
|
294
|
+
|
|
295
|
+
MIT。DeepSeek Harness 同样以 MIT 许可发布。本项目为独立社区插件,与深度求索公司无隶属或背书关系。
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-model-usage",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "DeepSeek Harness
|
|
3
|
+
"version": "0.8.2",
|
|
4
|
+
"description": "Provider-account usage for DeepSeek Harness: quota windows, credit balance and request counts for the model a conversation runs on, with per-provider adapters and manual target selection.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "liuchaoxu <ischaoxu@163.com>",
|
|
7
7
|
"repository": {
|
|
@@ -14,16 +14,23 @@
|
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
16
16
|
"dsh",
|
|
17
|
+
"dsh-plugin",
|
|
18
|
+
"deepseek",
|
|
17
19
|
"deepseek-harness",
|
|
18
|
-
"plugin",
|
|
19
|
-
"bundle",
|
|
20
20
|
"cordis",
|
|
21
|
+
"cordis-plugin",
|
|
22
|
+
"llm",
|
|
21
23
|
"usage",
|
|
24
|
+
"token-usage",
|
|
25
|
+
"quota",
|
|
26
|
+
"billing",
|
|
22
27
|
"balance",
|
|
23
28
|
"commandcode",
|
|
24
29
|
"openrouter",
|
|
30
|
+
"opencode",
|
|
25
31
|
"moonshot",
|
|
26
|
-
"siliconflow"
|
|
32
|
+
"siliconflow",
|
|
33
|
+
"new-api"
|
|
27
34
|
],
|
|
28
35
|
"type": "module",
|
|
29
36
|
"main": "lib/index.js",
|
|
@@ -57,6 +64,7 @@
|
|
|
57
64
|
"check": "node scripts/check.mjs",
|
|
58
65
|
"smoke": "node scripts/smoke.mjs",
|
|
59
66
|
"verify": "node scripts/check.mjs && node scripts/smoke.mjs",
|
|
67
|
+
"test": "node scripts/check.mjs && node scripts/smoke.mjs",
|
|
60
68
|
"prepublishOnly": "node scripts/check.mjs && node scripts/smoke.mjs"
|
|
61
69
|
},
|
|
62
70
|
"files": [
|
|
@@ -65,12 +73,12 @@
|
|
|
65
73
|
"cordis.patch.yml",
|
|
66
74
|
"locale/*.json",
|
|
67
75
|
"icon.svg",
|
|
68
|
-
"docs
|
|
76
|
+
"docs/screenshots/",
|
|
69
77
|
"README.md",
|
|
70
78
|
"CHANGELOG.md",
|
|
71
79
|
"LICENSE"
|
|
72
80
|
],
|
|
73
81
|
"publishConfig": {
|
|
74
|
-
|
|
82
|
+
"access": "public"
|
|
75
83
|
}
|
|
76
84
|
}
|