7月19提交:发布首个上游请求追踪插件

This commit is contained in:
zhanghaidong
2026-07-19 19:48:35 +08:00
parent 454955c5be
commit 3bd16056e4
5 changed files with 114 additions and 1 deletions
+11 -1
View File
@@ -1,4 +1,14 @@
{
"schema_version": 1,
"plugins": []
"plugins": [
{
"id": "request-trace",
"name": "上游请求追踪",
"version": "1.0.0",
"description": "为上游请求加入不含敏感信息的部署追踪标记,便于确认插件链和网关路由。",
"manifest_url": "https://git.9nn.cn/xiaodong/99code-plugins/raw/branch/main/request-trace/manifest.json",
"platforms": ["openai", "codex", "anthropic", "gemini", "antigravity", "grok", "xai", "kimi"],
"sha256": "20639523163a8253b8589dbf65ded5c1efe22c5534093e5ce729c9d181b08630"
}
]
}
+10
View File
@@ -0,0 +1,10 @@
# 请求追踪插件
这是 99code 插件商店的首个可安装生产插件。启用后,它会在上游请求中加入不含敏感信息的 `X-99Code-Plugin: request-trace/1.0.0` 标记,便于在反向代理、网关和上游抓包中确认插件链确实生效。
可在插件中心配置:
- `header_name`:必须以 `X-` 开头,最长 64 字符。
- `header_value`:最长 128 字符,禁止换行。
该插件不会读取、记录或上传账号凭据。
+30
View File
@@ -0,0 +1,30 @@
{
"schema_version": 1,
"id": "request-trace",
"name": "上游请求追踪",
"version": "1.0.0",
"description": "为上游请求加入不含敏感信息的部署追踪标记,便于确认插件链和网关路由。",
"entrypoint": "com.ninecode.plugins.RequestTracePlugin",
"platforms": [
"openai",
"codex",
"anthropic",
"gemini",
"antigravity",
"grok",
"xai",
"kimi"
],
"artifact": {
"url": "https://git.9nn.cn/xiaodong/99code-plugins/raw/branch/main/request-trace/request-trace-1.0.0.jar",
"sha256": "20639523163a8253b8589dbf65ded5c1efe22c5534093e5ce729c9d181b08630",
"signature": "",
"key_id": "",
"size": 2457
},
"credential_sync": null,
"metadata": {
"category": "observability",
"homepage": "https://git.9nn.cn/xiaodong/99code-plugins"
}
}
Binary file not shown.
@@ -0,0 +1,63 @@
package com.ninecode.plugins;
import com.ninecode.app.plugin.PluginRelayExchange;
import com.ninecode.app.plugin.ProviderPlugin;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/** Adds a non-sensitive deployment marker to outbound provider requests. */
public final class RequestTracePlugin implements ProviderPlugin {
private static final Set<String> SUPPORTED_PLATFORMS = Set.of(
"openai", "codex", "anthropic", "gemini", "antigravity", "grok", "xai", "kimi"
);
private String headerName = "X-99Code-Plugin";
private String headerValue = "request-trace/1.0.0";
@Override
public String id() {
return "request-trace";
}
@Override
public Set<String> platforms() {
return SUPPORTED_PLATFORMS;
}
@Override
public void start(Map<String, Object> configuration) {
String configuredName = text(configuration, "header_name");
String configuredValue = text(configuration, "header_value");
if (!configuredName.isBlank()) {
if (!configuredName.toLowerCase(Locale.ROOT).startsWith("x-") || configuredName.length() > 64) {
throw new IllegalArgumentException("header_name 必须是长度不超过 64 的 X- 自定义请求头");
}
headerName = configuredName;
}
if (!configuredValue.isBlank()) {
if (configuredValue.length() > 128 || configuredValue.contains("\r") || configuredValue.contains("\n")) {
throw new IllegalArgumentException("header_value 不能为空、换行或超过 128 个字符");
}
headerValue = configuredValue;
}
}
@Override
public PluginRelayExchange beforeRequest(PluginRelayExchange exchange) {
Map<String, List<String>> headers = new LinkedHashMap<>(exchange.headers());
headers.putIfAbsent(headerName, List.of(headerValue));
return new PluginRelayExchange(
exchange.pluginId(), exchange.platform(), exchange.accountId(), exchange.model(), exchange.method(),
exchange.uri(), headers, exchange.body(), exchange.statusCode()
);
}
private String text(Map<String, Object> configuration, String key) {
if (configuration == null || configuration.get(key) == null) {
return "";
}
return String.valueOf(configuration.get(key)).trim();
}
}