import { getLogger } from "@logtape/logtape"
import {
ChatSessionModelFunctions,
defineChatSessionFunction,
GbnfJsonSchema,
} from "npm:node-llama-cpp"
type RemoteFunction = {
name: string
description: string
parameters?: Readonly<GbnfJsonSchema>
[key: string]: unknown
}
const remoteLogger = getLogger(["remote-functions"])
export async function fetchRemoteFunctions(
host = Deno.env.get("REMOTE_FUNCTIONS_HOST") ||
"http://localhost:1337/",
): Promise<ChatSessionModelFunctions> {
const url = new URL(host)
url.pathname = url.pathname.replace(/\/$/, "") + "/client/functions.json"
const response = await fetch(url.toString(), {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${Deno.env.get("REMOTE_FUNCTIONS_TOKEN") || ""}`,
},
})
if (!response.ok) {
throw new Error(`Failed to call remote function: ${response.statusText}`)
}
const functions = await response.json() as RemoteFunction[]
if (!Array.isArray(functions)) {
throw new Error("Invalid response format, expected an array of functions")
}
return functions.reduce((acc, func) => {
if (!func.name || !func.description) {
throw new Error(
`Function ${JSON.stringify(func)} is missing required fields`,
)
}
acc[func.name] = defineChatSessionFunction({
description: func.description,
params: func.parameters,
handler: async (args) => {
try {
const url = new URL(host)
url.pathname = url.pathname.replace(/\/$/, "") +
"/client/remote-function/" + func.name
const requestBody = {
arguments: args,
}
const requestHeaders = {
"Content-Type": "application/json",
Authorization: `Bearer ${
Deno.env.get("REMOTE_FUNCTIONS_TOKEN") || ""
}`,
}
remoteLogger.info("Calling backend:", {
url: url.toString(),
headers: requestHeaders,
body: requestBody,
})
const response = await fetch(url.toString(), {
method: "POST",
headers: requestHeaders,
body: JSON.stringify(requestBody),
})
remoteLogger.info("Response status:", { status: response.status })
if (!response.ok) {
return (
`Failed to call remote function: ${response.statusText} ${await response
.text()}`
)
}
const responseText = await response.text()
remoteLogger.info("Response text:", { responseText })
return responseText
} catch (error) {
console.error(
`Error calling remote function ${func.name}: \n`,
error,
)
throw error
}
},
})
return acc
}, {} as Record<string, ReturnType<typeof defineChatSessionFunction>>)
}