abelworkflow 1.1.3 → 1.2.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/LICENSE +21 -0
- package/README.md +30 -29
- package/extensions/gpt-responses-compat.ts +213 -0
- package/extensions/k2think-compat.ts +40 -0
- package/lib/cli/args.mjs +5 -19
- package/lib/cli/main.mjs +105 -72
- package/lib/cli/prompts.mjs +9 -2
- package/lib/config/dotenv.mjs +1 -1
- package/lib/config/jsonc.mjs +1 -1
- package/lib/config/store.mjs +197 -45
- package/lib/config/toml.mjs +147 -453
- package/lib/installer/assets.mjs +67 -45
- package/lib/installer/install.mjs +202 -45
- package/lib/installer/links.mjs +124 -101
- package/lib/installer/lock.mjs +82 -0
- package/lib/installer/state.mjs +14 -21
- package/lib/paths.mjs +9 -9
- package/lib/providers/claude.mjs +58 -114
- package/lib/providers/codex.mjs +101 -198
- package/lib/providers/pi.mjs +331 -496
- package/lib/providers/skills.mjs +88 -68
- package/lib/providers/url.mjs +35 -0
- package/lib/templates/workflow/commands/abel-design.md +23 -161
- package/lib/templates/workflow/commands/abel-diagnose.md +30 -56
- package/lib/templates/workflow/commands/abel-implement.md +30 -150
- package/lib/templates/workflow/commands/abel-init.md +1 -1
- package/lib/tools/cli-installer.mjs +29 -190
- package/package.json +7 -9
- package/skills/context7-auto-research/context7-api.cjs +75 -17
- package/skills/dev-browser/SKILL.md +15 -1
- package/skills/dev-browser/dist/scripts/start.d.ts +0 -1
- package/skills/dev-browser/dist/scripts/start.js +8 -36
- package/skills/dev-browser/dist/src/client.d.ts +3 -4
- package/skills/dev-browser/dist/src/client.js +117 -117
- package/skills/dev-browser/dist/src/entrypoint.d.ts +1 -1
- package/skills/dev-browser/dist/src/entrypoint.js +38 -31
- package/skills/dev-browser/dist/src/index.d.ts +0 -1
- package/skills/dev-browser/dist/src/index.js +0 -1
- package/skills/dev-browser/dist/src/page-api.d.ts +0 -1
- package/skills/dev-browser/dist/src/page-api.js +0 -1
- package/skills/dev-browser/dist/src/relay.d.ts +0 -1
- package/skills/dev-browser/dist/src/relay.js +53 -7
- package/skills/dev-browser/dist/src/runtime.d.ts +3 -14
- package/skills/dev-browser/dist/src/runtime.js +5 -15
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +0 -1
- package/skills/dev-browser/dist/src/snapshot/browser-script.js +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.d.ts +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.js +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.js +0 -1
- package/skills/dev-browser/dist/src/standalone.d.ts +0 -1
- package/skills/dev-browser/dist/src/standalone.js +2 -3
- package/skills/dev-browser/dist/src/startup.d.ts +3 -12
- package/skills/dev-browser/dist/src/startup.js +6 -10
- package/skills/dev-browser/dist/src/target-registry.d.ts +0 -1
- package/skills/dev-browser/dist/src/target-registry.js +0 -1
- package/skills/dev-browser/dist/src/types.d.ts +0 -1
- package/skills/dev-browser/dist/src/types.js +0 -1
- package/skills/dev-browser/package-lock.json +1 -1
- package/skills/dev-browser/package.json +2 -2
- package/skills/grok-search/.env.example +1 -1
- package/skills/grok-search/SKILL.md +13 -16
- package/skills/grok-search/scripts/_dotenv.py +1 -1
- package/skills/grok-search/scripts/groksearch_cli.py +271 -195
- package/skills/time/SKILL.md +5 -2
- package/skills/time/scripts/time_cli.py +61 -22
- package/extensions/pi-gpt-responses-compat/index.ts +0 -25
- package/lib/templates/codex/config-base.toml +0 -103
- package/skills/dev-browser/dist/scripts/start.d.ts.map +0 -1
- package/skills/dev-browser/dist/scripts/start.js.map +0 -1
- package/skills/dev-browser/dist/src/client.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/client.js.map +0 -1
- package/skills/dev-browser/dist/src/entrypoint.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/entrypoint.js.map +0 -1
- package/skills/dev-browser/dist/src/index.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/index.js.map +0 -1
- package/skills/dev-browser/dist/src/page-api.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/page-api.js.map +0 -1
- package/skills/dev-browser/dist/src/relay.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/relay.js.map +0 -1
- package/skills/dev-browser/dist/src/runtime.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/runtime.js.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/index.js.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/snapshot/inject.js.map +0 -1
- package/skills/dev-browser/dist/src/standalone.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/standalone.js.map +0 -1
- package/skills/dev-browser/dist/src/startup.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/startup.js.map +0 -1
- package/skills/dev-browser/dist/src/target-registry.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/target-registry.js.map +0 -1
- package/skills/dev-browser/dist/src/types.d.ts.map +0 -1
- package/skills/dev-browser/dist/src/types.js.map +0 -1
- package/skills/grok-search/gitignore.template +0 -4
- package/skills/grok-search/scripts/groksearch_entry.py +0 -131
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AbelWorkflow contributors
|
|
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AbelWorkflow
|
|
2
2
|
|
|
3
|
-
AbelWorkflow 为 Codex、Claude Code 和 Pi 部署统一的 Skills、工作流命令、Agent
|
|
3
|
+
AbelWorkflow 为 Codex、Claude Code 和 Pi 部署统一的 Skills、工作流命令、Agent 配置与 Pi Extensions。
|
|
4
4
|
|
|
5
5
|
## 环境要求
|
|
6
6
|
|
|
@@ -8,16 +8,14 @@ AbelWorkflow 为 Codex、Claude Code 和 Pi 部署统一的 Skills、工作流
|
|
|
8
8
|
- npm 10 或兼容版本
|
|
9
9
|
- Linux、macOS 或原生 Windows
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## 工作流命令
|
|
12
12
|
|
|
13
13
|
| 命令 | 用途 |
|
|
14
14
|
| --- | --- |
|
|
15
15
|
| `/abel-init` | 初始化 OpenSpec 环境并验证工具链 |
|
|
16
|
-
| `/abel-design` |
|
|
17
|
-
| `/abel-implement` |
|
|
18
|
-
| `/abel-diagnose` |
|
|
19
|
-
|
|
20
|
-
工作流:
|
|
16
|
+
| `/abel-design` | 将需求整理为可实施、可验证的 OpenSpec 规格 |
|
|
17
|
+
| `/abel-implement` | 按 Red → Green → Refactor 实施变更 |
|
|
18
|
+
| `/abel-diagnose` | 基于证据定位根因并完成回归验证 |
|
|
21
19
|
|
|
22
20
|
```text
|
|
23
21
|
/abel-init → /abel-design → /abel-implement(TDD)
|
|
@@ -28,48 +26,55 @@ OpenSpec 命令保持独立:`/opsx:propose`、`/opsx:explore`、`/opsx:apply`
|
|
|
28
26
|
|
|
29
27
|
## 安装与更新
|
|
30
28
|
|
|
31
|
-
### npx
|
|
32
|
-
|
|
33
29
|
```bash
|
|
34
30
|
npx abelworkflow
|
|
35
31
|
npx abelworkflow install
|
|
36
32
|
npx abelworkflow@latest
|
|
37
33
|
```
|
|
38
34
|
|
|
39
|
-
无 TTY
|
|
35
|
+
无 TTY 环境必须显式运行 `npx abelworkflow install`。安装器将工作流部署到 `~/.agents`,再为 Claude、Codex 与 Pi 建立链接。已有目标与托管内容冲突时,安装会失败并保留原内容;确认目标后再处理冲突。
|
|
40
36
|
|
|
41
37
|
### 源码安装
|
|
42
38
|
|
|
43
|
-
|
|
39
|
+
源码目录与部署目录必须分离,不能相同或互相嵌套。不要把源码仓库直接克隆到 `~/.agents`。
|
|
44
40
|
|
|
45
41
|
```bash
|
|
46
42
|
git clone https://github.com/abelxiaoxing/AbelWorkflow ~/src/AbelWorkflow
|
|
47
43
|
cd ~/src/AbelWorkflow
|
|
48
44
|
npm ci
|
|
49
45
|
npm ci --prefix skills/dev-browser
|
|
50
|
-
npm run build
|
|
46
|
+
npm run build --prefix skills/dev-browser
|
|
51
47
|
node bin/abelworkflow.mjs install --agents-dir ~/.agents
|
|
52
48
|
```
|
|
53
49
|
|
|
54
|
-
Windows PowerShell
|
|
50
|
+
Windows PowerShell 使用独立源码目录,并显式指定部署目录:
|
|
55
51
|
|
|
56
52
|
```powershell
|
|
57
53
|
node .\bin\abelworkflow.mjs install --agents-dir "$HOME\.agents"
|
|
58
54
|
```
|
|
59
55
|
|
|
60
|
-
|
|
56
|
+
### 可选 Skill 依赖
|
|
61
57
|
|
|
62
|
-
|
|
58
|
+
源码安装需按上方命令在同步前构建 dev-browser。同步后如需使用 dev-browser,还需在部署目录中显式安装运行依赖;Time 独立 CLI 需要 Python 3.9+,Windows 还需 `tzdata` 提供 IANA 时区;Grok 依赖安装到用户自行选择的 Python 环境。普通运行不会自动安装依赖或浏览器:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm ci --omit=dev --prefix ~/.agents/skills/dev-browser
|
|
62
|
+
node ~/.agents/skills/dev-browser/node_modules/playwright/cli.js install chromium
|
|
63
|
+
python -m pip install tzdata
|
|
64
|
+
python -m pip install -r skills/grok-search/requirements.txt
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
部署后的 Skill 目录在普通运行时按只读处理。显式 Skill 密钥配置会写入部署目录的 `.env`;临时文件、浏览器 profile、虚拟环境和其他运行状态应放在操作系统临时目录或用户数据目录。
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
- Codex 只更新 AbelWorkflow 管理的认证字段,保留未知字段、其他 Provider 和用户 token。
|
|
66
|
-
- Pi 0.80.0 及以上版本按无会话启动时实际选中的当前有效 Provider 配置 API;首次使用且 Pi 明确没有可用模型时,引导创建 `gpt` 自定义 Provider。API Key 保存在 `~/.pi/agent/auth.json`,`models.json` 只保留模型定义;旧 models-only key 会按先写 auth、后删除旧 key 的顺序迁移。
|
|
67
|
-
- Grok 默认模型统一为 `grok-4.20-non-reasoning`。
|
|
68
|
-
- Context7 使用显式 CommonJS 入口 `context7-api.cjs`。
|
|
69
|
-
- dev-browser 发布运行时使用 Node ESM 编译产物,入口为 `node dist/scripts/start.js`,不依赖 Bun 或运行时 `npx tsx`。
|
|
70
|
-
- skill 密钥写入 `~/.agents/skills/<skill>/.env`;这些文件属于私密配置。
|
|
69
|
+
## 配置行为
|
|
71
70
|
|
|
72
|
-
|
|
71
|
+
- Claude 配置管理 `ANTHROPIC_BASE_URL`、`ANTHROPIC_API_KEY` 与 `ANTHROPIC_MODEL`;冲突的 `ANTHROPIC_AUTH_TOKEN` 会被删除,其他模型路由保持不变。
|
|
72
|
+
- Codex 配置管理 AbelWorkflow Provider 路由、`OPENAI_API_KEY` 与 `auth_mode`,清理冲突的 `preferred_auth_method`、`temp_env_key` 及目标 Provider 旧路由字段,保留其他认证字段。
|
|
73
|
+
- Pi 配置更新 `abelworkflow` Provider、对应凭据及全局 `defaultProvider`、`defaultModel`,保留其他 Provider 与无关设置。
|
|
74
|
+
- API 配置不注入全局权限;配置结构或路径无法安全合并时直接失败。
|
|
75
|
+
- Skill 密钥写入 `~/.agents/skills/<skill>/.env`,属于用户私密配置。
|
|
76
|
+
- `.skill-lock.json` 完全属于用户:安装器不读取、不修改、不备份、不删除,也不会将其放入发布包。
|
|
77
|
+
- 自定义 CA 应通过标准证书环境变量显式配置,不关闭 TLS 校验。
|
|
73
78
|
|
|
74
79
|
## 部署映射
|
|
75
80
|
|
|
@@ -78,11 +83,11 @@ node .\bin\abelworkflow.mjs install --agents-dir "$HOME\.agents"
|
|
|
78
83
|
| 工作流 AGENTS 模板 | `~/.claude/CLAUDE.md` | `~/.codex/AGENTS.md` | `~/.pi/agent/AGENTS.md` |
|
|
79
84
|
| `skills/<skill>/` | `~/.claude/skills/<skill>/` | `~/.codex/skills/<skill>/` | `~/.pi/agent/skills/<skill>/` |
|
|
80
85
|
| `abel-*.md` 命令 | `~/.claude/commands/` | `~/.codex/prompts/` | `~/.pi/agent/prompts/` |
|
|
81
|
-
| `extensions/*` | — | — | `~/.pi/agent/extensions/` |
|
|
86
|
+
| `extensions/*` | — | — | `~/.agents/extensions/` → `~/.pi/agent/extensions/` |
|
|
82
87
|
|
|
83
88
|
## 自签名 HTTPS 中转
|
|
84
89
|
|
|
85
|
-
|
|
90
|
+
取得中转的 CA PEM 后,在启动客户端前配置:
|
|
86
91
|
|
|
87
92
|
```bash
|
|
88
93
|
export NODE_EXTRA_CA_CERTS=/absolute/path/relay-ca.pem
|
|
@@ -90,7 +95,3 @@ export CODEX_CA_CERTIFICATE=/absolute/path/relay-ca.pem
|
|
|
90
95
|
```
|
|
91
96
|
|
|
92
97
|
不要全局设置 `NODE_TLS_REJECT_UNAUTHORIZED=0`。证书过期或主机名不匹配时应重新签发证书。
|
|
93
|
-
|
|
94
|
-
## 升级
|
|
95
|
-
|
|
96
|
-
从 0.x 升级前请阅读 [AbelWorkflow 1.0 迁移指南](https://github.com/abelxiaoxing/AbelWorkflow/blob/master/docs/migration-1.0.md)。指南涵盖源码/部署目录拆分、metadata v2、Provider 配置、用户状态、dev-browser 和回滚。
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
type JsonRecord = Record<string, any>;
|
|
4
|
+
|
|
5
|
+
const DEFAULT_INSTRUCTIONS = "You are a helpful assistant.";
|
|
6
|
+
const RESPONSES_API = "openai-responses";
|
|
7
|
+
const RESPONSES_PROVIDERS = new Set(["abelworkflow", "gpt"]);
|
|
8
|
+
const UNSUPPORTED_SPARK_REASONING_EFFORTS = new Set(["none", "minimal"]);
|
|
9
|
+
const TRANSIENT_UPSTREAM_ERROR = /\bupstream(?:[_ -]error|\s+request\s+failed)\b/i;
|
|
10
|
+
const EXPLICIT_ERROR_STATUS = /(?:^\s*|\b(?:upstream(?:[_ -]error|\s+request\s+failed)|code|error(?:[_ -]?code)?|http(?:\/\d+(?:\.\d+)*)?(?:[_ -]?status(?:[_ -]?code)?)?|status(?:[_ -]?code)?)\b\s*["']?\s*[:=(]?\s*)["']?([45]\d{2})["']?(?!\d)/i;
|
|
11
|
+
|
|
12
|
+
function isRecord(value: unknown): value is JsonRecord {
|
|
13
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isCompatibleResponsesModel(model: unknown): boolean {
|
|
17
|
+
return isRecord(model) &&
|
|
18
|
+
model.api === RESPONSES_API &&
|
|
19
|
+
RESPONSES_PROVIDERS.has(model.provider);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function textFromContent(content: unknown): string | undefined {
|
|
23
|
+
if (typeof content === "string") return content.trim() || undefined;
|
|
24
|
+
if (!Array.isArray(content)) return undefined;
|
|
25
|
+
|
|
26
|
+
const text = content
|
|
27
|
+
.map((part) => {
|
|
28
|
+
if (!isRecord(part)) return "";
|
|
29
|
+
if (typeof part.text === "string") return part.text;
|
|
30
|
+
if (typeof part.refusal === "string") return part.refusal;
|
|
31
|
+
return "";
|
|
32
|
+
})
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
.join("\n")
|
|
35
|
+
.trim();
|
|
36
|
+
|
|
37
|
+
return text || undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeUserContent(content: unknown): unknown {
|
|
41
|
+
if (typeof content === "string") {
|
|
42
|
+
return [{ type: "input_text", text: content }];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!Array.isArray(content)) return content;
|
|
46
|
+
|
|
47
|
+
return content
|
|
48
|
+
.map((part) => {
|
|
49
|
+
if (!isRecord(part)) return undefined;
|
|
50
|
+
if (part.type === "input_text" && typeof part.text === "string") {
|
|
51
|
+
return { type: "input_text", text: part.text };
|
|
52
|
+
}
|
|
53
|
+
if (part.type === "input_image" && typeof part.image_url === "string") {
|
|
54
|
+
return {
|
|
55
|
+
type: "input_image",
|
|
56
|
+
image_url: part.image_url,
|
|
57
|
+
...(typeof part.detail === "string" ? { detail: part.detail } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
})
|
|
62
|
+
.filter(Boolean);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeAssistantContent(content: unknown): unknown {
|
|
66
|
+
if (typeof content === "string") {
|
|
67
|
+
return [{ type: "output_text", text: content, annotations: [] }];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!Array.isArray(content)) return content;
|
|
71
|
+
|
|
72
|
+
return content
|
|
73
|
+
.map((part) => {
|
|
74
|
+
if (!isRecord(part)) return undefined;
|
|
75
|
+
if (part.type === "output_text" && typeof part.text === "string") {
|
|
76
|
+
return { type: "output_text", text: part.text, annotations: [] };
|
|
77
|
+
}
|
|
78
|
+
if (part.type === "refusal" && typeof part.refusal === "string") {
|
|
79
|
+
return { type: "refusal", refusal: part.refusal };
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
})
|
|
83
|
+
.filter(Boolean);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizeInputItem(item: unknown): unknown {
|
|
87
|
+
if (!isRecord(item)) return undefined;
|
|
88
|
+
|
|
89
|
+
// The Codex passthrough endpoint requires the prompt in `instructions`.
|
|
90
|
+
// Replaying developer/system messages inside `input` is redundant and can
|
|
91
|
+
// confuse proxies that only partially implement the Responses API.
|
|
92
|
+
if (item.role === "system" || item.role === "developer") return undefined;
|
|
93
|
+
|
|
94
|
+
// Reasoning items contain model-bound encrypted payloads. They are unsafe to
|
|
95
|
+
// replay when switching between GPT/Codex variants and are a common cause of
|
|
96
|
+
// upstream 5xx errors from passthrough providers. Dropping them preserves all
|
|
97
|
+
// visible conversation text and tool state.
|
|
98
|
+
if (item.type === "reasoning") return undefined;
|
|
99
|
+
|
|
100
|
+
if (item.type === "message") {
|
|
101
|
+
const content = normalizeAssistantContent(item.content);
|
|
102
|
+
if (Array.isArray(content) && content.length === 0) return undefined;
|
|
103
|
+
return { role: "assistant", content };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (item.role === "user") {
|
|
107
|
+
const content = normalizeUserContent(item.content);
|
|
108
|
+
if (Array.isArray(content) && content.length === 0) return undefined;
|
|
109
|
+
return { role: "user", content };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (item.role === "assistant") {
|
|
113
|
+
const content = normalizeAssistantContent(item.content);
|
|
114
|
+
if (Array.isArray(content) && content.length === 0) return undefined;
|
|
115
|
+
return { role: "assistant", content };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (item.type === "function_call") {
|
|
119
|
+
if (typeof item.call_id !== "string" || typeof item.name !== "string") return undefined;
|
|
120
|
+
return {
|
|
121
|
+
type: "function_call",
|
|
122
|
+
call_id: item.call_id,
|
|
123
|
+
name: item.name,
|
|
124
|
+
arguments: typeof item.arguments === "string" ? item.arguments : JSON.stringify(item.arguments ?? {}),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (item.type === "function_call_output") {
|
|
129
|
+
if (typeof item.call_id !== "string") return undefined;
|
|
130
|
+
return {
|
|
131
|
+
type: "function_call_output",
|
|
132
|
+
call_id: item.call_id,
|
|
133
|
+
output: Array.isArray(item.output) ? normalizeUserContent(item.output) : String(item.output ?? ""),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return item;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalizeInput(input: unknown): unknown[] {
|
|
141
|
+
const rawItems = Array.isArray(input)
|
|
142
|
+
? input
|
|
143
|
+
: typeof input === "string"
|
|
144
|
+
? [{ role: "user", content: input }]
|
|
145
|
+
: isRecord(input)
|
|
146
|
+
? [input]
|
|
147
|
+
: [];
|
|
148
|
+
|
|
149
|
+
return rawItems.map(normalizeInputItem).filter((item) => item !== undefined);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function normalizeReasoning(payload: JsonRecord, modelId: string | undefined): void {
|
|
153
|
+
if (modelId !== "gpt-5.3-codex-spark") return;
|
|
154
|
+
if (!isRecord(payload.reasoning)) return;
|
|
155
|
+
if (!UNSUPPORTED_SPARK_REASONING_EFFORTS.has(payload.reasoning.effort)) return;
|
|
156
|
+
|
|
157
|
+
payload.reasoning = { ...payload.reasoning, effort: "low" };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export default function (pi: ExtensionAPI) {
|
|
161
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
162
|
+
const payload = event.payload as unknown;
|
|
163
|
+
if (!isRecord(payload)) return;
|
|
164
|
+
if (!isCompatibleResponsesModel(ctx.model)) return;
|
|
165
|
+
|
|
166
|
+
const input = normalizeInput(payload.input);
|
|
167
|
+
const first = Array.isArray(payload.input) ? payload.input.find(isRecord) : undefined;
|
|
168
|
+
const firstIsPrompt = first?.role === "system" || first?.role === "developer";
|
|
169
|
+
const instructions =
|
|
170
|
+
textFromContent(payload.instructions) ||
|
|
171
|
+
(firstIsPrompt ? textFromContent(first.content) : undefined) ||
|
|
172
|
+
ctx.getSystemPrompt().trim() ||
|
|
173
|
+
DEFAULT_INSTRUCTIONS;
|
|
174
|
+
|
|
175
|
+
const nextPayload: JsonRecord = { ...payload, instructions, input, store: false };
|
|
176
|
+
|
|
177
|
+
// Unsupported or risky for this Codex passthrough endpoint.
|
|
178
|
+
delete nextPayload.max_output_tokens;
|
|
179
|
+
delete nextPayload.prompt_cache_key;
|
|
180
|
+
delete nextPayload.prompt_cache_retention;
|
|
181
|
+
delete nextPayload.previous_response_id;
|
|
182
|
+
|
|
183
|
+
normalizeReasoning(nextPayload, ctx.model?.id);
|
|
184
|
+
|
|
185
|
+
return nextPayload;
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
pi.on("message_end", (event, ctx) => {
|
|
189
|
+
const message = event.message;
|
|
190
|
+
if (
|
|
191
|
+
message.role !== "assistant" ||
|
|
192
|
+
message.stopReason !== "error" ||
|
|
193
|
+
!isCompatibleResponsesModel(ctx.model)
|
|
194
|
+
) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const errorMessage = message.errorMessage ?? "";
|
|
199
|
+
if (
|
|
200
|
+
EXPLICIT_ERROR_STATUS.test(errorMessage) ||
|
|
201
|
+
!TRANSIENT_UPSTREAM_ERROR.test(errorMessage)
|
|
202
|
+
) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
message: {
|
|
208
|
+
...message,
|
|
209
|
+
errorMessage: `503 ${errorMessage}`,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
type JsonRecord = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
const MODEL_ID = "MBZUAI-IFM/K2-Think-v2";
|
|
6
|
+
|
|
7
|
+
function isRecord(value: unknown): value is JsonRecord {
|
|
8
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeTextContent(content: unknown): unknown {
|
|
12
|
+
if (!Array.isArray(content)) return content;
|
|
13
|
+
if (
|
|
14
|
+
!content.every(
|
|
15
|
+
(part) =>
|
|
16
|
+
isRecord(part) &&
|
|
17
|
+
part.type === "text" &&
|
|
18
|
+
typeof part.text === "string",
|
|
19
|
+
)
|
|
20
|
+
) {
|
|
21
|
+
return content;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return content.map((part) => part.text).join("");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function (pi: ExtensionAPI) {
|
|
28
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
29
|
+
if (ctx.model?.provider !== "ifm" || ctx.model.id !== MODEL_ID) return;
|
|
30
|
+
if (!isRecord(event.payload) || !Array.isArray(event.payload.messages)) return;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
...event.payload,
|
|
34
|
+
messages: event.payload.messages.map((message) => {
|
|
35
|
+
if (!isRecord(message)) return message;
|
|
36
|
+
return { ...message, content: normalizeTextContent(message.content) };
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
package/lib/cli/args.mjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
function parseArgs(argv, { defaultAgentsDir, resolvePath
|
|
1
|
+
function parseArgs(argv, { defaultAgentsDir, resolvePath }) {
|
|
2
2
|
const options = {
|
|
3
3
|
agentsDir: defaultAgentsDir,
|
|
4
4
|
force: false,
|
|
5
|
-
relinkOnly: false,
|
|
6
|
-
nonInteractive: false,
|
|
7
5
|
command: "menu"
|
|
8
6
|
};
|
|
9
7
|
const positional = [];
|
|
@@ -15,23 +13,15 @@ function parseArgs(argv, { defaultAgentsDir, resolvePath, env = process.env }) {
|
|
|
15
13
|
options.force = true;
|
|
16
14
|
continue;
|
|
17
15
|
}
|
|
18
|
-
if (arg === "--link-only") {
|
|
19
|
-
options.relinkOnly = true;
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
16
|
if (arg === "--agents-dir") {
|
|
23
17
|
const value = argv[i + 1];
|
|
24
|
-
if (!value) {
|
|
18
|
+
if (!value || value.startsWith("-")) {
|
|
25
19
|
throw new Error("--agents-dir requires a path");
|
|
26
20
|
}
|
|
27
21
|
options.agentsDir = resolvePath(value);
|
|
28
22
|
i += 1;
|
|
29
23
|
continue;
|
|
30
24
|
}
|
|
31
|
-
if (arg === "--non-interactive") {
|
|
32
|
-
options.nonInteractive = true;
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
25
|
if (arg === "--help" || arg === "-h" || arg === "help") {
|
|
36
26
|
helpRequested = true;
|
|
37
27
|
options.command = "help";
|
|
@@ -57,17 +47,13 @@ function parseArgs(argv, { defaultAgentsDir, resolvePath, env = process.env }) {
|
|
|
57
47
|
}
|
|
58
48
|
}
|
|
59
49
|
|
|
60
|
-
if (options.command !== "install" && (options.force || options.
|
|
61
|
-
throw new Error("`--force`、`--
|
|
50
|
+
if (options.command !== "install" && (options.force || options.agentsDir !== defaultAgentsDir)) {
|
|
51
|
+
throw new Error("`--force`、`--agents-dir` 仅能与 `install` 命令一起使用");
|
|
62
52
|
}
|
|
63
|
-
if (!options.nonInteractive && env.CI) options.nonInteractive = true;
|
|
64
53
|
return options;
|
|
65
54
|
}
|
|
66
55
|
|
|
67
|
-
function assertInteractiveMenuSupported({ command, inputIsTTY, outputIsTTY
|
|
68
|
-
if (command === "menu" && nonInteractive) {
|
|
69
|
-
throw new Error("非交互模式已启用;请显式使用 `npx abelworkflow install` 进行安装");
|
|
70
|
-
}
|
|
56
|
+
function assertInteractiveMenuSupported({ command, inputIsTTY, outputIsTTY }) {
|
|
71
57
|
if (command === "menu" && (!inputIsTTY || !outputIsTTY)) {
|
|
72
58
|
throw new Error("交互式菜单需要 TTY 终端;非交互场景请显式使用 `npx abelworkflow install`");
|
|
73
59
|
}
|