-- ============================================================
-- 59_kb_select_ai_router_ords.sql
--
-- POST /ords/cb-ords/kb-select-ai-agent/run
-- Authorization: Bearer <VPD token>
-- {"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
