1+ import type { Context } from "hono"
2+
13import consola from "consola"
4+ import { createHash , randomUUID } from "node:crypto"
5+ import { networkInterfaces } from "node:os"
6+
7+ import type { AnthropicMessagesPayload } from "~/routes/messages/anthropic-types"
28
39import { getModels } from "~/services/copilot/get-models"
410import { getVSCodeVersion } from "~/services/get-vscode-version"
@@ -24,3 +30,154 @@ export const cacheVSCodeVersion = async () => {
2430
2531 consola . info ( `Using VSCode version: ${ response } ` )
2632}
33+
34+ const invalidMacAddresses = new Set ( [
35+ "00:00:00:00:00:00" ,
36+ "ff:ff:ff:ff:ff:ff" ,
37+ "ac:de:48:00:11:22" ,
38+ ] )
39+
40+ function validateMacAddress ( candidate : string ) : boolean {
41+ const tempCandidate = candidate . replaceAll ( "-" , ":" ) . toLowerCase ( )
42+ return ! invalidMacAddresses . has ( tempCandidate )
43+ }
44+
45+ export function getMac ( ) : string | null {
46+ const ifaces = networkInterfaces ( )
47+ // eslint-disable-next-line guard-for-in
48+ for ( const name in ifaces ) {
49+ const networkInterface = ifaces [ name ]
50+ if ( networkInterface ) {
51+ for ( const { mac } of networkInterface ) {
52+ if ( validateMacAddress ( mac ) ) {
53+ return mac
54+ }
55+ }
56+ }
57+ }
58+ return null
59+ }
60+
61+ export const cacheMacMachineId = ( ) => {
62+ const macAddress = getMac ( ) ?? randomUUID ( )
63+ state . macMachineId = createHash ( "sha256" )
64+ . update ( macAddress , "utf8" )
65+ . digest ( "hex" )
66+ consola . debug ( `Using machine ID: ${ state . macMachineId } ` )
67+ }
68+
69+ const SESSION_REFRESH_BASE_MS = 60 * 60 * 1000
70+ const SESSION_REFRESH_JITTER_MS = 20 * 60 * 1000
71+ let vsCodeSessionRefreshTimer : ReturnType < typeof setTimeout > | null = null
72+
73+ const generateSessionId = ( ) => {
74+ state . vsCodeSessionId = randomUUID ( ) + Date . now ( ) . toString ( )
75+ consola . debug ( `Generated VSCode session ID: ${ state . vsCodeSessionId } ` )
76+ }
77+
78+ export const stopVsCodeSessionRefreshLoop = ( ) => {
79+ if ( vsCodeSessionRefreshTimer ) {
80+ clearTimeout ( vsCodeSessionRefreshTimer )
81+ vsCodeSessionRefreshTimer = null
82+ }
83+ }
84+
85+ const scheduleSessionIdRefresh = ( ) => {
86+ const randomDelay = Math . floor ( Math . random ( ) * SESSION_REFRESH_JITTER_MS )
87+ const delay = SESSION_REFRESH_BASE_MS + randomDelay
88+ consola . debug (
89+ `Scheduling next VSCode session ID refresh in ${ Math . round (
90+ delay / 1000 ,
91+ ) } seconds`,
92+ )
93+
94+ stopVsCodeSessionRefreshLoop ( )
95+ vsCodeSessionRefreshTimer = setTimeout ( ( ) => {
96+ try {
97+ generateSessionId ( )
98+ } catch ( error ) {
99+ consola . error ( "Failed to refresh session ID, rescheduling..." , error )
100+ } finally {
101+ scheduleSessionIdRefresh ( )
102+ }
103+ } , delay )
104+ }
105+
106+ export const cacheVsCodeSessionId = ( ) => {
107+ stopVsCodeSessionRefreshLoop ( )
108+ generateSessionId ( )
109+ scheduleSessionIdRefresh ( )
110+ }
111+
112+ interface PayloadMessage {
113+ role ?: string
114+ content ?: string | Array < { type ?: string ; text ?: string } > | null
115+ type ?: string
116+ }
117+
118+ const findLastUserContent = (
119+ messages : Array < PayloadMessage > ,
120+ ) : string | null => {
121+ for ( let i = messages . length - 1 ; i >= 0 ; i -- ) {
122+ const msg = messages [ i ]
123+ if ( msg . role === "user" && msg . content ) {
124+ if ( typeof msg . content === "string" ) {
125+ return msg . content
126+ } else if ( Array . isArray ( msg . content ) ) {
127+ const array = msg . content
128+ . filter ( ( n ) => n . type !== "tool_result" )
129+ . map ( ( n ) => ( { ...n , cache_control : undefined } ) )
130+ if ( array . length > 0 ) {
131+ return JSON . stringify ( array )
132+ }
133+ }
134+ }
135+ }
136+ return null
137+ }
138+
139+ export const generateRequestIdFromPayload = (
140+ payload : {
141+ messages : string | Array < PayloadMessage > | undefined
142+ } ,
143+ sessionId ?: string ,
144+ ) : string => {
145+ const messages = payload . messages
146+ if ( messages ) {
147+ const lastUserContent =
148+ typeof messages === "string" ? messages : findLastUserContent ( messages )
149+
150+ if ( lastUserContent ) {
151+ return getUUID (
152+ ( sessionId ?? "" ) + ( state . macMachineId ?? "" ) + lastUserContent ,
153+ )
154+ }
155+ }
156+
157+ return randomUUID ( )
158+ }
159+
160+ export const getRootSessionId = (
161+ anthropicPayload : AnthropicMessagesPayload ,
162+ c : Context ,
163+ ) : string | undefined => {
164+ let sessionId : string | undefined
165+ if ( anthropicPayload . metadata ?. user_id ) {
166+ const sessionMatch = new RegExp ( / _ s e s s i o n _ ( .+ ) $ / ) . exec (
167+ anthropicPayload . metadata . user_id ,
168+ )
169+ sessionId = sessionMatch ? sessionMatch [ 1 ] : undefined
170+ } else {
171+ sessionId = c . req . header ( "x-session-id" )
172+ }
173+ if ( sessionId ) {
174+ return getUUID ( sessionId )
175+ }
176+ return sessionId
177+ }
178+
179+ export const getUUID = ( content : string ) : string => {
180+ const hash = createHash ( "sha256" ) . update ( content ) . digest ( "hex" )
181+ const hash32 = hash . slice ( 0 , 32 )
182+ return `${ hash32 . slice ( 0 , 8 ) } -${ hash32 . slice ( 8 , 12 ) } -${ hash32 . slice ( 12 , 16 ) } -${ hash32 . slice ( 16 , 20 ) } -${ hash32 . slice ( 20 ) } `
183+ }
0 commit comments