chore: import upstream snapshot with attribution
Code Quality / Python Lint & Format (push) Has been cancelled
Code Quality / Python Tests (push) Has been cancelled
Code Quality / JavaScript/TypeScript Lint (advisory) (push) Has been cancelled
Security Scan / CodeQL Analysis (python) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript-typescript) (push) Has been cancelled
Code Quality / Python Lint & Format (push) Has been cancelled
Code Quality / Python Tests (push) Has been cancelled
Code Quality / JavaScript/TypeScript Lint (advisory) (push) Has been cancelled
Security Scan / CodeQL Analysis (python) (push) Has been cancelled
Security Scan / Dependency Review (push) Has been cancelled
Security Scan / CodeQL Analysis (javascript-typescript) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
import ModelClient from "@azure-rest/ai-inference";
|
||||
import { AzureKeyCredential } from "@azure/core-auth";
|
||||
|
||||
// Get these from your Microsoft Foundry project's "Overview" page
|
||||
// (GitHub Models is retiring end of July 2026 - see https://ai.azure.com/catalog/models)
|
||||
const token = process.env["AZURE_INFERENCE_CREDENTIAL"];
|
||||
if (!token) {
|
||||
throw new Error("AZURE_INFERENCE_CREDENTIAL environment variable is required. Please set it before running this application.");
|
||||
}
|
||||
const endpoint = process.env["AZURE_INFERENCE_ENDPOINT"];
|
||||
if (!endpoint) {
|
||||
throw new Error("AZURE_INFERENCE_ENDPOINT environment variable is required. Please set it before running this application.");
|
||||
}
|
||||
|
||||
/* By using the Azure AI Inference SDK, you can easily experiment with different models
|
||||
by modifying the value of `modelName` in the code below. For this code sample
|
||||
you need a model supporting tools. The following compatible models are
|
||||
available in the Microsoft Foundry Models catalog:
|
||||
|
||||
Cohere: Cohere-command-r-08-2024, Cohere-command-r-plus-08-2024
|
||||
Mistral AI: Mistral-large-2411, Mistral-small-2503
|
||||
OpenAI: gpt-4o-mini, gpt-4o, gpt-4.1, gpt-4.1-mini */
|
||||
const modelName = "gpt-4o-mini";
|
||||
|
||||
function getFlightInfo({ originCity, destinationCity }) {
|
||||
if (originCity === "Seattle" && destinationCity === "Miami") {
|
||||
return JSON.stringify({
|
||||
airline: "Delta",
|
||||
flight_number: "DL123",
|
||||
flight_date: "May 7th, 2024",
|
||||
flight_time: "10:00AM"
|
||||
});
|
||||
}
|
||||
return JSON.stringify({ error: "No flights found between the cities" });
|
||||
}
|
||||
|
||||
function getHotelInfo({ destination }) {
|
||||
if ( destination === "Miami") {
|
||||
return JSON.stringify({
|
||||
hotelName: "Contoso Suites"
|
||||
});
|
||||
}
|
||||
return JSON.stringify({ error: "No available hotels found in this city" });
|
||||
}
|
||||
|
||||
const namesToFunctions = {
|
||||
getFlightInfo: (data) =>
|
||||
getFlightInfo(data),
|
||||
getHotelInfo: (data) =>
|
||||
getHotelInfo(data)
|
||||
};
|
||||
|
||||
export async function main() {
|
||||
|
||||
const tool = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
name: "getFlightInfo",
|
||||
description: "Returns information about the next flight between two cities." +
|
||||
"This includes the name of the airline, flight number and the date and time" +
|
||||
"of the next flight",
|
||||
parameters: {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"originCity": {
|
||||
"type": "string",
|
||||
"description": "The name of the city where the flight originates",
|
||||
},
|
||||
"destinationCity": {
|
||||
"type": "string",
|
||||
"description": "The flight destination city",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"originCity",
|
||||
"destinationCity"
|
||||
],
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const hotels ={
|
||||
"type": "function",
|
||||
"function": {
|
||||
name: "getHotelInfo",
|
||||
description: "Returns information about the hotel of the destination city.",
|
||||
parameters: {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"destination": {
|
||||
"type": "string",
|
||||
"description": "The city that the traveller would like to stay",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"destination"
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const client = new ModelClient(endpoint, new AzureKeyCredential(token));
|
||||
|
||||
let messages = [
|
||||
{ role: "system", content: "You an assistant that helps users find flight and hotel information." },
|
||||
{ role: "user", content: "I'm interested in going to Miami and staying in a hotel." },
|
||||
// { role: "user", content: "I'm interested in going to Seattle. Are there flights to Denver?" },
|
||||
|
||||
|
||||
|
||||
|
||||
];
|
||||
|
||||
let response = await client.path("/chat/completions").post({
|
||||
body: {
|
||||
messages: messages,
|
||||
tools: [tool, hotels],
|
||||
model: modelName
|
||||
}
|
||||
});
|
||||
if (response.status !== "200") {
|
||||
throw response.body.error;
|
||||
}
|
||||
|
||||
// We expect the model to ask for a tool call
|
||||
if (response.body.choices[0].finish_reason === "tool_calls") {
|
||||
|
||||
// Append the model response to the chat history
|
||||
messages.push(response.body.choices[0].message);
|
||||
|
||||
// We expect a single tool call
|
||||
if (response.body.choices[0].message && response.body.choices[0].message.tool_calls.length === 1) {
|
||||
|
||||
const toolCall = response.body.choices[0].message.tool_calls[0];
|
||||
// We expect the tool to be a function call
|
||||
if (toolCall.type === "function") {
|
||||
const toolCall = response.body.choices[0].message.tool_calls[0];
|
||||
|
||||
// SECURITY: Validate function name exists in allowed functions map
|
||||
const functionName = toolCall.function.name;
|
||||
if (!Object.prototype.hasOwnProperty.call(namesToFunctions, functionName)) {
|
||||
throw new Error(`Unknown function requested: ${functionName}. Only allowed functions are: ${Object.keys(namesToFunctions).join(', ')}`);
|
||||
}
|
||||
|
||||
// SECURITY: Safely parse JSON with error handling
|
||||
let functionArgs;
|
||||
try {
|
||||
functionArgs = JSON.parse(toolCall.function.arguments);
|
||||
} catch (parseError) {
|
||||
throw new Error(`Failed to parse function arguments: ${parseError.message}`);
|
||||
}
|
||||
|
||||
// Log function call (avoid logging sensitive data in production)
|
||||
console.log(`Calling function \`${functionName}\` with arguments ${toolCall.function.arguments}`);
|
||||
|
||||
const callableFunc = namesToFunctions[functionName];
|
||||
const functionReturn = callableFunc(functionArgs);
|
||||
console.log(`Function returned = ${functionReturn}`);
|
||||
|
||||
// Append the function call result fo the chat history
|
||||
messages.push(
|
||||
{
|
||||
"tool_call_id": toolCall.id,
|
||||
"role": "tool",
|
||||
"name": toolCall.function.name,
|
||||
"content": functionReturn,
|
||||
}
|
||||
)
|
||||
|
||||
response = await client.path("/chat/completions").post({
|
||||
body: {
|
||||
messages: messages,
|
||||
tools: [tool, hotels],
|
||||
model: modelName
|
||||
}
|
||||
});
|
||||
if (response.status !== "200") {
|
||||
throw response.body.error;
|
||||
}
|
||||
console.log(`Model response = ${response.body.choices[0].message.content}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("The sample encountered an error:", err);
|
||||
});
|
||||
@@ -0,0 +1,285 @@
|
||||
{
|
||||
"name": "js-githubmodels",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@azure-rest/ai-inference": "latest",
|
||||
"@azure/core-auth": "latest",
|
||||
"@azure/core-sse": "latest"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure-rest/ai-inference": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@azure-rest/ai-inference/-/ai-inference-1.0.0-beta.2.tgz",
|
||||
"integrity": "sha512-xPnXJmldQ7vufTT1GtoHPk+korWFnl72whoDB34tb9DALTGGBKIAWobDQBMGYdV2ce0/KdhRFPwnfeZjNyfVsw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure-rest/core-client": "^2.1.0",
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"@azure/core-auth": "^1.7.2",
|
||||
"@azure/core-lro": "^2.6.0",
|
||||
"@azure/core-rest-pipeline": "^1.14.0",
|
||||
"@azure/logger": "^1.0.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure-rest/core-client": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-2.2.0.tgz",
|
||||
"integrity": "sha512-2uPSZPRb2TRyYONl5IcsWhX7C1xbm6Gof/tcLlDnYg7fCVcEfASfySKZ9OTHxhNeFgo79LhrT6juEoxTHvrXkQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.0.0",
|
||||
"@azure/core-auth": "^1.3.0",
|
||||
"@azure/core-rest-pipeline": "^1.5.0",
|
||||
"@azure/core-tracing": "^1.0.1",
|
||||
"@azure/core-util": "^1.0.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure-rest/core-client/node_modules/@azure/abort-controller": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
|
||||
"integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/abort-controller": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz",
|
||||
"integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-auth": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.8.0.tgz",
|
||||
"integrity": "sha512-YvFMowkXzLbXNM11yZtVLhUCmuG0ex7JKOH366ipjmHBhL3vpDcPAeWF+jf0X+jVXwFqo3UhsWUq4kH0ZPdu/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.0.0",
|
||||
"@azure/core-util": "^1.1.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-auth/node_modules/@azure/abort-controller": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
|
||||
"integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-lro": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz",
|
||||
"integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.0.0",
|
||||
"@azure/core-util": "^1.2.0",
|
||||
"@azure/logger": "^1.0.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-lro/node_modules/@azure/abort-controller": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
|
||||
"integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-rest-pipeline": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.17.0.tgz",
|
||||
"integrity": "sha512-62Vv8nC+uPId3j86XJ0WI+sBf0jlqTqPUFCBNrGtlaUeQUIXWV/D8GE5A1d+Qx8H7OQojn2WguC8kChD6v0shA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.0.0",
|
||||
"@azure/core-auth": "^1.8.0",
|
||||
"@azure/core-tracing": "^1.0.1",
|
||||
"@azure/core-util": "^1.9.0",
|
||||
"@azure/logger": "^1.0.0",
|
||||
"http-proxy-agent": "^7.0.0",
|
||||
"https-proxy-agent": "^7.0.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-rest-pipeline/node_modules/@azure/abort-controller": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
|
||||
"integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-sse": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-sse/-/core-sse-2.1.3.tgz",
|
||||
"integrity": "sha512-KSSdIKy8kvWCpYr8Hzpu22j3wcXsVTYE0IlgmI1T/aHvBDsLgV91y90UTfVWnuiuApRLCCVC4gS09ApBGOmYQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-tracing": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.1.2.tgz",
|
||||
"integrity": "sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-util": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.10.0.tgz",
|
||||
"integrity": "sha512-dqLWQsh9Nro1YQU+405POVtXnwrIVqPyfUzc4zXCbThTg7+vNNaiMkwbX9AMXKyoFYFClxmB3s25ZFr3+jZkww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@azure/abort-controller": "^2.0.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/core-util/node_modules/@azure/abort-controller": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
|
||||
"integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/logger": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.1.4.tgz",
|
||||
"integrity": "sha512-4IXXzcCdLdlXuCG+8UKEwLA1T1NHqUfanhXYHiQTn+6sfWCZXduqbtXDGceg3Ce5QxTGo7EqmbV6Bi+aqKuClQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
|
||||
"integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/http-proxy-agent": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz",
|
||||
"integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.0.2",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
|
||||
"license": "0BSD"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@azure-rest/ai-inference": "latest",
|
||||
"@azure/core-auth": "latest",
|
||||
"@azure/core-sse": "latest"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user