@woyxiang/pi-provider-volcengine-agent-plan 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/README.zh-CN.md +120 -0
- package/index.ts +552 -0
- package/package.json +59 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# pi-provider-volcengine-agent-plan
|
|
2
|
+
|
|
3
|
+
## 0.3.0 (2026-01-13)
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Forked from `zhcsyncer/pi-extensions` as independent repository `@woyxiang/pi-provider-volcengine-agent-plan`.
|
|
8
|
+
- Add GLM 5.3 to the Volcengine Agent Plan catalog as a Small-tier, text-only Responses model with a 1,024,000-token context window and 128,000 max output tokens. Official Z.ai/Zhipu and Agent Plan docs say thinking cannot be disabled and only `low`/`high`/`max` effort is supported, so the card exposes those Pi thinking levels and copies GLM 5.2's public API reference rates. Live Agent Plan Responses checks accepted OpenAI `reasoning.effort` at `low`/`high`/`max`, streamed output text, and a full tool-result follow-up round through Pi; `thinking.type: disabled` is rejected with `InvalidParameter`. This package does not rewrite the request to Zhipu `thinking.type`. The 1M context window and 128k max output were not live-tested at their limits.
|
|
9
|
+
|
|
10
|
+
## 0.2.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- Declare image input for the 9 vision-capable Agent Plan models (Doubao Seed 2.0 Mini/Lite/Evolving/Code/Pro, MiniMax M3, Kimi K2.6/K2.7 Code/K3). MiniMax M2.7, GLM 5.2, and DeepSeek V4 Flash/Pro remain text-only because the Agent Plan gateway does not expose a multimodal path for them.
|
|
15
|
+
|
|
16
|
+
## 0.1.1
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- Correct Agent Plan model metadata by reporting public API reference cost estimates and exposing only the thinking levels supported by Kimi K3.
|
|
21
|
+
|
|
22
|
+
## 0.1.0
|
|
23
|
+
|
|
24
|
+
### Minor Changes
|
|
25
|
+
|
|
26
|
+
- Publish the standalone Volcengine Ark Agent Plan provider with native Pi login, tier-aware static models, mixed Responses and Chat routing, reasoning and tool compatibility hooks, and zero-inference API key validation.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 woyxiang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# pi-provider-volcengine-agent-plan
|
|
2
|
+
|
|
3
|
+
[简体中文](./README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
Unofficial [Pi](https://github.com/badlogic/pi-mono) provider extension for Volcengine Ark Agent Plan at `https://ark.cn-beijing.volces.com/api/plan/v3`.
|
|
6
|
+
|
|
7
|
+
This community package is not affiliated with or endorsed by Volcengine.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Native Pi provider registration and `/login` integration.
|
|
12
|
+
- Static catalog for the 11 current Agent Plan chat models.
|
|
13
|
+
- Image input for the 7 vision-capable models; text-only routing for DeepSeek V4 Flash/Pro, GLM 5.3, and Doubao Seed 2.1 Turbo.
|
|
14
|
+
- Tier-aware availability for Small, Medium, Large, and Max plans.
|
|
15
|
+
- OpenAI Responses by default, with Chat Completions routing for Kimi K2.7 Code.
|
|
16
|
+
- Streaming, reasoning, and tool-call support tested through the Agent Plan gateway.
|
|
17
|
+
- Documented `reasoning.effort` compatibility mapping per model family; GLM 5.3 stays limited to `low`/`high`/`max`.
|
|
18
|
+
- Zero-inference API key validation before Pi persists a login credential.
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Node.js 22.19 or newer.
|
|
23
|
+
- Pi and `@earendil-works/pi-ai` 0.84.0 or newer.
|
|
24
|
+
- A dedicated Ark Agent Plan API key. A regular Volcengine Ark API key does not work with the Plan endpoint.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pi install npm:@woyxiang/pi-provider-volcengine-agent-plan
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Restart Pi or run `/reload`, then verify the catalog:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pi --list-models volcengine-agent-plan
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Login and credentials
|
|
39
|
+
|
|
40
|
+
### Interactive login
|
|
41
|
+
|
|
42
|
+
Run:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
/login volcengine-agent-plan
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Pi prompts for the dedicated Agent Plan API key and the subscribed tier. The login flow sends an authenticated, intentionally incomplete Responses request. A valid key reaches `MissingParameter`; an invalid or unauthorized key returns 401/403 and is requested again. This validation does not start model inference.
|
|
49
|
+
|
|
50
|
+
Pi stores the API key and selected tier in its standard credential file, normally `~/.pi/agent/auth.json`. The package does not read a custom key file.
|
|
51
|
+
|
|
52
|
+
### Environment variables
|
|
53
|
+
|
|
54
|
+
Interactive login is recommended. Ambient credentials remain available for automated environments:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
export ARK_AGENT_PLAN_API_KEY='...'
|
|
58
|
+
export ARK_AGENT_PLAN_TIER='medium'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`VOLCENGINE_ARK_PLAN_API_KEY` is also accepted as an API key fallback. Supported tier values are `small`, `medium`, `large`, and `max`; the default is `medium` when no tier is configured.
|
|
62
|
+
|
|
63
|
+
## Models and tiers
|
|
64
|
+
|
|
65
|
+
The current catalog contains:
|
|
66
|
+
|
|
67
|
+
- Doubao Seed 2.0 Mini, Lite, and Evolving
|
|
68
|
+
- Doubao Seed 2.1 Turbo
|
|
69
|
+
- DeepSeek V4 Flash and Pro
|
|
70
|
+
- MiniMax M3
|
|
71
|
+
- GLM 5.3 Flash and GLM 5.3
|
|
72
|
+
- Kimi K2.7 Code and Kimi K3
|
|
73
|
+
|
|
74
|
+
Small exposes 10 models. Kimi K3 currently requires Medium or higher. Medium, Large, and Max expose all 11 current models.
|
|
75
|
+
|
|
76
|
+
## Compatibility
|
|
77
|
+
|
|
78
|
+
Kimi K2.7 Code uses Chat Completions because its Agent Plan Responses tool-call path returned repeated server errors during compatibility testing. Other catalog entries use Responses.
|
|
79
|
+
|
|
80
|
+
Kimi K2.7 Code does not support disabling thinking through the current gateway. Selecting Pi's `off` level therefore avoids sending an unsupported disable control but cannot guarantee that the model stops internal reasoning.
|
|
81
|
+
|
|
82
|
+
Kimi K3 inherits only model-intrinsic capabilities from Pi's Moonshot catalog. Agent Plan continues to own its protocol, limits, compatibility settings, and plan rules. Its available Pi thinking levels are `low`, `high`, and `max`.
|
|
83
|
+
|
|
84
|
+
Reasoning effort follows Volcengine's documented `reasoning.effort` compatibility mapping. Doubao Seed models close thinking for `none`/`minimal` and collapse `xhigh`/`max` to `high`; DeepSeek V4 Flash/Pro additionally collapse `medium` to `low`. Every model card keeps all seven Pi levels selectable and Pi sends the mapped effort value.
|
|
85
|
+
|
|
86
|
+
GLM 5.3 cannot disable thinking. Official Z.ai/Zhipu docs only allow `low`, `high`, and `max` effort, so those are the Pi thinking levels this card exposes. Pi sends OpenAI `reasoning.effort` and does not rewrite the request to Zhipu `thinking.type`.
|
|
87
|
+
|
|
88
|
+
GLM 5.3 Flash accepts all seven effort levels, but thinking cannot be fully disabled: `none` falls back to `low`, `medium`/`high` become `high`, and `xhigh`/`max` become `max`.
|
|
89
|
+
|
|
90
|
+
## Cost reporting
|
|
91
|
+
|
|
92
|
+
The catalog retains public pay-as-you-go API reference rates in USD per million tokens, allowing Pi to estimate session cost from actual token usage. Models with an existing Pi upstream card selectively inherit its rates; the Doubao models use API estimates normalized from [Volcengine's public price table](https://www.volcengine.com/docs/82379/1544106). Doubao Seed 2.1 Turbo has no published pay-as-you-go reference rate yet, so its cost is a zero placeholder and Pi reports no spend for it.
|
|
93
|
+
|
|
94
|
+
This value compares session resource usage; it is not the Agent Plan bill. Volcengine still calculates the subscription price, AFP usage, and remaining quota separately.
|
|
95
|
+
|
|
96
|
+
## Security
|
|
97
|
+
|
|
98
|
+
Pi's standard `auth.json` is protected by filesystem permissions but is not an operating-system keychain. Do not commit credentials, paste them into issue reports, or place them in project configuration.
|
|
99
|
+
|
|
100
|
+
The API key validation request never logs the key or response body. Temporary network or service failures let the user retry, cancel, or explicitly save without validation.
|
|
101
|
+
|
|
102
|
+
## Development
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pnpm check
|
|
106
|
+
pi --no-extensions -e . --list-models volcengine-agent-plan
|
|
107
|
+
npm pack --dry-run --json .
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Unit tests use mocked credentials and fetch responses. Real-key contract tests are intentionally excluded from normal CI.
|
|
111
|
+
|
|
112
|
+
## Limitations
|
|
113
|
+
|
|
114
|
+
Agent Plan does not expose a usable `/models` endpoint, so the catalog and model metadata are versioned statically. Volcengine may change aliases, protocol behavior, limits, or tier availability before this package is updated.
|
|
115
|
+
|
|
116
|
+
The catalog declares image input for the 7 vision-capable models (Doubao Seed 2.0 Mini/Lite/Evolving, MiniMax M3, Kimi K2.7 Code/K3, and GLM 5.3 Flash). DeepSeek V4 Flash/Pro, GLM 5.3, and Doubao Seed 2.1 Turbo remain text-only. Extreme context windows, maximum-length output, concurrency, rate limits, and subscription quota reporting are not covered.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# pi-provider-volcengine-agent-plan
|
|
2
|
+
|
|
3
|
+
[English](./README.md)
|
|
4
|
+
|
|
5
|
+
用于火山方舟 Agent Plan `https://ark.cn-beijing.volces.com/api/plan/v3` 的非官方 [Pi](https://github.com/badlogic/pi-mono) provider 扩展。
|
|
6
|
+
|
|
7
|
+
这是社区包,与火山引擎无隶属关系,也未获得火山引擎官方背书。
|
|
8
|
+
|
|
9
|
+
## 功能
|
|
10
|
+
|
|
11
|
+
- 原生注册 Pi provider,并集成 `/login`。
|
|
12
|
+
- 静态维护当前 11 个 Agent Plan 文本生成模型。
|
|
13
|
+
- 7 个支持视觉的模型可接收图片输入;DeepSeek V4 Flash/Pro、GLM 5.3、Doubao Seed 2.1 Turbo 仅文本。
|
|
14
|
+
- 按 Small、Medium、Large 和 Max 套餐过滤可用模型。
|
|
15
|
+
- 默认使用 OpenAI Responses;Kimi K2.7 Code 路由到 Chat Completions。
|
|
16
|
+
- 已通过 Agent Plan 网关验证流式、reasoning 和工具调用。
|
|
17
|
+
- 按模型族遵循官方 `reasoning.effort` 兼容映射;GLM 5.3 仍仅 `low`/`high`/`max`。
|
|
18
|
+
- Pi 持久化登录凭证前执行零推理 API Key 校验。
|
|
19
|
+
|
|
20
|
+
## 要求
|
|
21
|
+
|
|
22
|
+
- Node.js 22.19 或更高版本。
|
|
23
|
+
- Pi 和 `@earendil-works/pi-ai` 0.84.0 或更新版本。
|
|
24
|
+
- Agent Plan 专属 API Key。普通火山方舟 API Key 不能用于 Plan 端点。
|
|
25
|
+
|
|
26
|
+
## 安装
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pi install npm:@woyxiang/pi-provider-volcengine-agent-plan
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
重启 Pi 或执行 `/reload`,然后检查模型目录:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pi --list-models volcengine-agent-plan
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 登录与凭证
|
|
39
|
+
|
|
40
|
+
### 交互登录
|
|
41
|
+
|
|
42
|
+
执行:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
/login volcengine-agent-plan
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Pi 会提示输入 Agent Plan 专属 API Key 和已订阅套餐。登录流程会发送一个已鉴权但故意缺少参数的 Responses 请求:有效 Key 会到达 `MissingParameter`,无效或无权限的 Key 返回 401/403,并提示重新输入。该校验不会启动模型推理。
|
|
49
|
+
|
|
50
|
+
Pi 将 API Key 和套餐保存到标准凭证文件,通常为 `~/.pi/agent/auth.json`。本包不会读取自定义 Key 文件。
|
|
51
|
+
|
|
52
|
+
### 环境变量
|
|
53
|
+
|
|
54
|
+
推荐使用交互登录。自动化环境也可以提供环境凭证:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
export ARK_AGENT_PLAN_API_KEY='...'
|
|
58
|
+
export ARK_AGENT_PLAN_TIER='medium'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
也支持使用 `VOLCENGINE_ARK_PLAN_API_KEY` 作为 API Key fallback。套餐可取 `small`、`medium`、`large` 或 `max`;未配置套餐时默认使用 `medium`。
|
|
62
|
+
|
|
63
|
+
## 模型与套餐
|
|
64
|
+
|
|
65
|
+
当前目录包含:
|
|
66
|
+
|
|
67
|
+
- Doubao Seed 2.0 Mini、Lite 和 Evolving
|
|
68
|
+
- Doubao Seed 2.1 Turbo
|
|
69
|
+
- DeepSeek V4 Flash 和 Pro
|
|
70
|
+
- MiniMax M3
|
|
71
|
+
- GLM 5.3 Flash 和 GLM 5.3
|
|
72
|
+
- Kimi K2.7 Code 和 Kimi K3
|
|
73
|
+
|
|
74
|
+
Small 展示 10 个模型。Kimi K3 当前要求 Medium 或更高套餐。Medium、Large 和 Max 展示当前全部 11 个模型。
|
|
75
|
+
|
|
76
|
+
## 兼容性
|
|
77
|
+
|
|
78
|
+
Kimi K2.7 Code 使用 Chat Completions,因为兼容性测试中它通过 Agent Plan Responses 执行工具调用会重复返回服务端错误。其余目录模型使用 Responses。
|
|
79
|
+
|
|
80
|
+
当前网关不支持关闭 Kimi K2.7 Code 的 thinking。Pi 选择 `off` 时,本包不会发送不受支持的禁用参数,但无法保证模型停止内部推理。
|
|
81
|
+
|
|
82
|
+
Kimi K3 只从 Pi 的 Moonshot 模型目录继承模型固有能力,并继续使用 Agent Plan 自己的协议、限额、兼容配置和套餐规则。Pi 中可选的 thinking 档位为 `low`、`high` 和 `max`。
|
|
83
|
+
|
|
84
|
+
reasoning effort 遵循火山官方 `reasoning.effort` 兼容映射。Doubao Seed 模型传入 `none`/`minimal` 会关闭思考,`xhigh`/`max` 折叠为 `high`;DeepSeek V4 Flash/Pro 的 `medium` 还会折叠为 `low`。每个模型卡都保留 Pi 全部 7 个档位,Pi 发送映射后的 effort 值。
|
|
85
|
+
|
|
86
|
+
GLM 5.3 不能关闭 thinking。智谱 / Z.ai 官方只允许 `low`、`high`、`max` 三档 effort,因此本卡只向 Pi 暴露这些档位。Pi 发送 OpenAI `reasoning.effort`,不会改写成智谱 `thinking.type`。
|
|
87
|
+
|
|
88
|
+
GLM 5.3 Flash 接受全部 7 个 effort 档位,但无法完全关闭思考:`none` 会回退为 `low`,`medium`/`high` 变成 `high`,`xhigh`/`max` 变成 `max`。
|
|
89
|
+
|
|
90
|
+
## 费用显示
|
|
91
|
+
|
|
92
|
+
模型目录保留公共按量 API 的美元/百万 token 参考单价,因此 Pi 会根据实际 token usage 在 session 级估算费用。已有 Pi 上游模型卡的模型选择性继承其价格;Doubao 模型使用从[火山方舟公开价格表](https://www.volcengine.com/docs/82379/1544106)标准化得到的 API 估价。Doubao Seed 2.1 Turbo 暂无公开按量参考价,其价格为 0 占位,Pi 不会为其估算费用。
|
|
93
|
+
|
|
94
|
+
这个数值用于比较会话资源消耗,并不是 Agent Plan 的实际账单;套餐价格、AFP 消耗和剩余额度仍由火山方舟单独计算。
|
|
95
|
+
|
|
96
|
+
## 安全
|
|
97
|
+
|
|
98
|
+
Pi 标准 `auth.json` 由文件系统权限保护,但不是操作系统 Keychain。请勿提交凭证、将凭证粘贴到 issue,或把凭证写入项目配置。
|
|
99
|
+
|
|
100
|
+
API Key 校验请求不会记录 Key 或响应正文。遇到临时网络或服务错误时,用户可以重试、取消,或明确选择未经验证仍然保存。
|
|
101
|
+
|
|
102
|
+
## 开发
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pnpm check
|
|
106
|
+
pi --no-extensions -e . --list-models volcengine-agent-plan
|
|
107
|
+
npm pack --dry-run --json .
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
单元测试使用模拟凭证和 fetch 响应。需要真实 Key 的契约测试不会进入普通 CI。
|
|
111
|
+
|
|
112
|
+
## 限制
|
|
113
|
+
|
|
114
|
+
Agent Plan 没有可用的 `/models` 端点,因此模型目录和元数据采用静态版本维护。火山引擎可能在本包更新前修改别名、协议行为、限制或套餐权限。
|
|
115
|
+
|
|
116
|
+
目录为 7 个支持视觉的模型(Doubao Seed 2.0 Mini/Lite/Evolving、MiniMax M3、Kimi K2.7 Code/K3、GLM 5.3 Flash)声明图片输入;DeepSeek V4 Flash/Pro、GLM 5.3、Doubao Seed 2.1 Turbo 仍仅文本。极限上下文、最大长度输出、并发、限流和套餐余量展示不在当前覆盖范围内。
|
|
117
|
+
|
|
118
|
+
## 许可证
|
|
119
|
+
|
|
120
|
+
MIT
|
package/index.ts
ADDED
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createProvider,
|
|
3
|
+
openAICompletionsApi,
|
|
4
|
+
openAIResponsesApi,
|
|
5
|
+
type ApiKeyCredential,
|
|
6
|
+
type AuthContext,
|
|
7
|
+
type AuthInteraction,
|
|
8
|
+
type Credential,
|
|
9
|
+
type Model,
|
|
10
|
+
type ThinkingLevelMap,
|
|
11
|
+
} from "@earendil-works/pi-ai";
|
|
12
|
+
import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all";
|
|
13
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
14
|
+
|
|
15
|
+
const PROVIDER_ID = "volcengine-agent-plan";
|
|
16
|
+
const BASE_URL = "https://ark.cn-beijing.volces.com/api/plan/v3";
|
|
17
|
+
const TIER_ENV = "ARK_AGENT_PLAN_TIER";
|
|
18
|
+
const KEY_ENV_NAMES = ["ARK_AGENT_PLAN_API_KEY", "VOLCENGINE_ARK_PLAN_API_KEY"] as const;
|
|
19
|
+
const KEY_VALIDATION_URL = `${BASE_URL}/responses`;
|
|
20
|
+
const KEY_VALIDATION_TIMEOUT_MS = 12_000;
|
|
21
|
+
|
|
22
|
+
const RESPONSES_COMPAT = {
|
|
23
|
+
supportsDeveloperRole: true,
|
|
24
|
+
supportsLongCacheRetention: true,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const KIMI_CHAT_COMPAT = {
|
|
28
|
+
supportsDeveloperRole: false,
|
|
29
|
+
supportsReasoningEffort: true,
|
|
30
|
+
supportsStore: true,
|
|
31
|
+
supportsUsageInStreaming: true,
|
|
32
|
+
supportsLongCacheRetention: false,
|
|
33
|
+
maxTokensField: "max_completion_tokens" as const,
|
|
34
|
+
requiresReasoningContentOnAssistantMessages: true,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type ArkApi = "openai-responses" | "openai-completions";
|
|
38
|
+
type PlanTier = "small" | "medium" | "large" | "max";
|
|
39
|
+
|
|
40
|
+
type CatalogEntry = Omit<Model<ArkApi>, "provider" | "baseUrl"> & {
|
|
41
|
+
minimumTier: PlanTier;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type EstimatedApiCost = Model<ArkApi>["cost"];
|
|
45
|
+
|
|
46
|
+
type InheritedModelCapabilities = Pick<
|
|
47
|
+
Model<ArkApi>,
|
|
48
|
+
"name" | "reasoning" | "thinkingLevelMap" | "input" | "cost" | "contextWindow" | "maxTokens"
|
|
49
|
+
>;
|
|
50
|
+
|
|
51
|
+
type RouteCapabilityLimits = Pick<Model<ArkApi>, "input" | "contextWindow" | "maxTokens">;
|
|
52
|
+
|
|
53
|
+
function copyEstimatedApiCost(cost: EstimatedApiCost): EstimatedApiCost {
|
|
54
|
+
return {
|
|
55
|
+
...cost,
|
|
56
|
+
...(cost.tiers ? { tiers: cost.tiers.map((tier) => ({ ...tier })) } : {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function inheritModelCapabilities(
|
|
61
|
+
upstream: InheritedModelCapabilities,
|
|
62
|
+
routeLimits: RouteCapabilityLimits,
|
|
63
|
+
): InheritedModelCapabilities {
|
|
64
|
+
return {
|
|
65
|
+
name: upstream.name,
|
|
66
|
+
reasoning: upstream.reasoning,
|
|
67
|
+
...(upstream.thinkingLevelMap
|
|
68
|
+
? { thinkingLevelMap: { ...upstream.thinkingLevelMap } }
|
|
69
|
+
: {}),
|
|
70
|
+
input: upstream.input.filter((modality) => routeLimits.input.includes(modality)),
|
|
71
|
+
cost: copyEstimatedApiCost(upstream.cost),
|
|
72
|
+
contextWindow: Math.min(upstream.contextWindow, routeLimits.contextWindow),
|
|
73
|
+
maxTokens: Math.min(upstream.maxTokens, routeLimits.maxTokens),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Pi has no native Doubao Seed catalog yet. These USD-per-million-token
|
|
78
|
+
// estimates follow public API listings normalized from Volcengine's price table.
|
|
79
|
+
// Doubao Seed 2.1 Turbo has no published pay-as-you-go reference rate yet, so its
|
|
80
|
+
// cost stays a zero placeholder and Pi does not estimate spend for it.
|
|
81
|
+
const DOUBAO_API_COSTS = {
|
|
82
|
+
"doubao-seed-2.0-mini": { input: 0.03, output: 0.28, cacheRead: 0.01, cacheWrite: 0.0024 },
|
|
83
|
+
"doubao-seed-2.0-lite": { input: 0.09, output: 0.51, cacheRead: 0.02, cacheWrite: 0.0024 },
|
|
84
|
+
"doubao-seed-evolving": { input: 0.9, output: 4.5, cacheRead: 0.18, cacheWrite: 0 },
|
|
85
|
+
"doubao-seed-2.1-turbo": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
86
|
+
} satisfies Record<string, EstimatedApiCost>;
|
|
87
|
+
|
|
88
|
+
// Volcengine's reasoning_effort compatibility mapping (official docs). Every model
|
|
89
|
+
// below accepts all seven levels; some values auto-map to an equivalent level.
|
|
90
|
+
// Doubao Seed family: `none`/`minimal` close thinking, `xhigh`/`max` collapse to `high`.
|
|
91
|
+
const DOUBAO_EFFORT_MAP = {
|
|
92
|
+
off: "none",
|
|
93
|
+
minimal: "minimal",
|
|
94
|
+
low: "low",
|
|
95
|
+
medium: "medium",
|
|
96
|
+
high: "high",
|
|
97
|
+
xhigh: "high",
|
|
98
|
+
max: "high",
|
|
99
|
+
} satisfies ThinkingLevelMap;
|
|
100
|
+
|
|
101
|
+
// DeepSeek V4 GA family: `none`/`minimal` close thinking, `medium` collapses to `low`,
|
|
102
|
+
// `xhigh`/`max` collapse to `high`.
|
|
103
|
+
const DEEPSEEK_GA_EFFORT_MAP = {
|
|
104
|
+
off: "none",
|
|
105
|
+
minimal: "minimal",
|
|
106
|
+
low: "low",
|
|
107
|
+
medium: "low",
|
|
108
|
+
high: "high",
|
|
109
|
+
xhigh: "high",
|
|
110
|
+
max: "high",
|
|
111
|
+
} satisfies ThinkingLevelMap;
|
|
112
|
+
|
|
113
|
+
// GLM 5.3 Flash accepts every level; thinking cannot be fully disabled and `none`
|
|
114
|
+
// falls back to `low`. `medium`/`high` become `high`, `xhigh`/`max` become `max`.
|
|
115
|
+
const GLM_53_FLASH_EFFORT_MAP = {
|
|
116
|
+
off: "none",
|
|
117
|
+
minimal: "low",
|
|
118
|
+
low: "low",
|
|
119
|
+
medium: "high",
|
|
120
|
+
high: "high",
|
|
121
|
+
xhigh: "max",
|
|
122
|
+
max: "max",
|
|
123
|
+
} satisfies ThinkingLevelMap;
|
|
124
|
+
|
|
125
|
+
// Only model capabilities and reference API pricing are inherited. Agent Plan
|
|
126
|
+
// continues to own protocol, endpoint, compatibility, tier gating, and route limits.
|
|
127
|
+
const KIMI_K3_CAPABILITIES = inheritModelCapabilities(getBuiltinModel("moonshotai", "kimi-k3"), {
|
|
128
|
+
input: ["text", "image"],
|
|
129
|
+
contextWindow: 1_024_000,
|
|
130
|
+
maxTokens: 128_000,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const TIER_RANK: Record<PlanTier, number> = {
|
|
134
|
+
small: 0,
|
|
135
|
+
medium: 1,
|
|
136
|
+
large: 2,
|
|
137
|
+
max: 3,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const CATALOG: CatalogEntry[] = [
|
|
141
|
+
{
|
|
142
|
+
id: "doubao-seed-2.0-mini",
|
|
143
|
+
name: "Doubao Seed 2.0 Mini",
|
|
144
|
+
api: "openai-responses",
|
|
145
|
+
minimumTier: "small",
|
|
146
|
+
reasoning: true,
|
|
147
|
+
thinkingLevelMap: DOUBAO_EFFORT_MAP,
|
|
148
|
+
input: ["text", "image"],
|
|
149
|
+
contextWindow: 256_000,
|
|
150
|
+
maxTokens: 128_000,
|
|
151
|
+
cost: copyEstimatedApiCost(DOUBAO_API_COSTS["doubao-seed-2.0-mini"]),
|
|
152
|
+
compat: RESPONSES_COMPAT,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "doubao-seed-2.0-lite",
|
|
156
|
+
name: "Doubao Seed 2.0 Lite",
|
|
157
|
+
api: "openai-responses",
|
|
158
|
+
minimumTier: "small",
|
|
159
|
+
reasoning: true,
|
|
160
|
+
thinkingLevelMap: DOUBAO_EFFORT_MAP,
|
|
161
|
+
input: ["text", "image"],
|
|
162
|
+
contextWindow: 256_000,
|
|
163
|
+
maxTokens: 128_000,
|
|
164
|
+
cost: copyEstimatedApiCost(DOUBAO_API_COSTS["doubao-seed-2.0-lite"]),
|
|
165
|
+
compat: RESPONSES_COMPAT,
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "deepseek-v4-flash",
|
|
169
|
+
name: "DeepSeek V4 Flash",
|
|
170
|
+
api: "openai-responses",
|
|
171
|
+
minimumTier: "small",
|
|
172
|
+
reasoning: true,
|
|
173
|
+
thinkingLevelMap: DEEPSEEK_GA_EFFORT_MAP,
|
|
174
|
+
input: ["text"],
|
|
175
|
+
contextWindow: 1_024_000,
|
|
176
|
+
maxTokens: 384_000,
|
|
177
|
+
cost: copyEstimatedApiCost(getBuiltinModel("deepseek", "deepseek-v4-flash").cost),
|
|
178
|
+
compat: RESPONSES_COMPAT,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: "doubao-seed-evolving",
|
|
182
|
+
name: "Doubao Seed Evolving",
|
|
183
|
+
api: "openai-responses",
|
|
184
|
+
minimumTier: "small",
|
|
185
|
+
reasoning: true,
|
|
186
|
+
thinkingLevelMap: DOUBAO_EFFORT_MAP,
|
|
187
|
+
input: ["text", "image"],
|
|
188
|
+
contextWindow: 1_024_000,
|
|
189
|
+
maxTokens: 256_000,
|
|
190
|
+
cost: copyEstimatedApiCost(DOUBAO_API_COSTS["doubao-seed-evolving"]),
|
|
191
|
+
compat: RESPONSES_COMPAT,
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: "doubao-seed-2.1-turbo",
|
|
195
|
+
name: "Doubao Seed 2.1 Turbo",
|
|
196
|
+
api: "openai-responses",
|
|
197
|
+
minimumTier: "small",
|
|
198
|
+
reasoning: true,
|
|
199
|
+
thinkingLevelMap: DOUBAO_EFFORT_MAP,
|
|
200
|
+
input: ["text"],
|
|
201
|
+
contextWindow: 256_000,
|
|
202
|
+
maxTokens: 256_000,
|
|
203
|
+
cost: copyEstimatedApiCost(DOUBAO_API_COSTS["doubao-seed-2.1-turbo"]),
|
|
204
|
+
compat: RESPONSES_COMPAT,
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "minimax-m3",
|
|
208
|
+
name: "MiniMax M3",
|
|
209
|
+
api: "openai-responses",
|
|
210
|
+
minimumTier: "small",
|
|
211
|
+
reasoning: true,
|
|
212
|
+
input: ["text", "image"],
|
|
213
|
+
contextWindow: 1_024_000,
|
|
214
|
+
maxTokens: 128_000,
|
|
215
|
+
cost: copyEstimatedApiCost(getBuiltinModel("minimax", "MiniMax-M3").cost),
|
|
216
|
+
compat: RESPONSES_COMPAT,
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: "glm-5.3-flash",
|
|
220
|
+
name: "GLM 5.3 Flash",
|
|
221
|
+
api: "openai-responses",
|
|
222
|
+
minimumTier: "small",
|
|
223
|
+
reasoning: true,
|
|
224
|
+
thinkingLevelMap: GLM_53_FLASH_EFFORT_MAP,
|
|
225
|
+
input: ["text", "image"],
|
|
226
|
+
contextWindow: 1_024_000,
|
|
227
|
+
maxTokens: 128_000,
|
|
228
|
+
cost: copyEstimatedApiCost(getBuiltinModel("opencode-go", "glm-5.2").cost),
|
|
229
|
+
compat: RESPONSES_COMPAT,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: "glm-5.3",
|
|
233
|
+
name: "GLM 5.3",
|
|
234
|
+
api: "openai-responses",
|
|
235
|
+
minimumTier: "small",
|
|
236
|
+
reasoning: true,
|
|
237
|
+
thinkingLevelMap: {
|
|
238
|
+
off: null,
|
|
239
|
+
minimal: null,
|
|
240
|
+
low: "low",
|
|
241
|
+
medium: null,
|
|
242
|
+
high: "high",
|
|
243
|
+
xhigh: null,
|
|
244
|
+
max: "max",
|
|
245
|
+
},
|
|
246
|
+
input: ["text"],
|
|
247
|
+
contextWindow: 1_024_000,
|
|
248
|
+
maxTokens: 128_000,
|
|
249
|
+
cost: copyEstimatedApiCost(getBuiltinModel("opencode-go", "glm-5.2").cost),
|
|
250
|
+
compat: RESPONSES_COMPAT,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
id: "kimi-k2.7-code",
|
|
254
|
+
name: "Kimi K2.7 Code",
|
|
255
|
+
api: "openai-completions",
|
|
256
|
+
minimumTier: "small",
|
|
257
|
+
reasoning: true,
|
|
258
|
+
thinkingLevelMap: { off: null },
|
|
259
|
+
input: ["text", "image"],
|
|
260
|
+
contextWindow: 256_000,
|
|
261
|
+
maxTokens: 32_000,
|
|
262
|
+
cost: copyEstimatedApiCost(getBuiltinModel("moonshotai", "kimi-k2.7-code").cost),
|
|
263
|
+
compat: KIMI_CHAT_COMPAT,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: "deepseek-v4-pro",
|
|
267
|
+
name: "DeepSeek V4 Pro",
|
|
268
|
+
api: "openai-responses",
|
|
269
|
+
minimumTier: "small",
|
|
270
|
+
reasoning: true,
|
|
271
|
+
thinkingLevelMap: DEEPSEEK_GA_EFFORT_MAP,
|
|
272
|
+
input: ["text"],
|
|
273
|
+
contextWindow: 1_024_000,
|
|
274
|
+
maxTokens: 384_000,
|
|
275
|
+
cost: copyEstimatedApiCost(getBuiltinModel("deepseek", "deepseek-v4-pro").cost),
|
|
276
|
+
compat: RESPONSES_COMPAT,
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: "kimi-k3",
|
|
280
|
+
...KIMI_K3_CAPABILITIES,
|
|
281
|
+
api: "openai-responses",
|
|
282
|
+
minimumTier: "medium",
|
|
283
|
+
compat: RESPONSES_COMPAT,
|
|
284
|
+
},
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
const MINIMUM_TIER = new Map(CATALOG.map((model) => [model.id, model.minimumTier]));
|
|
288
|
+
const MODELS: Model<ArkApi>[] = CATALOG.map(({ minimumTier: _minimumTier, ...model }) => ({
|
|
289
|
+
...model,
|
|
290
|
+
provider: PROVIDER_ID,
|
|
291
|
+
baseUrl: BASE_URL,
|
|
292
|
+
}));
|
|
293
|
+
|
|
294
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
295
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
type KeyValidationResult =
|
|
299
|
+
| { status: "valid" }
|
|
300
|
+
| { status: "invalid" }
|
|
301
|
+
| { status: "unavailable"; reason: string };
|
|
302
|
+
|
|
303
|
+
interface KeyValidationOptions {
|
|
304
|
+
fetchImpl?: typeof fetch;
|
|
305
|
+
signal?: AbortSignal;
|
|
306
|
+
timeoutMs?: number;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function responseErrorCode(response: Response): Promise<string | undefined> {
|
|
310
|
+
try {
|
|
311
|
+
const payload: unknown = await response.json();
|
|
312
|
+
if (!isRecord(payload)) return undefined;
|
|
313
|
+
const error = payload.error;
|
|
314
|
+
if (isRecord(error) && typeof error.code === "string") return error.code;
|
|
315
|
+
return typeof payload.code === "string" ? payload.code : undefined;
|
|
316
|
+
} catch {
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export async function validateAgentPlanKey(
|
|
322
|
+
key: string,
|
|
323
|
+
options: KeyValidationOptions = {},
|
|
324
|
+
): Promise<KeyValidationResult> {
|
|
325
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
326
|
+
const controller = new AbortController();
|
|
327
|
+
let timedOut = false;
|
|
328
|
+
const timeout = setTimeout(() => {
|
|
329
|
+
timedOut = true;
|
|
330
|
+
controller.abort();
|
|
331
|
+
}, options.timeoutMs ?? KEY_VALIDATION_TIMEOUT_MS);
|
|
332
|
+
const abortFromLogin = () => controller.abort(options.signal?.reason);
|
|
333
|
+
if (options.signal?.aborted) abortFromLogin();
|
|
334
|
+
else options.signal?.addEventListener("abort", abortFromLogin, { once: true });
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
// An authenticated empty request is rejected as MissingParameter before any
|
|
338
|
+
// model inference. Invalid credentials fail earlier with 401/403, allowing
|
|
339
|
+
// login validation without consuming Agent Plan tokens.
|
|
340
|
+
const response = await fetchImpl(KEY_VALIDATION_URL, {
|
|
341
|
+
method: "POST",
|
|
342
|
+
headers: {
|
|
343
|
+
Authorization: `Bearer ${key}`,
|
|
344
|
+
"Content-Type": "application/json",
|
|
345
|
+
},
|
|
346
|
+
body: "{}",
|
|
347
|
+
signal: controller.signal,
|
|
348
|
+
});
|
|
349
|
+
const errorCode = await responseErrorCode(response);
|
|
350
|
+
|
|
351
|
+
if (response.status === 401 || response.status === 403) return { status: "invalid" };
|
|
352
|
+
if (response.ok || (response.status === 400 && errorCode === "MissingParameter")) {
|
|
353
|
+
return { status: "valid" };
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
status: "unavailable",
|
|
357
|
+
reason: `HTTP ${response.status}${errorCode ? ` (${errorCode})` : ""}`,
|
|
358
|
+
};
|
|
359
|
+
} catch (error) {
|
|
360
|
+
if (options.signal?.aborted) {
|
|
361
|
+
throw options.signal.reason instanceof Error
|
|
362
|
+
? options.signal.reason
|
|
363
|
+
: new Error("Agent Plan 登录已取消");
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
status: "unavailable",
|
|
367
|
+
reason: timedOut
|
|
368
|
+
? "请求超时"
|
|
369
|
+
: error instanceof Error
|
|
370
|
+
? error.message
|
|
371
|
+
: "未知网络错误",
|
|
372
|
+
};
|
|
373
|
+
} finally {
|
|
374
|
+
clearTimeout(timeout);
|
|
375
|
+
options.signal?.removeEventListener("abort", abortFromLogin);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function promptValidatedAgentPlanKey(
|
|
380
|
+
interaction: AuthInteraction,
|
|
381
|
+
fetchImpl: typeof fetch,
|
|
382
|
+
): Promise<string> {
|
|
383
|
+
keyPrompt: while (true) {
|
|
384
|
+
const key = (
|
|
385
|
+
await interaction.prompt({
|
|
386
|
+
type: "secret",
|
|
387
|
+
message: "Agent Plan API Key",
|
|
388
|
+
placeholder: "输入后将安全保存到 Pi auth.json",
|
|
389
|
+
})
|
|
390
|
+
).trim();
|
|
391
|
+
if (!key) throw new Error("Agent Plan API Key 不能为空");
|
|
392
|
+
|
|
393
|
+
while (true) {
|
|
394
|
+
interaction.notify({ type: "progress", message: "正在验证 Agent Plan API Key…" });
|
|
395
|
+
const result = await validateAgentPlanKey(key, {
|
|
396
|
+
fetchImpl,
|
|
397
|
+
signal: interaction.signal,
|
|
398
|
+
});
|
|
399
|
+
if (result.status === "valid") {
|
|
400
|
+
interaction.notify({ type: "info", message: "Agent Plan API Key 验证通过。" });
|
|
401
|
+
return key;
|
|
402
|
+
}
|
|
403
|
+
if (result.status === "invalid") {
|
|
404
|
+
interaction.notify({
|
|
405
|
+
type: "info",
|
|
406
|
+
message: "API Key 无效或无权访问 Agent Plan,请重新输入。",
|
|
407
|
+
});
|
|
408
|
+
continue keyPrompt;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const action = await interaction.prompt({
|
|
412
|
+
type: "select",
|
|
413
|
+
message: `暂时无法验证 API Key:${result.reason}`,
|
|
414
|
+
options: [
|
|
415
|
+
{ id: "retry", label: "重试", description: "再次连接 Agent Plan 验证" },
|
|
416
|
+
{ id: "save", label: "仍然保存", description: "跳过验证并在首次请求时确认" },
|
|
417
|
+
],
|
|
418
|
+
});
|
|
419
|
+
if (action === "retry") continue;
|
|
420
|
+
return key;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function parsePlanTier(value: unknown): PlanTier | undefined {
|
|
426
|
+
if (typeof value !== "string") return undefined;
|
|
427
|
+
const normalized = value.trim().toLowerCase();
|
|
428
|
+
return normalized === "small" || normalized === "medium" || normalized === "large" || normalized === "max"
|
|
429
|
+
? normalized
|
|
430
|
+
: undefined;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function processPlanTier(): PlanTier {
|
|
434
|
+
return parsePlanTier(process.env[TIER_ENV]) ?? "medium";
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function credentialPlanTier(credential: Credential | undefined): PlanTier {
|
|
438
|
+
if (credential?.type === "api_key") {
|
|
439
|
+
const stored = parsePlanTier(credential.env?.[TIER_ENV]);
|
|
440
|
+
if (stored) return stored;
|
|
441
|
+
}
|
|
442
|
+
return processPlanTier();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
interface ResolvedKey {
|
|
446
|
+
key: string;
|
|
447
|
+
source: string;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function resolveKey(ctx: AuthContext, credential?: ApiKeyCredential): Promise<ResolvedKey | undefined> {
|
|
451
|
+
const stored = credential?.key?.trim();
|
|
452
|
+
if (stored) return { key: stored, source: "Pi auth.json" };
|
|
453
|
+
|
|
454
|
+
for (const variable of KEY_ENV_NAMES) {
|
|
455
|
+
const value = (await ctx.env(variable))?.trim();
|
|
456
|
+
if (value) return { key: value, source: variable };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return undefined;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function resolveTier(ctx: AuthContext, credential?: ApiKeyCredential): Promise<PlanTier> {
|
|
463
|
+
return (
|
|
464
|
+
parsePlanTier(credential?.env?.[TIER_ENV]) ??
|
|
465
|
+
parsePlanTier(await ctx.env(TIER_ENV)) ??
|
|
466
|
+
"medium"
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function filterModelsByTier(models: readonly Model<ArkApi>[], tier: PlanTier): readonly Model<ArkApi>[] {
|
|
471
|
+
return models.filter((model) => {
|
|
472
|
+
const minimum = MINIMUM_TIER.get(model.id) ?? "small";
|
|
473
|
+
return TIER_RANK[tier] >= TIER_RANK[minimum];
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export interface AgentPlanProviderOptions {
|
|
478
|
+
fetchImpl?: typeof fetch;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function createAgentPlanProvider(options: AgentPlanProviderOptions = {}) {
|
|
482
|
+
return createProvider<ArkApi>({
|
|
483
|
+
id: PROVIDER_ID,
|
|
484
|
+
name: "Volcengine Ark Agent Plan",
|
|
485
|
+
baseUrl: BASE_URL,
|
|
486
|
+
auth: {
|
|
487
|
+
apiKey: {
|
|
488
|
+
name: "Ark Agent Plan API key",
|
|
489
|
+
async login(interaction) {
|
|
490
|
+
interaction.notify({
|
|
491
|
+
type: "info",
|
|
492
|
+
message: "请使用 Agent Plan 专属 API Key;普通方舟 API Key 不适用于该端点。",
|
|
493
|
+
});
|
|
494
|
+
const key = await promptValidatedAgentPlanKey(
|
|
495
|
+
interaction,
|
|
496
|
+
options.fetchImpl ?? fetch,
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
const tier = parsePlanTier(
|
|
500
|
+
await interaction.prompt({
|
|
501
|
+
type: "select",
|
|
502
|
+
message: "选择 Agent Plan 订阅级别",
|
|
503
|
+
options: [
|
|
504
|
+
{ id: "small", label: "Small", description: "隐藏 Medium 起可用的 Kimi K3" },
|
|
505
|
+
{ id: "medium", label: "Medium", description: "当前套餐;显示全部文本模型" },
|
|
506
|
+
{ id: "large", label: "Large", description: "显示全部文本模型" },
|
|
507
|
+
{ id: "max", label: "Max", description: "显示全部文本模型" },
|
|
508
|
+
],
|
|
509
|
+
}),
|
|
510
|
+
);
|
|
511
|
+
if (!tier) throw new Error("无效的 Agent Plan 订阅级别");
|
|
512
|
+
|
|
513
|
+
interaction.notify({
|
|
514
|
+
type: "progress",
|
|
515
|
+
message: "正在保存凭证并刷新 provider 状态,请稍候…",
|
|
516
|
+
});
|
|
517
|
+
return {
|
|
518
|
+
type: "api_key",
|
|
519
|
+
key,
|
|
520
|
+
env: { [TIER_ENV]: tier },
|
|
521
|
+
};
|
|
522
|
+
},
|
|
523
|
+
async check({ ctx, credential }) {
|
|
524
|
+
const resolved = await resolveKey(ctx, credential);
|
|
525
|
+
return resolved ? { type: "api_key", source: resolved.source } : undefined;
|
|
526
|
+
},
|
|
527
|
+
async resolve({ ctx, credential }) {
|
|
528
|
+
const resolved = await resolveKey(ctx, credential);
|
|
529
|
+
if (!resolved) return undefined;
|
|
530
|
+
const tier = await resolveTier(ctx, credential);
|
|
531
|
+
return {
|
|
532
|
+
auth: { apiKey: resolved.key },
|
|
533
|
+
env: { [TIER_ENV]: tier },
|
|
534
|
+
source: resolved.source,
|
|
535
|
+
};
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
models: MODELS,
|
|
540
|
+
filterModels(models, credential) {
|
|
541
|
+
return filterModelsByTier(models, credentialPlanTier(credential));
|
|
542
|
+
},
|
|
543
|
+
api: {
|
|
544
|
+
"openai-responses": openAIResponsesApi(),
|
|
545
|
+
"openai-completions": openAICompletionsApi(),
|
|
546
|
+
},
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export default function volcengineAgentPlan(pi: ExtensionAPI) {
|
|
551
|
+
pi.registerProvider(createAgentPlanProvider());
|
|
552
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@woyxiang/pi-provider-volcengine-agent-plan",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Unofficial Pi provider extension for Volcengine Ark Agent Plan.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"pi-extension",
|
|
8
|
+
"pi-provider",
|
|
9
|
+
"volcengine",
|
|
10
|
+
"ark",
|
|
11
|
+
"agent-plan"
|
|
12
|
+
],
|
|
13
|
+
"author": "woyxiang",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/woyxiang/pi-provider-volcengine-agent-plan.git"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/woyxiang/pi-provider-volcengine-agent-plan#readme",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/woyxiang/pi-provider-volcengine-agent-plan/issues"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22.19.0"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"index.ts",
|
|
29
|
+
"README.md",
|
|
30
|
+
"README.zh-CN.md",
|
|
31
|
+
"CHANGELOG.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"pi": {
|
|
38
|
+
"extensions": [
|
|
39
|
+
"./index.ts"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@earendil-works/pi-ai": "*",
|
|
44
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@earendil-works/pi-ai": "0.84.0",
|
|
48
|
+
"@earendil-works/pi-coding-agent": "0.84.0",
|
|
49
|
+
"@types/node": "25.6.0",
|
|
50
|
+
"jiti": "2.7.0",
|
|
51
|
+
"tsx": "4.22.4",
|
|
52
|
+
"typescript": "6.0.3"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
56
|
+
"test": "tsx --test \"test/**/*.test.ts\"",
|
|
57
|
+
"check": "pnpm typecheck && pnpm test"
|
|
58
|
+
}
|
|
59
|
+
}
|