7月19提交:发布首个上游请求追踪插件
This commit is contained in:
+11
-1
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# 请求追踪插件
|
||||
|
||||
这是 99code 插件商店的首个可安装生产插件。启用后,它会在上游请求中加入不含敏感信息的 `X-99Code-Plugin: request-trace/1.0.0` 标记,便于在反向代理、网关和上游抓包中确认插件链确实生效。
|
||||
|
||||
可在插件中心配置:
|
||||
|
||||
- `header_name`:必须以 `X-` 开头,最长 64 字符。
|
||||
- `header_value`:最长 128 字符,禁止换行。
|
||||
|
||||
该插件不会读取、记录或上传账号凭据。
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user