Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions src/app/v1/_lib/proxy/session-guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,11 @@ export class ProxySessionGuard {
systemSettings.interceptAnthropicWarmupRequests;

// 1. 尝试从客户端提取 session_id(metadata.session_id)
const clientSessionId =
SessionManager.extractClientSessionId(
session.request.message,
session.headers,
session.userAgent
) || session.generateDeterministicSessionId();
const clientSessionId = SessionManager.extractClientSessionId(
session.request.message,
session.headers,
session.userAgent
);

// 2. 获取 messages 数组
const messages = session.getMessages();
Expand Down
33 changes: 0 additions & 33 deletions src/app/v1/_lib/proxy/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import crypto from "node:crypto";
import type { Context } from "hono";
import { logger } from "@/lib/logger";
import { clientRequestsContext1m as clientRequestsContext1mHelper } from "@/lib/special-attributes";
Expand Down Expand Up @@ -352,38 +351,6 @@ export class ProxySession {
return this.providersSnapshot;
}

/**
* 生成基于请求指纹的确定性 Session ID
*
* 优先级与参考实现一致:
* - API Key 前缀(x-api-key / x-goog-api-key 的前10位)
* - User-Agent
* - 客户端 IP(x-forwarded-for / x-real-ip)
*
* 当客户端未提供 metadata.session_id 时,可用于稳定绑定会话。
*/
generateDeterministicSessionId(): string | null {
const apiKeyHeader = this.headers.get("x-api-key") || this.headers.get("x-goog-api-key");
const apiKeyPrefix = apiKeyHeader ? apiKeyHeader.substring(0, 10) : null;

const userAgent = this.headers.get("user-agent");

// 取链路上的首个 IP
const forwardedFor = this.headers.get("x-forwarded-for");
const realIp = this.headers.get("x-real-ip");
const ip =
forwardedFor?.split(",").map((ip) => ip.trim())[0] || (realIp ? realIp.trim() : null);

const parts = [userAgent, ip, apiKeyPrefix].filter(Boolean);
if (parts.length === 0) {
return null;
}

const hash = crypto.createHash("sha256").update(parts.join(":"), "utf8").digest("hex");
// 格式对齐为 sess_{8位}_{12位}
return `sess_${hash.substring(0, 8)}_${hash.substring(8, 20)}`;
}

/**
* 获取 messages 数组长度(支持 Claude、Codex 和 Gemini 格式)
*/
Expand Down
15 changes: 0 additions & 15 deletions tests/unit/proxy/metadata-injection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,18 +128,3 @@ describe("injectClaudeMetadataUserId", () => {
expect(metadata.user_id).toMatch(/^user_[a-f0-9]{64}_account__session_sess_abc123$/);
});
});

describe("ProxySession.generateDeterministicSessionId", () => {
it("输出格式应匹配 sess_{8hex}_{12hex}", () => {
const session = Object.create(ProxySession.prototype) as ProxySession;
(session as Record<string, unknown>).headers = new Headers([
["x-api-key", "sk-test-abcdef123456"],
["user-agent", "Vitest/1.0"],
["x-forwarded-for", "203.0.113.1"],
]);

const deterministicSessionId = session.generateDeterministicSessionId();

expect(deterministicSessionId).toMatch(/^sess_[a-f0-9]{8}_[a-f0-9]{12}$/);
});
});
3 changes: 0 additions & 3 deletions tests/unit/proxy/session-guard-warmup-intercept.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,6 @@ function createMockSession(overrides: Partial<ProxySession> = {}): ProxySession
getRequestSequence() {
return this.requestSequence ?? 1;
},
generateDeterministicSessionId() {
return "deterministic_session_id";
},
getMessages() {
return [];
},
Expand Down
Loading