Ins0mn1a commited on
Commit
024552e
·
1 Parent(s): 07748ea

Update README.md

Browse files
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
  title: MOHBIM Custom Livekit API V1
3
  emoji: 🚦
4
- colorFrom: indigo
5
- colorTo: yellow
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
- import { callSchema, env, agentDispatchClient, sipClient } from './utils.js'
5
- import { agentsRouter } from './agents.js'
6
- import { query } from './db.js'
 
 
 
 
7
 
8
  const app = new Hono()
9
- app.route('/agents', agentsRouter)
10
-
11
- app.post('/call', async (c) => {
12
- // console.log('got here: ', JSON.stringify(await c.req.text()))
13
- let oriqReqTxt = await c.req.text()
14
- // console.log('oriqReqTxt is of type: ', typeof(oriqReqTxt))
15
- let fixedReq = oriqReqTxt.replaceAll(': }', ': \{\} }')
16
- const result = callSchema.safeParse(JSON.parse(fixedReq))
17
- console.log('result.data is: ', JSON.stringify(result.data))
18
-
19
- if (!result.success) return c.json({ error: result.error.issues[0].message }, 400)
20
- // const { callTo: formattedNumber, sipTrunkId, agentName, variables } = result.data
21
- const { callTo: formattedNumber, agentId, variables } = result.data
22
- // fetch agent to verify it exists + get sipTrunkId
23
- const { rows } = await query('SELECT id FROM agents WHERE id = $1', [agentId])
24
- if (!rows.length) return c.json({ error: 'Agent not found' }, 404)
25
-
26
-
27
- const roomName = `room_${Math.floor(Math.random() * 10_000)}`
28
- const participantIdentity = `caller_${formattedNumber}`
29
-
30
- try {
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-sip-call-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
  "@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
- import { z } from 'zod'
4
-
5
- const agentSchema = z.object({
6
- name: z.string().min(1),
7
- // systemPrompt: z.string().min(1),
8
- systemPrompt: z.string().min(1), // base64 encoded
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
- llm: z.object({
15
- provider: z.enum(['azure', 'litellm']),
16
- endpoint: z.string().url(),
17
- model: z.string().min(1),
18
- apiKey: z.string().min(1),
19
- apiVersion: z.string().optional(),
20
- }),
21
- tts: z.object({
22
- provider: z.enum(['deepgram', 'azure']),
23
- model: z.string().min(1),
24
- voice: z.string().optional(),
25
- }),
26
- variablesSchema: z.record(z.string()).optional().default({}),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  })
28
 
29
- export const agentsRouter = new Hono()
30
 
31
- agentsRouter.post('/', async (c) => {
 
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
- const { rows } = await query(
37
- 'INSERT INTO agents (name, system_prompt, stt, llm, tts, variables_schema) \
38
- VALUES ($1, $2, $3, $4, $5, $6) RETURNING id',
39
- // [name, systemPrompt, JSON.stringify(stt), JSON.stringify(llm), JSON.stringify(tts), JSON.stringify(variablesSchema)]
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
- agentsRouter.get('/:agentId', async (c) => {
47
- // const { rows } = await query(`SELECT * FROM agents WHERE id = $1`, [c.req.param('agentId')])
48
- const { rows } = await query('SELECT * FROM agents WHERE id = $1', [c.req.param('agentId')])
49
- if (!rows.length) return c.json({ error: 'Agent not found' }, 404)
50
- return c.json(rows[0])
 
 
 
 
51
  })
52
 
53
- agentsRouter.get('/', async (c) => {
54
- const { rows } = await query('SELECT id, name, created_at FROM agents ORDER BY created_at DESC')
55
- return c.json(rows)
 
 
56
  })
57
 
58
- agentsRouter.put('/:agentId', async (c) => {
 
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
- const { name, systemPrompt, stt, llm, tts, variablesSchema } = result.data
 
 
 
 
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
- const { rows } = await query(
78
- `UPDATE agents SET ${fields.join(', ')} WHERE id = $${i} RETURNING id`,
79
- values
80
- )
81
- if (!rows.length) return c.json({ error: 'Agent not found' }, 404)
82
- return c.json({ agentId: rows[0].id })
 
 
 
 
 
 
 
 
 
 
83
  })
84
 
85
- agentsRouter.delete('/:agentId', async (c) => {
86
- const { rows } = await query('DELETE FROM agents WHERE id = $1 RETURNING id', [c.req.param('agentId')])
87
- if (!rows.length) return c.json({ error: 'Agent not found' }, 404)
88
- return c.json({ deleted: rows[0].id })
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
- const callSchema = z.object({
 
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 env = z.object({
19
- LIVEKIT_API_KEY: z.string().min(1),
20
- LIVEKIT_API_SECRET: z.string().min(1),
21
- LIVEKIT_URL: z.string().url(),
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
+ })