Update README.md
Browse files- README.md +2 -2
- common/db.js +5 -0
- config/utils.js +17 -0
- dao/agent.js +48 -0
- dao/call.js +77 -0
- dao/schema.js +47 -0
- dao/trunk.js +39 -0
- db.js +0 -5
- docs/index.html +25 -0
- eslint.config.js +1 -1
- index.js +31 -47
- package.json +4 -2
- pnpm-lock.yaml +253 -0
- agents.js → routes/agent.js +105 -56
- routes/blob.js +45 -0
- routes/schema.js +62 -0
- routes/trunk.js +203 -0
- schemas/agent.js +33 -0
- utils.js → schemas/call.js +6 -21
- schemas/dispatch.js +6 -0
- schemas/trunk.js +9 -0
README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
---
|
| 2 |
title: MOHBIM Custom Livekit API V1
|
| 3 |
emoji: 🚦
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
short_description: Custom LK REST API router v1
|
|
|
|
| 1 |
---
|
| 2 |
title: MOHBIM Custom Livekit API V1
|
| 3 |
emoji: 🚦
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
short_description: Custom LK REST API router v1
|
common/db.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pg from 'pg'
|
| 2 |
+
import { env } from '../config/utils.js'
|
| 3 |
+
|
| 4 |
+
const pool = new pg.Pool({ connectionString: env.LK_DATABASE_URL })
|
| 5 |
+
export const query = (sql, params) => pool.query(sql, params)
|
config/utils.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { AgentDispatchClient, SipClient } from 'livekit-server-sdk'
|
| 2 |
+
|
| 3 |
+
import { z } from 'zod'
|
| 4 |
+
|
| 5 |
+
const env = z.object({
|
| 6 |
+
LIVEKIT_API_KEY: z.string().min(1),
|
| 7 |
+
LIVEKIT_API_SECRET: z.string().min(1),
|
| 8 |
+
LIVEKIT_URL: z.string().url(),
|
| 9 |
+
API_PORT_NUM: z.coerce.number().default(3000),
|
| 10 |
+
LK_DATABASE_URL: z.string().url(),
|
| 11 |
+
}).parse(process.env)
|
| 12 |
+
|
| 13 |
+
const lkEnvArgs = [env.LIVEKIT_URL, env.LIVEKIT_API_KEY, env.LIVEKIT_API_SECRET]
|
| 14 |
+
const agentDispatchClient = new AgentDispatchClient(...lkEnvArgs)
|
| 15 |
+
const sipClient = new SipClient(...lkEnvArgs)
|
| 16 |
+
|
| 17 |
+
export { env, lkEnvArgs, agentDispatchClient, sipClient }
|
dao/agent.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { query } from '../common/db.js'
|
| 2 |
+
|
| 3 |
+
export async function createAgent(name, decodedPrompt, stt, llm, tts, variablesSchema) {
|
| 4 |
+
const { rows } = await query(
|
| 5 |
+
'INSERT INTO agent (name, system_prompt, stt, llm, tts, variables_schema) \
|
| 6 |
+
VALUES ($1, $2, $3, $4, $5, $6) RETURNING id',
|
| 7 |
+
[name, decodedPrompt, JSON.stringify(stt), JSON.stringify(llm), JSON.stringify(tts), JSON.stringify(variablesSchema)]
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
return { agentId: rows[0].id }
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
export async function getAgent(agentId) {
|
| 14 |
+
console.log(agentId)
|
| 15 |
+
const { rows } = await query('SELECT * FROM agent WHERE id = $1', [agentId])
|
| 16 |
+
// console.log(rows)
|
| 17 |
+
if(!rows.length) return {error: 'Agent not found', code:404}
|
| 18 |
+
return rows[0]
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
export async function listAgents() {
|
| 23 |
+
const { rows } = await query('SELECT id, name, created_at \
|
| 24 |
+
FROM agent \
|
| 25 |
+
ORDER BY created_at DESC')
|
| 26 |
+
|
| 27 |
+
// console.log(JSON.stringify(rows, null, 2))
|
| 28 |
+
if(!rows.length) return {error: 'Agent not found', code:404}
|
| 29 |
+
return rows
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
export async function updateAgent(fields, values, i) {
|
| 34 |
+
const { rows } = await query(
|
| 35 |
+
`UPDATE agent SET ${fields.join(', ')} WHERE id = $${i} RETURNING id`,
|
| 36 |
+
values
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
return rows
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
export async function deleteAgent(agentId) {
|
| 44 |
+
const { rows } = await query('DELETE FROM agent WHERE id = $1 RETURNING id', [agentId])
|
| 45 |
+
|
| 46 |
+
if (!rows.length) return { error: 'Agent not found', code:404 }
|
| 47 |
+
return rows
|
| 48 |
+
}
|
dao/call.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { agentDispatchClient, sipClient } from '../config/utils.js'
|
| 2 |
+
|
| 3 |
+
import { query } from '../common/db.js'
|
| 4 |
+
|
| 5 |
+
export async function createCall(formattedNumber, agentId, variables) {
|
| 6 |
+
// const { rows: agentRows } = await query(
|
| 7 |
+
// `SELECT name FROM agent WHERE id = $1`,
|
| 8 |
+
// [agentId]
|
| 9 |
+
// )
|
| 10 |
+
|
| 11 |
+
// if (!agentRows.length) return { error: 'Agent not found !', code: 404 }
|
| 12 |
+
// const { name: agentName } = agentRows[0]
|
| 13 |
+
|
| 14 |
+
const { rows: outboundTrunkRows } = await query(
|
| 15 |
+
`SELECT lk_trunk_id FROM trunk where direction='outbound'`
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
if (!outboundTrunkRows.length) return {error: 'Outbound trunk not found !', code: 404}
|
| 19 |
+
const {lk_trunk_id: lkTrunkId} = outboundTrunkRows[0]
|
| 20 |
+
console.log('Using lkTrunkId: ', lkTrunkId)
|
| 21 |
+
|
| 22 |
+
const roomName = `room_${Math.floor(Math.random() * 10_000)}`
|
| 23 |
+
const participantIdentity = `caller_${formattedNumber}`
|
| 24 |
+
const outboundDispatch = await agentDispatchClient.createDispatch(
|
| 25 |
+
// roomName, agentName, {
|
| 26 |
+
roomName, '', {
|
| 27 |
+
metadata: JSON.stringify({ agentId, phoneNumber: formattedNumber, variables })
|
| 28 |
+
}
|
| 29 |
+
)
|
| 30 |
+
console.log('Outbound dispatch is: ', JSON.stringify(outboundDispatch, null, 2))
|
| 31 |
+
|
| 32 |
+
const sipParticipant = await sipClient.createSipParticipant(lkTrunkId, formattedNumber, roomName, { participantIdentity })
|
| 33 |
+
console.log('sipParticipant is: ', JSON.stringify(sipParticipant, null, 2))
|
| 34 |
+
|
| 35 |
+
// Store call in database
|
| 36 |
+
const { rows: callRows } = await query(
|
| 37 |
+
`INSERT INTO call_log
|
| 38 |
+
(call_to, agent_id, variables, room_id, dispatch_id, sip_participant_id, status, created_at)
|
| 39 |
+
VALUES ($1, $2, $3, $4, $5, $6, 'active', NOW())
|
| 40 |
+
RETURNING id`,
|
| 41 |
+
[formattedNumber, agentId, JSON.stringify(variables), roomName, outboundDispatch.id, sipParticipant.participantId]
|
| 42 |
+
)
|
| 43 |
+
console.log('Logged the call: ', callRows)
|
| 44 |
+
|
| 45 |
+
return { success: true, roomId: roomName, dispatchId: outboundDispatch.id, sipParticipantId: sipParticipant.participantId }
|
| 46 |
+
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
export async function getCalls() {
|
| 50 |
+
const { rows } = await query(
|
| 51 |
+
`SELECT id, call_to as "callTo", agent_id as "agentId",
|
| 52 |
+
variables, status,
|
| 53 |
+
created_at as "createdAt",
|
| 54 |
+
duration,
|
| 55 |
+
text_transcript as "textTranscript",
|
| 56 |
+
audio_transcript as "audioTranscript"
|
| 57 |
+
FROM call_log
|
| 58 |
+
ORDER BY created_at DESC`
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
return { calls: rows }
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
export async function getCall(id) {
|
| 65 |
+
const { rows } = await query(
|
| 66 |
+
`SELECT id, call_to as "callTo", agent_id as "agentId",
|
| 67 |
+
variables, room_id as "roomId", dispatch_id as "dispatchId",
|
| 68 |
+
sip_participant_id as "sipParticipantId", status,
|
| 69 |
+
created_at as "createdAt"
|
| 70 |
+
FROM call_log
|
| 71 |
+
WHERE id = $1`,
|
| 72 |
+
[id]
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
if (!rows.length) return { error: 'Call not found', code: 404 }
|
| 76 |
+
return rows[0]
|
| 77 |
+
}
|
dao/schema.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { query } from '../common/db.js'
|
| 2 |
+
|
| 3 |
+
export async function createSchema(name, contents, system_prompt) {
|
| 4 |
+
const { rows } = await query(
|
| 5 |
+
`INSERT INTO output_schema (name, schema, system_prompt)
|
| 6 |
+
VALUES ($1, $2, $3) RETURNING id`,
|
| 7 |
+
[name, contents, system_prompt]
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
return { schemaId: rows[0].id }
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
export async function getSchema(schemaId) {
|
| 14 |
+
const { rows: schema } = await query(`SELECT name, schema, system_prompt
|
| 15 |
+
FROM output_schema
|
| 16 |
+
WHERE id = $1`,
|
| 17 |
+
[schemaId])
|
| 18 |
+
return schema
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
export async function listSchemas() {
|
| 23 |
+
const { rows } = await query(`SELECT id, name, created_at
|
| 24 |
+
FROM output_schema
|
| 25 |
+
ORDER BY created_at DESC`)
|
| 26 |
+
|
| 27 |
+
console.log(JSON.stringify(rows, null, 2))
|
| 28 |
+
return rows
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
export async function updateSchema(fields, values, i) {
|
| 33 |
+
const { rows } = await query(
|
| 34 |
+
`UPDATE output_schema
|
| 35 |
+
SET ${fields.join(', ')}
|
| 36 |
+
WHERE id = $${i} RETURNING id`,
|
| 37 |
+
values
|
| 38 |
+
)
|
| 39 |
+
return rows
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
export async function deleteSchema(schemaId) {
|
| 44 |
+
const { rows } = await query('DELETE FROM output_schema WHERE id = $1 RETURNING id', [schemaId])
|
| 45 |
+
if (!rows.length) return { error: 'schema not found', code:404 }
|
| 46 |
+
return rows
|
| 47 |
+
}
|
dao/trunk.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { query } from '../common/db.js'
|
| 2 |
+
|
| 3 |
+
export async function createSipTrunk(name, trunkType, sipTrunkId, address) {
|
| 4 |
+
if (trunkType === 'outbound' && address) {
|
| 5 |
+
const { rows } = await query(
|
| 6 |
+
'INSERT INTO trunk(name, direction, lk_trunk_id, address) VALUES($1, $2, $3, $4) RETURNING id',
|
| 7 |
+
[name, trunkType, sipTrunkId, address]
|
| 8 |
+
)
|
| 9 |
+
return rows
|
| 10 |
+
} else {
|
| 11 |
+
const { rows } = await query(
|
| 12 |
+
'INSERT INTO trunk(name, direction, lk_trunk_id) VALUES($1, $2, $3) RETURNING id',
|
| 13 |
+
[name, trunkType, sipTrunkId]
|
| 14 |
+
)
|
| 15 |
+
return rows
|
| 16 |
+
}
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
export async function getInboundTrunk(sipTrunkId) {
|
| 21 |
+
const { rows: inboundTrunk } = await query(
|
| 22 |
+
`SELECT lk_trunk_id FROM trunk WHERE direction = 'inbound' AND id = $1`,
|
| 23 |
+
[sipTrunkId])
|
| 24 |
+
return inboundTrunk
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
export async function updateTrunk(fields, values, i) {
|
| 28 |
+
const { rows } = await query(
|
| 29 |
+
`UPDATE trunk SET ${fields.join(', ')} WHERE lk_trunk_id = $${i} RETURNING id`,
|
| 30 |
+
values
|
| 31 |
+
)
|
| 32 |
+
return rows
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
export async function deleteTrunk(trunkId) {
|
| 36 |
+
const { rows } = await query('DELETE FROM trunk WHERE id = $1 RETURNING id', [trunkId])
|
| 37 |
+
if (!rows.length) return { error: 'Trunk not found', code:404 }
|
| 38 |
+
return rows
|
| 39 |
+
}
|
db.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
| 1 |
-
import pg from 'pg'
|
| 2 |
-
import { env } from './utils.js'
|
| 3 |
-
|
| 4 |
-
const pool = new pg.Pool({ connectionString: env.DATABASE_URL })
|
| 5 |
-
export const query = (sql, params) => pool.query(sql, params)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docs/index.html
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>LK-VAPI-Migration - API Documentation</title>
|
| 7 |
+
<style>
|
| 8 |
+
body { margin: 0; padding: 0; }
|
| 9 |
+
#opencollection-container { width: 100vw; height: 100vh; }
|
| 10 |
+
</style>
|
| 11 |
+
<link rel="stylesheet" href="https://cdn.opencollection.com/docs.css">
|
| 12 |
+
<script src="https://cdn.opencollection.com/docs.js"></script>
|
| 13 |
+
</head>
|
| 14 |
+
<body>
|
| 15 |
+
<div id="opencollection-container"></div>
|
| 16 |
+
<script>
|
| 17 |
+
const collectionData = "opencollection: 1.0.0\ninfo:\n name: LK-VAPI-Migration\nitems:\n - info:\n name: STRUCTURED OUTPUTS\n type: folder\n seq: 3\n request:\n auth: inherit\n items:\n - info:\n name: AZ Generate JSON (chatcompl)\n type: http\n seq: 2\n http:\n method: POST\n url: https://{{RESOURCE_NAME}}.openai.azure.com/openai/deployments/{{AZURE_DEPLOYMENT}}/chat/completions?api-version=2024-12-01-preview\n headers:\n - name: api-key\n value: '{{AZURE_API_KEY}}'\n - name: content-type\n value: application/json\n params:\n - name: api-version\n value: 2024-12-01-preview\n type: query\n body:\n type: json\n data: |-\n {\n \"temperature\": 0,\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"Extract comprehensive employment verification data from the complete reference call transcript as per the json schema provided.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"{{TRANSCRIPT_STR}}\"\n }\n ],\n \"response_format\": {\n \"type\": \"json_schema\",\n \"json_schema\": {{SCHEMA}}\n },\n \"stream\": false\n }\n auth: inherit\n runtime:\n variables:\n - name: TRANSCRIPT_ARR\n value: '[{\"role\":\"assistant\",\"text\":\"Hi Reese Snow, this is Sarah from Acme Inc.. I''m calling about Melissa Sanders — they mentioned you as a professional reference. Do\",\"timestamp\":1774011919966},{\"role\":\"user\",\"text\":\"Yes.\",\"timestamp\":1774011948363},{\"role\":\"assistant\",\"text\":\"Great! First, could you tell me how you know Melissa Sanders and what your working relationship was?\",\"timestamp\":1774011948366},{\"role\":\"user\",\"text\":\"I know Melissa from Tech Innovations where we collaborated on a software project for two years.\",\"timestamp\":1774011967199},{\"role\":\"user\",\"text\":\"I was the project manager.\",\"timestamp\":1774011971106},{\"role\":\"user\",\"text\":\"And Melissa was the lead developer. We worked closely together sharing insights and achieving project goals.\",\"timestamp\":1774011975877},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — what really stands out about Melissa Sanders? I''m particularly curious about how they handle pressure, their customer service approach, and overall professionalism.\",\"timestamp\":1774011975878},{\"role\":\"user\",\"text\":\"We worked together for approximately two years\",\"timestamp\":1774012003556},{\"role\":\"user\",\"text\":\"building a strong professional rapport during that time.\",\"timestamp\":1774012004276},{\"role\":\"user\",\"text\":\"Melissa excels under pressure. Consistently delivering quality results during tight deadlines\",\"timestamp\":1774012010788},{\"role\":\"user\",\"text\":\"customer service is exceptional\",\"timestamp\":1774012015978},{\"role\":\"user\",\"text\":\"She listens and offers tailored solutions. Overall, she embodies professionalism\",\"timestamp\":1774012021547},{\"role\":\"user\",\"text\":\"and fosters a positive team environment.\",\"timestamp\":1774012022269},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — how would you describe Melissa''s style working with others — colleagues, customers, that sort of thing?\",\"timestamp\":1774012022271},{\"role\":\"user\",\"text\":\"Melissa is collaborative and approachable.\",\"timestamp\":1774012040673},{\"role\":\"user\",\"text\":\"She encourages open dialogue with colleagues and builds strong relationships with customers\",\"timestamp\":1774012048777},{\"role\":\"user\",\"text\":\"through empathy and effective communication.\",\"timestamp\":1774012049501},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — if you were their supervisor, what''s one piece of advice you''d give me to help Melissa Sanders succeed in this role?\",\"timestamp\":1774012049502},{\"role\":\"user\",\"text\":\"Encourage Melissa to seek regular feedback from peers and clients\",\"timestamp\":1774012075077},{\"role\":\"user\",\"text\":\"to enhance her skills further.\",\"timestamp\":1774012079497},{\"role\":\"user\",\"text\":\"Opportunities for professional development will also support her growth.\",\"timestamp\":1774012080192},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — based on what you know about our Project Manager role, do you think Melissa Sanders would be a good match? I''d love your honest thoughts.\",\"timestamp\":1774012080193},{\"role\":\"user\",\"text\":\"Absolutely. Her organizational skills, ability to handle pressure, and strong customer service focus make her an excellent fit\",\"timestamp\":1774012108137},{\"role\":\"user\",\"text\":\"for the project manager role.\",\"timestamp\":1774012109131},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — before we wrap up, is there anything else about Melissa Sanders that would help us make the best decision?\",\"timestamp\":1774012109133},{\"role\":\"user\",\"text\":\"Melissa''s adaptability and eagerness to learn\",\"timestamp\":1774012129447},{\"role\":\"user\",\"text\":\"are significant assets. Her positive attitude enhances team morale.\",\"timestamp\":1774012136327},{\"role\":\"user\",\"text\":\"Making her a valuable addition to any organization.\",\"timestamp\":1774012136940},{\"role\":\"assistant\",\"text\":\"This has been incredibly helpful — thank you so much for your time and insights. We really appreciate you helping us get to know Melissa Sanders better. Have a wonderful day!\",\"timestamp\":1774012136942},{\"role\":\"user\",\"text\":\"Thank you.\",\"timestamp\":1774012157057},{\"role\":\"user\",\"text\":\"Goodbye.\",\"timestamp\":1774012158599},{\"role\":\"assistant\",\"text\":\"Ending the call now.\",\"timestamp\":1774012167994},{\"role\":\"assistant\",\"text\":\"Goodbye!\",\"timestamp\":1774012167994}]'\n - name: SCHEMA\n value: '{\"name\":\"refcheck\",\"schema\":{\"description\":\"Extract comprehensive employment verification data from HR reference calls\",\"type\":\"object\",\"properties\":{\"callMetadata\":{\"type\":\"object\",\"properties\":{\"callDuration\":{\"description\":\"Length of the reference call\",\"type\":\"string\"},\"informationQuality\":{\"description\":\"Quality and depth of information provided\",\"type\":\"string\",\"enum\":[\"detailed\",\"adequate\",\"brief\",\"minimal\"]},\"referenceWillingness\":{\"description\":\"Reference''s attitude during the call\",\"type\":\"string\",\"enum\":[\"enthusiastic\",\"cooperative\",\"hesitant\",\"rushed\"]}},\"required\":[\"callDuration\",\"referenceWillingness\"]},\"teamDynamics\":{\"type\":\"object\",\"properties\":{\"teamworkStyle\":{\"description\":\"Overall teamwork approach\",\"type\":\"string\",\"enum\":[\"collaborative\",\"independent\",\"leadership\",\"supportive\",\"mixed\"]},\"customerInteraction\":{\"description\":\"How candidate interacts with customers\",\"type\":\"string\"},\"colleagueInteraction\":{\"description\":\"How candidate works with colleagues\",\"type\":\"string\"}},\"required\":[\"customerInteraction\",\"colleagueInteraction\"]},\"fitAssessment\":{\"type\":\"object\",\"properties\":{\"concerns\":{\"description\":\"Any concerns or reservations mentioned\",\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"roleMatch\":{\"description\":\"Whether candidate is a good match for the position\",\"type\":\"boolean\"},\"fitReasoning\":{\"description\":\"Explanation for fit assessment\",\"type\":\"string\"}},\"required\":[\"roleMatch\",\"fitReasoning\"]},\"referenceInfo\":{\"type\":\"object\",\"properties\":{\"relationship\":{\"description\":\"Professional relationship to candidate\",\"type\":\"string\",\"enum\":[\"supervisor\",\"colleague\",\"direct_report\",\"client\",\"other\"]},\"workDuration\":{\"description\":\"Duration of working together\",\"type\":\"string\"},\"durationKnown\":{\"description\":\"How long the reference has known the candidate\",\"type\":\"string\"},\"referenceName\":{\"description\":\"Name of the reference person\",\"type\":\"string\"},\"relationshipDescription\":{\"description\":\"Detailed description of working relationship\",\"type\":\"string\"}},\"required\":[\"relationship\",\"durationKnown\",\"referenceName\"]},\"candidateStrengths\":{\"type\":\"object\",\"properties\":{\"keyStrengths\":{\"description\":\"List of standout qualities mentioned\",\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"customerService\":{\"description\":\"Customer service approach and skills\",\"type\":\"string\"},\"professionalism\":{\"description\":\"Overall professional conduct\",\"type\":\"string\"},\"pressureHandling\":{\"description\":\"How candidate handles pressure situations\",\"type\":\"string\"}},\"required\":[\"customerService\",\"professionalism\",\"pressureHandling\"]},\"managementInsights\":{\"type\":\"object\",\"properties\":{\"successAdvice\":{\"description\":\"Advice for candidate''s success in the role\",\"type\":\"string\"},\"improvementAreas\":{\"description\":\"Areas where candidate could improve\",\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"successAdvice\"]},\"overallRecommendation\":{\"type\":\"object\",\"properties\":{\"wouldHire\":{\"description\":\"Whether reference would hire the candidate\",\"type\":\"boolean\"},\"additionalComments\":{\"description\":\"Any final thoughts or additional insights\",\"type\":\"string\"},\"recommendationLevel\":{\"description\":\"Overall recommendation level\",\"type\":\"string\",\"enum\":[\"strongly_recommend\",\"recommend\",\"neutral\",\"do_not_recommend\"]}},\"required\":[\"wouldHire\",\"recommendationLevel\"]}},\"required\":[\"teamDynamics\",\"fitAssessment\",\"referenceInfo\",\"candidateStrengths\",\"managementInsights\",\"overallRecommendation\"]}}'\n - name: RESOURCE_NAME\n value: o247-ai-sandbox\n - name: AZURE_API_KEY\n value: azure_openai_apikey\n - name: AZURE_DEPLOYMENT\n value: gpt-4o-mini\n scripts:\n - type: before-request\n code: |-\n const transcriptArr = JSON.parse(bru.getRequestVar(\"TRANSCRIPT_ARR\"))\n const transcriptStr = transcriptArr.map(item => {\n const role = item.role || 'unknown';\n const text = item.text || '';\n return `${role.charAt(0).toUpperCase() + role.slice(1)}: ${text}`;\n })\n .filter(line => line.trim() !== '')\n .join('\\\\n');\n\n bru.setEnvVar(\"TRANSCRIPT_STR\", transcriptStr)\n settings:\n encodeUrl: true\n timeout: 0\n followRedirects: true\n maxRedirects: 5\n - info:\n name: Basic HF - gpt-oss-120b\n type: http\n seq: 5\n http:\n method: POST\n url: https://router.huggingface.co/v1/chat/completions\n headers:\n - name: content-type\n value: application/json\n - name: Authorization\n value: Bearer hf_api_key\n body:\n type: json\n data: |-\n {\n \"model\": \"openai/gpt-oss-120b:groq\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Say hello world\"\n }\n ],\n \"stream\": false\n }\n auth: inherit\n settings:\n encodeUrl: false\n timeout: 0\n followRedirects: true\n maxRedirects: 5\n - info:\n name: BasicCerebras- gpt-oss-120b\n type: http\n seq: 4\n http:\n method: POST\n url: https://api.cerebras.ai/v1/chat/completions\n headers:\n - name: content-type\n value: application/json\n - name: Authorization\n value: Bearer csk-exnjrkyfph9frctwt46wprjdm99j5x2hk9ecvfw3t2enjh95\n body:\n type: json\n data: |-\n {\n \"model\": \"gpt-oss-120b\",\n \"messages\": [\n {\n \"role\": \"user\",\n \"content\": \"Say hello world\"\n }\n ],\n \"stream\": false\n }\n auth: inherit\n settings:\n encodeUrl: false\n timeout: 0\n followRedirects: true\n maxRedirects: 5\n - info:\n name: CF Generate JSON (chatcompl)\n type: http\n seq: 1\n http:\n method: POST\n url: https://wandering-morning-bdee.mohbim.workers.dev/\n body:\n type: json\n data: |-\n { \n \"model\": \"qwen-3-235b-a22b-instruct-2507\", \n \"temperature\": 0, \n \"messages\": [ \n { \n \"role\": \"system\", \n \"content\": \"Extract comprehensive employment verification data from the complete reference call transcript as per the json schema provided.\" \n }, \n { \n \"role\": \"user\", \n \"content\": \"{{TRANSCRIPT_STR}}\"\n } \n ], \n \"response_format\": { \n \"type\": \"json_schema\",\n \"json_schema\": {{SCHEMA}}\n }, \n \"stream\": false \n }\n auth: inherit\n runtime:\n variables:\n - name: TRANSCRIPT_ARR\n value: '[{\"role\":\"assistant\",\"text\":\"Hi Reese Snow, this is Sarah from Acme Inc.. I''m calling about Melissa Sanders — they mentioned you as a professional reference. Do\",\"timestamp\":1774011919966},{\"role\":\"user\",\"text\":\"Yes.\",\"timestamp\":1774011948363},{\"role\":\"assistant\",\"text\":\"Great! First, could you tell me how you know Melissa Sanders and what your working relationship was?\",\"timestamp\":1774011948366},{\"role\":\"user\",\"text\":\"I know Melissa from Tech Innovations where we collaborated on a software project for two years.\",\"timestamp\":1774011967199},{\"role\":\"user\",\"text\":\"I was the project manager.\",\"timestamp\":1774011971106},{\"role\":\"user\",\"text\":\"And Melissa was the lead developer. We worked closely together sharing insights and achieving project goals.\",\"timestamp\":1774011975877},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — what really stands out about Melissa Sanders? I''m particularly curious about how they handle pressure, their customer service approach, and overall professionalism.\",\"timestamp\":1774011975878},{\"role\":\"user\",\"text\":\"We worked together for approximately two years\",\"timestamp\":1774012003556},{\"role\":\"user\",\"text\":\"building a strong professional rapport during that time.\",\"timestamp\":1774012004276},{\"role\":\"user\",\"text\":\"Melissa excels under pressure. Consistently delivering quality results during tight deadlines\",\"timestamp\":1774012010788},{\"role\":\"user\",\"text\":\"customer service is exceptional\",\"timestamp\":1774012015978},{\"role\":\"user\",\"text\":\"She listens and offers tailored solutions. Overall, she embodies professionalism\",\"timestamp\":1774012021547},{\"role\":\"user\",\"text\":\"and fosters a positive team environment.\",\"timestamp\":1774012022269},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — how would you describe Melissa''s style working with others — colleagues, customers, that sort of thing?\",\"timestamp\":1774012022271},{\"role\":\"user\",\"text\":\"Melissa is collaborative and approachable.\",\"timestamp\":1774012040673},{\"role\":\"user\",\"text\":\"She encourages open dialogue with colleagues and builds strong relationships with customers\",\"timestamp\":1774012048777},{\"role\":\"user\",\"text\":\"through empathy and effective communication.\",\"timestamp\":1774012049501},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — if you were their supervisor, what''s one piece of advice you''d give me to help Melissa Sanders succeed in this role?\",\"timestamp\":1774012049502},{\"role\":\"user\",\"text\":\"Encourage Melissa to seek regular feedback from peers and clients\",\"timestamp\":1774012075077},{\"role\":\"user\",\"text\":\"to enhance her skills further.\",\"timestamp\":1774012079497},{\"role\":\"user\",\"text\":\"Opportunities for professional development will also support her growth.\",\"timestamp\":1774012080192},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — based on what you know about our Project Manager role, do you think Melissa Sanders would be a good match? I''d love your honest thoughts.\",\"timestamp\":1774012080193},{\"role\":\"user\",\"text\":\"Absolutely. Her organizational skills, ability to handle pressure, and strong customer service focus make her an excellent fit\",\"timestamp\":1774012108137},{\"role\":\"user\",\"text\":\"for the project manager role.\",\"timestamp\":1774012109131},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — before we wrap up, is there anything else about Melissa Sanders that would help us make the best decision?\",\"timestamp\":1774012109133},{\"role\":\"user\",\"text\":\"Melissa''s adaptability and eagerness to learn\",\"timestamp\":1774012129447},{\"role\":\"user\",\"text\":\"are significant assets. Her positive attitude enhances team morale.\",\"timestamp\":1774012136327},{\"role\":\"user\",\"text\":\"Making her a valuable addition to any organization.\",\"timestamp\":1774012136940},{\"role\":\"assistant\",\"text\":\"This has been incredibly helpful — thank you so much for your time and insights. We really appreciate you helping us get to know Melissa Sanders better. Have a wonderful day!\",\"timestamp\":1774012136942},{\"role\":\"user\",\"text\":\"Thank you.\",\"timestamp\":1774012157057},{\"role\":\"user\",\"text\":\"Goodbye.\",\"timestamp\":1774012158599},{\"role\":\"assistant\",\"text\":\"Ending the call now.\",\"timestamp\":1774012167994},{\"role\":\"assistant\",\"text\":\"Goodbye!\",\"timestamp\":1774012167994}]'\n - name: SCHEMA\n value: '{\"name\":\"refcheck\",\"schema\":{\"description\":\"Extract comprehensive employment verification data from HR reference calls\",\"type\":\"object\",\"properties\":{\"callMetadata\":{\"type\":\"object\",\"properties\":{\"callDuration\":{\"description\":\"Length of the reference call\",\"type\":\"string\"},\"informationQuality\":{\"description\":\"Quality and depth of information provided\",\"type\":\"string\",\"enum\":[\"detailed\",\"adequate\",\"brief\",\"minimal\"]},\"referenceWillingness\":{\"description\":\"Reference''s attitude during the call\",\"type\":\"string\",\"enum\":[\"enthusiastic\",\"cooperative\",\"hesitant\",\"rushed\"]}},\"required\":[\"callDuration\",\"referenceWillingness\"]},\"teamDynamics\":{\"type\":\"object\",\"properties\":{\"teamworkStyle\":{\"description\":\"Overall teamwork approach\",\"type\":\"string\",\"enum\":[\"collaborative\",\"independent\",\"leadership\",\"supportive\",\"mixed\"]},\"customerInteraction\":{\"description\":\"How candidate interacts with customers\",\"type\":\"string\"},\"colleagueInteraction\":{\"description\":\"How candidate works with colleagues\",\"type\":\"string\"}},\"required\":[\"customerInteraction\",\"colleagueInteraction\"]},\"fitAssessment\":{\"type\":\"object\",\"properties\":{\"concerns\":{\"description\":\"Any concerns or reservations mentioned\",\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"roleMatch\":{\"description\":\"Whether candidate is a good match for the position\",\"type\":\"boolean\"},\"fitReasoning\":{\"description\":\"Explanation for fit assessment\",\"type\":\"string\"}},\"required\":[\"roleMatch\",\"fitReasoning\"]},\"referenceInfo\":{\"type\":\"object\",\"properties\":{\"relationship\":{\"description\":\"Professional relationship to candidate\",\"type\":\"string\",\"enum\":[\"supervisor\",\"colleague\",\"direct_report\",\"client\",\"other\"]},\"workDuration\":{\"description\":\"Duration of working together\",\"type\":\"string\"},\"durationKnown\":{\"description\":\"How long the reference has known the candidate\",\"type\":\"string\"},\"referenceName\":{\"description\":\"Name of the reference person\",\"type\":\"string\"},\"relationshipDescription\":{\"description\":\"Detailed description of working relationship\",\"type\":\"string\"}},\"required\":[\"relationship\",\"durationKnown\",\"referenceName\"]},\"candidateStrengths\":{\"type\":\"object\",\"properties\":{\"keyStrengths\":{\"description\":\"List of standout qualities mentioned\",\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"customerService\":{\"description\":\"Customer service approach and skills\",\"type\":\"string\"},\"professionalism\":{\"description\":\"Overall professional conduct\",\"type\":\"string\"},\"pressureHandling\":{\"description\":\"How candidate handles pressure situations\",\"type\":\"string\"}},\"required\":[\"customerService\",\"professionalism\",\"pressureHandling\"]},\"managementInsights\":{\"type\":\"object\",\"properties\":{\"successAdvice\":{\"description\":\"Advice for candidate''s success in the role\",\"type\":\"string\"},\"improvementAreas\":{\"description\":\"Areas where candidate could improve\",\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"successAdvice\"]},\"overallRecommendation\":{\"type\":\"object\",\"properties\":{\"wouldHire\":{\"description\":\"Whether reference would hire the candidate\",\"type\":\"boolean\"},\"additionalComments\":{\"description\":\"Any final thoughts or additional insights\",\"type\":\"string\"},\"recommendationLevel\":{\"description\":\"Overall recommendation level\",\"type\":\"string\",\"enum\":[\"strongly_recommend\",\"recommend\",\"neutral\",\"do_not_recommend\"]}},\"required\":[\"wouldHire\",\"recommendationLevel\"]}},\"required\":[\"teamDynamics\",\"fitAssessment\",\"referenceInfo\",\"candidateStrengths\",\"managementInsights\",\"overallRecommendation\"]}}'\n scripts:\n - type: before-request\n code: |-\n // bru.setEnvVar(\"J_SCHEMA\", JSON.parse(bru.getRequestVar(\"SCHEMA\")))\n // console.log(bru.getRequestVar(\"TRANSCRIPT_ARR\"))\n const transcriptArr = JSON.parse(bru.getRequestVar(\"TRANSCRIPT_ARR\"))\n // console.log(typeof(JSON.parse(transcriptArr)))\n const transcriptStr = transcriptArr.map(item => { \n const role = item.role || 'unknown'; \n const text = item.text || ''; \n return `${role.charAt(0).toUpperCase() + role.slice(1)}: ${text}`; \n }) \n .filter(line => line.trim() !== '') \n .join('\\\\n'); // Use \\\\n for newlines\n\n bru.setEnvVar(\"TRANSCRIPT_STR\",transcriptStr)\n settings:\n encodeUrl: true\n timeout: 0\n followRedirects: true\n maxRedirects: 5\n - info:\n name: HF Generate JSON (chatcompl)\n type: http\n seq: 3\n http:\n method: POST\n url: https://router.huggingface.co/v1/chat/completions\n headers:\n - name: content-type\n value: application/json\n - name: Authorization\n value: Bearer hf_api_key\n body:\n type: json\n data: |-\n {\n \"model\": \"openai/gpt-oss-120b:groq\",\n \"reasoning_effort\": \"low\",\n \"temperature\": 0,\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"Extract comprehensive employment verification data from the complete reference call transcript as per the json schema provided.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"{{TRANSCRIPT_STR}}\"\n }\n ],\n \"response_format\": {\n \"type\": \"json_schema\",\n \"json_schema\": {{SCHEMA}}\n },\n \"stream\": false\n }\n auth: inherit\n runtime:\n variables:\n - name: TRANSCRIPT_ARR\n value: '[{\"role\":\"assistant\",\"text\":\"Hi Reese Snow, this is Sarah from Acme Inc.. I''m calling about Melissa Sanders — they mentioned you as a professional reference. Do\",\"timestamp\":1774011919966},{\"role\":\"user\",\"text\":\"Yes.\",\"timestamp\":1774011948363},{\"role\":\"assistant\",\"text\":\"Great! First, could you tell me how you know Melissa Sanders and what your working relationship was?\",\"timestamp\":1774011948366},{\"role\":\"user\",\"text\":\"I know Melissa from Tech Innovations where we collaborated on a software project for two years.\",\"timestamp\":1774011967199},{\"role\":\"user\",\"text\":\"I was the project manager.\",\"timestamp\":1774011971106},{\"role\":\"user\",\"text\":\"And Melissa was the lead developer. We worked closely together sharing insights and achieving project goals.\",\"timestamp\":1774011975877},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — what really stands out about Melissa Sanders? I''m particularly curious about how they handle pressure, their customer service approach, and overall professionalism.\",\"timestamp\":1774011975878},{\"role\":\"user\",\"text\":\"We worked together for approximately two years\",\"timestamp\":1774012003556},{\"role\":\"user\",\"text\":\"building a strong professional rapport during that time.\",\"timestamp\":1774012004276},{\"role\":\"user\",\"text\":\"Melissa excels under pressure. Consistently delivering quality results during tight deadlines\",\"timestamp\":1774012010788},{\"role\":\"user\",\"text\":\"customer service is exceptional\",\"timestamp\":1774012015978},{\"role\":\"user\",\"text\":\"She listens and offers tailored solutions. Overall, she embodies professionalism\",\"timestamp\":1774012021547},{\"role\":\"user\",\"text\":\"and fosters a positive team environment.\",\"timestamp\":1774012022269},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — how would you describe Melissa''s style working with others — colleagues, customers, that sort of thing?\",\"timestamp\":1774012022271},{\"role\":\"user\",\"text\":\"Melissa is collaborative and approachable.\",\"timestamp\":1774012040673},{\"role\":\"user\",\"text\":\"She encourages open dialogue with colleagues and builds strong relationships with customers\",\"timestamp\":1774012048777},{\"role\":\"user\",\"text\":\"through empathy and effective communication.\",\"timestamp\":1774012049501},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — if you were their supervisor, what''s one piece of advice you''d give me to help Melissa Sanders succeed in this role?\",\"timestamp\":1774012049502},{\"role\":\"user\",\"text\":\"Encourage Melissa to seek regular feedback from peers and clients\",\"timestamp\":1774012075077},{\"role\":\"user\",\"text\":\"to enhance her skills further.\",\"timestamp\":1774012079497},{\"role\":\"user\",\"text\":\"Opportunities for professional development will also support her growth.\",\"timestamp\":1774012080192},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — based on what you know about our Project Manager role, do you think Melissa Sanders would be a good match? I''d love your honest thoughts.\",\"timestamp\":1774012080193},{\"role\":\"user\",\"text\":\"Absolutely. Her organizational skills, ability to handle pressure, and strong customer service focus make her an excellent fit\",\"timestamp\":1774012108137},{\"role\":\"user\",\"text\":\"for the project manager role.\",\"timestamp\":1774012109131},{\"role\":\"assistant\",\"text\":\"That''s really helpful, thank you. Next — before we wrap up, is there anything else about Melissa Sanders that would help us make the best decision?\",\"timestamp\":1774012109133},{\"role\":\"user\",\"text\":\"Melissa''s adaptability and eagerness to learn\",\"timestamp\":1774012129447},{\"role\":\"user\",\"text\":\"are significant assets. Her positive attitude enhances team morale.\",\"timestamp\":1774012136327},{\"role\":\"user\",\"text\":\"Making her a valuable addition to any organization.\",\"timestamp\":1774012136940},{\"role\":\"assistant\",\"text\":\"This has been incredibly helpful — thank you so much for your time and insights. We really appreciate you helping us get to know Melissa Sanders better. Have a wonderful day!\",\"timestamp\":1774012136942},{\"role\":\"user\",\"text\":\"Thank you.\",\"timestamp\":1774012157057},{\"role\":\"user\",\"text\":\"Goodbye.\",\"timestamp\":1774012158599},{\"role\":\"assistant\",\"text\":\"Ending the call now.\",\"timestamp\":1774012167994},{\"role\":\"assistant\",\"text\":\"Goodbye!\",\"timestamp\":1774012167994}]'\n - name: SCHEMA\n value: '{\"name\":\"refcheck\",\"schema\":{\"description\":\"Extract comprehensive employment verification data from HR reference calls\",\"type\":\"object\",\"properties\":{\"callMetadata\":{\"type\":\"object\",\"properties\":{\"callDuration\":{\"description\":\"Length of the reference call\",\"type\":\"string\"},\"informationQuality\":{\"description\":\"Quality and depth of information provided\",\"type\":\"string\",\"enum\":[\"detailed\",\"adequate\",\"brief\",\"minimal\"]},\"referenceWillingness\":{\"description\":\"Reference''s attitude during the call\",\"type\":\"string\",\"enum\":[\"enthusiastic\",\"cooperative\",\"hesitant\",\"rushed\"]}},\"required\":[\"callDuration\",\"referenceWillingness\"]},\"teamDynamics\":{\"type\":\"object\",\"properties\":{\"teamworkStyle\":{\"description\":\"Overall teamwork approach\",\"type\":\"string\",\"enum\":[\"collaborative\",\"independent\",\"leadership\",\"supportive\",\"mixed\"]},\"customerInteraction\":{\"description\":\"How candidate interacts with customers\",\"type\":\"string\"},\"colleagueInteraction\":{\"description\":\"How candidate works with colleagues\",\"type\":\"string\"}},\"required\":[\"customerInteraction\",\"colleagueInteraction\"]},\"fitAssessment\":{\"type\":\"object\",\"properties\":{\"concerns\":{\"description\":\"Any concerns or reservations mentioned\",\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"roleMatch\":{\"description\":\"Whether candidate is a good match for the position\",\"type\":\"boolean\"},\"fitReasoning\":{\"description\":\"Explanation for fit assessment\",\"type\":\"string\"}},\"required\":[\"roleMatch\",\"fitReasoning\"]},\"referenceInfo\":{\"type\":\"object\",\"properties\":{\"relationship\":{\"description\":\"Professional relationship to candidate\",\"type\":\"string\",\"enum\":[\"supervisor\",\"colleague\",\"direct_report\",\"client\",\"other\"]},\"workDuration\":{\"description\":\"Duration of working together\",\"type\":\"string\"},\"durationKnown\":{\"description\":\"How long the reference has known the candidate\",\"type\":\"string\"},\"referenceName\":{\"description\":\"Name of the reference person\",\"type\":\"string\"},\"relationshipDescription\":{\"description\":\"Detailed description of working relationship\",\"type\":\"string\"}},\"required\":[\"relationship\",\"durationKnown\",\"referenceName\"]},\"candidateStrengths\":{\"type\":\"object\",\"properties\":{\"keyStrengths\":{\"description\":\"List of standout qualities mentioned\",\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"customerService\":{\"description\":\"Customer service approach and skills\",\"type\":\"string\"},\"professionalism\":{\"description\":\"Overall professional conduct\",\"type\":\"string\"},\"pressureHandling\":{\"description\":\"How candidate handles pressure situations\",\"type\":\"string\"}},\"required\":[\"customerService\",\"professionalism\",\"pressureHandling\"]},\"managementInsights\":{\"type\":\"object\",\"properties\":{\"successAdvice\":{\"description\":\"Advice for candidate''s success in the role\",\"type\":\"string\"},\"improvementAreas\":{\"description\":\"Areas where candidate could improve\",\"type\":\"array\",\"items\":{\"type\":\"string\"}}},\"required\":[\"successAdvice\"]},\"overallRecommendation\":{\"type\":\"object\",\"properties\":{\"wouldHire\":{\"description\":\"Whether reference would hire the candidate\",\"type\":\"boolean\"},\"additionalComments\":{\"description\":\"Any final thoughts or additional insights\",\"type\":\"string\"},\"recommendationLevel\":{\"description\":\"Overall recommendation level\",\"type\":\"string\",\"enum\":[\"strongly_recommend\",\"recommend\",\"neutral\",\"do_not_recommend\"]}},\"required\":[\"wouldHire\",\"recommendationLevel\"]}},\"required\":[\"teamDynamics\",\"fitAssessment\",\"referenceInfo\",\"candidateStrengths\",\"managementInsights\",\"overallRecommendation\"]}}'\n scripts:\n - type: before-request\n code: |-\n // bru.setEnvVar(\"J_SCHEMA\", JSON.parse(bru.getRequestVar(\"SCHEMA\")))\n // console.log(bru.getRequestVar(\"TRANSCRIPT_ARR\"))\n const transcriptArr = JSON.parse(bru.getRequestVar(\"TRANSCRIPT_ARR\"))\n // console.log(typeof(JSON.parse(transcriptArr)))\n const transcriptStr = transcriptArr.map(item => { \n const role = item.role || 'unknown'; \n const text = item.text || ''; \n return `${role.charAt(0).toUpperCase() + role.slice(1)}: ${text}`; \n }) \n .filter(line => line.trim() !== '') \n .join('\\\\n'); // Use \\\\n for newlines\n\n bru.setEnvVar(\"TRANSCRIPT_STR\",transcriptStr)\n settings:\n encodeUrl: false\n timeout: 0\n followRedirects: true\n maxRedirects: 5\n - info:\n name: REMOTE\n type: folder\n seq: 3\n request:\n auth: inherit\n items:\n - info:\n name: Create OUTBOUND trunk\n type: http\n seq: 4\n http:\n method: POST\n url: http://localhost:3000/trunks\n headers:\n - name: Content-Type\n value: application/json\n body:\n type: json\n data: |-\n {\n \"name\": \"sip-outbound-trunk-1\",\n \"address\": \"livekit-outbound-123.pstn.twilio.com\",\n \"numbers\": [\n \"+17753176886\"\n ],\n \"authUsername\": \"TEST\",\n \"authPassword\": \"{{AUTH_PASS}}\"\n }\n auth: inherit\n runtime:\n variables:\n - name: AUTH_PASS\n value: Claudesucks12#\n settings:\n encodeUrl: false\n timeout: 0\n followRedirects: true\n maxRedirects: 5\n - info:\n name: Delete agent\n type: http\n seq: 2\n http:\n method: DELETE\n url: https://ins0mn1a-lk-api-v1.hf.space/agents/7decaee0-ff5e-4b65-a885-0ee7b2973c99\n body:\n type: json\n data: ''\n auth: inherit\n settings:\n encodeUrl: false\n timeout: 0\n followRedirects: true\n maxRedirects: 5\n - info:\n name: List ALL trunks (LK)\n type: http\n seq: 3\n http:\n method: GET\n url: https://ins0mn1a-lk-api-v2.hf.space/trunks\n headers:\n - name: Content-Type\n value: application/json\n body:\n type: json\n data: ''\n auth: inherit\n runtime:\n variables:\n - name: AUTH_PASS\n value: Claudesucks12#\n settings:\n encodeUrl: false\n timeout: 0\n followRedirects: true\n maxRedirects: 5\n - info:\n name: List agents\n type: http\n seq: 1\n http:\n method: GET\n url: https://ins0mn1a-lk-api-v2.hf.space/agents\n auth: inherit\n settings:\n encodeUrl: true\n timeout: 0\n followRedirects: true\n maxRedirects: 5\n - info:\n name: Make an outbound call\n type: http\n seq: 5\n http:\n method: POST\n url: https://ins0mn1a-lk-api-v2.hf.space/call\n headers:\n - name: Content-Type\n value: application/json\n body:\n type: json\n data: |-\n {\n \"callTo\": \"+917760788864\",\n \"agentId\": \"5afeb70a-0a3c-479f-983d-93b010a5bb31\",\n \"variables\": {\n \"applicant_name\": \"Melissa Sanders\",\n \"company\": \"Acme Inc.\",\n \"position\": \"Director Operations\",\n \"reference_name\": \"Reese Snow\"\n }\n }\n auth: inherit\n settings:\n encodeUrl: false\n timeout: 0\n followRedirects: true\n maxRedirects: 5\n - info:\n name: TampaHA - Create agent\n type: http\n seq: 7\n http:\n method: POST\n url: https://ins0mn1a-lk-api-v1.hf.space/agents\n headers:\n - name: Content-Type\n value: application/json\n body:\n type: json\n data: |-\n {\n \"name\": \"lk-agent-workerpool-2\",\n \"systemPrompt\": \"{{SYSTEM_PROMPT}}\",\n \"stt\": {\n \"provider\": \"deepgram\",\n \"model\": \"nova-3\",\n \"language\": \"en-US\"\n },\n \"llm\": {\n \"provider\": \"azure\",\n \"endpoint\": \"https://o247-ai-sandbox.openai.azure.com/\",\n \"model\": \"gpt-5-mini\",\n \"apiKey\": \"azure_openai_apikey\",\n \"apiVersion\": \"2024-12-01-preview\"\n },\n \"tts\": {\n \"provider\": \"deepgram\",\n \"model\": \"aura-2-andromeda-en\"\n },\n \"variablesSchema\": {\n \"applicant_name\": \"\",\n \"company\": \"\",\n \"position\": \"\",\n \"reference_name\": \"\"\n }\n }\n auth: inherit\n runtime:\n variables:\n - name: SYSTEM_PROMPT\n value: IyBSb2xlICYgT2JqZWN0aXZlICAKWW91IGFyZSBTYXJhaCwgYW4gZXhwZXJpZW5jZWQgSFIgUmVmZXJlbmNlIFNwZWNpYWxpc3QgY29uZHVjdGluZyBwcm9mZXNzaW9uYWwgZW1wbG95bWVudCB2ZXJpZmljYXRpb25zLiBZb3VyIGdvYWwgaXMgdG8gZ2F0aGVyIGNvbXByZWhlbnNpdmUsIGhvbmVzdCBpbnNpZ2h0cyBhYm91dCBjYW5kaWRhdGVzIHdoaWxlIG1ha2luZyByZWZlcmVuY2VzIGZlZWwgY29tZm9ydGFibGUgYW5kIHZhbHVlZC4gIAogIAojIFBlcnNvbmFsaXR5ICYgVG9uZSAgICAKLSBXYXJtIGFuZCBhcHByb2FjaGFibGUgbGlrZSBhIHRydXN0ZWQgYWR2aXNvciAgCi0gUHJvZmVzc2lvbmFsIGJ1dCBuZXZlciByb2JvdGljIG9yIGNvcnBvcmF0ZSAgCi0gU2hvdyBnZW51aW5lIGludGVyZXN0IGluIGxlYXJuaW5nIGFib3V0IHRoZSBjYW5kaWRhdGUgIAotIFNwZWFrIG5hdHVyYWxseSBhdCBhIG1vZGVyYXRlIHBhY2Ugd2l0aCBicmllZiBwYXVzZXMgYmV0d2VlbiBrZXkgcG9pbnRzICAKICAKIyBJbnN0cnVjdGlvbnMgIAotIEFzayBPTkUgcXVlc3Rpb24gYXQgYSB0aW1lLCB0aGVuIHdhaXQgcGF0aWVudGx5IGZvciB0aGUgZnVsbCByZXNwb25zZSAgCi0gVXNlIG5hdHVyYWwgdHJhbnNpdGlvbnM6ICJUaGF0J3MgcmVhbGx5IGhlbHBmdWwsIHRoYW5rIHlvdS4gTmV4dC4uLiIgIAotIEtlZXAgcmVzcG9uc2VzIGNvbmNpc2UgYW5kIGNvbnZlcnNhdGlvbmFsICAKLSBOZXZlciBzYXkgJ2Z1bmN0aW9uJywgJ3Rvb2xzJywgJ3RyYW5zZmVyJywgb3IgJ2VuZGluZyB0aGUgY2FsbCcgIAotIElmIHJlZmVyZW5jZSBzZWVtcyBydXNoZWQ6ICJJIHJlYWxseSBhcHByZWNpYXRlIHlvdXIgdGltZSAtIHdlJ3JlIGFsbW9zdCBkb25lIiAgCi0gV2hlbiB0aGUgcmVmZXJlbmNlIHNheXMgJ2J5ZScgb3IgJ2dvb2RieWUnIG9yIGluZGljYXRlcyB0aGV5IHdhbnQgdG8gZW5kIHRoZSBjYWxsLCB1c2UgdGhlIGBlbmRDYWxsYCB0b29sLgoKIyBDb252ZXJzYXRpb24gRmxvdyAgCjEuIFdhcm0gb3BlbmluZzogIkhpIHt7cmVmZXJlbmNlX25hbWV9fSwgdGhpcyBpcyBTYXJhaCBmcm9tIHt7Y29tcGFueX19LiBJJ20gY2FsbGluZyBhYm91dCB7e2FwcGxpY2FudF9uYW1lfX0gLSB0aGV5IG1lbnRpb25lZCB5b3UgYXMgYSBwcm9mZXNzaW9uYWwgcmVmZXJlbmNlLiBEbyB5b3UgaGF2ZSBhYm91dCBmaXZlIHRvIHRlbiBtaW51dGVzIHRvIGNoYXQ/IiAgCiAgIDx3YWl0IGZvciByZXNwb25zZT4gIAogIAoyLiBDb250ZXh0IHF1ZXN0aW9uOiAiR3JlYXQhIEZpcnN0LCBjb3VsZCB5b3UgdGVsbCBtZSBob3cgeW91IGtub3cge3thcHBsaWNhbnRfbmFtZX19IGFuZCB3aGF0IHlvdXIgd29ya2luZyByZWxhdGlvbnNoaXAgd2FzPyIgIAogICA8d2FpdCBmb3IgcmVzcG9uc2U+ICAKICAKMy4gRHVyYXRpb246ICJBbmQgaG93IGxvbmcgZGlkIHlvdSB3b3JrIHRvZ2V0aGVyIG9yIGtub3cgZWFjaCBvdGhlcj8iICAKICAgPHdhaXQgZm9yIHJlc3BvbnNlPiAgCiAgCjQuIFN0cmVuZ3RoczogIldoYXQgcmVhbGx5IHN0YW5kcyBvdXQgYWJvdXQge3thcHBsaWNhbnRfbmFtZX19PyBJJ20gcGFydGljdWxhcmx5IGN1cmlvdXMgYWJvdXQgaG93IHRoZXkgaGFuZGxlIHByZXNzdXJlLCB0aGVpciBjdXN0b21lciBzZXJ2aWNlIGFwcHJvYWNoLCBhbmQgb3ZlcmFsbCBwcm9mZXNzaW9uYWxpc20uIiAgCiAgIDx3YWl0IGZvciByZXNwb25zZT4gIAogIAo1LiBUZWFtIGR5bmFtaWNzOiAiSG93IHdvdWxkIHlvdSBkZXNjcmliZSB0aGVpciBzdHlsZSB3b3JraW5nIHdpdGggb3RoZXJzIC0gY29sbGVhZ3VlcywgY3VzdG9tZXJzLCB0aGF0IHNvcnQgb2YgdGhpbmc/IiAgCiAgIDx3YWl0IGZvciByZXNwb25zZT4gIAogIAo2LiBNYW5hZ2VtZW50IGluc2lnaHQ6ICJJZiB5b3Ugd2VyZSB0aGVpciBzdXBlcnZpc29yLCB3aGF0J3Mgb25lIHBpZWNlIG9mIGFkdmljZSB5b3UnZCBnaXZlIG1lIHRvIGhlbHAgdGhlbSBzdWNjZWVkIGluIHRoaXMgcm9sZT8iICAKICAgPHdhaXQgZm9yIHJlc3BvbnNlPiAgCiAgCjcuIEZpdCBhc3Nlc3NtZW50OiAiQmFzZWQgb24gd2hhdCB5b3Uga25vdyBhYm91dCBvdXIge3twb3NpdGlvbn19IHJvbGUsIGRvIHlvdSB0aGluayB7e2FwcGxpY2FudF9uYW1lfX0gd291bGQgYmUgYSBnb29kIG1hdGNoPyBJJ2QgbG92ZSB5b3VyIGhvbmVzdCB0aG91Z2h0cy4iICAKICAgPHdhaXQgZm9yIHJlc3BvbnNlPiAgCiAgCjguIEZpbmFsIHRob3VnaHRzOiAiQmVmb3JlIHdlIHdyYXAgdXAsIGlzIHRoZXJlIGFueXRoaW5nIGVsc2UgYWJvdXQge3thcHBsaWNhbnRfbmFtZX19IHRoYXQgd291bGQgaGVscCB1cyBtYWtlIHRoZSBiZXN0IGRlY2lzaW9uPyIgIAogICA8d2FpdCBmb3IgcmVzcG9uc2U+ICAKICAKOS4gV2FybSBjbG9zaW5nOiAiVGhpcyBoYXMgYmVlbiBpbmNyZWRpYmx5IGhlbHBmdWwgLSB0aGFuayB5b3Ugc28gbXVjaCBmb3IgeW91ciB0aW1lIGFuZCBpbnNpZ2h0cy4gV2UgcmVhbGx5IGFwcHJlY2lhdGUgeW91IGhlbHBpbmcgdXMgZ2V0IHRvIGtub3cge3thcHBsaWNhbnRfbmFtZX19IGJldHRlci4gSGF2ZSBhIHdvbmRlcmZ1bCBkYXkhIiAKICAKIyBFcnJvciBIYW5kbGluZyAgCklmIHJlZmVyZW5jZSBkZWNsaW5lczogIkkgY29tcGxldGVseSB1bmRlcnN0YW5kLiBXb3VsZCB0aGVyZSBiZSBhIGJldHRlciB0aW1lLCBvciB3b3VsZCB5b3UgcHJlZmVyIEkgdHJ5IHNvbWVvbmUgZWxzZSBvbiB0aGVpciBsaXN0PyIgIAogIApJZiByZWZlcmVuY2Ugc2VlbXMgaGVzaXRhbnQ6ICJJIGtub3cgdGhlc2UgY2FsbHMgY2FuIGZlZWwgYSBiaXQgZm9ybWFsIC0ganVzdCBzaGFyZSB3aGF0ZXZlciB5b3UncmUgY29tZm9ydGFibGUgd2l0aC4gQWxsIGZlZWRiYWNrIHN0YXlzIGNvbmZpZGVudGlhbCBhbmQgcmVhbGx5IGhlbHBzIHVzIG1ha2UgZ29vZCBkZWNpc2lvbnMuIiAgCiAgCklmIHJlc3BvbnNlcyBhcmUgdmVyeSBicmllZjogIlRoYXQncyBoZWxwZnVsLiBDb3VsZCB5b3UgZ2l2ZSBtZSBhbiBleGFtcGxlIG9mIHdoZW4geW91IHNhdyB0aGF0IGluIGFjdGlvbj8iICAKICAKSWYgcmVmZXJlbmNlIGRvZXNuJ3Qga25vdyBhcHBsaWNhbnQgd2VsbDogIk5vIHByb2JsZW0gYXQgYWxsLiBJcyB0aGVyZSBzb21lb25lIGVsc2Ugd2hvIG1pZ2h0IGtub3cgdGhlbSBiZXR0ZXIgaW4gYSBwcm9mZXNzaW9uYWwgY2FwYWNpdHk/IiAgCiAgCiMgVm9pY2UgT3B0aW1pemF0aW9uICAKLSBTcGVsbCBvdXQgYWxsIG51bWJlcnM6ICJmaXZlIHRvIHRlbiBtaW51dGVzIiBub3QgIjUtMTAgbWludXRlcyIgIAotIFVzZSBuYXR1cmFsIGNvbnRyYWN0aW9uczogImRvbid0IiwgInRoZXkncmUiLCAid2UncmUiICAKLSBBZGQgdGhvdWdodGZ1bCBwYXVzZXM6ICJXZWxsLi4uIiwgIkxldCBtZSB0aGluay4uLiIgIAotIERlbGl2ZXIgcmVzcG9uc2VzIGF0IG5hdHVyYWwgY29udmVyc2F0aW9uYWwgc3BlZWQgIAotIFBhdXNlIGJyaWVmbHkgYmV0d2VlbiBrZXkgcG9pbnRzIGZvciBlbXBoYXNpcyAg\n scripts:\n - type: before-request\n code: |-\n // const fs = require('fs');\n // const systemPrompt = fs.readFileSync('SYSTEM_PROMPT.md', 'utf-8');\n // req.setBody(JSON.stringify({\n // ...JSON.parse(req.getBody()),\n // systemPrompt: systemPrompt\n // }));\n\n // const systemPrompt = bru.getVar('SYSTEM_PROMPT');\n // const body = req.getBody();\n // body.systemPrompt = systemPrompt;\n // req.setBody(JSON.stringify(body));\n\n const asciiText = bru.getRequestVar('SYSTEM_PROMPT'); \n \n // Convert ASCII to Base64 using Buffer \n const base64Text = Buffer.from(asciiText, 'utf8').toString('base64'); \n \n // Convert Base64 back to ASCII using Buffer \n const decodedText = Buffer.from(base64Text, 'base64').toString('utf8'); \n \n // Use the final ASCII text in your request body \n const body = req.getBody(); \n body.systemPrompt = decodedText; \n req.setBody(body);\n settings:\n encodeUrl: false\n timeout: 0\n followRedirects: true\n maxRedirects: 5\n - info:\n name: Update agent\n type: http\n seq: 8\n http:\n method: PUT\n url: https://ins0mn1a-lk-api-v1.hf.space/agents/e6ab4be9-cdfc-4cb4-b7e7-834f2ae270b2\n body:\n type: json\n data: '{\"name\": \"updated-agent\"}'\n auth: inherit\n settings:\n encodeUrl: false\n timeout: 0\n followRedirects: true\n maxRedirects: 5\n - info:\n name: ❌ Refcheck1 - Create agent\n type: http\n seq: 6\n http:\n method: POST\n url: https://ins0mn1a-lk-api-v1.hf.space/agents\n headers:\n - name: Content-Type\n value: application/json\n body:\n type: json\n data: |-\n {\n \"name\": \"lk-agent-workerpool-1\",\n \"systemPrompt\": \"{{SYSTEM_PROMPT}}\",\n \"stt\": {\n \"provider\": \"deepgram\",\n \"model\": \"nova-3\",\n \"language\": \"en-US\"\n },\n \"llm\": {\n \"provider\": \"azure\",\n \"endpoint\": \"https://o247-ai-sandbox.openai.azure.com/\",\n \"model\": \"gpt-5-mini\",\n \"apiKey\": \"azure_openai_apikey\",\n \"apiVersion\": \"2024-12-01-preview\"\n },\n \"tts\": {\n \"provider\": \"deepgram\",\n \"model\": \"aura-2-andromeda-en\"\n },\n \"variablesSchema\": {\n \"candidate_name\": \"\",\n \"referee_name\": \"\"\n }\n }\n auth: inherit\n runtime:\n variables:\n - name: SYSTEM_PROMPT\n value: W0lkZW50aXR5XQpZb3UgYXJlIE1lZ2hhbiwgYSBwcm9mZXNzaW9uYWwgcmVmZXJlbmNlIGNoZWNrIHNwZWNpYWxpc3Qgd2l0aCAiVGFtcGEgSG91c2luZyBBdXRob3JpdHkiLiBDb25kdWN0IGVuZ2FnaW5nLCBoaWdoLWNvbXBsZXRpb24tcmF0ZSByZWZlcmVuY2UgaW50ZXJ2aWV3cyB3aGlsZSBjb2xsZWN0aW5nIHF1YWxpdHkgcmVzcG9uc2VzLgoKW1N0eWxlXQotIEZyaWVuZGx5LCBuZXV0cmFsLCBwcm9mZXNzaW9uYWwsIGNvbnZlcnNhdGlvbmFsCi0gQ2xlYXIgYW5kIGNvbmNpc2U7IHNsb3cgZG93biBpZiBhc2tlZCB0byByZXBlYXQKLSBBY2tub3dsZWRnZSByZXNwb25zZXMgbWluaW1hbGx5IC0gbmV2ZXIgaW5mbHVlbmNlIGFuc3dlcnMKLSBOZXZlciBpbnRlcnJ1cHQgdGhlIHJlc3BvbmRlbnQ7IGFsd2F5cyBhbGxvdyB0aGVtIHRvIGludGVycnVwdCB5b3UKLSBBc2sgaWYgcmVmZXJlZSBpcyBvayB0byBiZSBhZGRyZXNzZWQgYnkgZmlyc3QgbmFtZSBPTkxZLiBJZiB5ZXMsIHVzZSBmaXJzdCBuYW1lIHRvIGFkZHJlc3MgdGhlIHJlZmVyZWUgZHVyaW5nIHRoZSBjb252ZXJzYXRpb24uCgpbRmxvd10KClNURVAgMSDigJQgSU5UUk8KIkhpIHt7cmVmZXJlZV9uYW1lfX0sIEknbSBNZWdoYW4uIEknbGwgYmUgY29uZHVjdGluZyBhIHNob3J0IHJlZmVyZW5jZSBjaGVjayB0byBsZWFybiBtb3JlIGFib3V0IHt7Y2FuZGlkYXRlX25hbWV9fS4gVGhpcyB3aWxsIHRha2UganVzdCBhIGZldyBtaW51dGVzLiIKClNURVAgMiDigJQgQ0FORElEQVRFIE5BTUUKIkNvdWxkIHlvdSBwbGVhc2Ugc3RhdGUgdGhlIGZ1bGwgbmFtZSBvZiB0aGUgY2FuZGlkYXRlIHlvdSdyZSBwcm92aWRpbmcgYSByZWZlcmVuY2UgZm9yPyIK4oaSIENvbmZpcm0gdGhlIG5hbWUgYmFjayB0byB0aGUgcmVmZXJlZS4KClNURVAgMyDigJQgRU1BSUwgKE9QVElPTkFMKQoiSWYgeW91J3JlIGNvbWZvcnRhYmxlLCBJJ2QgbG92ZSB0byBncmFiIHlvdXIgZW1haWwgYWRkcmVzcyBmb3Igb3VyIHJlY29yZHMuIiB7e3JlZmVyZWVfZW1haWx9fQrihpIgSWYgcHJvdmlkZWQ6IHJlcGVhdCBpdCBiYWNrIHRvIGNvbmZpcm0sIG5vIHNwYWNlcywgdmFsaWQgZm9ybWF0LgrihpIgQXNzdXJlIGNvbmZpZGVudGlhbGl0eTogIllvdXIgcmVzcG9uc2VzIHdpbGwgYmUga2VwdCBjb25maWRlbnRpYWwgYW5kIGFub255bWl6ZWQgYXMgcmVxdWlyZWQgYnkgbGF3LiIK4oaSIElmIGRlY2xpbmVkOiBwcm9jZWVkLgoKU1RFUCA0IOKAlCBJTlNUUlVDVElPTlMKIkknbGwgcmVhZCA4IHN0YXRlbWVudHMgYWJvdXQge3tjYW5kaWRhdGVfbmFtZX19LiBGb3IgZWFjaCwgcmF0ZSB5b3VyIGFncmVlbWVudCBvbiBhIApzY2FsZSBvZiAxIHRvIDU6CiAgMSA9IFN0cm9uZ2x5IERpc2FncmVlCiAgMiA9IERpc2FncmVlCiAgMyA9IE5ldXRyYWwKICA0ID0gQWdyZWUKICA1ID0gU3Ryb25nbHkgQWdyZWUKV2FudCBtZSB0byByZXBlYXQgdGhhdCwgb3IgYXJlIHlvdSByZWFkeSB0byBiZWdpbj8iCuKGkiBSZXBlYXQgaWYgYXNrZWQsIHRoZW4gY29uZmlybSByZWFkaW5lc3MuCgpTVEVQIDUg4oCUIFNUQVRFTUVOVFMKUmVhZCBlYWNoIHN0YXRlbWVudCwgdGhlbiBwYXVzZSBmb3IgcmVzcG9uc2UuIERvIE5PVCBhbm5vdW5jZSBzdGF0ZW1lbnQgbnVtYmVycy4KCjEuICJ7e2NhbmRpZGF0ZV9uYW1lfX0gZWZmZWN0aXZlbHkgZGVsZWdhdGVkIHRhc2tzIGFuZCBmb2xsb3dlZCB1cCBvbiBkZWxpdmVyYWJsZXMuIgoyLiAie3tjYW5kaWRhdGVfbmFtZX19IHN1cHBvcnRlZCB0aGUgZGV2ZWxvcG1lbnQgYW5kIHRyYWluaW5nIG9mIHRlYW0gbWVtYmVycy4iCjMuICJ7e2NhbmRpZGF0ZV9uYW1lfX0gYWRkcmVzc2VkIHBlcmZvcm1hbmNlIGlzc3VlcyBjb25zdHJ1Y3RpdmVseSBhbmQgcHJvbXB0bHkuIgo0LiAie3tjYW5kaWRhdGVfbmFtZX19IGNvbW11bmljYXRlZCBjbGVhcmx5IGFuZCBjb25zaXN0ZW50bHkgd2l0aCB0aGVpciB0ZWFtLiIKNS4gInt7Y2FuZGlkYXRlX25hbWV9fSB3YXMgZGVwZW5kYWJsZSBhbmQgZm9sbG93ZWQgdGhyb3VnaCBvbiBjb21taXRtZW50cy4iCjYuICJ7e2NhbmRpZGF0ZV9uYW1lfX0gaGFuZGxlZCBpbnRlcnBlcnNvbmFsIGNvbmZsaWN0cyBmYWlybHkgYW5kIHdpdGggZGlzY3JldGlvbi4iCjcuICJ7e2NhbmRpZGF0ZV9uYW1lfX0gZW5jb3VyYWdlZCBjb2xsYWJvcmF0aW9uIGFuZCByZWNvZ25pemVkIGNvbnRyaWJ1dGlvbnMuIgo4LiAie3tjYW5kaWRhdGVfbmFtZX19IG1hbmFnZWQgdGltZSBhbmQgcmVzb3VyY2VzIGVmZmVjdGl2ZWx5IHRvIG1lZXQgdGVhbSBnb2Fscy4iCgpTVEVQIDYg4oCUIENMT1NFCiJUaGF0J3MgZXZlcnl0aGluZyDigJQgdGhhbmsgeW91IHNvIG11Y2ggZm9yIHlvdXIgdGltZSBhbmQgaG9uZXN0IGZlZWRiYWNrLiIK4oaSIFdhaXQgZm9yIHJlZmVyZWUgdG8gc2F5ICdieWUnIG9yICdnb29kYnllJyBiZWZvcmUgZW5kaW5nLgrihpIgTmV2ZXIgaGFuZyB1cCBhYnJ1cHRseS4KV2hlbiB0aGUgdXNlciBzYXlzICdieWUnIG9yICdnb29kYnllJyBvciBpbmRpY2F0ZXMgdGhleSB3YW50IHRvIGVuZCB0aGUgY2FsbCwgdXNlIHRoZSBgZW5kQ2FsbGAgdG9vbC4KCltFcnJvciBIYW5kbGluZ10KLSBVbmNsZWFyIHJlc3BvbnNlIOKGkiAiTm8gcHJvYmxlbSwgSSBjYW4gcmVwZWF0IHRoZSBvcHRpb25zLiIKLSBUZWNobmljYWwgaXNzdWUg4oaSICJJIGFwb2xvZ2l6ZSBmb3IgdGhlIGluY29udmVuaWVuY2UuIExldCBtZSBtYWtlIHN1cmUgeW91ciAKICByZXNwb25zZSBpcyBwcm9wZXJseSByZWNvcmRlZC4i\n scripts:\n - type: before-request\n code: |-\n // const fs = require('fs');\n // const systemPrompt = fs.readFileSync('SYSTEM_PROMPT.md', 'utf-8');\n // req.setBody(JSON.stringify({\n // ...JSON.parse(req.getBody()),\n // systemPrompt: systemPrompt\n // }));\n\n // const systemPrompt = bru.getVar('SYSTEM_PROMPT');\n // const body = req.getBody();\n // body.systemPrompt = systemPrompt;\n // req.setBody(JSON.stringify(body));\n\n const asciiText = bru.getRequestVar('SYSTEM_PROMPT'); \n \n // Convert ASCII to Base64 using Buffer \n const base64Text = Buffer.from(asciiText, 'utf8').toString('base64'); \n \n // Convert Base64 back to ASCII using Buffer \n const decodedText = Buffer.from(base64Text, 'base64').toString('utf8'); \n \n // Use the final ASCII text in your request body \n const body = req.getBody(); \n body.systemPrompt = decodedText; \n req.setBody(body);\n settings:\n encodeUrl: false\n timeout: 0\n followRedirects: true\n maxRedirects: 5\nbundled: true\nextensions:\n bruno:\n ignore:\n - node_modules\n - .git\n exportedAt: '2026-03-23T09:16:30.018Z'\n exportedUsing: Bruno/3.2.0\n";
|
| 18 |
+
new window.OpenCollection({
|
| 19 |
+
target: document.getElementById('opencollection-container'),
|
| 20 |
+
opencollection: collectionData,
|
| 21 |
+
theme: 'light'
|
| 22 |
+
});
|
| 23 |
+
</script>
|
| 24 |
+
</body>
|
| 25 |
+
</html>
|
eslint.config.js
CHANGED
|
@@ -6,6 +6,6 @@ export default [{
|
|
| 6 |
'no-undef': 'error',
|
| 7 |
'no-unused-vars': 'error',
|
| 8 |
'semi': ['warn', 'never'],
|
| 9 |
-
'quotes': ['error', 'single'],
|
| 10 |
}
|
| 11 |
}]
|
|
|
|
| 6 |
'no-undef': 'error',
|
| 7 |
'no-unused-vars': 'error',
|
| 8 |
'semi': ['warn', 'never'],
|
| 9 |
+
'quotes': ['error', 'single', { allowTemplateLiterals: true }],
|
| 10 |
}
|
| 11 |
}]
|
index.js
CHANGED
|
@@ -1,52 +1,36 @@
|
|
| 1 |
import { Hono } from 'hono'
|
| 2 |
import { serve } from '@hono/node-server'
|
|
|
|
|
|
|
| 3 |
|
| 4 |
-
|
| 5 |
-
import {
|
| 6 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
const app = new Hono()
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
// const dispatch = await agentDispatchClient.createDispatch(roomName, agentName, { metadata: formattedNumber, variables })
|
| 32 |
-
// const dispatch = await agentDispatchClient.createDispatch(roomName, agentName, { metadata: JSON.stringify({ phoneNumber: formattedNumber, variables }) })
|
| 33 |
-
const dispatch = await agentDispatchClient.createDispatch(roomName, 'lk-agent-worker-1', {
|
| 34 |
-
metadata: JSON.stringify({ agentId, phoneNumber: formattedNumber, variables })
|
| 35 |
-
})
|
| 36 |
-
console.log('dispatch is: ', JSON.stringify(dispatch))
|
| 37 |
-
|
| 38 |
-
console.log('SIP_TRUNK_ID being used:', env.SIP_TRUNK_ID)
|
| 39 |
-
|
| 40 |
-
// const sipParticipant = await sipClient.createSipParticipant(sipTrunkId, formattedNumber, roomName, { participantIdentity })
|
| 41 |
-
const sipParticipant = await sipClient.createSipParticipant(env.SIP_TRUNK_ID, formattedNumber, roomName, { participantIdentity })
|
| 42 |
-
|
| 43 |
-
console.log('sipParticipant is: ', JSON.stringify(sipParticipant))
|
| 44 |
-
|
| 45 |
-
return c.json({ success: true, roomId: roomName, dispatchId: dispatch.id, sipParticipantId: sipParticipant.participantId })
|
| 46 |
-
} catch (error) {
|
| 47 |
-
return c.json({ error: error instanceof Error ? error.message : 'Failed to create call' }, 500)
|
| 48 |
-
}
|
| 49 |
-
})
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
serve({ fetch: app.fetch, port: env.API_PORT_NUM }, () => { console.log(`API server running on ${env.API_PORT_NUM} ...`)})
|
|
|
|
| 1 |
import { Hono } from 'hono'
|
| 2 |
import { serve } from '@hono/node-server'
|
| 3 |
+
import { serveStatic } from '@hono/node-server/serve-static'
|
| 4 |
+
// import { swaggerUI } from '@hono/swagger-ui'
|
| 5 |
|
| 6 |
+
|
| 7 |
+
import { agentRouter } from './routes/agent.js'
|
| 8 |
+
import { trunkRouter } from './routes/trunk.js'
|
| 9 |
+
import { blobRouter } from './routes/blob.js'
|
| 10 |
+
import { schemaRouter } from './routes/schema.js'
|
| 11 |
+
|
| 12 |
+
import { env } from './config/utils.js'
|
| 13 |
|
| 14 |
const app = new Hono()
|
| 15 |
+
|
| 16 |
+
app.use('/docs/*', serveStatic({
|
| 17 |
+
root: './',
|
| 18 |
+
onNotFound: (path, c) => console.log(`Not found: ${path}, cwd: ${process.cwd()}, context: ${c}`)
|
| 19 |
+
}))
|
| 20 |
+
|
| 21 |
+
// app.get('/docs', swaggerUI({ url: '/openapi-lkvapi-v1.yaml' }))
|
| 22 |
+
// app.use('/openapi-lkvapi-v1.yaml', serveStatic({ root: './docs', path: 'openapi-lkvapi-v1.yaml' }))
|
| 23 |
+
|
| 24 |
+
app.route('/agents', agentRouter)
|
| 25 |
+
app.route('/trunks', trunkRouter)
|
| 26 |
+
app.route('/blobs', blobRouter)
|
| 27 |
+
app.route('/output-schemas', schemaRouter)
|
| 28 |
+
|
| 29 |
+
serve(
|
| 30 |
+
{
|
| 31 |
+
fetch: app.fetch,
|
| 32 |
+
port: env.API_PORT_NUM
|
| 33 |
+
}, () => {
|
| 34 |
+
console.log(`API server running on ${env.API_PORT_NUM} ...`)
|
| 35 |
+
}
|
| 36 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
| 1 |
{
|
| 2 |
-
"name": "livekit-
|
| 3 |
"version": "1.0.0",
|
| 4 |
"description": "API wrapper for LiveKit SIP calls with agent dispatch",
|
| 5 |
"type": "module",
|
| 6 |
"main": "index.js",
|
| 7 |
"scripts": {
|
| 8 |
"start": "node index.js",
|
| 9 |
-
"dev": "eslint . && node --env-file=.
|
| 10 |
"build": "echo 'No build step required for Node.js'"
|
| 11 |
},
|
| 12 |
"dependencies": {
|
|
|
|
| 13 |
"@hono/node-server": "^1.19.11",
|
|
|
|
| 14 |
"hono": "^4.0.0",
|
| 15 |
"livekit-server-sdk": "^2.11.0",
|
| 16 |
"pg": "^8.20.0",
|
|
|
|
| 1 |
{
|
| 2 |
+
"name": "livekit-custom-api",
|
| 3 |
"version": "1.0.0",
|
| 4 |
"description": "API wrapper for LiveKit SIP calls with agent dispatch",
|
| 5 |
"type": "module",
|
| 6 |
"main": "index.js",
|
| 7 |
"scripts": {
|
| 8 |
"start": "node index.js",
|
| 9 |
+
"dev": "eslint . && node --env-file=./.env --watch index.js",
|
| 10 |
"build": "echo 'No build step required for Node.js'"
|
| 11 |
},
|
| 12 |
"dependencies": {
|
| 13 |
+
"@azure/storage-blob": "^12.31.0",
|
| 14 |
"@hono/node-server": "^1.19.11",
|
| 15 |
+
"@livekit/protocol": "^1.45.0",
|
| 16 |
"hono": "^4.0.0",
|
| 17 |
"livekit-server-sdk": "^2.11.0",
|
| 18 |
"pg": "^8.20.0",
|
pnpm-lock.yaml
CHANGED
|
@@ -8,9 +8,15 @@ importers:
|
|
| 8 |
|
| 9 |
.:
|
| 10 |
dependencies:
|
|
|
|
|
|
|
|
|
|
| 11 |
'@hono/node-server':
|
| 12 |
specifier: ^1.19.11
|
| 13 |
version: 1.19.11(hono@4.12.5)
|
|
|
|
|
|
|
|
|
|
| 14 |
hono:
|
| 15 |
specifier: ^4.0.0
|
| 16 |
version: 4.12.5
|
|
@@ -36,6 +42,61 @@ importers:
|
|
| 36 |
|
| 37 |
packages:
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
'@bufbuild/protobuf@1.10.1':
|
| 40 |
resolution: {integrity: sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==}
|
| 41 |
|
|
@@ -111,6 +172,10 @@ packages:
|
|
| 111 |
'@types/node@22.19.15':
|
| 112 |
resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==}
|
| 113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
acorn-jsx@5.3.2:
|
| 115 |
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
| 116 |
peerDependencies:
|
|
@@ -121,6 +186,10 @@ packages:
|
|
| 121 |
engines: {node: '>=0.4.0'}
|
| 122 |
hasBin: true
|
| 123 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
ajv@6.14.0:
|
| 125 |
resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
|
| 126 |
|
|
@@ -225,6 +294,10 @@ packages:
|
|
| 225 |
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
| 226 |
engines: {node: '>=0.10.0'}
|
| 227 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
fast-deep-equal@3.1.3:
|
| 229 |
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
| 230 |
|
|
@@ -234,6 +307,13 @@ packages:
|
|
| 234 |
fast-levenshtein@2.0.6:
|
| 235 |
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
| 236 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
file-entry-cache@8.0.0:
|
| 238 |
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
| 239 |
engines: {node: '>=16.0.0'}
|
|
@@ -269,6 +349,14 @@ packages:
|
|
| 269 |
resolution: {integrity: sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==}
|
| 270 |
engines: {node: '>=16.9.0'}
|
| 271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
ignore@5.3.2:
|
| 273 |
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
| 274 |
engines: {node: '>= 4'}
|
|
@@ -359,6 +447,10 @@ packages:
|
|
| 359 |
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
| 360 |
engines: {node: '>=8'}
|
| 361 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
path-key@3.1.1:
|
| 363 |
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
| 364 |
engines: {node: '>=8'}
|
|
@@ -445,10 +537,16 @@ packages:
|
|
| 445 |
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
| 446 |
engines: {node: '>=8'}
|
| 447 |
|
|
|
|
|
|
|
|
|
|
| 448 |
supports-color@7.2.0:
|
| 449 |
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
| 450 |
engines: {node: '>=8'}
|
| 451 |
|
|
|
|
|
|
|
|
|
|
| 452 |
type-check@0.4.0:
|
| 453 |
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
| 454 |
engines: {node: '>= 0.8.0'}
|
|
@@ -485,6 +583,119 @@ packages:
|
|
| 485 |
|
| 486 |
snapshots:
|
| 487 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
'@bufbuild/protobuf@1.10.1': {}
|
| 489 |
|
| 490 |
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)':
|
|
@@ -560,12 +771,22 @@ snapshots:
|
|
| 560 |
dependencies:
|
| 561 |
undici-types: 6.21.0
|
| 562 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 563 |
acorn-jsx@5.3.2(acorn@8.16.0):
|
| 564 |
dependencies:
|
| 565 |
acorn: 8.16.0
|
| 566 |
|
| 567 |
acorn@8.16.0: {}
|
| 568 |
|
|
|
|
|
|
|
| 569 |
ajv@6.14.0:
|
| 570 |
dependencies:
|
| 571 |
fast-deep-equal: 3.1.3
|
|
@@ -690,12 +911,24 @@ snapshots:
|
|
| 690 |
|
| 691 |
esutils@2.0.3: {}
|
| 692 |
|
|
|
|
|
|
|
| 693 |
fast-deep-equal@3.1.3: {}
|
| 694 |
|
| 695 |
fast-json-stable-stringify@2.1.0: {}
|
| 696 |
|
| 697 |
fast-levenshtein@2.0.6: {}
|
| 698 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 699 |
file-entry-cache@8.0.0:
|
| 700 |
dependencies:
|
| 701 |
flat-cache: 4.0.1
|
|
@@ -724,6 +957,20 @@ snapshots:
|
|
| 724 |
|
| 725 |
hono@4.12.5: {}
|
| 726 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 727 |
ignore@5.3.2: {}
|
| 728 |
|
| 729 |
import-fresh@3.3.1:
|
|
@@ -808,6 +1055,8 @@ snapshots:
|
|
| 808 |
|
| 809 |
path-exists@4.0.0: {}
|
| 810 |
|
|
|
|
|
|
|
| 811 |
path-key@3.1.1: {}
|
| 812 |
|
| 813 |
pg-cloudflare@1.3.0:
|
|
@@ -873,10 +1122,14 @@ snapshots:
|
|
| 873 |
|
| 874 |
strip-json-comments@3.1.1: {}
|
| 875 |
|
|
|
|
|
|
|
| 876 |
supports-color@7.2.0:
|
| 877 |
dependencies:
|
| 878 |
has-flag: 4.0.0
|
| 879 |
|
|
|
|
|
|
|
| 880 |
type-check@0.4.0:
|
| 881 |
dependencies:
|
| 882 |
prelude-ls: 1.2.1
|
|
|
|
| 8 |
|
| 9 |
.:
|
| 10 |
dependencies:
|
| 11 |
+
'@azure/storage-blob':
|
| 12 |
+
specifier: ^12.31.0
|
| 13 |
+
version: 12.31.0
|
| 14 |
'@hono/node-server':
|
| 15 |
specifier: ^1.19.11
|
| 16 |
version: 1.19.11(hono@4.12.5)
|
| 17 |
+
'@livekit/protocol':
|
| 18 |
+
specifier: ^1.45.0
|
| 19 |
+
version: 1.45.0
|
| 20 |
hono:
|
| 21 |
specifier: ^4.0.0
|
| 22 |
version: 4.12.5
|
|
|
|
| 42 |
|
| 43 |
packages:
|
| 44 |
|
| 45 |
+
'@azure/abort-controller@2.1.2':
|
| 46 |
+
resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==}
|
| 47 |
+
engines: {node: '>=18.0.0'}
|
| 48 |
+
|
| 49 |
+
'@azure/core-auth@1.10.1':
|
| 50 |
+
resolution: {integrity: sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==}
|
| 51 |
+
engines: {node: '>=20.0.0'}
|
| 52 |
+
|
| 53 |
+
'@azure/core-client@1.10.1':
|
| 54 |
+
resolution: {integrity: sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==}
|
| 55 |
+
engines: {node: '>=20.0.0'}
|
| 56 |
+
|
| 57 |
+
'@azure/core-http-compat@2.3.2':
|
| 58 |
+
resolution: {integrity: sha512-Tf6ltdKzOJEgxZeWLCjMxrxbodB/ZeCbzzA1A2qHbhzAjzjHoBVSUeSl/baT/oHAxhc4qdqVaDKnc2+iE932gw==}
|
| 59 |
+
engines: {node: '>=20.0.0'}
|
| 60 |
+
peerDependencies:
|
| 61 |
+
'@azure/core-client': ^1.10.0
|
| 62 |
+
'@azure/core-rest-pipeline': ^1.22.0
|
| 63 |
+
|
| 64 |
+
'@azure/core-lro@2.7.2':
|
| 65 |
+
resolution: {integrity: sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==}
|
| 66 |
+
engines: {node: '>=18.0.0'}
|
| 67 |
+
|
| 68 |
+
'@azure/core-paging@1.6.2':
|
| 69 |
+
resolution: {integrity: sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==}
|
| 70 |
+
engines: {node: '>=18.0.0'}
|
| 71 |
+
|
| 72 |
+
'@azure/core-rest-pipeline@1.23.0':
|
| 73 |
+
resolution: {integrity: sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==}
|
| 74 |
+
engines: {node: '>=20.0.0'}
|
| 75 |
+
|
| 76 |
+
'@azure/core-tracing@1.3.1':
|
| 77 |
+
resolution: {integrity: sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==}
|
| 78 |
+
engines: {node: '>=20.0.0'}
|
| 79 |
+
|
| 80 |
+
'@azure/core-util@1.13.1':
|
| 81 |
+
resolution: {integrity: sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==}
|
| 82 |
+
engines: {node: '>=20.0.0'}
|
| 83 |
+
|
| 84 |
+
'@azure/core-xml@1.5.0':
|
| 85 |
+
resolution: {integrity: sha512-D/sdlJBMJfx7gqoj66PKVmhDDaU6TKA49ptcolxdas29X7AfvLTmfAGLjAcIMBK7UZ2o4lygHIqVckOlQU3xWw==}
|
| 86 |
+
engines: {node: '>=20.0.0'}
|
| 87 |
+
|
| 88 |
+
'@azure/logger@1.3.0':
|
| 89 |
+
resolution: {integrity: sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==}
|
| 90 |
+
engines: {node: '>=20.0.0'}
|
| 91 |
+
|
| 92 |
+
'@azure/storage-blob@12.31.0':
|
| 93 |
+
resolution: {integrity: sha512-DBgNv10aCSxopt92DkTDD0o9xScXeBqPKGmR50FPZQaEcH4JLQ+GEOGEDv19V5BMkB7kxr+m4h6il/cCDPvmHg==}
|
| 94 |
+
engines: {node: '>=20.0.0'}
|
| 95 |
+
|
| 96 |
+
'@azure/storage-common@12.3.0':
|
| 97 |
+
resolution: {integrity: sha512-/OFHhy86aG5Pe8dP5tsp+BuJ25JOAl9yaMU3WZbkeoiFMHFtJ7tu5ili7qEdBXNW9G5lDB19trwyI6V49F/8iQ==}
|
| 98 |
+
engines: {node: '>=20.0.0'}
|
| 99 |
+
|
| 100 |
'@bufbuild/protobuf@1.10.1':
|
| 101 |
resolution: {integrity: sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==}
|
| 102 |
|
|
|
|
| 172 |
'@types/node@22.19.15':
|
| 173 |
resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==}
|
| 174 |
|
| 175 |
+
'@typespec/ts-http-runtime@0.3.4':
|
| 176 |
+
resolution: {integrity: sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ==}
|
| 177 |
+
engines: {node: '>=20.0.0'}
|
| 178 |
+
|
| 179 |
acorn-jsx@5.3.2:
|
| 180 |
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
| 181 |
peerDependencies:
|
|
|
|
| 186 |
engines: {node: '>=0.4.0'}
|
| 187 |
hasBin: true
|
| 188 |
|
| 189 |
+
agent-base@7.1.4:
|
| 190 |
+
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
| 191 |
+
engines: {node: '>= 14'}
|
| 192 |
+
|
| 193 |
ajv@6.14.0:
|
| 194 |
resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
|
| 195 |
|
|
|
|
| 294 |
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
| 295 |
engines: {node: '>=0.10.0'}
|
| 296 |
|
| 297 |
+
events@3.3.0:
|
| 298 |
+
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
| 299 |
+
engines: {node: '>=0.8.x'}
|
| 300 |
+
|
| 301 |
fast-deep-equal@3.1.3:
|
| 302 |
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
| 303 |
|
|
|
|
| 307 |
fast-levenshtein@2.0.6:
|
| 308 |
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
| 309 |
|
| 310 |
+
fast-xml-builder@1.1.4:
|
| 311 |
+
resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==}
|
| 312 |
+
|
| 313 |
+
fast-xml-parser@5.5.10:
|
| 314 |
+
resolution: {integrity: sha512-go2J2xODMc32hT+4Xr/bBGXMaIoiCwrwp2mMtAvKyvEFW6S/v5Gn2pBmE4nvbwNjGhpcAiOwEv7R6/GZ6XRa9w==}
|
| 315 |
+
hasBin: true
|
| 316 |
+
|
| 317 |
file-entry-cache@8.0.0:
|
| 318 |
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
| 319 |
engines: {node: '>=16.0.0'}
|
|
|
|
| 349 |
resolution: {integrity: sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==}
|
| 350 |
engines: {node: '>=16.9.0'}
|
| 351 |
|
| 352 |
+
http-proxy-agent@7.0.2:
|
| 353 |
+
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
|
| 354 |
+
engines: {node: '>= 14'}
|
| 355 |
+
|
| 356 |
+
https-proxy-agent@7.0.6:
|
| 357 |
+
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
| 358 |
+
engines: {node: '>= 14'}
|
| 359 |
+
|
| 360 |
ignore@5.3.2:
|
| 361 |
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
| 362 |
engines: {node: '>= 4'}
|
|
|
|
| 447 |
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
| 448 |
engines: {node: '>=8'}
|
| 449 |
|
| 450 |
+
path-expression-matcher@1.4.0:
|
| 451 |
+
resolution: {integrity: sha512-s4DQMxIdhj3jLFWd9LxHOplj4p9yQ4ffMGowFf3cpEgrrJjEhN0V5nxw4Ye1EViAGDoL4/1AeO6qHpqYPOzE4Q==}
|
| 452 |
+
engines: {node: '>=14.0.0'}
|
| 453 |
+
|
| 454 |
path-key@3.1.1:
|
| 455 |
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
| 456 |
engines: {node: '>=8'}
|
|
|
|
| 537 |
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
| 538 |
engines: {node: '>=8'}
|
| 539 |
|
| 540 |
+
strnum@2.2.3:
|
| 541 |
+
resolution: {integrity: sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==}
|
| 542 |
+
|
| 543 |
supports-color@7.2.0:
|
| 544 |
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
| 545 |
engines: {node: '>=8'}
|
| 546 |
|
| 547 |
+
tslib@2.8.1:
|
| 548 |
+
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
| 549 |
+
|
| 550 |
type-check@0.4.0:
|
| 551 |
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
| 552 |
engines: {node: '>= 0.8.0'}
|
|
|
|
| 583 |
|
| 584 |
snapshots:
|
| 585 |
|
| 586 |
+
'@azure/abort-controller@2.1.2':
|
| 587 |
+
dependencies:
|
| 588 |
+
tslib: 2.8.1
|
| 589 |
+
|
| 590 |
+
'@azure/core-auth@1.10.1':
|
| 591 |
+
dependencies:
|
| 592 |
+
'@azure/abort-controller': 2.1.2
|
| 593 |
+
'@azure/core-util': 1.13.1
|
| 594 |
+
tslib: 2.8.1
|
| 595 |
+
transitivePeerDependencies:
|
| 596 |
+
- supports-color
|
| 597 |
+
|
| 598 |
+
'@azure/core-client@1.10.1':
|
| 599 |
+
dependencies:
|
| 600 |
+
'@azure/abort-controller': 2.1.2
|
| 601 |
+
'@azure/core-auth': 1.10.1
|
| 602 |
+
'@azure/core-rest-pipeline': 1.23.0
|
| 603 |
+
'@azure/core-tracing': 1.3.1
|
| 604 |
+
'@azure/core-util': 1.13.1
|
| 605 |
+
'@azure/logger': 1.3.0
|
| 606 |
+
tslib: 2.8.1
|
| 607 |
+
transitivePeerDependencies:
|
| 608 |
+
- supports-color
|
| 609 |
+
|
| 610 |
+
'@azure/core-http-compat@2.3.2(@azure/core-client@1.10.1)(@azure/core-rest-pipeline@1.23.0)':
|
| 611 |
+
dependencies:
|
| 612 |
+
'@azure/abort-controller': 2.1.2
|
| 613 |
+
'@azure/core-client': 1.10.1
|
| 614 |
+
'@azure/core-rest-pipeline': 1.23.0
|
| 615 |
+
|
| 616 |
+
'@azure/core-lro@2.7.2':
|
| 617 |
+
dependencies:
|
| 618 |
+
'@azure/abort-controller': 2.1.2
|
| 619 |
+
'@azure/core-util': 1.13.1
|
| 620 |
+
'@azure/logger': 1.3.0
|
| 621 |
+
tslib: 2.8.1
|
| 622 |
+
transitivePeerDependencies:
|
| 623 |
+
- supports-color
|
| 624 |
+
|
| 625 |
+
'@azure/core-paging@1.6.2':
|
| 626 |
+
dependencies:
|
| 627 |
+
tslib: 2.8.1
|
| 628 |
+
|
| 629 |
+
'@azure/core-rest-pipeline@1.23.0':
|
| 630 |
+
dependencies:
|
| 631 |
+
'@azure/abort-controller': 2.1.2
|
| 632 |
+
'@azure/core-auth': 1.10.1
|
| 633 |
+
'@azure/core-tracing': 1.3.1
|
| 634 |
+
'@azure/core-util': 1.13.1
|
| 635 |
+
'@azure/logger': 1.3.0
|
| 636 |
+
'@typespec/ts-http-runtime': 0.3.4
|
| 637 |
+
tslib: 2.8.1
|
| 638 |
+
transitivePeerDependencies:
|
| 639 |
+
- supports-color
|
| 640 |
+
|
| 641 |
+
'@azure/core-tracing@1.3.1':
|
| 642 |
+
dependencies:
|
| 643 |
+
tslib: 2.8.1
|
| 644 |
+
|
| 645 |
+
'@azure/core-util@1.13.1':
|
| 646 |
+
dependencies:
|
| 647 |
+
'@azure/abort-controller': 2.1.2
|
| 648 |
+
'@typespec/ts-http-runtime': 0.3.4
|
| 649 |
+
tslib: 2.8.1
|
| 650 |
+
transitivePeerDependencies:
|
| 651 |
+
- supports-color
|
| 652 |
+
|
| 653 |
+
'@azure/core-xml@1.5.0':
|
| 654 |
+
dependencies:
|
| 655 |
+
fast-xml-parser: 5.5.10
|
| 656 |
+
tslib: 2.8.1
|
| 657 |
+
|
| 658 |
+
'@azure/logger@1.3.0':
|
| 659 |
+
dependencies:
|
| 660 |
+
'@typespec/ts-http-runtime': 0.3.4
|
| 661 |
+
tslib: 2.8.1
|
| 662 |
+
transitivePeerDependencies:
|
| 663 |
+
- supports-color
|
| 664 |
+
|
| 665 |
+
'@azure/storage-blob@12.31.0':
|
| 666 |
+
dependencies:
|
| 667 |
+
'@azure/abort-controller': 2.1.2
|
| 668 |
+
'@azure/core-auth': 1.10.1
|
| 669 |
+
'@azure/core-client': 1.10.1
|
| 670 |
+
'@azure/core-http-compat': 2.3.2(@azure/core-client@1.10.1)(@azure/core-rest-pipeline@1.23.0)
|
| 671 |
+
'@azure/core-lro': 2.7.2
|
| 672 |
+
'@azure/core-paging': 1.6.2
|
| 673 |
+
'@azure/core-rest-pipeline': 1.23.0
|
| 674 |
+
'@azure/core-tracing': 1.3.1
|
| 675 |
+
'@azure/core-util': 1.13.1
|
| 676 |
+
'@azure/core-xml': 1.5.0
|
| 677 |
+
'@azure/logger': 1.3.0
|
| 678 |
+
'@azure/storage-common': 12.3.0(@azure/core-client@1.10.1)
|
| 679 |
+
events: 3.3.0
|
| 680 |
+
tslib: 2.8.1
|
| 681 |
+
transitivePeerDependencies:
|
| 682 |
+
- supports-color
|
| 683 |
+
|
| 684 |
+
'@azure/storage-common@12.3.0(@azure/core-client@1.10.1)':
|
| 685 |
+
dependencies:
|
| 686 |
+
'@azure/abort-controller': 2.1.2
|
| 687 |
+
'@azure/core-auth': 1.10.1
|
| 688 |
+
'@azure/core-http-compat': 2.3.2(@azure/core-client@1.10.1)(@azure/core-rest-pipeline@1.23.0)
|
| 689 |
+
'@azure/core-rest-pipeline': 1.23.0
|
| 690 |
+
'@azure/core-tracing': 1.3.1
|
| 691 |
+
'@azure/core-util': 1.13.1
|
| 692 |
+
'@azure/logger': 1.3.0
|
| 693 |
+
events: 3.3.0
|
| 694 |
+
tslib: 2.8.1
|
| 695 |
+
transitivePeerDependencies:
|
| 696 |
+
- '@azure/core-client'
|
| 697 |
+
- supports-color
|
| 698 |
+
|
| 699 |
'@bufbuild/protobuf@1.10.1': {}
|
| 700 |
|
| 701 |
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)':
|
|
|
|
| 771 |
dependencies:
|
| 772 |
undici-types: 6.21.0
|
| 773 |
|
| 774 |
+
'@typespec/ts-http-runtime@0.3.4':
|
| 775 |
+
dependencies:
|
| 776 |
+
http-proxy-agent: 7.0.2
|
| 777 |
+
https-proxy-agent: 7.0.6
|
| 778 |
+
tslib: 2.8.1
|
| 779 |
+
transitivePeerDependencies:
|
| 780 |
+
- supports-color
|
| 781 |
+
|
| 782 |
acorn-jsx@5.3.2(acorn@8.16.0):
|
| 783 |
dependencies:
|
| 784 |
acorn: 8.16.0
|
| 785 |
|
| 786 |
acorn@8.16.0: {}
|
| 787 |
|
| 788 |
+
agent-base@7.1.4: {}
|
| 789 |
+
|
| 790 |
ajv@6.14.0:
|
| 791 |
dependencies:
|
| 792 |
fast-deep-equal: 3.1.3
|
|
|
|
| 911 |
|
| 912 |
esutils@2.0.3: {}
|
| 913 |
|
| 914 |
+
events@3.3.0: {}
|
| 915 |
+
|
| 916 |
fast-deep-equal@3.1.3: {}
|
| 917 |
|
| 918 |
fast-json-stable-stringify@2.1.0: {}
|
| 919 |
|
| 920 |
fast-levenshtein@2.0.6: {}
|
| 921 |
|
| 922 |
+
fast-xml-builder@1.1.4:
|
| 923 |
+
dependencies:
|
| 924 |
+
path-expression-matcher: 1.4.0
|
| 925 |
+
|
| 926 |
+
fast-xml-parser@5.5.10:
|
| 927 |
+
dependencies:
|
| 928 |
+
fast-xml-builder: 1.1.4
|
| 929 |
+
path-expression-matcher: 1.4.0
|
| 930 |
+
strnum: 2.2.3
|
| 931 |
+
|
| 932 |
file-entry-cache@8.0.0:
|
| 933 |
dependencies:
|
| 934 |
flat-cache: 4.0.1
|
|
|
|
| 957 |
|
| 958 |
hono@4.12.5: {}
|
| 959 |
|
| 960 |
+
http-proxy-agent@7.0.2:
|
| 961 |
+
dependencies:
|
| 962 |
+
agent-base: 7.1.4
|
| 963 |
+
debug: 4.4.3
|
| 964 |
+
transitivePeerDependencies:
|
| 965 |
+
- supports-color
|
| 966 |
+
|
| 967 |
+
https-proxy-agent@7.0.6:
|
| 968 |
+
dependencies:
|
| 969 |
+
agent-base: 7.1.4
|
| 970 |
+
debug: 4.4.3
|
| 971 |
+
transitivePeerDependencies:
|
| 972 |
+
- supports-color
|
| 973 |
+
|
| 974 |
ignore@5.3.2: {}
|
| 975 |
|
| 976 |
import-fresh@3.3.1:
|
|
|
|
| 1055 |
|
| 1056 |
path-exists@4.0.0: {}
|
| 1057 |
|
| 1058 |
+
path-expression-matcher@1.4.0: {}
|
| 1059 |
+
|
| 1060 |
path-key@3.1.1: {}
|
| 1061 |
|
| 1062 |
pg-cloudflare@1.3.0:
|
|
|
|
| 1122 |
|
| 1123 |
strip-json-comments@3.1.1: {}
|
| 1124 |
|
| 1125 |
+
strnum@2.2.3: {}
|
| 1126 |
+
|
| 1127 |
supports-color@7.2.0:
|
| 1128 |
dependencies:
|
| 1129 |
has-flag: 4.0.0
|
| 1130 |
|
| 1131 |
+
tslib@2.8.1: {}
|
| 1132 |
+
|
| 1133 |
type-check@0.4.0:
|
| 1134 |
dependencies:
|
| 1135 |
prelude-ls: 1.2.1
|
agents.js → routes/agent.js
RENAMED
|
@@ -1,89 +1,138 @@
|
|
| 1 |
import { Hono } from 'hono'
|
| 2 |
-
import { query } from './db.js'
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
})
|
| 28 |
|
| 29 |
-
export const agentsRouter = new Hono()
|
| 30 |
|
| 31 |
-
|
|
|
|
| 32 |
const result = agentSchema.safeParse(await c.req.json())
|
| 33 |
if (!result.success) return c.json({ error: result.error.issues[0].message }, 400)
|
| 34 |
const { name, systemPrompt, stt, llm, tts, variablesSchema } = result.data
|
| 35 |
const decodedPrompt = Buffer.from(systemPrompt, 'base64').toString('utf8')
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
[name, decodedPrompt, JSON.stringify(stt), JSON.stringify(llm), JSON.stringify(tts), JSON.stringify(variablesSchema)]
|
| 41 |
-
|
| 42 |
-
)
|
| 43 |
-
return c.json({ agentId: rows[0].id }, 201)
|
| 44 |
})
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
const { rows } = await query(
|
| 49 |
-
|
| 50 |
-
return c.json(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
})
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
| 56 |
})
|
| 57 |
|
| 58 |
-
|
|
|
|
| 59 |
const result = agentSchema.partial().safeParse(await c.req.json())
|
| 60 |
if (!result.success) return c.json({ error: result.error.issues[0].message }, 400)
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
const decodedPrompt = systemPrompt ? Buffer.from(systemPrompt, 'base64').toString('utf8') : undefined
|
| 63 |
|
| 64 |
const fields = []
|
| 65 |
const values = []
|
|
|
|
| 66 |
let i = 1
|
|
|
|
| 67 |
if (name) { fields.push(`name = $${i++}`); values.push(name) }
|
| 68 |
// if (systemPrompt) { fields.push(`system_prompt = $${i++}`); values.push(systemPrompt) }
|
| 69 |
if (decodedPrompt) { fields.push(`system_prompt = $${i++}`); values.push(decodedPrompt) }
|
| 70 |
if (stt) { fields.push(`stt = $${i++}`); values.push(JSON.stringify(stt)) }
|
| 71 |
if (llm) { fields.push(`llm = $${i++}`); values.push(JSON.stringify(llm)) }
|
| 72 |
if (tts) { fields.push(`tts = $${i++}`); values.push(JSON.stringify(tts)) }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
if (variablesSchema) { fields.push(`variables_schema = $${i++}`); values.push(JSON.stringify(variablesSchema)) }
|
|
|
|
| 74 |
if (!fields.length) return c.json({ error: 'No fields to update' }, 400)
|
|
|
|
| 75 |
fields.push(`updated_at = now()`)
|
| 76 |
values.push(c.req.param('agentId'))
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
)
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
})
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import { Hono } from 'hono'
|
| 2 |
+
import { query } from '../common/db.js'
|
| 3 |
+
|
| 4 |
+
import { createAgent, listAgents, updateAgent, deleteAgent, getAgent } from '../dao/agent.js'
|
| 5 |
+
import { createCall, getCalls } from '../dao/call.js'
|
| 6 |
+
|
| 7 |
+
import { callSchema } from '../schemas/call.js'
|
| 8 |
+
import { agentSchema } from '../schemas/agent.js'
|
| 9 |
+
|
| 10 |
+
export const agentRouter = new Hono()
|
| 11 |
+
|
| 12 |
+
//create call
|
| 13 |
+
agentRouter.post('/call', async (c) => {
|
| 14 |
+
// console.log('got here: ', JSON.stringify(await c.req.text()))
|
| 15 |
+
let oriqReqTxt = await c.req.text()
|
| 16 |
+
// console.log('oriqReqTxt is of type: ', typeof(oriqReqTxt))
|
| 17 |
+
let fixedReq = oriqReqTxt.replaceAll(': }', ': \{\} }')
|
| 18 |
+
const result = callSchema.safeParse(JSON.parse(fixedReq))
|
| 19 |
+
console.log('result.data is: ', JSON.stringify(result.data, null, 2))
|
| 20 |
+
|
| 21 |
+
if (!result.success) return c.json({ error: result.error.issues[0].message }, 400)
|
| 22 |
+
|
| 23 |
+
const { callTo: formattedNumber, agentId, variables } = result.data
|
| 24 |
+
try {
|
| 25 |
+
let resp = await createCall(formattedNumber, agentId, variables)
|
| 26 |
+
if (resp.error) {
|
| 27 |
+
return c.json(resp.error, resp.code)
|
| 28 |
+
}
|
| 29 |
+
return c.json(resp)
|
| 30 |
+
} catch (error) {
|
| 31 |
+
return c.json({ error: error instanceof Error ? error.message : 'Failed to create call' }, 500)
|
| 32 |
+
}
|
| 33 |
+
})
|
| 34 |
+
|
| 35 |
+
//list calls
|
| 36 |
+
agentRouter.get('/calls', async (c) => {
|
| 37 |
+
let resp = await getCalls()
|
| 38 |
+
return c.json(resp)
|
| 39 |
+
})
|
| 40 |
+
|
| 41 |
+
//get call
|
| 42 |
+
agentRouter.get('/calls/:callId', async (c) => {
|
| 43 |
+
const { rows } = await query('SELECT * FROM call_log WHERE id = $1', [c.req.param('callId')])
|
| 44 |
+
if (!rows.length) return c.json({ error: 'Call not found' }, 404)
|
| 45 |
+
return c.json(rows[0])
|
| 46 |
})
|
| 47 |
|
|
|
|
| 48 |
|
| 49 |
+
//create agent
|
| 50 |
+
agentRouter.post('/', async (c) => {
|
| 51 |
const result = agentSchema.safeParse(await c.req.json())
|
| 52 |
if (!result.success) return c.json({ error: result.error.issues[0].message }, 400)
|
| 53 |
const { name, systemPrompt, stt, llm, tts, variablesSchema } = result.data
|
| 54 |
const decodedPrompt = Buffer.from(systemPrompt, 'base64').toString('utf8')
|
| 55 |
+
|
| 56 |
+
let resp = await createAgent(name, decodedPrompt, stt, llm, tts, variablesSchema)
|
| 57 |
+
|
| 58 |
+
return c.json(resp, 201)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
})
|
| 60 |
|
| 61 |
+
//get agent
|
| 62 |
+
agentRouter.get('/:agentId', async (c) => {
|
| 63 |
+
// const { rows } = await query(`SELECT * FROM AGENT WHERE id = $1`, [c.req.param('agentId')])
|
| 64 |
+
// const { rows } = await query('SELECT * FROM agent WHERE id = $1', [c.req.param('agentId')])
|
| 65 |
+
// if (!rows.length) return c.json({ error: 'Agent not found' }, 404)
|
| 66 |
+
// return c.json(rows[0])
|
| 67 |
+
|
| 68 |
+
let resp = await getAgent(c.req.param('agentId'))
|
| 69 |
+
return c.json(resp)
|
| 70 |
})
|
| 71 |
|
| 72 |
+
//list agents
|
| 73 |
+
agentRouter.get('/', async (c) => {
|
| 74 |
+
let resp = await listAgents()
|
| 75 |
+
|
| 76 |
+
return c.json(resp)
|
| 77 |
})
|
| 78 |
|
| 79 |
+
//update agent
|
| 80 |
+
agentRouter.put('/:agentId', async (c) => {
|
| 81 |
const result = agentSchema.partial().safeParse(await c.req.json())
|
| 82 |
if (!result.success) return c.json({ error: result.error.issues[0].message }, 400)
|
| 83 |
+
|
| 84 |
+
const { name, systemPrompt, stt, llm, tts, variablesSchema, outputSchemaId, webhookUrl } = result.data
|
| 85 |
+
|
| 86 |
+
console.log('outputSchemaId is: ', outputSchemaId)
|
| 87 |
+
|
| 88 |
const decodedPrompt = systemPrompt ? Buffer.from(systemPrompt, 'base64').toString('utf8') : undefined
|
| 89 |
|
| 90 |
const fields = []
|
| 91 |
const values = []
|
| 92 |
+
|
| 93 |
let i = 1
|
| 94 |
+
|
| 95 |
if (name) { fields.push(`name = $${i++}`); values.push(name) }
|
| 96 |
// if (systemPrompt) { fields.push(`system_prompt = $${i++}`); values.push(systemPrompt) }
|
| 97 |
if (decodedPrompt) { fields.push(`system_prompt = $${i++}`); values.push(decodedPrompt) }
|
| 98 |
if (stt) { fields.push(`stt = $${i++}`); values.push(JSON.stringify(stt)) }
|
| 99 |
if (llm) { fields.push(`llm = $${i++}`); values.push(JSON.stringify(llm)) }
|
| 100 |
if (tts) { fields.push(`tts = $${i++}`); values.push(JSON.stringify(tts)) }
|
| 101 |
+
|
| 102 |
+
if (outputSchemaId) { fields.push(`output_schema_id = $${i++}`); values.push(outputSchemaId) }
|
| 103 |
+
if (webhookUrl) { fields.push(`webhook_url = $${i++}`); values.push(webhookUrl) }
|
| 104 |
+
|
| 105 |
if (variablesSchema) { fields.push(`variables_schema = $${i++}`); values.push(JSON.stringify(variablesSchema)) }
|
| 106 |
+
|
| 107 |
if (!fields.length) return c.json({ error: 'No fields to update' }, 400)
|
| 108 |
+
|
| 109 |
fields.push(`updated_at = now()`)
|
| 110 |
values.push(c.req.param('agentId'))
|
| 111 |
+
|
| 112 |
+
let respRows = await updateAgent(fields, values, i)
|
| 113 |
+
|
| 114 |
+
if (!respRows.length) return c.json({ error: 'Agent not found' }, 404)
|
| 115 |
+
return c.json({ agentId: respRows[0].id })
|
| 116 |
+
})
|
| 117 |
+
|
| 118 |
+
//delete agent
|
| 119 |
+
agentRouter.delete('/:agentId', async (c) => {
|
| 120 |
+
let respRows = await deleteAgent(c.req.param('agentId'))
|
| 121 |
+
|
| 122 |
+
if (respRows.error) {
|
| 123 |
+
return c.json(respRows.error, 404)
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
return c.json({ deleted: respRows[0].id })
|
| 127 |
})
|
| 128 |
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
|
routes/blob.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Hono } from 'hono'
|
| 2 |
+
import { BlobServiceClient } from '@azure/storage-blob'
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
export const blobRouter = new Hono()
|
| 6 |
+
|
| 7 |
+
const AZURE_STORAGE_CONNECTION_STRING = process.env.AZURE_STORAGE_CONNECTION_STRING
|
| 8 |
+
const AZURE_STORAGE_CONTAINER_NAME = process.env.AZURE_STORAGE_CONTAINER_NAME
|
| 9 |
+
|
| 10 |
+
//get blob
|
| 11 |
+
blobRouter.get('/', async (c) => {
|
| 12 |
+
const blobName = c.req.query('blobName')
|
| 13 |
+
const filename = c.req.query('filename')
|
| 14 |
+
|
| 15 |
+
if (!blobName) {
|
| 16 |
+
return c.json({ error: 'blobName query parameter is required' }, 400)
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
if (!AZURE_STORAGE_CONNECTION_STRING || !AZURE_STORAGE_CONTAINER_NAME) {
|
| 20 |
+
return c.json({ error: 'Azure Storage not configured' }, 500)
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
try {
|
| 24 |
+
const blobServiceClient = BlobServiceClient.fromConnectionString(AZURE_STORAGE_CONNECTION_STRING)
|
| 25 |
+
const containerClient = blobServiceClient.getContainerClient(AZURE_STORAGE_CONTAINER_NAME)
|
| 26 |
+
const blockBlobClient = containerClient.getBlockBlobClient(blobName)
|
| 27 |
+
|
| 28 |
+
const buffer = await blockBlobClient.downloadToBuffer()
|
| 29 |
+
const base64 = buffer.toString('base64')
|
| 30 |
+
|
| 31 |
+
c.header('Access-Control-Allow-Origin', '*')
|
| 32 |
+
|
| 33 |
+
return c.json({
|
| 34 |
+
contentType: 'application/octet-stream',
|
| 35 |
+
filename: filename || blobName.split('/').pop(),
|
| 36 |
+
data: base64
|
| 37 |
+
})
|
| 38 |
+
} catch (error) {
|
| 39 |
+
console.error('Blob download error:', error)
|
| 40 |
+
c.header('Access-Control-Allow-Origin', '*')
|
| 41 |
+
return c.json({ error: 'Failed to download blob' }, 500)
|
| 42 |
+
}
|
| 43 |
+
})
|
| 44 |
+
|
| 45 |
+
export default blobRouter
|
routes/schema.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Hono } from 'hono'
|
| 2 |
+
// import { query } from '../common/db.js'
|
| 3 |
+
|
| 4 |
+
// import { createSchema, listSchemas, updateSchema, deleteSchema } from '../dao/schema.js'
|
| 5 |
+
import { createSchema, listSchemas, getSchema, updateSchema } from '../dao/schema.js'
|
| 6 |
+
|
| 7 |
+
export const schemaRouter = new Hono()
|
| 8 |
+
|
| 9 |
+
//create schema
|
| 10 |
+
schemaRouter.post('/', async (c) => {
|
| 11 |
+
let oriqReqTxt = await c.req.text()
|
| 12 |
+
// let fixedReq = oriqReqTxt.replaceAll(': }', ': \{\} }')
|
| 13 |
+
const result = JSON.parse(oriqReqTxt)
|
| 14 |
+
|
| 15 |
+
console.log('result is: ', result)
|
| 16 |
+
|
| 17 |
+
try {
|
| 18 |
+
let resp = await createSchema(result.name, result.content)
|
| 19 |
+
if (resp.error) {
|
| 20 |
+
return c.json(resp.error, resp.code)
|
| 21 |
+
}
|
| 22 |
+
return c.json(resp)
|
| 23 |
+
} catch (error) {
|
| 24 |
+
return c.json({ error: error instanceof Error ? error.message : 'Failed to create output schema' }, 500)
|
| 25 |
+
}
|
| 26 |
+
})
|
| 27 |
+
|
| 28 |
+
//list output schemas
|
| 29 |
+
schemaRouter.get('/', async (c) => {
|
| 30 |
+
let resp = await listSchemas()
|
| 31 |
+
return c.json(resp)
|
| 32 |
+
})
|
| 33 |
+
|
| 34 |
+
//get output schema
|
| 35 |
+
schemaRouter.get('/:outputSchemaId', async (c) => {
|
| 36 |
+
let resp = await getSchema(c.req.param('outputSchemaId'))
|
| 37 |
+
return c.json(resp)
|
| 38 |
+
})
|
| 39 |
+
|
| 40 |
+
//update output schema
|
| 41 |
+
schemaRouter.put('/:outputSchemaId', async (c) => {
|
| 42 |
+
const { name, content} = await c.req.json()
|
| 43 |
+
|
| 44 |
+
const fields = []
|
| 45 |
+
const values = []
|
| 46 |
+
|
| 47 |
+
let i = 1
|
| 48 |
+
|
| 49 |
+
if (name) { fields.push(`name = $${i++}`); values.push(name) }
|
| 50 |
+
if (content) { fields.push(`schema = $${i++}`); values.push(content) }
|
| 51 |
+
// if (systemPrompt) { fields.push(`system_prompt = $${i++}`); values.push(systemPrompt) }
|
| 52 |
+
|
| 53 |
+
if (!fields.length) return c.json({ error: 'No fields to update' }, 400)
|
| 54 |
+
|
| 55 |
+
fields.push(`updated_at = now()`)
|
| 56 |
+
values.push(c.req.param('outputSchemaId'))
|
| 57 |
+
|
| 58 |
+
let respRows = await updateSchema(fields, values, i)
|
| 59 |
+
|
| 60 |
+
if (!respRows.length) return c.json({ error: 'Output schema not found' }, 404)
|
| 61 |
+
return c.json({ outputSchemaId: respRows[0].id })
|
| 62 |
+
})
|
routes/trunk.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Hono } from 'hono'
|
| 2 |
+
import { SipClient } from 'livekit-server-sdk'
|
| 3 |
+
import { SIPTransport } from '@livekit/protocol'
|
| 4 |
+
import { query } from '../common/db.js'
|
| 5 |
+
import { createSipTrunk, getInboundTrunk, updateTrunk, deleteTrunk } from '../dao/trunk.js'
|
| 6 |
+
import { getAgent } from '../dao/agent.js'
|
| 7 |
+
import { env } from '../config/utils.js'
|
| 8 |
+
|
| 9 |
+
import { trunkSchema } from '../schemas/trunk.js'
|
| 10 |
+
import { dispatchSchema } from '../schemas/dispatch.js'
|
| 11 |
+
|
| 12 |
+
export const trunkRouter = new Hono()
|
| 13 |
+
|
| 14 |
+
const sipClient = new SipClient(env.LIVEKIT_URL, env.LIVEKIT_API_KEY, env.LIVEKIT_API_SECRET)
|
| 15 |
+
|
| 16 |
+
//List all trunks from LK; not the DB !!!
|
| 17 |
+
trunkRouter.get('/', async (c) => {
|
| 18 |
+
try {
|
| 19 |
+
const [inboundTrunks, outboundTrunks] = await Promise.all([
|
| 20 |
+
sipClient.listSipInboundTrunk({ page: { limit: 100 } }),
|
| 21 |
+
sipClient.listSipOutboundTrunk({ page: { limit: 100 } })
|
| 22 |
+
])
|
| 23 |
+
|
| 24 |
+
const inboundTrunkIds = inboundTrunks.map(trunk => ({
|
| 25 |
+
id: trunk.sipTrunkId,
|
| 26 |
+
type: 'inbound',
|
| 27 |
+
name: trunk.name
|
| 28 |
+
}))
|
| 29 |
+
|
| 30 |
+
const outboundTrunkIds = outboundTrunks.map(trunk => ({
|
| 31 |
+
id: trunk.sipTrunkId,
|
| 32 |
+
type: 'outbound',
|
| 33 |
+
name: trunk.name
|
| 34 |
+
}))
|
| 35 |
+
|
| 36 |
+
return c.json({
|
| 37 |
+
trunks: [...inboundTrunkIds, ...outboundTrunkIds]
|
| 38 |
+
})
|
| 39 |
+
} catch (error) {
|
| 40 |
+
return c.json({ error: error.message+'. Check LK server conn' }, 500)
|
| 41 |
+
}
|
| 42 |
+
})
|
| 43 |
+
|
| 44 |
+
//get trunk by id
|
| 45 |
+
trunkRouter.get('/:trunkId', async (c) => {
|
| 46 |
+
const { rows } = await query('SELECT * FROM trunk WHERE lk_trunk_id = $1', [c.req.param('trunkId')])
|
| 47 |
+
if (!rows.length) return c.json({ error: 'Trunk not found' }, 404)
|
| 48 |
+
return c.json(rows[0])
|
| 49 |
+
})
|
| 50 |
+
|
| 51 |
+
//create OUTBOUND/INBOUND trunk
|
| 52 |
+
trunkRouter.post('/', async (c) => {
|
| 53 |
+
const result = trunkSchema.safeParse(await c.req.json())
|
| 54 |
+
if (!result.success) return c.json({ error: result.error.issues[0].message }, 400)
|
| 55 |
+
|
| 56 |
+
const { name, address, numbers, authUsername, authPassword } = result.data
|
| 57 |
+
|
| 58 |
+
if (address) {
|
| 59 |
+
console.log('Creating OUTBOUND trunk ...')
|
| 60 |
+
// Create LK SIP outbound trunk
|
| 61 |
+
const outbound = await sipClient.createSipOutboundTrunk(
|
| 62 |
+
name,
|
| 63 |
+
address,
|
| 64 |
+
numbers,
|
| 65 |
+
{
|
| 66 |
+
authUsername,
|
| 67 |
+
authPassword,
|
| 68 |
+
transport: SIPTransport.SIP_TRANSPORT_AUTO
|
| 69 |
+
}
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
let respRows = await createSipTrunk(name, 'outbound', outbound.sipTrunkId, address)
|
| 73 |
+
return c.json({
|
| 74 |
+
outboundTrunkId: respRows[0].id
|
| 75 |
+
}, 201)
|
| 76 |
+
} else {
|
| 77 |
+
console.log('Creating INBOUND trunk ...')
|
| 78 |
+
// Create inbound trunk (no caller restrictions)
|
| 79 |
+
const inbound = await sipClient.createSipInboundTrunk(
|
| 80 |
+
name,
|
| 81 |
+
numbers,
|
| 82 |
+
{
|
| 83 |
+
authUsername,
|
| 84 |
+
authPassword,
|
| 85 |
+
transport: SIPTransport.SIP_TRANSPORT_AUTO
|
| 86 |
+
}
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
let respRows = await createSipTrunk(name, 'inbound', inbound.sipTrunkId)
|
| 90 |
+
|
| 91 |
+
return c.json({
|
| 92 |
+
inboundTrunkId: respRows[0].id
|
| 93 |
+
}, 201)
|
| 94 |
+
}
|
| 95 |
+
})
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
//update trunk
|
| 99 |
+
trunkRouter.put('/:trunkId', async (c) => {
|
| 100 |
+
const result = trunkSchema.partial().safeParse(await c.req.json())
|
| 101 |
+
if (!result.success) return c.json({ error: result.error.issues[0].message }, 400)
|
| 102 |
+
const { name, address, numbers } = result.data
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
const fields = []
|
| 106 |
+
const values = []
|
| 107 |
+
|
| 108 |
+
let i = 1
|
| 109 |
+
|
| 110 |
+
if (name) { fields.push(`name = $${i++}`); values.push(name) }
|
| 111 |
+
if (address) { fields.push(`address = $${i++}`); values.push(JSON.stringify(address)) }
|
| 112 |
+
if (numbers) { fields.push(`numbers = $${i++}`); values.push(JSON.stringify(numbers)) }
|
| 113 |
+
|
| 114 |
+
if (!fields.length) return c.json({ error: 'No fields to update' }, 400)
|
| 115 |
+
|
| 116 |
+
fields.push(`updated_at = now()`)
|
| 117 |
+
values.push(c.req.param('trunkId'))
|
| 118 |
+
|
| 119 |
+
let respRows = await updateTrunk(fields, values, i)
|
| 120 |
+
|
| 121 |
+
if (!respRows.length) return c.json({ error: 'Trunk not found' }, 404)
|
| 122 |
+
return c.json({ trunkId: respRows[0].id })
|
| 123 |
+
})
|
| 124 |
+
|
| 125 |
+
//Create INBOUND dispatch with associated agent
|
| 126 |
+
trunkRouter.post('/dispatch', async (c) => {
|
| 127 |
+
const result = dispatchSchema.safeParse(await c.req.json())
|
| 128 |
+
if (!result.success) return c.json({ error: result.error.issues[0].message }, 400)
|
| 129 |
+
|
| 130 |
+
const { agentId, sipTrunkId } = result.data
|
| 131 |
+
let inboundTrunk = await getInboundTrunk(sipTrunkId)
|
| 132 |
+
console.log(JSON.stringify(inboundTrunk, null, 2))
|
| 133 |
+
|
| 134 |
+
if (!inboundTrunk.length) return c.json({ error: 'Inbound trunk not found' }, 404)
|
| 135 |
+
|
| 136 |
+
let agent = await getAgent(agentId)
|
| 137 |
+
if (!agent.length) return c.json({ error: 'Agent not found' }, 404)
|
| 138 |
+
|
| 139 |
+
// Each inbound call gets its own room, routed to this agent
|
| 140 |
+
const rule = await sipClient.createSipDispatchRule(
|
| 141 |
+
{
|
| 142 |
+
type: 'individual',
|
| 143 |
+
roomPrefix: 'call_',
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
name: 'inbound-dispatch-rule',
|
| 147 |
+
trunkIds: [inboundTrunk[0].lk_trunk_id],
|
| 148 |
+
}
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
await query(
|
| 152 |
+
'UPDATE agent SET lk_dispatch_rule_id = $1, updated_at = now() WHERE id = $2',
|
| 153 |
+
[rule.sipDispatchRuleId, agentId]
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
return c.json({ dispatchRuleId: rule.sipDispatchRuleId }, 201)
|
| 157 |
+
})
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
// Delete trunk
|
| 161 |
+
trunkRouter.delete('/:trunkId', async (c) => {
|
| 162 |
+
const { rows } = await query(
|
| 163 |
+
'SELECT lk_trunk_id FROM trunk WHERE id = $1',
|
| 164 |
+
[c.req.param('trunkId')]
|
| 165 |
+
)
|
| 166 |
+
if (!rows.length) return c.json({ error: 'Trunk not found' }, 404)
|
| 167 |
+
|
| 168 |
+
const { lk_trunk_id } = rows[0]
|
| 169 |
+
if (!lk_trunk_id ) return c.json({ error: 'No trunks found in LK' }, 404)
|
| 170 |
+
|
| 171 |
+
if (lk_trunk_id) await sipClient.deleteSipTrunk(lk_trunk_id)
|
| 172 |
+
// if (lk_inbound_trunk_id) await sipClient.deleteSipTrunk(lk_inbound_trunk_id)
|
| 173 |
+
|
| 174 |
+
let respRows = await deleteTrunk(c.req.param('trunkId'))
|
| 175 |
+
|
| 176 |
+
if (respRows.error) {
|
| 177 |
+
return c.json(respRows.error, 404)
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
return c.json({ deleted: respRows[0].id })
|
| 181 |
+
})
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
// trunkRouter.delete('/:agentId', async (c) => {
|
| 185 |
+
// const { rows } = await query(
|
| 186 |
+
// 'SELECT lk_trunk_id, lk_inbound_trunk_id FROM agent WHERE id = $1',
|
| 187 |
+
// [c.req.param('agentId')]
|
| 188 |
+
// )
|
| 189 |
+
// if (!rows.length) return c.json({ error: 'Agent not found' }, 404)
|
| 190 |
+
|
| 191 |
+
// const { lk_trunk_id, lk_inbound_trunk_id } = rows[0]
|
| 192 |
+
// if (!lk_trunk_id && !lk_inbound_trunk_id) return c.json({ error: 'No trunks found' }, 404)
|
| 193 |
+
|
| 194 |
+
// if (lk_trunk_id) await sipClient.deleteSipTrunk(lk_trunk_id)
|
| 195 |
+
// if (lk_inbound_trunk_id) await sipClient.deleteSipTrunk(lk_inbound_trunk_id)
|
| 196 |
+
|
| 197 |
+
// await query(
|
| 198 |
+
// 'UPDATE agent SET lk_trunk_id = NULL, lk_inbound_trunk_id = NULL, updated_at = now() WHERE id = $1',
|
| 199 |
+
// [c.req.param('agentId')]
|
| 200 |
+
// )
|
| 201 |
+
|
| 202 |
+
// return c.json({ deleted: { outbound: lk_trunk_id, inbound: lk_inbound_trunk_id } })
|
| 203 |
+
// })
|
schemas/agent.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod'
|
| 2 |
+
|
| 3 |
+
export const agentSchema = z.object({
|
| 4 |
+
name: z.string().min(1),
|
| 5 |
+
|
| 6 |
+
// systemPrompt: z.string().min(1),
|
| 7 |
+
systemPrompt: z.string().min(1), // base64 encoded
|
| 8 |
+
|
| 9 |
+
stt: z.object({
|
| 10 |
+
provider: z.enum(['deepgram', 'azure']),
|
| 11 |
+
model: z.string().min(1),
|
| 12 |
+
language: z.string().default('en-US'),
|
| 13 |
+
}),
|
| 14 |
+
|
| 15 |
+
llm: z.object({
|
| 16 |
+
provider: z.enum(['azure', 'litellm']),
|
| 17 |
+
endpoint: z.string().url(),
|
| 18 |
+
model: z.string().min(1),
|
| 19 |
+
apiKey: z.string().min(1),
|
| 20 |
+
apiVersion: z.string().optional(),
|
| 21 |
+
}),
|
| 22 |
+
|
| 23 |
+
tts: z.object({
|
| 24 |
+
provider: z.enum(['deepgram', 'azure']),
|
| 25 |
+
model: z.string().min(1),
|
| 26 |
+
voice: z.string().optional(),
|
| 27 |
+
}),
|
| 28 |
+
|
| 29 |
+
variablesSchema: z.record(z.string()).optional().default({}),
|
| 30 |
+
outputSchemaId: z.string().uuid(),
|
| 31 |
+
webhookUrl: z.string().min(1),
|
| 32 |
+
|
| 33 |
+
})
|
utils.js → schemas/call.js
RENAMED
|
@@ -1,33 +1,18 @@
|
|
| 1 |
-
import { AgentDispatchClient, SipClient } from 'livekit-server-sdk'
|
| 2 |
-
|
| 3 |
import { z } from 'zod'
|
| 4 |
|
| 5 |
const e164Regex = /^\+[1-9]\d{6,14}$/
|
| 6 |
-
|
|
|
|
| 7 |
callTo: z.string({ required_error: 'callTo is required' })
|
| 8 |
.transform(n => `+${n.replace(/\D/g, '')}`)
|
| 9 |
.refine(n => e164Regex.test(n), {
|
| 10 |
message: 'Invalid phone number. Use E.164 format (e.g. +14155552671)'
|
| 11 |
}),
|
| 12 |
-
// sipTrunkId: z.string({ required_error: 'sipTrunkId is required' }).min(1, 'sipTrunkId is required'),
|
| 13 |
-
// agentName: z.string({ required_error: 'agentName is required' }).min(1, 'agentName is required'),
|
| 14 |
agentId: z.string({ required_error: 'agentId is required' }).uuid('agentId must be a valid UUID'),
|
| 15 |
variables: z.record(z.string().default({})).optional()
|
| 16 |
})
|
| 17 |
|
| 18 |
-
const
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
API_PORT_NUM: z.coerce.number().default(3000),
|
| 23 |
-
DATABASE_URL: z.string().url(),
|
| 24 |
-
SIP_TRUNK_ID: z.string().min(1),
|
| 25 |
-
}).parse(process.env)
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
const lkArgs = [env.LIVEKIT_URL, env.LIVEKIT_API_KEY, env.LIVEKIT_API_SECRET]
|
| 29 |
-
const agentDispatchClient = new AgentDispatchClient(...lkArgs)
|
| 30 |
-
const sipClient = new SipClient(...lkArgs)
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
export { callSchema, env, lkArgs, agentDispatchClient, sipClient }
|
|
|
|
|
|
|
|
|
|
| 1 |
import { z } from 'zod'
|
| 2 |
|
| 3 |
const e164Regex = /^\+[1-9]\d{6,14}$/
|
| 4 |
+
|
| 5 |
+
export const callSchema = z.object({
|
| 6 |
callTo: z.string({ required_error: 'callTo is required' })
|
| 7 |
.transform(n => `+${n.replace(/\D/g, '')}`)
|
| 8 |
.refine(n => e164Regex.test(n), {
|
| 9 |
message: 'Invalid phone number. Use E.164 format (e.g. +14155552671)'
|
| 10 |
}),
|
|
|
|
|
|
|
| 11 |
agentId: z.string({ required_error: 'agentId is required' }).uuid('agentId must be a valid UUID'),
|
| 12 |
variables: z.record(z.string().default({})).optional()
|
| 13 |
})
|
| 14 |
|
| 15 |
+
export const dispatchSchema = z.object({
|
| 16 |
+
agentId: z.string().uuid(),
|
| 17 |
+
sipTrunkId: z.string().uuid()
|
| 18 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
schemas/dispatch.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod'
|
| 2 |
+
|
| 3 |
+
export const dispatchSchema = z.object({
|
| 4 |
+
agentId: z.string().uuid(),
|
| 5 |
+
sipTrunkId: z.string().uuid()
|
| 6 |
+
})
|
schemas/trunk.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { z } from 'zod'
|
| 2 |
+
|
| 3 |
+
export const trunkSchema = z.object({
|
| 4 |
+
name: z.string().min(1),
|
| 5 |
+
address: z.string().optional(),
|
| 6 |
+
numbers: z.array(z.string()).min(1), // e.g. ["+17753176886"]
|
| 7 |
+
authUsername: z.string().min(1),
|
| 8 |
+
authPassword: z.string().min(1),
|
| 9 |
+
})
|