I have a problem testing my Skill on actions on google and in my mobile. It works fine with dialogflow but when I arrive to the Intent defined by the function questions_ready on the assistent of google or in the actions google web page to test it, I have the error "'final_response' must be set.". But it works on Dialogflow, so I dont know where it is the error. I leave here my code hoping some of you can help me! Thank you!
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {WebhookClient} = require('dialogflow-fulfillment');
process.env.DEBUG = 'dialogflow:*'; // enables lib debugging statements
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
// Variables
var procedure;
var procedure;
var finish = "Ok, thank you. We have finished the questions";
var status_questions = [
{question: 'Ok, here is your first question. Please, could you tell me how do you feel on a scale from 1 to 10 ? Where 1 is very bad and 10 is very good'},
{question: 'Your workload is very high, high, normal, low or very low?'},
{question: 'Tell me what is your level of monotony on a scale from 1 to 10. Where 1 is very bad and 10 is very good'}];
var currentIndex = 0;
var currentQuestion = "";
var flag = 0;
var list_answers = [];
var list_index = [];
var list_timestamps = [];
var list_questions = [];
const dialogflowAgentRef = db.collection('dialogflow').doc();
//
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
function welcome(agent) {
agent.add(`Welcome to my agent!`);
}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
function manual(agent) {
const problem = agent.parameters.manual_problems;
if (problem == "DDoS problem"){
procedure = `Ok! Let's see how to solve your `+ problem + `. Specify here the procedure to resolve DDoS problem`;
}
else {
procedure = `Sorry, we don't have a manual to resolve that problem`;
}
agent.add((procedure));
}
function user_name(agent){
const user = agent.parameters.names;
return db.runTransaction(t => {
t.set(dialogflowAgentRef, {name: user});
return Promise.resolve('Write complete');
}).then(doc => {
agent.add(`Ok ${user}, are you ready?`);
}).catch(err => {
console.log(`Error writing to Firestore: ${err}`);
agent.add(`Failed to write "${user}" to the Firestore database.`);
});
}
//WHEN IT IS CALLING THIS FUNCTION I GOT THE ERROR
function questions_ready(agent) {
if(currentIndex === 0){
list_index=[];
list_answers=[];
list_questions=[];
list_timestamps=[];
list_index.push(currentIndex);
currentQuestion = status_questions[currentIndex++].question;
}
else if(currentIndex >=1 && currentIndex <= 10){
list_index.push(currentIndex);
currentQuestion = status_questions[currentIndex++].question;
flag = 1;
const answParam = agent.parameters.answers;
const ans = answParam;
list_answers.push(ans);
}
else {
currentIndex = 0;
currentQuestion = finish;
flag=0;
const answParam = agent.parameters.answers;
const ans = answParam;
list_answers.push(ans);
agent.add(currentQuestion);
return db.runTransaction(t => {
t.update(dialogflowAgentRef, {time: list_timestamps});
t.update(dialogflowAgentRef, {index: list_index});
t.update(dialogflowAgentRef, {question: list_questions});
t.update(dialogflowAgentRef, {answer: list_answers});
return Promise.resolve('Write complete');
});
}
const ques = currentQuestion;
list_questions.push(ques);
var timestamp = Date.now();
list_timestamps.push(timestamp);
let ctx = {'name': 'projects/prueba-firebase-v1/agent/sessions/545ec712-8f69-6999-a50b-4127d38bce82/contexts/questions_ready', 'lifespan': 14,
'parameters': {'timestamp': list_timestamps, 'list_index': list_index, 'list_questions': list_questions, 'list_answers': list_answers}}; //, 'list_index': list_index
agent.setContext(ctx);
agent.add(currentQuestion);
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
intentMap.set(enter code here'Manual', manual);
intentMap.set('User_Name', user_name);
intentMap.set('Questions_Ready', questions_ready);
agent.handleRequest(intentMap);
});
The messages I obtain on Actions on Google are:
-On Simulator Display:
Prueba application isn't responding right now. Try again soon.
-Errors:
MalformedResponse
'final_response' must be set.
-Debug:
{
"response": "Prueba application isn't responding right now. Try again soon.",
"expectUserResponse": false,
"conversationToken": "GidzaW11bG...",
"audioResponse": "//NExAASWK...",
"debugInfo": {
"assistantToAgentDebug": {
"curlCommand": "curl -v 'https://api.api.ai/api/integrations/google?token=6094e3dbd9e242679d0dcc603568b120&versionId=3' -H 'Content-Type: application/json;charset=UTF-8' -H 'Google-Actions-API-Version: 2' -H 'Authorization: eyJhbGciOiJSUzI1NiIsImtpZCI6ImQxZTg2OWU3YmY0MGRkYzNkM2RlMDgwNDI1OThiYTgzNTA5NzBmMGEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhdWQiOiJjeWJlcm9wcy1maXJlYmFzZS12MSIsIm5iZiI6MTU0MjcxMTY5MCwiaWF0IjoxNTQyNzExOTkwLCJleHAiOjE1NDI3MTIxMTAsImp0aSI6Ijc5NWYyZTViZGRjNzk5ZDAxMTY2MDZhZmEyZjJiMDRlYjU3MDk4ZGQifQ.XNBl3DcL2Zhw9bXHvPG52U21ATIb52snsQ5YF9T57cf9HrEeau6XTPfbtALdkiTEqhRfcihQTwLu7wAMdvmqTeeDaRW3F8C2xDCitT2bjPryeDJ3eyoJvI2cTy5Vhf1oN3WwsHdlM0D59JYyNtTH1NE-B60bnLCPQNe7Mv23aUnipdo-LsAytF_d9Bpz93SR_WZITqP6-FpqHSSuUHL3qi8idqGNQrtFF6RQ5-AGKkLkqE-V_Sa2iLmpqDsi4fP3RYW0bajuSFrn74JvrziQKQR4ZaFc4ITjPtJlhboCTgJusOqpFvOYV_-LF5FqgswaiMqUtaX8YBW_EKLLMLoS2A' -A 'Mozilla/5.0 (compatible; Google-Cloud-Functions/2.1; +http://www.google.com/bot.html)' -X POST -d '{\"user\":{\"userId\":\"ABwppHFD2VxLtzrmnMXp4XsxyE13Xc7mxOhaf7cbxMUQg7OEe_I1qRVlcDck8Rl-bESCZBPi3cHvESEbHYfvecHr59o\",\"locale\":\"en-US\",\"lastSeen\":\"2018-11-20T10:51:47Z\"},\"conversation\":{\"conversationId\":\"ABwppHH7q6m-4okbTf3aKCU-dgpEAoOmeCLle2AZjocfLI6i8BS1Lhqcx4InD3QBKboVr4yyTPcaOhAOMgQsaHIgHRU\",\"type\":\"ACTIVE\",\"conversationToken\":\"[]\"},\"inputs\":[{\"intent\":\"actions.intent.TEXT\",\"rawInputs\":[{\"inputType\":\"KEYBOARD\",\"query\":\"yes\"}],\"arguments\":[{\"name\":\"text\",\"rawText\":\"yes\",\"textValue\":\"yes\"}]}],\"surface\":{\"capabilities\":[{\"name\":\"actions.capability.AUDIO_OUTPUT\"},{\"name\":\"actions.capability.MEDIA_RESPONSE_AUDIO\"},{\"name\":\"actions.capability.WEB_BROWSER\"},{\"name\":\"actions.capability.SCREEN_OUTPUT\"}]},\"isInSandbox\":true,\"availableSurfaces\":[{\"capabilities\":[{\"name\":\"actions.capability.AUDIO_OUTPUT\"},{\"name\":\"actions.capability.WEB_BROWSER\"},{\"name\":\"actions.capability.SCREEN_OUTPUT\"}]}],\"requestType\":\"SIMULATOR\"}'",
"assistantToAgentJson": "{\"user\":{\"userId\":\"ABwppHFD2VxLtzrmnMXp4XsxyE13Xc7mxOhaf7cbxMUQg7OEe_I1qRVlcDck8Rl-bESCZBPi3cHvESEbHYfvecHr59o\",\"locale\":\"en-US\",\"lastSeen\":\"2018-11-20T10:51:47Z\"},\"conversation\":{\"conversationId\":\"ABwppHH7q6m-4okbTf3aKCU-dgpEAoOmeCLle2AZjocfLI6i8BS1Lhqcx4InD3QBKboVr4yyTPcaOhAOMgQsaHIgHRU\",\"type\":\"ACTIVE\",\"conversationToken\":\"[]\"},\"inputs\":[{\"intent\":\"actions.intent.TEXT\",\"rawInputs\":[{\"inputType\":\"KEYBOARD\",\"query\":\"yes\"}],\"arguments\":[{\"name\":\"text\",\"rawText\":\"yes\",\"textValue\":\"yes\"}]}],\"surface\":{\"capabilities\":[{\"name\":\"actions.capability.AUDIO_OUTPUT\"},{\"name\":\"actions.capability.MEDIA_RESPONSE_AUDIO\"},{\"name\":\"actions.capability.WEB_BROWSER\"},{\"name\":\"actions.capability.SCREEN_OUTPUT\"}]},\"isInSandbox\":true,\"availableSurfaces\":[{\"capabilities\":[{\"name\":\"actions.capability.AUDIO_OUTPUT\"},{\"name\":\"actions.capability.WEB_BROWSER\"},{\"name\":\"actions.capability.SCREEN_OUTPUT\"}]}],\"requestType\":\"SIMULATOR\"}"
},
"agentToAssistantDebug": {
"agentToAssistantJson": "{\n \"responseMetadata\": {\n \"status\": {\n \"code\": 10,\n \"message\": \"Failed to parse Dialogflow response into AppResponse because of empty speech response\",\n \"details\": [{\n \"#type\": \"type.googleapis.com/google.protobuf.Value\",\n \"value\": \"{\\\"id\\\":\\\"95a80a4d-4a98-461f-b183-88a358d87ebc\\\",\\\"timestamp\\\":\\\"2018-11-20T11:06:30.765Z\\\",\\\"lang\\\":\\\"en-us\\\",\\\"result\\\":{},\\\"alternateResult\\\":{},\\\"status\\\":{\\\"code\\\":206,\\\"errorType\\\":\\\"partial_content\\\",\\\"errorDetails\\\":\\\"Webhook call failed. Error: Webhook response was empty.\\\"},\\\"sessionId\\\":\\\"ABwppHH7q6m-4okbTf3aKCU-dgpEAoOmeCLle2AZjocfLI6i8BS1Lhqcx4InD3QBKboVr4yyTPcaOhAOMgQsaHIgHRU\\\"}\"\n }]\n }\n }\n}"
},
"sharedDebugInfoList": [
{
"name": "ResponseValidation",
"debugInfo": "",
"subDebugEntryList": [
{
"name": "MalformedResponse",
"debugInfo": "'final_response' must be set.",
"subDebugEntryList": []
}
]
}
]
},
"visualResponse": {
"visualElementsList": [
{
"displayText": {
"content": "Prueba application isn't responding right now. Try again soon."
}
}
],
"suggestionsList": [],
"agentLogoUrl": ""
},
"clientError": 0,
"is3pResponse": 1,
"clientOperationList": []
}
Here there are the images I have on my Questions_Ready Intent and the Dialog established by Dialogflow where all work perfectly. The problem is when I try to test the skill on Actions on Google
Questions_Ready Intent (I)
Questions_Ready Intent (II)
Answers Entities
Dialogflow dialog (I)
Dialogflow dialog (II)
Result obtained on Actions on Google
On the Debug json:
debugInfo.agentToAssistantDebug.agentToAssistantJson: "Failed to parse Dialogflow response into AppResponse because of empty speech response"
We can see that Dialogflow is not returning any response that Actions on Google understands. Example response for AoG
So, my thoughts are that the webhook is not returning the correct format for Actions on Google, which is different from Dialogflow.
Here you have a repository for examples of the responses for Actions on Google: https://github.com/dialogflow/fulfillment-webhook-json/tree/master/responses/v2/ActionsOnGoogle/RichResponses
I'm sorry I can't help further with the node.js code, I implemented the webhooks in another language, but the json response is the same for all of them.
From the code sample you provided, it seems like you've been using Dialogflow's Fulfillment library and not the Actions on Google client library, you can test out various samples and take a look at the dependencies up on Github, take this sample for example. The index.js file will have the following import declared when using the Actions on Google Node.js library:
const {dialogflow} = require('actions-on-google');
Related
I need help with Displaying the response I get from my API to Dialogflow UI. Here is my code. I am currently using WebHook to connect Dialogflow to backend in Heroku.
My code
const functions = require('firebase-functions');
var admin = require("firebase-admin");
var serviceAccount = require("../../reactpageagent-dxug-firebase-adminsdk-26f6q-e1563ff30f.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://reactpageagent-dxug.firebaseio.com"
});
const { WebhookClient } = require('dialogflow-fulfillment');
const { Card, Suggestion } = require('dialogflow-fulfillment');
const axios = require('axios');
module.exports = (request, response) => {
const agent = new WebhookClient({ request, response });
function welcome(agent) {
agent.add('Welcome to my agent');
}
function rhymingWordHandler(agent) {
const word = agent.parameters.word;
agent.add(`Here are the rhyming words for ${word}`)
axios.get(`https://api.datamuse.com/words?rel_rhy=${word}`)
.then((result) => {
console.log(result.data);
result.data.map(wordObj => {
console.log(wordObj.word);
agent.add(JSON.stringify(wordObj.word));
return;
// agent.end(`${wordObj.word}`);
});
});
};
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('rhymingWord', rhymingWordHandler);
agent.handleRequest(intentMap);
}
When I console.log my the result. I get the data from the API in my console.log output, but the Data is not displayed in Dialogflow UI I also do not get any error.
Heroku log
I had real trouble with the result.data.map line of code.
In the end, I avoided it and instead processed result.data after the .then((result) => { line by checking the array length that my API returned, and if it was > 0, loop through it to output each line individually, using agent.add. If the array length was 0, I used agent.add to display a message saying 'No records found'. I used a catch to log any errors (again, using agent.add to send an error message to the user).
I have written webhook functions in the inline editor.
I get the deadline exceeded error intermittently.
All webhooks which failed with deadline exceeded error has webhook_latency_ms : 4992ms
In Dialogflow documentation (https://cloud.google.com/dialogflow/docs/fulfillment-how) it's mentioned that default timeout is 5sec, as per this my webhook should not throw this error because it's within 5sec.
The webhook has a very simple code that will take no more than 20-30ms.
Most of the time, the same function has webhook_latency_ms: less than 50ms.
What are the factors that could contribute to increased latency?
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
function testHandler(agent) {
let pendingHabits = agent.getContext('pendinghabits').parameters.habits;
let message = "Ok, let me know when you complete these habits:";
for (let i = 0; i < pendingHabits.length; i++) {
message = message + "\n" + pendingHabits[i];
}
let payload = {
type: 'message',
isPositive: false,
messages: [{ type: 0, text: message }]
};
agent.add(new Payload(agent.UNSPECIFIED, payload));
}})
You are missing your handler call, as i can see the code you have provided,
const functions = require('firebase-functions');
const {WebhookClient, Card, Suggestion} = require('dialogflow-fulfillment');
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((req, res) => {
const agent = new WebhookClient({ req, res });
function intentHandler(agent) {
agent.add('This message is from Dialogflow\'s Cloud Functions for Firebase editor!');
agent.add(new Card({
title: 'Title: this is a card title',
imageUrl: 'https://developers.google.com/actions/assistant.png',
text: 'This is the body text of a card. You can even use line\n breaks and emoji! 💁',
buttonText: 'This is a button',
buttonUrl: 'https://assistant.google.com/'
})
);
agent.add(new Suggestion('Quick Reply'));
agent.add(new Suggestion('Suggestion'));
}
agent.handleRequest(intentHandler);
});
Im wondering if there is a way to keep track of contexts of the intents as they follow. I know that you can use the output context of the previous intent as the input context of the followup intent. but this means i have to keep track of the lifespan as well. Is there another way???
var context = [];
async function TextRecognition(queryText) {
const sessionId = uuid.v4();
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
text: {
// The query to send to the dialogflow agent
text: queryText,
// The language used by the client (en-US)
languageCode: 'en-US',
},
},
};
if (contextList !== null && contextList.length !== 0) {
console.log(contextList);
request.queryParams = {
contexts: context,
};
}
const responses = await sessionClient.detectIntent(request);
const result = responses[0].queryResult;
if (result.intent) {
if (typeof result.outputContexts[0] !== 'undefined') {
result.outputContexts.forEach(context => {
context.push(context);
});
}
return result.fulfillmentText;
} else {
console.log(`No intent matched.`);
}
}
found the answer :) there wasnt a problem in the first place just make sure to create sessionid for each client you dont need to keep track of anythiing dialogflow does it for you :p
I am developing an app on Actions on Google and I've noticed that when using the Dialogflow Fulfillment library I can't save data between conversations.
Here is the code using the WebhookClient:
const { WebhookClient, Card, Suggestion } = require('dialogflow-fulfillment');
exports.aog_app = functions.https.onRequest((request, response)=>{
let agent = new WebhookClient({request, response});
let intentMap = new Map();
intentMap.set('Default Welcome Intent', (agent)=>{
agent.add("hello there!") ;
});
intentMap.set('presentation', (agent)=>{
let conv = agent.conv();
let counter = conv.data.counter;
console.log("counter", counter)
if(counter){
conv.data.counter = counter+1;
}else{
conv.data.counter = 1;
}
agent.add("counter is "+counter) ;
});
agent.handleRequest(intentMap)
});
counter remains undefined on each turn.
But when using the Action on Google Nodejs Library I can save data without issues:
const {
dialogflow,
SimpleResponse,
BasicCard,
Permission,
Suggestions,
BrowseCarousel,
BrowseCarouselItem,
Button,
Carousel,
DateTime,
Image,
DialogflowConversation
} = require('actions-on-google');
const app = dialogflow({debug: true});
app.intent('Default Welcome Intent', (conv)=>{
conv.ask("hello there!");
});
app.intent('presentation', (conv)=>{
let counter = conv.data.counter;
console.log("counter", counter)
if(counter){
conv.data.counter = counter+1;
}else{
conv.data.counter = 1;
}
conv.ask("counter is "+counter)
})
exports.aog_app = functions.https.onRequest(app);
counter is incremented on each turn.
Is there a way to save data between conversations using the Dialogflow fulfillment library?
you need to add the conv back to agent after you update the conv.data
agent.add(conv);
Google's Dialogflow team published a code sample showing how to implement data persistence by integrating Google Cloud's Firebase Cloud Firestore.
You can find the sample here: https://github.com/dialogflow/fulfillment-firestore-nodejs
This is (probably) the code you're looking for:
function writeToDb (agent) {
// Get parameter from Dialogflow with the string to add to the database
const databaseEntry = agent.parameters.databaseEntry;
// Get the database collection 'dialogflow' and document 'agent' and store
// the document {entry: "<value of database entry>"} in the 'agent' document
const dialogflowAgentRef = db.collection('dialogflow').doc('agent');
return db.runTransaction(t => {
t.set(dialogflowAgentRef, {entry: databaseEntry});
return Promise.resolve('Write complete');
}).then(doc => {
agent.add(`Wrote "${databaseEntry}" to the Firestore database.`);
}).catch(err => {
console.log(`Error writing to Firestore: ${err}`);
agent.add(`Failed to write "${databaseEntry}" to the Firestore database.`);
});
}
I made some API call as node.js and I checked it works well in my local.
So I tried to put into Dialogflow.
But It doesn't give any variable.
This is the errors.
Error: could not handle the request
I explain my procedure below.
[1]
This works well in my local with node.js.
It gives a value as ['aa','bb','cc',...,'dd']
const request = require('request');
const url = "https://datos.madrid.es/portal/site/egob/menuitem.ac61933d6ee3c31cae77ae7784f1a5a0/?vgnextoid=00149033f2201410VgnVCM100000171f5a0aRCRD&format=json&file=0&filename=201132-0-museos&mgmtid=118f2fdbecc63410VgnVCM1000000b205a0aRCRD&preview=full";
function information(){
request({
url: url,
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200)
{
}
var bd = body['#graph'];
var model = [];
var i = i;
for (i = 0; i < Object.keys(bd).length; i++)
{
model[i] = bd[i].title;
}
return model;
});
}
[2]
Therefore I made some code on inline editor to check it works well with this kind of value.
I attached a function most below in this code.
When I type 'hello', it says '1 , 2'. That works well.
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
const request = require('request');
process.env.DEBUG = 'dialogflow:debug';
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
function hello(agent) {
var model2 = information();
agent.add(model2);
}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
let intentMap = new Map();
intentMap.set('hello', hello);
intentMap.set('Default Fallback Intent', fallback);
agent.handleRequest(intentMap);
});
function information(){
return ['1','2'];
}
[3]
At last, I tried to do with my API call.
But It doesn't give any value.
It has every package moduel also.
I wonder why it doesn't work on same situation.
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
const request = require('request');
const url = "https://datos.madrid.es/portal/site/egob/menuitem.ac61933d6ee3c31cae77ae7784f1a5a0/?vgnextoid=00149033f2201410VgnVCM100000171f5a0aRCRD&format=json&file=0&filename=201132-0-museos&mgmtid=118f2fdbecc63410VgnVCM1000000b205a0aRCRD&preview=full";
process.env.DEBUG = 'dialogflow:debug';
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
function hello(agent) {
var model2 = information();
agent.add(model2);
}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
let intentMap = new Map();
intentMap.set('hello', hello);
intentMap.set('Default Fallback Intent', fallback);
agent.handleRequest(intentMap);
});
function information(){
request({
url: url,
json: true
}, function (error, response, body) {
if (!error && response.statusCode === 200)
{
}
var bd = body['#graph'];
var model = [];
var i = i;
for (i = 0; i < Object.keys(bd).length; i++)
{
model[i] = bd[i].title;
}
return model;
});
}
You don't show the error you're getting, but there are likely two problems with what you're trying to do.
The first is that request returns its information asynchronously, through a callback. The dialogflow-fulfillment library requires that any Intent Handler that does asynchronous operations return a Promise.
While you can wrap this in a Promise, even easier is to use a version of request that will work directly with Promises, such as request-promise-native. (See, for example, https://stackoverflow.com/a/49751176/1405634)
Your other issue is that if you're using the built-in code editor for Dialogflow, you're working on top of Cloud Functions for Firebase. By default, network calls are only allowed inside Google's network. In order to access external addresses, you need to make sure you're using a paid plan such as the Blaze plan. There is, however, a free tier which is sufficient for development and most testing, and likely even light production.
You can make an app on heroku and send the fullfilment through to it, instead of using inclide code editor for google cloud.