Skip to content

Ask the desk and handle the click

Register an invoice list widget type

By the end of this lesson, a message to the desk returns an invoice list that your package built, and a click on a row reaches an APEX page.

Lesson 3 of 4~25 minScript: 03_ask_the_desk.sql

In lesson 2 you wrote the answer yourself. In this lesson the model writes it.

The chat region does two things when a user sends a message. It writes one row to uc_ai_chat_messages for the message, and it starts the procedure uc_ai_chat.execute_agent_job in a scheduler job. 03_ask_the_desk.sql writes a similar row and calls the same procedure in your own session, so you can test the widget without an APEX page. An APEX region can also store Session Init Code on that row. The job runs this code before the agent, for example to initialize application context. This script leaves it empty.

The row has the values that the region attributes give it:

ColumnValue in the scriptRegion attribute
agent_codeSC_DESKAgent Code
agent_request_paramsengineer_name, today, questionAgent Input Mapping
run_contextcontract_id 88 and engineerAgent Run Context
widgetssc_invoicesWidgets and Custom Widgets

The script calls the model of the agent SC_DESK, so a call to a model must work in your schema (see lesson 1). It starts a new conversation for each run. Each session ID starts with SCW_. The question is a constant at the top of the script.

Terminal window
sql your_user/your_password@your_db @03_ask_the_desk.sql
Recorded answergpt-5.6-terra4s2026-10-06

Invoices with an uncredited amount: INV-1001 and INV-1002. INV-1003 is fully credited.

{"type":"sc_invoices","invoice_nos":["INV-1003","INV-1001","INV-1002"],"title":"Contract invoices"}

Your wording will differ. What must match is the data, and the checks below.

The script prints the answer of the model and the parts that the chat stored:

Turn : done in 4 seconds
Tool calls:
SC_LIST_INVOICES
Parts :
1 text
2 widget item_list ["INV-1003","INV-1001","INV-1002"]

The recording is the first of three runs, all on the same day, in a schema that 00_setup.sql created. In all three runs the model called SC_LIST_INVOICES once, wrote a block that the package accepted, and put the same three invoice numbers in the same order. These values varied:

RunSecondsWording of the first sentence
14”Invoices with an uncredited amount: INV-1001 and INV-1002.”
23”Invoices with an uncredited balance: INV-1001 and INV-1002.”
33”There are 3 invoices. Two still have an uncredited amount: INV-1001 and INV-1002.”

These recordings show three runs on one day. Your run can call more tools or name other invoices.

The chat stores the parts on the row of the answer. This query reads the newest conversation that the script started:

select jt.title, jt.badge
from uc_ai_chat_messages m
, json_table(
m.parts
, '$[*].spec.items[*]'
columns (
title varchar2(40 char) path '$.title'
, badge varchar2(40 char) path '$.badges[0].text'
)
) jt
where m.session_id = (
select max(session_id)
from uc_ai_chat_messages
where session_id like 'SCW\_%' escape '\'
)
and m.role = 'assistant';
TITLE BADGE
----------- ---------------
INV-1003 Fully credited
INV-1001 Not credited
INV-1002 Not credited

The badges come from your package and from your tables. The model wrote only the three numbers. The chat renders the answer once, after the run, and stores the result. A later credit note does not change the stored badges. Lesson 4 explains this behavior.

In the browser, the stored list looks like this. The screenshot comes from the local test page of the plug-in, with the values of this run.

A chat answer with the text Invoices with an uncredited amount: INV-1001 and INV-1002. INV-1003 is fully credited, and a list titled Contract invoices. The list has three rows: INV-1003 with a green Fully credited badge, and INV-1001 and INV-1002 with a blue Not credited badge. Each row has a document icon, the invoice date, the total, parts and labour amounts, and an Open button

This part is optional. It uses the chat region and the page item P10_CONTRACT_ID from lessons 1 and 2 of Put an Agent in APEX. The lesson has no recorded output for the page steps.

A click on a row raises the browser event uc-ai-chat-action. The event bubbles up to the chat region. It carries action, id, label, and title: the name, id, and label of the action of the row, and the title of the item. The guide describes them.

  1. Set Custom Widgets of the chat region to sc_invoices.

  2. Add a hidden page item P10_INVOICE_NO. Set Value Protected of the item to No, because JavaScript sets the value in the browser. Add a classic report region with the static ID invoice-details, and this query:

    select i.invoice_no
    , i.invoiced_on
    , i.parts_amount
    , i.labour_amount
    from sc_invoices i
    where i.contract_id = :P10_CONTRACT_ID
    and i.invoice_no = :P10_INVOICE_NO

    Set Page Items to Submit of the region to P10_INVOICE_NO.

  3. Create a dynamic action on the page with these values:

    SettingValue
    EventCustom Event
    Custom Eventuc-ai-chat-action
    Selection TypeRegion
    RegionThe chat region

    Add a true action of the type Execute JavaScript Code:

    if (this.data.action === "sc-open-invoice") {
    apex.item("P10_INVOICE_NO").setValue(this.data.id);
    apex.region("invoice-details").refresh();
    }

    In a dynamic action, this.data holds the data of the custom event.

  4. Open the page. In the console of the browser, raise the event on the chat region yourself. Replace chat-region-id with the static ID of your chat region:

    apex.event.trigger("#chat-region-id", "uc-ai-chat-action",
    { action: "sc-open-invoice", id: "INV-1001", label: "Open", title: "INV-1001" });

    The report region invoice-details then shows INV-1001. A click on a real row raises the same event with the values of that row.

The query of the report repeats the contract condition. The click comes from the browser, and a user can raise the same event with any invoice number from the browser console, as step 4 does. The page must check on the server that the invoice belongs to the contract of the page. The widget limits what the model can show. It does not limit what the browser can send.

  • The region sets the types, the run context, and the input values. The script writes the same row, so you can test the path without a page.
  • The package read the amounts from the tables and calculated the badges.
  • The click is a browser event. The page must check the id again on the server.