chore: upgrade CLIProxyAPI to v7.2.145
This commit is contained in:
+1
-1
Submodule .externals/CLIProxyAPI updated: 0a14eb70ce...d9cea8904b
@@ -34,8 +34,8 @@
|
||||
|
||||
- 本地 CPA 在 WSL 中运行,项目运行目录统一使用 `cpa-plugin/.runtime`;Windows 客户端经 `127.0.0.1` 访问 WSL 时,localhost 转发层可能不会立即向 CPA 传递客户端断开,取消链路测试优先在 WSL 内直连执行。
|
||||
- 从 PowerShell 跨 WSL 或 SSH 执行包含 Bash 变量、命令替换或多层引号的逻辑时,优先在 `.runtime/tmp/` 创建临时脚本后传入目标环境执行,不拼接复杂的单行 shell;临时脚本不提交。
|
||||
- CLIProxyAPI 宿主补丁保存在 `patch/`,当前已验证基线为 `v7.2.139` / `0a14eb70`,必须按 `usage-context`、`usage-identity`、`request-lifecycle-cancel` 的顺序应用;升级 CPA 后按 `docs/operations.md` 核对补丁和已知上游测试例外。
|
||||
- 构建本项目 patched CPA 时必须按上游 release workflow 使用 `-ldflags` 注入 `main.Version`、`main.Commit` 和 `main.BuildDate`。`Version` 固定使用“当前上游最新 tag + `-dev`”格式(例如 `v7.2.139-dev`),用于明确区分官方 Release 与本项目补丁构建;不得发布只显示 `dev`、`none` 或 `unknown` 的产物。上传前先在本地执行二进制并核对完整版本输出。
|
||||
- CLIProxyAPI 宿主补丁保存在 `patch/`,当前基线为 `v7.2.145` / `d9cea890`,必须按 `usage-identity`、`request-lifecycle-cancel` 的顺序应用;原 `usage-context` 修复已由上游吸收。升级 CPA 后按 `docs/operations.md` 核对补丁和已知上游测试例外。
|
||||
- 构建本项目 patched CPA 时必须按上游 release workflow 使用 `-ldflags` 注入 `main.Version`、`main.Commit` 和 `main.BuildDate`。`Version` 固定使用“当前上游最新 tag + `-dev`”格式(例如 `v7.2.145-dev`),用于明确区分官方 Release 与本项目补丁构建;不得发布只显示 `dev`、`none` 或 `unknown` 的产物。上传前先在本地执行二进制并核对完整版本输出。
|
||||
- CPA Linux 可执行文件解压后约 60 MiB,`gzip -9` 后约 20 MiB。上传远端时使用 gzip/tar.gz 或 `scp -C`,不要直接传输未压缩二进制;部署后的运行文件仍需解压为原始可执行文件。
|
||||
- 实际服务器为 `root@akko.pchuan.top`,CPA 根目录为 `/root/cpa`。只维护本项目的 `billing` 和已要求启用的 `keeper`,不启用或部署其他参考插件。
|
||||
- 远端替换 CPA 或 billing 前,先停止 `/root/cpa/cpa.sh` 管理的进程,并把旧二进制、插件和 `data/cpa-ext.db*` 备份到 `/root/cpa/backups/<timestamp>/`;新版本启动失败时恢复旧二进制和插件。
|
||||
|
||||
@@ -66,10 +66,10 @@ CLIProxyAPI 负责 HTTP 接入、协议转换、上游凭证和实际请求执
|
||||
|
||||
## 当前兼容目标
|
||||
|
||||
- CLIProxyAPI 基线:`v7.2.139` / `0a14eb70ce19fac1d114bcdb4a476d61adc819e2`,由 `.externals/CLIProxyAPI` submodule 锁定,需依次应用 `patch/` 中三项宿主补丁
|
||||
- CLIProxyAPI 基线:`v7.2.145` / `d9cea8904b14fbbebb77ef26e98ef08f6b48a724`,由 `.externals/CLIProxyAPI` submodule 锁定,需依次应用 `patch/` 中两项宿主补丁
|
||||
- 插件版本:`0.1.0`
|
||||
- Native ABI:`1`
|
||||
- RPC schema:最高 `4`,注册时按宿主版本向下协商;schema v4 提供 Usage 请求身份
|
||||
- RPC schema:最高 `5`,注册时按宿主版本向下协商;schema v5 提供 Usage 请求身份,schema v4 保留上游 WebSocket 响应观察契约
|
||||
- 插件 ID / 动态库文件名:`billing`
|
||||
- 已声明能力:`frontend_auth_provider`(独占)、`scheduler`、`request_interceptor`、`request_lifecycle_plugin`、`usage_plugin`、`management_api`
|
||||
|
||||
@@ -143,7 +143,7 @@ CGO_ENABLED=1 go test ./...
|
||||
|
||||
请求明细由服务端分页,每页 100 条,默认按请求时间和稳定 ID 倒序展示。管理接口支持时间范围、用户 Key、模型、结果、上游 Auth ID、端点和完整 Request ID 筛选;连续翻页使用与筛选条件绑定的游标,数字页码跳转使用 SQLite 索引定位。用户页的今日汇总、各用户用量和近 7 日 Token 由数据库独立聚合,不受当前明细页影响。
|
||||
|
||||
升级已有数据库时,插件会从原始 Usage 和请求终态事实自动建立轻量查询投影。事实表、计费账目和历史统计保持不变;没有 Usage 的取消或失败请求仍然可见。patched CPA 使用 RPC schema v4 向 billing 传递 Usage Request ID 和 Trace ID,可以按 Request ID 精确关联请求终态。旧宿主或旧 schema 没有请求身份时,只有模型和时间足够接近且匹配关系唯一才关联;存在并发歧义时保留为独立记录。
|
||||
升级已有数据库时,插件会从原始 Usage 和请求终态事实自动建立轻量查询投影。事实表、计费账目和历史统计保持不变;没有 Usage 的取消或失败请求仍然可见。patched CPA 使用 RPC schema v5 向 billing 传递 Usage Request ID 和 Trace ID,可以按 Request ID 精确关联请求终态。旧宿主或旧 schema 没有请求身份时,只有模型和时间足够接近且匹配关系唯一才关联;存在并发歧义时保留为独立记录。
|
||||
|
||||
## 工程布局
|
||||
|
||||
|
||||
@@ -33,14 +33,14 @@ CLIProxyAPI 会通过两个独立回调提供请求信息:
|
||||
## 请求关联与独立用量
|
||||
|
||||
- Request ID 用于关联一次下游请求的生命周期。
|
||||
- RPC schema v4 的 Usage 契约提供 Request ID 和 Trace ID,用于与请求终态精确关联。
|
||||
- RPC schema v5 的 Usage 契约提供 Request ID 和 Trace ID,用于与请求终态精确关联。
|
||||
- 旧 schema 或历史 Usage 没有请求身份时,仍使用保守的唯一时间窗口匹配作为兼容路径。
|
||||
- 每条可区分的 Usage 事实分别保存,不会为了得到一条整齐记录而把多个用量相加。
|
||||
- 当前契约无法保证把每次上游重试稳定标记为某个 Execution ID,因此管理台不声明这种保证。
|
||||
- 没有 Usage 的拒绝、取消或失败请求仍然显示请求终态。
|
||||
- 只有 Usage、暂时没有终态的记录也可以单独显示,终态到达后投影会自动更新。
|
||||
|
||||
schema v4 记录按 Request ID 精确归并,不受同模型并发影响。只有旧 schema 或历史孤立记录才使用模型和请求时间匹配;存在并发歧义时宁可保留为两条,也不会错误关联到其他用户的请求。
|
||||
schema v5 记录按 Request ID 精确归并,不受同模型并发影响。只有旧 schema 或历史孤立记录才使用模型和请求时间匹配;存在并发歧义时宁可保留为两条,也不会错误关联到其他用户的请求。
|
||||
|
||||
## 请求结果
|
||||
|
||||
|
||||
+10
-29
@@ -4,11 +4,11 @@
|
||||
|
||||
| 项目 | 当前值 |
|
||||
|---|---|
|
||||
| CLIProxyAPI Tag | `v7.2.139` |
|
||||
| submodule 提交 | `0a14eb70ce19fac1d114bcdb4a476d61adc819e2` |
|
||||
| patched 构建版本 | `v7.2.139-dev` |
|
||||
| CLIProxyAPI Tag | `v7.2.145` |
|
||||
| submodule 提交 | `d9cea8904b14fbbebb77ef26e98ef08f6b48a724` |
|
||||
| patched 构建版本 | `v7.2.145-dev` |
|
||||
| Native ABI | `1` |
|
||||
| RPC schema | `4` |
|
||||
| RPC schema | `5` |
|
||||
| Go | `1.26.x` |
|
||||
| 本地运行目录 | `.runtime` |
|
||||
| 本地管理页面 | `http://127.0.0.1:8317/management.html` |
|
||||
@@ -27,35 +27,17 @@ DeepSeek 必须配置在 CPA 的 `codex-api-key` 通道,`base-url` 使用 `htt
|
||||
|
||||
升级宿主或插件时保留配置和数据库,不重复初始化 Key、价格和额度。
|
||||
|
||||
## 已知上游测试失败
|
||||
## 上游测试状态
|
||||
|
||||
CLIProxyAPI `0a14eb70` 在 Debian WSL 中运行 `go test ./... -count=1` 时有以下失败:
|
||||
|
||||
| 测试 | 实际值 | 期望值 |
|
||||
|---|---|---|
|
||||
| `TestApplyClaudeHeaders_DisableDeviceProfileStabilization` | `X-Stainless-Os=MacOS` | `Linux` |
|
||||
| `TestApplyClaudeHeaders_LegacyModePreservesConfiguredUserAgentOverrideForClaudeClients` | `X-Stainless-Os=MacOS` | `Linux` |
|
||||
| `TestClaudeExecutor_NonClaudeRequestUsesClaudeCode220CLIFingerprint` | `X-Stainless-Os=MacOS` | `Linux` |
|
||||
| `TestInPlaceByteWritesAreReviewed` | `internal/home/client.go` 清单项已过期 | 文件已无原地字节写入 |
|
||||
| `TestInPlaceByteWritesAreReviewed` | `internal/pluginstore/auth.go` 清单项已过期 | 文件已无原地字节写入 |
|
||||
|
||||
2026-08-22 已在未应用任何 billing 补丁的纯上游 `0a14eb70` 临时 worktree 中复现以上 5 项。三项补丁不修改 `internal/runtime/executor`、`internal/util`、`internal/home` 或 `internal/pluginstore`,因此同一提交、同一 WSL 环境、同一失败内容再次出现时,直接记为已知上游失败,不修复、不再创建干净 worktree复测。
|
||||
|
||||
出现以下任一情况时重新验证:
|
||||
|
||||
- submodule 提交不再是 `0a14eb70`;
|
||||
- 上游修改了 `internal/runtime/executor/claude_executor_test.go`、`claude_executor_request.go` 或 `helps/claude_device_profile.go`;
|
||||
- 失败测试、行号、实际值或期望值发生变化;
|
||||
- 三项补丁开始修改 `internal/runtime/executor`。
|
||||
CLIProxyAPI `v7.2.145` / `d9cea890` 在 Debian WSL 中运行 `go test ./... -count=1` 全部通过。`v7.2.139` 记录的 3 个 Claude 指纹失败和 2 个原地字节写入清单失败均已由上游修复,不再列为当前例外。
|
||||
|
||||
## submodule 升级
|
||||
|
||||
当前 submodule 工作区包含三项已应用补丁。升级前必须按反方向撤销,再切换上游提交:
|
||||
当前 submodule 工作区包含两项已应用补丁。升级前必须按反方向撤销,再切换上游提交:
|
||||
|
||||
```bash
|
||||
git -C .externals/CLIProxyAPI apply --reverse ../../patch/cli-proxy-api-request-lifecycle-cancel.patch
|
||||
git -C .externals/CLIProxyAPI apply --reverse ../../patch/cli-proxy-api-usage-identity.patch
|
||||
git -C .externals/CLIProxyAPI apply --reverse ../../patch/cli-proxy-api-usage-context.patch
|
||||
git -C .externals/CLIProxyAPI fetch --tags --prune origin
|
||||
git -C .externals/CLIProxyAPI switch --detach origin/main
|
||||
```
|
||||
@@ -63,7 +45,6 @@ git -C .externals/CLIProxyAPI switch --detach origin/main
|
||||
重新应用补丁:
|
||||
|
||||
```bash
|
||||
git -C .externals/CLIProxyAPI apply ../../patch/cli-proxy-api-usage-context.patch
|
||||
git -C .externals/CLIProxyAPI apply ../../patch/cli-proxy-api-usage-identity.patch
|
||||
git -C .externals/CLIProxyAPI apply ../../patch/cli-proxy-api-request-lifecycle-cancel.patch
|
||||
git -C .externals/CLIProxyAPI diff --check
|
||||
@@ -98,7 +79,7 @@ go test -race ./... -count=1
|
||||
|
||||
补丁正确性的最低标准如下:
|
||||
|
||||
- 三项补丁按顺序应用成功,`git diff --check` 通过;
|
||||
- 两项补丁按顺序应用成功,`git diff --check` 通过;
|
||||
- `internal/pluginhost`、`sdk/api/handlers`、`sdk/cliproxy/usage` race 测试通过;
|
||||
- billing 全量测试和全量 race 测试通过;
|
||||
- CPA 全量测试除已记录的纯上游失败外通过;
|
||||
@@ -110,7 +91,7 @@ CPA 版本必须使用当前最新 Tag 加 `-dev`,提交取 submodule HEAD:
|
||||
|
||||
```bash
|
||||
cd .externals/CLIProxyAPI
|
||||
version=v7.2.139-dev
|
||||
version=v7.2.145-dev
|
||||
commit="$(git rev-parse --short=8 HEAD)"
|
||||
build_date="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
CGO_ENABLED=1 go build -buildvcs=false \
|
||||
@@ -171,7 +152,7 @@ bash scripts/run-test-host.sh
|
||||
|
||||
- fetch `origin/main` 并要求 submodule HEAD 与其一致;
|
||||
- 要求 submodule 修改文件集合与 `patch/*.patch` 声明一致;
|
||||
- 运行三个补丁相关包的 race 测试;
|
||||
- 运行补丁相关三个包的 race 测试;
|
||||
- 使用最新 Tag 加 `-dev` 和当前提交、构建时间编译核心;
|
||||
- 生成 gzip 最高压缩级别的核心包和 SHA-256;
|
||||
- 上传后在远端重新校验压缩包、版本和核心哈希;
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
const (
|
||||
ABIVersion uint32 = 1
|
||||
SchemaVersion uint32 = 4
|
||||
SchemaVersion uint32 = 5
|
||||
PluginName = "billing"
|
||||
Version = "0.1.0"
|
||||
)
|
||||
|
||||
+19
-23
@@ -1,17 +1,16 @@
|
||||
# CLIProxyAPI 宿主补丁
|
||||
|
||||
这里保存 billing 最初为 CLIProxyAPI `v7.2.133` 制作、已在 `v7.2.139` 重新验证的三项宿主修复。三项补丁都不改变计费规则或数据库结构,必须按下列顺序应用:
|
||||
这里保存 billing 最初为 CLIProxyAPI `v7.2.133` 制作、已在 `v7.2.145` 重新验证的两项宿主修复。补丁都不改变计费规则或数据库结构,必须按下列顺序应用:
|
||||
|
||||
1. [`cli-proxy-api-usage-context.patch`](cli-proxy-api-usage-context.patch)
|
||||
2. [`cli-proxy-api-usage-identity.patch`](cli-proxy-api-usage-identity.patch)
|
||||
3. [`cli-proxy-api-request-lifecycle-cancel.patch`](cli-proxy-api-request-lifecycle-cancel.patch)
|
||||
1. [`cli-proxy-api-usage-identity.patch`](cli-proxy-api-usage-identity.patch)
|
||||
2. [`cli-proxy-api-request-lifecycle-cancel.patch`](cli-proxy-api-request-lifecycle-cancel.patch)
|
||||
|
||||
适用基线:
|
||||
|
||||
- CLIProxyAPI:`v7.2.139`
|
||||
- 提交:`0a14eb70ce19fac1d114bcdb4a476d61adc819e2`
|
||||
- CLIProxyAPI:`v7.2.145`
|
||||
- 提交:`d9cea8904b14fbbebb77ef26e98ef08f6b48a724`
|
||||
- Native ABI:`1`
|
||||
- 第二项补丁把 RPC schema 从 `3` 提升到 `4`
|
||||
- 上游 RPC schema 为 `4`;第一项补丁把 schema 提升到 `5`
|
||||
|
||||
升级 CLIProxyAPI 后不要直接假设补丁仍然需要或仍能应用。应先在新版本复测,再根据新版本的宿主契约重新核对。
|
||||
|
||||
@@ -25,7 +24,7 @@
|
||||
|
||||
CLIProxyAPI 会异步分发最终用量记录,但分发任务沿用了原始 HTTP 请求上下文。非流式响应很快结束后,该上下文会被取消;动态插件加载器在调用 C ABI 前检查 `ctx.Done()`,于是跳过插件调用。内置插件不经过动态 C ABI 检查,所以仍能收到记录。
|
||||
|
||||
第一项补丁在动态用量适配器入口使用 `context.WithoutCancel`:
|
||||
该修复已由上游 `b7f6c15f` 在 `v7.2.143` 吸收。上游现在会在动态用量适配器入口使用 `context.WithoutCancel`:
|
||||
|
||||
- 保留上下文值;
|
||||
- 不再继承已经结束的 HTTP 请求的取消信号和截止时间;
|
||||
@@ -38,13 +37,13 @@ CLIProxyAPI 的请求生命周期会生成唯一 Request ID,并通过 `request
|
||||
|
||||
串行请求通常可以唯一匹配;同用户、同模型高并发时会出现多个等价候选。billing 为避免错配,保守地保留一条 request-only 和一条 usage-only,因此请求明细数量可能翻倍,但底层用量和扣费并未重复。
|
||||
|
||||
第二项补丁引入 RPC schema v4:
|
||||
第一项补丁引入 RPC schema v5:
|
||||
|
||||
- 请求生命周期创建后,把 Request ID 和父 Trace ID 写入实际执行上下文;
|
||||
- `usage.handle` 增加可选的 `RequestID` 与 `TraceID`;
|
||||
- 只有协商到 schema v4 的插件收到新增字段;
|
||||
- schema v1–v3 插件保持原有载荷,能够继续加载;
|
||||
- billing v4 按 Request ID 精确关联,旧宿主和历史数据仍走保守兼容逻辑。
|
||||
- 只有协商到 schema v5 的插件收到新增字段;
|
||||
- schema v1–v4 插件保持原有载荷,能够继续加载,其中 schema v4 继续表示上游 WebSocket observer 契约;
|
||||
- billing v5 按 Request ID 精确关联,旧宿主和历史数据仍走保守兼容逻辑。
|
||||
|
||||
补丁不虚构 Execution ID。一次生命周期内可能存在多个上游尝试时,只有宿主能够提供真实的逐尝试身份;当前修复只传递已经存在且语义明确的生命周期 Request ID。
|
||||
|
||||
@@ -54,7 +53,7 @@ billing 在请求拦截阶段创建并发占用,依赖 CPA 后续发送唯一
|
||||
|
||||
如果执行上下文恰好在上游连接、鉴权选择或首段流初始化期间取消,流消费协程可能尚未建立。此时 `request.complete` 必须等待同步启动调用返回;若自定义执行器或异常上游没有及时响应取消,就会表现为并发槽位长期占用。内置 Codex 执行器通常会随 context 立即返回,所以这是生命周期层的防御性加固,不是内置执行器的稳定必现缺陷。
|
||||
|
||||
第三项补丁在请求生命周期创建时直接注册上下文取消回调:
|
||||
第二项补丁在请求生命周期创建时直接注册上下文取消回调:
|
||||
|
||||
- 不依赖执行器或流转发协程是否已经启动;
|
||||
- 取消后立即发送 `outcome=canceled`、`status_code=0` 的终态;
|
||||
@@ -75,8 +74,6 @@ git submodule update --init --recursive
|
||||
然后从主仓库根目录执行:
|
||||
|
||||
```bash
|
||||
git -C .externals/CLIProxyAPI apply --check ../../patch/cli-proxy-api-usage-context.patch
|
||||
git -C .externals/CLIProxyAPI apply ../../patch/cli-proxy-api-usage-context.patch
|
||||
git -C .externals/CLIProxyAPI apply --check ../../patch/cli-proxy-api-usage-identity.patch
|
||||
git -C .externals/CLIProxyAPI apply ../../patch/cli-proxy-api-usage-identity.patch
|
||||
git -C .externals/CLIProxyAPI apply --check ../../patch/cli-proxy-api-request-lifecycle-cancel.patch
|
||||
@@ -84,8 +81,8 @@ git -C .externals/CLIProxyAPI apply ../../patch/cli-proxy-api-request-lifecycle-
|
||||
cd .externals/CLIProxyAPI
|
||||
gofmt -w internal/pluginhost sdk/api/handlers sdk/cliproxy/usage sdk/pluginabi sdk/pluginapi
|
||||
go test -race ./internal/pluginhost ./sdk/api/handlers ./sdk/cliproxy/usage -count=1
|
||||
version=v7.2.139-dev
|
||||
commit=0a14eb70
|
||||
version=v7.2.145-dev
|
||||
commit=d9cea890
|
||||
build_date="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
CGO_ENABLED=1 go build -buildvcs=false \
|
||||
-ldflags="-s -w -X main.Version=$version -X main.Commit=$commit -X main.BuildDate=$build_date" \
|
||||
@@ -98,7 +95,7 @@ rm test-output
|
||||
|
||||
应用补丁后 submodule 显示工作区修改是预期状态;主仓库只提交 submodule 的官方基线指针和 `patch/`,不提交官方仓库中的派生提交。
|
||||
|
||||
billing 必须使用 RPC schema v4 重新构建;旧 billing 动态库会向下协商到旧 schema,无法获得 Usage 请求身份。
|
||||
billing 必须使用 RPC schema v5 重新构建;旧 billing 动态库会向下协商到旧 schema,无法获得 Usage 请求身份。
|
||||
|
||||
## 验证标准
|
||||
|
||||
@@ -116,7 +113,7 @@ billing 必须使用 RPC schema v4 重新构建;旧 billing 动态库会向下
|
||||
|
||||
## 已验证结果
|
||||
|
||||
三个补丁与 billing schema v4 使用真实 DeepSeek 上游完成了本地验证:
|
||||
原三个补丁与 billing schema v4 使用真实 DeepSeek 上游完成过本地验证;`v7.2.145` 的两项补丁与 schema v5 已通过静态测试、全量测试和构建,真实模型链路仍需在本地 CPA 替换后重新验收:
|
||||
|
||||
- `cpads` 有 Key 成功、无 Key 返回 401;
|
||||
- 非流式和 SSE 各一次:2 条明细、2 次扣费,等待 4 秒无重复;
|
||||
@@ -124,7 +121,7 @@ billing 必须使用 RPC schema v4 重新构建;旧 billing 动态库会向下
|
||||
- 流式与非流式各 6 条,终态、状态码、Token 和 Trace ID 完整;
|
||||
- keeper 同步增加 12 条,等待 5 秒没有迟到重复;
|
||||
- WSL 内直连分别在 50ms(首字节前)和 1s(流传输中)强制断开:两次均只有一条 canceled 明细,并发占用立即回到零;
|
||||
- 仅前两项补丁与三项补丁在内置 Codex 执行器上的直连取消 A/B 都能正常终止;第三项补丁额外覆盖执行器启动阻塞且未响应 context 的单元场景;
|
||||
- 取消补丁额外覆盖执行器启动阻塞且未响应 context 的单元场景;
|
||||
- 请求正常结束后 `active_requests=0`,空闲 5 秒没有持续 CPU 占用;
|
||||
- billing 全部测试、billing race 测试及 CLIProxyAPI 相关包 race 测试通过;
|
||||
- schema v3 对照插件仍能正常注册,新增字段不会发送给旧 schema。
|
||||
@@ -133,7 +130,7 @@ billing 必须使用 RPC schema v4 重新构建;旧 billing 动态库会向下
|
||||
|
||||
## 性能影响
|
||||
|
||||
三项修复都不增加队列、重试或流式分块回调。每个最终用量和请求终态仍最多调用插件一次;新增工作是创建轻量上下文包装、在 schema v4 JSON 中增加两个短字符串,以及为活跃请求注册一个随正常完成立即撤销的标准库取消回调。并发验证中没有观察到持续 CPU 或内存增长。
|
||||
两项补丁都不增加队列、重试或流式分块回调。每个最终用量和请求终态仍最多调用插件一次;新增工作是在 schema v5 JSON 中增加两个短字符串,以及为活跃请求注册一个随正常完成立即撤销的标准库取消回调。
|
||||
|
||||
## 回滚
|
||||
|
||||
@@ -142,7 +139,6 @@ billing 必须使用 RPC schema v4 重新构建;旧 billing 动态库会向下
|
||||
```bash
|
||||
git apply -R /path/to/billing/patch/cli-proxy-api-request-lifecycle-cancel.patch
|
||||
git apply -R /path/to/billing/patch/cli-proxy-api-usage-identity.patch
|
||||
git apply -R /path/to/billing/patch/cli-proxy-api-usage-context.patch
|
||||
```
|
||||
|
||||
部署回滚应恢复升级前保存的 CLIProxyAPI 和 billing 动态库后重启宿主。数据库不需要回滚;schema v4 只让新记录获得精确身份,不改变历史账目。
|
||||
部署回滚应恢复升级前保存的 CLIProxyAPI 和 billing 动态库后重启宿主。数据库不需要回滚;schema v5 只让新记录获得精确身份,不改变历史账目。
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
diff --git a/sdk/api/handlers/handlers_interceptors.go b/sdk/api/handlers/handlers_interceptors.go
|
||||
index 47d56e2a..621f0d37 100644
|
||||
--- a/sdk/api/handlers/handlers_interceptors.go
|
||||
+++ b/sdk/api/handlers/handlers_interceptors.go
|
||||
@@ -1,6 +1,7 @@
|
||||
@@ -9,7 +10,7 @@ diff --git a/sdk/api/handlers/handlers_interceptors.go b/sdk/api/handlers/handle
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -66,6 +67,9 @@ type requestLifecycleSkipHost interface {
|
||||
@@ -88,6 +89,9 @@ func webSocketResponseObserversEnabled(host PluginInterceptorHost) bool {
|
||||
|
||||
type requestLifecycleTracker struct {
|
||||
once sync.Once
|
||||
@@ -19,7 +20,7 @@ diff --git a/sdk/api/handlers/handlers_interceptors.go b/sdk/api/handlers/handle
|
||||
ctx context.Context
|
||||
host PluginInterceptorHost
|
||||
skipPluginID string
|
||||
@@ -76,7 +80,7 @@ func (h *BaseAPIHandler) newRequestLifecycleTracker(ctx context.Context, sourceF
|
||||
@@ -98,7 +102,7 @@ func (h *BaseAPIHandler) newRequestLifecycleTracker(ctx context.Context, sourceF
|
||||
requestID := uuid.NewString()
|
||||
traceID := logging.GetRequestID(ctx)
|
||||
ctx = coreusage.WithRequestIdentity(ctx, requestID, traceID)
|
||||
@@ -28,7 +29,7 @@ diff --git a/sdk/api/handlers/handlers_interceptors.go b/sdk/api/handlers/handle
|
||||
ctx: ctx,
|
||||
host: h.interceptorHost(),
|
||||
skipPluginID: skipPluginID,
|
||||
@@ -91,6 +95,39 @@ func (h *BaseAPIHandler) newRequestLifecycleTracker(ctx context.Context, sourceF
|
||||
@@ -113,6 +117,39 @@ func (h *BaseAPIHandler) newRequestLifecycleTracker(ctx context.Context, sourceF
|
||||
Metadata: metadata,
|
||||
},
|
||||
}
|
||||
@@ -68,7 +69,7 @@ diff --git a/sdk/api/handlers/handlers_interceptors.go b/sdk/api/handlers/handle
|
||||
}
|
||||
|
||||
func (t *requestLifecycleTracker) executionContext() context.Context {
|
||||
@@ -112,6 +149,7 @@ func (t *requestLifecycleTracker) complete(outcome pluginapi.RequestCompletionOu
|
||||
@@ -134,6 +171,7 @@ func (t *requestLifecycleTracker) complete(outcome pluginapi.RequestCompletionOu
|
||||
return
|
||||
}
|
||||
t.once.Do(func() {
|
||||
@@ -77,9 +78,10 @@ diff --git a/sdk/api/handlers/handlers_interceptors.go b/sdk/api/handlers/handle
|
||||
completion.Outcome = outcome
|
||||
completion.StatusCode = statusCode
|
||||
diff --git a/sdk/api/handlers/handlers_interceptors_test.go b/sdk/api/handlers/handlers_interceptors_test.go
|
||||
index 861583fe..422b8f86 100644
|
||||
--- a/sdk/api/handlers/handlers_interceptors_test.go
|
||||
+++ b/sdk/api/handlers/handlers_interceptors_test.go
|
||||
@@ -516,6 +516,78 @@ func TestHandlerLifecycleCompletesCanceledStream(t *testing.T) {
|
||||
@@ -531,6 +531,78 @@ func TestHandlerLifecycleCompletesCanceledStream(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
diff --git a/internal/pluginhost/adapters_test.go b/internal/pluginhost/adapters_test.go
|
||||
index de62918e..302b1f38 100644
|
||||
--- a/internal/pluginhost/adapters_test.go
|
||||
+++ b/internal/pluginhost/adapters_test.go
|
||||
@@ -2234,6 +2234,39 @@ func TestUsageAdapterPanicFusesPlugin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
+func TestUsageAdapterDetachesCanceledRequestContext(t *testing.T) {
|
||||
+ type contextKey struct{}
|
||||
+ const contextValue = "usage-context-value"
|
||||
+
|
||||
+ called := 0
|
||||
+ plugin := usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
|
||||
+ called++
|
||||
+ if errContext := ctx.Err(); errContext != nil {
|
||||
+ t.Fatalf("usage context error = %v, want nil", errContext)
|
||||
+ }
|
||||
+ if value := ctx.Value(contextKey{}); value != contextValue {
|
||||
+ t.Fatalf("usage context value = %v, want %q", value, contextValue)
|
||||
+ }
|
||||
+ })
|
||||
+ host := newHostWithRecords(capabilityRecord{
|
||||
+ id: "usage-canceled-context",
|
||||
+ plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
|
||||
+ UsagePlugin: plugin,
|
||||
+ }},
|
||||
+ })
|
||||
+ adapter := &usageAdapter{
|
||||
+ host: host,
|
||||
+ pluginID: "usage-canceled-context",
|
||||
+ }
|
||||
+
|
||||
+ ctx, cancel := context.WithCancel(context.WithValue(context.Background(), contextKey{}, contextValue))
|
||||
+ cancel()
|
||||
+ adapter.HandleUsage(ctx, coreusage.Record{Provider: "plugin-provider"})
|
||||
+ if called != 1 {
|
||||
+ t.Fatalf("usage plugin calls = %d, want 1", called)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
func TestUsageAdapterNormalizesOmittedGenerateToTrue(t *testing.T) {
|
||||
var gotGenerate bool
|
||||
plugin := usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
|
||||
diff --git a/internal/pluginhost/adapters_usage_translation.go b/internal/pluginhost/adapters_usage_translation.go
|
||||
index 2201eb6c..93a4c8a5 100644
|
||||
--- a/internal/pluginhost/adapters_usage_translation.go
|
||||
+++ b/internal/pluginhost/adapters_usage_translation.go
|
||||
@@ -132,6 +132,11 @@ func (a *usageAdapter) HandleUsage(ctx context.Context, record coreusage.Record)
|
||||
if a == nil {
|
||||
return
|
||||
}
|
||||
+ if ctx == nil {
|
||||
+ ctx = context.Background()
|
||||
+ } else {
|
||||
+ ctx = context.WithoutCancel(ctx)
|
||||
+ }
|
||||
plugin := a.host.currentUsagePlugin(a.pluginID)
|
||||
if plugin == nil {
|
||||
return
|
||||
@@ -1,8 +1,8 @@
|
||||
diff --git a/internal/pluginhost/adapters_test.go b/internal/pluginhost/adapters_test.go
|
||||
index 302b1f38..610a3753 100644
|
||||
index 5b40f84c..55783b98 100644
|
||||
--- a/internal/pluginhost/adapters_test.go
|
||||
+++ b/internal/pluginhost/adapters_test.go
|
||||
@@ -2266,6 +2266,54 @@ func TestUsageAdapterDetachesCanceledRequestContext(t *testing.T) {
|
||||
@@ -2234,6 +2234,54 @@ func TestUsageAdapterPanicFusesPlugin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ index 302b1f38..610a3753 100644
|
||||
var gotGenerate bool
|
||||
plugin := usagePluginFunc(func(ctx context.Context, record pluginapi.UsageRecord) {
|
||||
diff --git a/internal/pluginhost/adapters_usage_translation.go b/internal/pluginhost/adapters_usage_translation.go
|
||||
index 93a4c8a5..ad046780 100644
|
||||
index a257db3e..2a822fc2 100644
|
||||
--- a/internal/pluginhost/adapters_usage_translation.go
|
||||
+++ b/internal/pluginhost/adapters_usage_translation.go
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
@@ -112,10 +112,10 @@ index 93a4c8a5..ad046780 100644
|
||||
ExecutorType: record.ExecutorType,
|
||||
Model: record.Model,
|
||||
diff --git a/sdk/api/handlers/handlers_execution.go b/sdk/api/handlers/handlers_execution.go
|
||||
index 994bd33a..3cc7743e 100644
|
||||
index ac521ec9..824aa7ad 100644
|
||||
--- a/sdk/api/handlers/handlers_execution.go
|
||||
+++ b/sdk/api/handlers/handlers_execution.go
|
||||
@@ -71,6 +71,7 @@ func (h *BaseAPIHandler) executeWithAuthManagerFormats(ctx context.Context, entr
|
||||
@@ -72,6 +72,7 @@ func (h *BaseAPIHandler) executeWithAuthManagerFormats(ctx context.Context, entr
|
||||
}
|
||||
afterAuthCapture := &requestAfterAuthCapture{}
|
||||
lifecycle := h.newRequestLifecycleTracker(ctx, entryProtocol, normalizedModel, originalRequestedModel, false, reqMeta, execOptions.SkipInterceptorPluginID)
|
||||
@@ -123,7 +123,7 @@ index 994bd33a..3cc7743e 100644
|
||||
opts := coreexecutor.Options{
|
||||
Stream: false,
|
||||
Alt: alt,
|
||||
@@ -136,6 +137,7 @@ func (h *BaseAPIHandler) executeCountWithAuthManager(ctx context.Context, handle
|
||||
@@ -138,6 +139,7 @@ func (h *BaseAPIHandler) executeCountWithAuthManager(ctx context.Context, handle
|
||||
}
|
||||
afterAuthCapture := &requestAfterAuthCapture{}
|
||||
lifecycle := h.newRequestLifecycleTracker(ctx, handlerType, normalizedModel, originalRequestedModel, false, reqMeta, execOptions.SkipInterceptorPluginID)
|
||||
@@ -131,7 +131,7 @@ index 994bd33a..3cc7743e 100644
|
||||
opts := coreexecutor.Options{
|
||||
Stream: false,
|
||||
Alt: alt,
|
||||
@@ -179,6 +181,7 @@ func (h *BaseAPIHandler) executeWithPluginExecutor(ctx context.Context, entryPro
|
||||
@@ -181,6 +183,7 @@ func (h *BaseAPIHandler) executeWithPluginExecutor(ctx context.Context, entryPro
|
||||
execCtx, nestedTracker := withNestedExecutionTracker(ctx)
|
||||
req, opts := h.pluginExecutorRequest(execCtx, entryProtocol, responseProtocol, modelName, originalRequestedModel, rawJSON, alt, false, execOptions)
|
||||
lifecycle := h.newRequestLifecycleTracker(execCtx, entryProtocol, modelName, originalRequestedModel, false, opts.Metadata, execOptions.SkipInterceptorPluginID)
|
||||
@@ -139,7 +139,7 @@ index 994bd33a..3cc7743e 100644
|
||||
var interceptErr *interfaces.ErrorMessage
|
||||
req, opts, interceptErr = h.applyRequestInterceptorsBeforeAuth(execCtx, entryProtocol, originalRequestedModel, lifecycle.requestID(), req, opts, execOptions.SkipInterceptorPluginID)
|
||||
if interceptErr != nil {
|
||||
@@ -211,6 +214,7 @@ func (h *BaseAPIHandler) countWithPluginExecutor(ctx context.Context, handlerTyp
|
||||
@@ -228,6 +231,7 @@ func (h *BaseAPIHandler) countWithPluginExecutor(ctx context.Context, handlerTyp
|
||||
}
|
||||
req, opts := h.pluginExecutorRequest(ctx, handlerType, handlerType, modelName, originalRequestedModel, rawJSON, alt, false, execOptions)
|
||||
lifecycle := h.newRequestLifecycleTracker(ctx, handlerType, modelName, originalRequestedModel, false, opts.Metadata, execOptions.SkipInterceptorPluginID)
|
||||
@@ -148,7 +148,7 @@ index 994bd33a..3cc7743e 100644
|
||||
req, opts, interceptErr = h.applyRequestInterceptorsBeforeAuth(ctx, handlerType, originalRequestedModel, lifecycle.requestID(), req, opts, execOptions.SkipInterceptorPluginID)
|
||||
if interceptErr != nil {
|
||||
diff --git a/sdk/api/handlers/handlers_interceptors.go b/sdk/api/handlers/handlers_interceptors.go
|
||||
index a8b35604..d27198c9 100644
|
||||
index d1b5d442..47d56e2a 100644
|
||||
--- a/sdk/api/handlers/handlers_interceptors.go
|
||||
+++ b/sdk/api/handlers/handlers_interceptors.go
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
@@ -159,7 +159,7 @@ index a8b35604..d27198c9 100644
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginapi"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
@@ -74,6 +75,7 @@ type requestLifecycleTracker struct {
|
||||
@@ -96,6 +97,7 @@ type requestLifecycleTracker struct {
|
||||
func (h *BaseAPIHandler) newRequestLifecycleTracker(ctx context.Context, sourceFormat, model, requestedModel string, stream bool, metadata map[string]any, skipPluginID string) *requestLifecycleTracker {
|
||||
requestID := uuid.NewString()
|
||||
traceID := logging.GetRequestID(ctx)
|
||||
@@ -167,7 +167,7 @@ index a8b35604..d27198c9 100644
|
||||
return &requestLifecycleTracker{
|
||||
ctx: ctx,
|
||||
host: h.interceptorHost(),
|
||||
@@ -91,6 +93,13 @@ func (h *BaseAPIHandler) newRequestLifecycleTracker(ctx context.Context, sourceF
|
||||
@@ -113,6 +115,13 @@ func (h *BaseAPIHandler) newRequestLifecycleTracker(ctx context.Context, sourceF
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ index a8b35604..d27198c9 100644
|
||||
if t == nil {
|
||||
return ""
|
||||
diff --git a/sdk/api/handlers/handlers_interceptors_test.go b/sdk/api/handlers/handlers_interceptors_test.go
|
||||
index 0b328f3e..1ad85527 100644
|
||||
index 42db2bcb..861583fe 100644
|
||||
--- a/sdk/api/handlers/handlers_interceptors_test.go
|
||||
+++ b/sdk/api/handlers/handlers_interceptors_test.go
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
@@ -193,7 +193,7 @@ index 0b328f3e..1ad85527 100644
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginapi"
|
||||
)
|
||||
@@ -230,6 +231,11 @@ func TestRequestLifecycleTrackerUsesUniqueExecutionIDs(t *testing.T) {
|
||||
@@ -245,6 +246,11 @@ func TestRequestLifecycleTrackerUsesUniqueExecutionIDs(t *testing.T) {
|
||||
if first.completion.TraceID != "trace-1" || second.completion.TraceID != "trace-1" {
|
||||
t.Fatalf("trace IDs = %q and %q", first.completion.TraceID, second.completion.TraceID)
|
||||
}
|
||||
@@ -206,7 +206,7 @@ index 0b328f3e..1ad85527 100644
|
||||
|
||||
func TestHandlerRequestInterceptorTerminatesBeforeAuth(t *testing.T) {
|
||||
diff --git a/sdk/api/handlers/handlers_stream.go b/sdk/api/handlers/handlers_stream.go
|
||||
index 9cceec54..dcdc08df 100644
|
||||
index f476e104..e72cb0af 100644
|
||||
--- a/sdk/api/handlers/handlers_stream.go
|
||||
+++ b/sdk/api/handlers/handlers_stream.go
|
||||
@@ -43,6 +43,7 @@ func (h *BaseAPIHandler) streamWithPluginExecutor(ctx context.Context, entryProt
|
||||
@@ -217,7 +217,7 @@ index 9cceec54..dcdc08df 100644
|
||||
var interceptErr *interfaces.ErrorMessage
|
||||
req, opts, interceptErr = h.applyRequestInterceptorsBeforeAuth(execCtx, entryProtocol, originalRequestedModel, lifecycle.requestID(), req, opts, execOptions.SkipInterceptorPluginID)
|
||||
if interceptErr != nil {
|
||||
@@ -305,6 +306,7 @@ func (h *BaseAPIHandler) executeStreamWithAuthManagerFormats(ctx context.Context
|
||||
@@ -330,6 +331,7 @@ func (h *BaseAPIHandler) executeStreamWithAuthManagerFormats(ctx context.Context
|
||||
}
|
||||
afterAuthCapture := &requestAfterAuthCapture{}
|
||||
lifecycle := h.newRequestLifecycleTracker(ctx, entryProtocol, normalizedModel, originalRequestedModel, true, reqMeta, execOptions.SkipInterceptorPluginID)
|
||||
@@ -291,51 +291,54 @@ index 6f7b1fbb..fc2b3120 100644
|
||||
// Existing callers construct Record without setting Generate.
|
||||
// Omission must remain distinguishable from explicit false and default to true.
|
||||
diff --git a/sdk/pluginabi/types.go b/sdk/pluginabi/types.go
|
||||
index 97c41a13..a8a3e470 100644
|
||||
index 0df98f49..7f2fcedb 100644
|
||||
--- a/sdk/pluginabi/types.go
|
||||
+++ b/sdk/pluginabi/types.go
|
||||
@@ -10,10 +10,13 @@ const (
|
||||
// Version 3 omits OriginalRequest/RequestBody on payload stream chunks
|
||||
@@ -11,13 +11,16 @@ const (
|
||||
// (ChunkIndex >= 0); those fields remain on StreamChunkHeaderInitIndex only.
|
||||
// Plugins that still need per-chunk request bodies should keep schema_version < 3.
|
||||
- SchemaVersion uint32 = 3
|
||||
+ SchemaVersion uint32 = 4
|
||||
// Version 4 adds upstream WebSocket response event observation.
|
||||
- SchemaVersion uint32 = 4
|
||||
+ SchemaVersion uint32 = 5
|
||||
// SchemaVersionStreamChunkOmitRequestBody is the first schema version that omits
|
||||
// request bodies on payload stream-chunk interceptor calls.
|
||||
SchemaVersionStreamChunkOmitRequestBody uint32 = 3
|
||||
// SchemaVersionWebSocketResponseObserver is the first schema version that supports
|
||||
// upstream WebSocket response event observation.
|
||||
SchemaVersionWebSocketResponseObserver uint32 = 4
|
||||
+ // SchemaVersionUsageIdentity is the first schema version that attaches
|
||||
+ // request lifecycle identity to usage records.
|
||||
+ SchemaVersionUsageIdentity uint32 = 4
|
||||
+ SchemaVersionUsageIdentity uint32 = 5
|
||||
)
|
||||
|
||||
const (
|
||||
diff --git a/sdk/pluginabi/types_test.go b/sdk/pluginabi/types_test.go
|
||||
index 8fa63542..86253848 100644
|
||||
index 60489e72..393529d3 100644
|
||||
--- a/sdk/pluginabi/types_test.go
|
||||
+++ b/sdk/pluginabi/types_test.go
|
||||
@@ -27,12 +27,15 @@ func TestEnvelopeRoundTrip(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMethodNamesAreStable(t *testing.T) {
|
||||
- if SchemaVersion != 3 {
|
||||
- t.Fatalf("SchemaVersion = %d, want 3", SchemaVersion)
|
||||
+ if SchemaVersion != 4 {
|
||||
+ t.Fatalf("SchemaVersion = %d, want 4", SchemaVersion)
|
||||
- if SchemaVersion != 4 {
|
||||
- t.Fatalf("SchemaVersion = %d, want 4", SchemaVersion)
|
||||
+ if SchemaVersion != 5 {
|
||||
+ t.Fatalf("SchemaVersion = %d, want 5", SchemaVersion)
|
||||
}
|
||||
if SchemaVersionWebSocketResponseObserver != 4 {
|
||||
t.Fatalf("SchemaVersionWebSocketResponseObserver = %d, want 4", SchemaVersionWebSocketResponseObserver)
|
||||
}
|
||||
+ if SchemaVersionUsageIdentity != 5 {
|
||||
+ t.Fatalf("SchemaVersionUsageIdentity = %d, want 5", SchemaVersionUsageIdentity)
|
||||
+ }
|
||||
if SchemaVersionStreamChunkOmitRequestBody != 3 {
|
||||
t.Fatalf("SchemaVersionStreamChunkOmitRequestBody = %d, want 3", SchemaVersionStreamChunkOmitRequestBody)
|
||||
}
|
||||
+ if SchemaVersionUsageIdentity != 4 {
|
||||
+ t.Fatalf("SchemaVersionUsageIdentity = %d, want 4", SchemaVersionUsageIdentity)
|
||||
+ }
|
||||
if MethodPluginRegister != "plugin.register" {
|
||||
t.Fatalf("MethodPluginRegister = %q", MethodPluginRegister)
|
||||
}
|
||||
diff --git a/sdk/pluginapi/types.go b/sdk/pluginapi/types.go
|
||||
index 6add5d69..894e3b77 100644
|
||||
index adcf1ff1..fb16a58f 100644
|
||||
--- a/sdk/pluginapi/types.go
|
||||
+++ b/sdk/pluginapi/types.go
|
||||
@@ -1316,6 +1316,10 @@ type ManagementResponse struct {
|
||||
@@ -1339,6 +1339,10 @@ type ManagementResponse struct {
|
||||
|
||||
// UsageRecord describes request usage and billing metadata.
|
||||
type UsageRecord struct {
|
||||
|
||||
@@ -13,5 +13,5 @@ function Resolve-Tool([string]$Name) {
|
||||
|
||||
$go = Resolve-Tool 'go'
|
||||
Write-Host "Go: $(& $go version)"
|
||||
Write-Host 'CLIProxyAPI target: ABI 1 / RPC schema 4'
|
||||
Write-Host 'CLIProxyAPI target: ABI 1 / RPC schema 5'
|
||||
Write-Warning '本项目默认在 WSL/Linux 构建;请优先运行 wsl bash ./scripts/build.sh。'
|
||||
|
||||
@@ -13,4 +13,4 @@ command -v gcc >/dev/null 2>&1 || {
|
||||
echo "Go bootstrap: $(go version)"
|
||||
echo "Go effective: $(go env GOVERSION)"
|
||||
echo "GCC: $(gcc --version | head -n 1)"
|
||||
echo 'CLIProxyAPI target: ABI 1 / RPC schema 4'
|
||||
echo 'CLIProxyAPI target: ABI 1 / RPC schema 5'
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ printf '%s\n' \
|
||||
"target=linux/amd64" \
|
||||
"native_abi=1" \
|
||||
"rpc_schema=4" \
|
||||
"cliproxyapi_revision=${cpa_revision}+usage-context+usage-identity+request-lifecycle-cancel" \
|
||||
"cliproxyapi_revision=${cpa_revision}+usage-identity+request-lifecycle-cancel" \
|
||||
>"$package_dir/VERSION.txt"
|
||||
|
||||
archive="$root/dist/$name.tar.gz"
|
||||
|
||||
Reference in New Issue
Block a user