-- ============================================================ -- 59_kb_select_ai_router_ords.sql -- -- POST /ords/cb-ords/kb-select-ai-agent/run -- Authorization: Bearer -- {"prompt":"...","conversationId":"optional-safe-id"} -- -- The ORDS runtime validates the bearer and sets the application context -- before invoking the POC_2-owned Team API. The context is session scoped, -- so VPD still applies to any SQL action selected by the Select AI tool. -- -- Execute as CB_ORDS after 22 and 58. -- ============================================================ WHENEVER SQLERROR EXIT SQL.SQLCODE SET ECHO OFF SET FEEDBACK ON SET DEFINE OFF PROMPT === Creating local ORDS Team API wrapper === CREATE OR REPLACE PACKAGE cb_select_ai_ords_api AUTHID DEFINER AS FUNCTION run_team( p_prompt IN CLOB, p_conversation_id IN VARCHAR2 ) RETURN CLOB; END cb_select_ai_ords_api; / CREATE OR REPLACE PACKAGE BODY cb_select_ai_ords_api AS FUNCTION run_team( p_prompt IN CLOB, p_conversation_id IN VARCHAR2 ) RETURN CLOB AS BEGIN RETURN poc_2.kb_select_ai_router_api.run_team( p_prompt => p_prompt, p_conversation_id => p_conversation_id ); END run_team; END cb_select_ai_ords_api; / SHOW ERRORS -- ORDS validates named PL/SQL references through its gateway account before -- dispatching to the CB_ORDS parsing schema. These are the only two local -- wrappers exposed to that account; both remain definer-rights and fixed in -- scope (Bearer context setup and one fixed Team invocation). GRANT EXECUTE ON cb_ords_handler_pkg TO ords_public_user; GRANT EXECUTE ON cb_select_ai_ords_api TO ords_public_user; PROMPT === Resetting only the KB Select AI Agent ORDS module === BEGIN ORDS.DELETE_MODULE(p_module_name => 'kb.select.ai.agent'); EXCEPTION WHEN OTHERS THEN NULL; END; / PROMPT === Creating KB Select AI Agent ORDS endpoint === BEGIN ORDS.DEFINE_MODULE( p_module_name => 'kb.select.ai.agent', p_base_path => 'kb-select-ai-agent/', p_items_per_page => 0, p_status => 'PUBLISHED' ); ORDS.DEFINE_TEMPLATE( p_module_name => 'kb.select.ai.agent', p_pattern => 'run' ); ORDS.DEFINE_TEMPLATE( p_module_name => 'kb.select.ai.agent', p_pattern => 'health' ); ORDS.DEFINE_HANDLER( p_module_name => 'kb.select.ai.agent', p_pattern => 'health', p_method => 'GET', p_source_type => ORDS.source_type_plsql, p_source => q'~ BEGIN :status_code := 200; OWA_UTIL.MIME_HEADER('application/json', FALSE); HTP.P('Cache-Control: no-store'); OWA_UTIL.HTTP_HEADER_CLOSE; HTP.P('{"status":"ok","team":"KB_SELECT_AI_ROUTER_TEAM"}'); END; ~', p_items_per_page => 0 ); ORDS.DEFINE_HANDLER( p_module_name => 'kb.select.ai.agent', p_pattern => 'run', p_method => 'POST', p_source_type => ORDS.source_type_plsql, p_source => q'~ DECLARE v_body_text CLOB; v_prompt VARCHAR2(32767); v_requested_conversation_id VARCHAR2(128); v_internal_conversation_id VARCHAR2(128); v_stakeholder_user_id VARCHAR2(4000); v_answer CLOB; v_response CLOB; v_error_code NUMBER; v_error_message VARCHAR2(4000); BEGIN cb_ords_handler_pkg.set_vpd_context(:auth_header, :probe_id); -- ORDS exposes request JSON as a stream bind. Read it once only. v_body_text := :body_text; v_prompt := JSON_VALUE(v_body_text, '$.prompt' RETURNING VARCHAR2(32767)); v_requested_conversation_id := JSON_VALUE(v_body_text, '$.conversationId' RETURNING VARCHAR2(128)); IF v_prompt IS NULL OR TRIM(v_prompt) IS NULL THEN RAISE_APPLICATION_ERROR(-20801, 'prompt is required'); END IF; IF v_requested_conversation_id IS NULL THEN v_requested_conversation_id := 'kb-' || LOWER(RAWTOHEX(SYS_GUID())); ELSIF NOT REGEXP_LIKE(v_requested_conversation_id, '^[A-Za-z0-9._:-]{1,128}$') THEN RAISE_APPLICATION_ERROR(-20802, 'conversationId contains unsupported characters'); END IF; -- A connection-pooled ORDS session can be reused. Scope the Team's -- conversation memory to the VPD subject as well as the caller's public ID. v_stakeholder_user_id := NVL(SYS_CONTEXT('CB_AGENT_CTX', 'STAKEHOLDER_USER_ID'), 'anonymous'); v_internal_conversation_id := 'kb-' || LOWER(RAWTOHEX(STANDARD_HASH( v_stakeholder_user_id || ':' || v_requested_conversation_id, 'SHA256' ))); -- ORDS validates static PL/SQL references before executing a Handler. -- Invoke the local, fixed-scope Team wrapper through bound dynamic PL/SQL -- so the runtime parser only sees the trusted local context package. EXECUTE IMMEDIATE 'BEGIN :result := cb_select_ai_ords_api.run_team(:prompt, :conversation_id); END;' USING OUT v_answer, IN v_prompt, IN v_internal_conversation_id; SELECT JSON_OBJECT( 'team' VALUE 'KB_SELECT_AI_ROUTER_TEAM', 'conversationId' VALUE v_requested_conversation_id, 'answer' VALUE v_answer RETURNING CLOB ) INTO v_response FROM dual; :status_code := 200; OWA_UTIL.MIME_HEADER('application/json', FALSE); HTP.P('Cache-Control: no-store'); OWA_UTIL.HTTP_HEADER_CLOSE; HTP.P(v_response); cb_ords_handler_pkg.clear_vpd_context; EXCEPTION WHEN OTHERS THEN v_error_code := SQLCODE; v_error_message := SQLERRM; cb_ords_handler_pkg.clear_vpd_context; :status_code := CASE WHEN v_error_code IN (-20801, -20802) OR v_error_code BETWEEN -40599 AND -40400 THEN 400 WHEN v_error_code BETWEEN -20199 AND -20100 THEN 403 ELSE 500 END; SELECT JSON_OBJECT( 'errorCode' VALUE v_error_code, 'error' VALUE v_error_message RETURNING CLOB ) INTO v_response FROM dual; OWA_UTIL.MIME_HEADER('application/json', FALSE); HTP.P('Cache-Control: no-store'); OWA_UTIL.HTTP_HEADER_CLOSE; HTP.P(v_response); END; ~', p_items_per_page => 0 ); ORDS.DEFINE_PARAMETER( p_module_name => 'kb.select.ai.agent', p_pattern => 'run', p_method => 'POST', p_name => 'Authorization', p_bind_variable_name => 'auth_header', p_source_type => 'HEADER', p_param_type => 'STRING', p_access_method => 'IN' ); ORDS.DEFINE_PARAMETER( p_module_name => 'kb.select.ai.agent', p_pattern => 'run', p_method => 'POST', p_name => 'X-VPD-Probe-Id', p_bind_variable_name => 'probe_id', p_source_type => 'HEADER', p_param_type => 'STRING', p_access_method => 'IN' ); COMMIT; END; / PROMPT === KB Select AI Agent ORDS endpoint ready === PROMPT Path: /ords/cb-ords/kb-select-ai-agent/run EXIT