2026年6月11日木曜日

Codexを使って端末エミュレーターをOracle APEXのページに組み込む

端末エミュレーターをOracle APEXのページに組み込めるかどうか、確認してみました。手作業でAPEXアプリケーションの雛形を作り、OpenAI Codexで機能を追加しています。

作業の目的はAPEXアプリケーションに端末エミュレータを組み込めるかどうかの事前調査ですが、 アプリケーションの雛形の作成とCodexを使った機能追加までの作業手順を紹介します。

Oracle APEXの標準コンポーネントに端末エミュレーターはありません。そのため、ブラウザに組み込める端末エミュレータのJavaScriptコンポーネントを、ChatGPTに探してもらいました。

ブラウザでターミナル・エミュレータとして動かせるJavaScriptコンポーネントはありますか?

色々なコンポーネントを紹介されましたが、xterm.jsが事実上の標準ということで一推しだったので、xterm.jsを採用することにしました。

xterm.js

Orade APEXのページに組み込むにあたって、端末エミュレーターとバックエンドとなるデータベースとの通信はHTTPに限られます。キーボード入力をリアルタイムでバックエンドに送信することはできません。Enterの入力によりそれまで入力した文字列を、HTTP POSTでバックエンドに送信し、レスポンスを端末に表示するような実装が可能かどうか確認しました。

xterm.jsはEnterの入力で送信、返信を画面に出力するという形式でHTTPでバックエンドと通信できますか?

「できます。」という回答とサンプルとなるコードが提示されたので、APEXアプリケーションの作成を始めることにしました。

端末からの入力を受け取ってレスポンスを返すバックエンドのサービスが必要なので、そのサービスに生成AIを使うことにしました。端末の入力をLLMに送り、レスポンスを端末に表示します。LLMとの単純なチャットとは今更なアプリケーションですが、あくまで目的は端末エミュレーターの動作確認です。

APEX 26.1のアプリケーションから生成AIのサービスを呼び出すための設定を行います。今回はローカルLLMのLM Studioと、モデルとしてGPT-OSS-120Bを使うことにします。

ワークスペース・ユーティリティ生成AIを開きます。


生成AIサービス作成を開始します。


APEX 26.1は、以下のプロバイダをサポートしています。

OCI生成AIサービスOpenAICohereGoogle GeminiAnthropic ClaudeMistral AIOllama、汎用(OpenAI互換API)

APEX 24.2でサポートされていたのはOCI生成AIサービス、OpenAI、Cohereの3つでした。GoogleやAnthropicなどの主要なプロバイダを含む、多くのプロバイダが追加されています。

OpenAIはResponses APIではなく、Chat Completions APIの呼び出しになっています。Ollamaについては汎用(OpenAI互換API)より呼び出すこともできます。


今回はローカルLLMのLM StudioをAIプロバイダとして使用します。

AIプロバイダとして汎用(OpenAI API互換)を選択します。この設定ではChat Completions APIが呼び出されます。プロバイダの名前Local GPT-OSS-120Bとします。

APEXはローカルのOracle AI Database 26ai Freeのコンテナで実行しています。設定エンドポイントURLとして、http://host.docker.internal:1234/v1/chat/completionsを設定します。

資格証明は不要なので資格証明の作成オフ詳細AIモデルとしてunsloth/gpt-oss-120bを設定します。単にチャットができれば十分なので、もっとメモリ要件の少ないモデル(GPT-OSS-20Bなど)を選択することも可能です。


接続のテストを実行し、接続が成功することを確認します。

以上で生成AIサービスを作成します。


以上でLM StudioのGPT-OSS-120Bを呼び出す、生成AIサービスが作成されました。


空のAPEXアプリケーションを作成します。名前Sample Terminal Emulatorとします。


アプリケーションSample Terminal Emulatorが作成されます。

アプリケーション定義AIを開き、デフォルトで使用する生成AIサービスを設定します。

今回はサービスとして先ほど作成したLocal GPT-OSS-120Bを選択します。


APEX 26.1にて、Request Handler ProcedureおよびResponse Handler Procedureの設定が追加されています。

Request Handler Procedureに設定したプロシージャにより、メッセージをLLMに送信する前に監査または置き換えができます。Response Handler Procedureに設定したプロシージャにより、LLMから受け取ったレスポンスの監査または置き換えができます。

生成AIを呼び出すAIエージェントを作成します。これはAPEX 24.2ではAI構成という名称でした。


新規にAIエージェントを作成します。


作成するAIエージェントの名前chatとします。サービスとしてアプリケーション・デフォルトを使用します。アプリケーション・デフォルトは先ほど、アプリケーション定義AIにてLocal GPT-OSS-120Bを設定しています。

システム・プロンプトに「あなたは親切な話し相手です。」と記述します。レスポンス形式タイプテキストとします。

詳細静的IDchatとなっています。これはパッケージAPEX_AIのプロシージャを呼び出す際に引数として与えます。

以上でAIエージェントを作成します。


AIエージェントが作成されると、ツールの設定が現れます。APEX 24.2ではRAGソースという名前でしたが、設定内容が大幅に増えています。

本記事ではこのツールについては触れません。別の記事で解説しようと思います。


APEX 26.1ではレスポンス形式タイプとしてJSONオブジェクトを選択できます。APEX 24.2ではレスポンス形式はテキストのみであったため、レスポンス形式の設定はありません。


タイプJSONオブジェクトを選択すると、レスポンスとなるJSONオブジェクトを規定するJSONスキーマを記述する必要があります。この設定はLLMへ送信するメッセージに含まれます。OpenAI Chat Completions APIでは、以下のようにresponse_formatオブジェクトとjson_schemaがメッセージに含まれます。
"response_format": {
    "type": "json_schema",
    "json_schema": {...}
}
レスポンスとしてJSONオブジェクトが得られるかどうかは、AIプロバイダとモデルに依存します。ここで、タイプをJSONオブジェクトにしても、必ず指定したJSONスキーマどおりのJSONオブジェクトがレスポンスとして返されるとは限りません。

以上でAIエージェントとして、chatが作成されました。


ホーム・ページに端末エミュレータを組み込みます。

ページ・デザイナでホーム・ページを開き、Bodyに静的コンテンツのリージョンを作成します。リージョンの識別名前Terminalとし、ソースHTMLコードに以下を記述します。

<div id="terminal"></div>


ページ・プロパティに以下を設定します。

JavaScriptファイルURLに以下を設定します。JSDelivrからxterm.jsのライブラリを読み込みます。

https://cdn.jsdelivr.net/npm/@xterm/xterm@6.0.0/lib/xterm.min.js
https://cdn.jsdelivr.net/npm/@xterm/addon-fit/lib/addon-fit.js

ファンクションおよびグローバル変数の宣言に以下を記述します。端末からのEnterの入力を受けて、それまで入力していたテキストを引数として、AjaxコールバックCHATを呼び出します。AjaxコールバックCHATからAIエージェントchatを呼び出します。

CSSファイルURLとして以下を記述します。

https://cdn.jsdelivr.net/npm/@xterm/xterm@6.0.0/css/xterm.min.css


AjaxコールバックとしてCHATを作成します。識別タイプコードを実行です。

ソースPL/SQLコードとして以下を記述します。



以上で、何となく端末エミュレーターが組み込まれたAPEXアプリケーションが作成できました。

作成したAPEXアプリケーションを実行してみます。


これから、作成したアプリをCodexを使って拡張します。

作成したアプリのアプリケーションIDを確認します。今回の作業では116でした。


作業ディレクトリを作成し、そこに移動します。

mkdir apexapps
cd apexapps

アプリケーションを作成したデータベースに接続し、APEXlang形式でアプリケーションをエクスポートします。

apex export -applicationid 116 -exptype apexlang

SQL> apex export -applicationid 116 -exptype apexlang

ワークスペースAPEXDEVをエクスポートしています - アプリケーション116:Sample Terminal Emulator

ファイルsample-terminal-emulator/application.apxが作成されました


SQL> 


APEXlangのスキルをインストールします。Microsoft APMを使用します。

ファイルapm.ymlを以下の内容で作成します。
name: Simple Terminal Emulator
version: 0.1.0
description: Oracle APEX 26.1のAPEXlangを使ってアプリケーションを作成する。
author: Yuji
dependencies:
  apm:
    # Oracle Database Skills
    - git: https://github.com/oracle/skills
      path: db
      ref: main
    # Oracle APEX Skills
    - git: https://github.com/oracle/skills
      path: apex
      ref: main
Codex向けにスキルをインストールします。

apm install --target codex

apexapps % apm install --target codex

[>] Installing dependencies from apm.yml...

[>] Resolving skills-db...

[>] Resolving skills-apex...

[i] Targets: codex  (source: --target flag)

  [+] github.com/oracle/skills/apex#main #main @55679630

  |-- Skill integrated -> .agents/skills/

  [+] github.com/oracle/skills/db#main #main @55679630

  |-- Skill integrated -> .agents/skills/

[i] Added apm_modules/ to .gitignore


[*] Installed 2 APM dependencies in 1.3s.

apexapps % 


CodexはSQLclをCLIとして呼び出すこともできるため、SQLcl MCPサーバーの設定は必ずしも必要ありませんが、今回は設定します。

.codex/config.tomlを作成し、以下を記述します。
[mcp_servers.oracle-apexdev]
command = "/opt/homebrew/Caskroom/sqlcl/26.1.2.132.1334/sqlcl/bin/sql"
args = [ "-R", "4", "-mcp" ]

[sandbox_workspace_write]
network_access = true
注)APEX 26.1でAPEXlangでエクスポートされたアプリをAPEXワークスペースにインポートする際に、パーシング・スキーマがSELECT_CATALOG_ROLEを持っているとインポートに失敗します。この事象を回避するためにスキーマからSELECT_CATALOG_ROLEを落としていると、データベースのスキーマの情報を読めないため生成AIによるコード生成の精度が落ちるようです。そのため、生成AIでのコード生成を行う場合はスキーマにSELECT_CATALOG_ROLEを付与し、APEXlangのアプリのインポートはSQLclで実施するようにします。

Codexを起動します。

codex --sandbox workspace-write

最初にこれからの作業の条件を指示します。

「データベースの接続にはsql -name local-26ai-apexdevを使用します。アプリケーションが作成されているAPEXワークスペースはAPEXDEVです。今回、修正対象のAPEXアプリケーションはsample-terminal-emulator以下にAPEXlang形式でエクスポートされています。」

apexapps % codex --sandbox workspace-write

╭──────────────────────────────────────────────╮

│ >_ OpenAI Codex (v0.139.0)                  

                                             

│ model:     gpt-5.5 medium   /model to change │

│ directory: ~/Documents/apexapps             

╰──────────────────────────────────────────────╯


  Tip: New Use /fast to enable our fastest inference with increased plan usage.



データベースの接続にはsql -name local-26ai-apexdevを使用します。アプリケーションが作成されているAPEXワーク

  スペースはAPEXDEVです。今回、修正対象のAPEXアプリケーションはsample-terminal-emulator以下にAPEXlang形式でエ

  クスポートされています。



了解しました。対象は sample-terminal-emulator 配下の APEXlang エクスポート、DB 接続は local-26ai-apexdev

  APEX ワークスペースは APEXDEV として扱います。


  修正依頼を受けたら、まず sample-terminal-emulator の構成と APEXlang 定義を確認し、必要に応じて apex /

  apexlang スキルと sql -name local-26ai-apexdev 相当の接続前提で進めます。


 

Write tests for @filename

 

  gpt-5.5 medium · ~/Documents/apexapps


現状は端末から送信したメッセージはそれぞれレスポンスを受け取り、それまでの履歴は送信するメッセージに含まれません。これを、履歴もメッセージに含んでAIエージェントを呼び出すように更新します。

「今はAjaxコールバックCHATは、受信したメッセージ毎にAIエージェントを呼び出しています。これをAPEX_AI.CHATを呼び出すように変更し、APEXで同一セッションの間は、それまでの会話履歴を含めて生成AIを呼び出して、レスポンスを得るように変更してください。」

Codexが色々と作業を始めます。

今はAjaxコールバックCHATは、受信したメッセージ毎にAIエージェントを呼び出しています。これをAPEX_AI.CHATを呼

  び出すように変更し、APEXで同一セッションの間は、それまでの会話履歴を含めて生成AIを呼び出して、レスポンスを

  得るように変更してください。



apex / apexlang スキルを使って進めます。まずローカルの APEXlang 定義と既存の Ajax コールバック実装を確認しま

  す。


Explored

  Read SKILL.md (apexlang skill), SKILL.md (apex skill)


──────────────────────────────────────────────────────────────────────────────────────────────────────────────


対象アプリの場所はユーザー指定で確定しているので、sample-terminal-emulator 配下だけを見ます。並行して

  APEXlang のルーティング資産も最小限確認します。


Explored

  Read routing-catalog-main.json, routing-load-policy.json, domains-catalog.json

    List sample-terminal-emulator

    Read workspace-intelligence.json

    Search CHAT|AI|AGENT|APEX_AI|Ajax|ajax|callback in sample-terminal-emulator


──────────────────────────────────────────────────────────────────────────────────────────────────────────────


CHAT はページ 1 の Ajax callback と共有コンポーネントの AI Agent に分かれていそうです。次に PL/SQL の実装と

  アプリ設定を読んで、履歴保存先を APEX セッション内でどう持つのが既存構成に合うか確認します。


Explored

  Read p00001-home.apx, application.apx, chat.apx, local-gpt-oss-120b.apx


Ran node /Users/ynakakoshi/Documents/apexapps/.agents/skills/apex/apexlang/tools/apexctl.mjs workspace probe

  --db-connection-name local-26ai-apexdev --workspace-name APEXDEV

  └ {

      "status": "unresolved",

    … +263 lines (ctrl + t to view transcript)

      }

    }


──────────────────────────────────────────────────────────────────────────────────────────────────────────────


既存実装は apex_ai.generate(p_agent_static_id => 'chat', p_prompt => l_prompt) で単発呼び出しです。

  APEX_AI.CHAT の正確なシグネチャは環境の APEX バージョンに依存するため、接続先 DB のパッケージ定義から確認し

  ます。


変更が完了したようです。APEXlangのスキルに、改変したAPEXアプリケーションをいきなりインポートしないように記載されているようです。

作成されたアプリケーションをインポートするように指示します。

「作成したアプリケーションをアプリケーションID 116 として上書きしてインポートしてください。」

作成したアプリケーションをアプリケーションID 116 として上書きしてインポートしてください。



アプリ ID 116 への上書き import として進めます。まず deployments/default.json の現在値を確認し、APEXlang 側

  のターゲット指定を 116 に合わせてから validate-and-import を実行します。


[中略]


インポート完了しました。


  sample-terminal-emulator/deployments/default.json の app id は既に 116 で、APEXDEV の workspace_id

  6339125071034524 と確認しました。そのうえで以下の条件で上書き import しています。


  - DB 接続: local-26ai-apexdev

  - Workspace: APEXDEV

  - Application ID: 116

  - Mode: validate-and-import

  - Target resolution: update-existing


  結果は live_validate: passimport_status: passruntime_gate_status: pass です。ローカル lint は既存の構成

  指摘で advisory fail ですが、ライブ検証と import は成功しています。


──────────────────────────────────────────────────────────────────────────────────────────────────────────────


 

Write tests for @filename

 

  gpt-5.5 medium · ~/Documents/apexapps


APEXアプケーションを実行し、動作を確認します。

「いま、1という数字があります。」
「この数字に1を足してください。」

答えは2となるので、チャットの履歴は渡っていそうです。


APEXセッションが継続する限り、それまで送信したメッセージがLLMに渡されます。/newコマンドで履歴をリセットするように、機能を追加します。

「プロンプトとして/newコマンドを入力すると、それまでの履歴を消去して新たなチャットが始まるように機能を追加してください。」

プロンプトとして/newコマンドを入力すると、それまでの履歴を消去して新たなチャットが始まるように機能を追加し

  てください。



apex / apexlang スキルを使って進めます。今回は既存の Ajax callback に /new 分岐を追加し、サーバ側のセッショ

  ン履歴 CHAT_HISTORY を消す形にします。


Explored

  Read SKILL.md (apexlang skill), SKILL.md (apex skill), p00001-home.apx


──────────────────────────────────────────────────────────────────────────────────────────────────────────────


既存 callback は全入力を AI に渡す構造なので、/new は AI 呼び出し前のサーバ側コマンドとして処理します。これ

  により /new 自体は履歴にも送られません。


[中略]


/new コマンドを追加しました。


  sample-terminal-emulator/pages/p00001-home.apx:117 の Ajax callback CHAT で、入力を trim + lowercase 判定

  し、/new の場合は CHAT_HISTORY APEX collection を truncate して AI 呼び出しをスキップします。端末には 新しい

  チャットを開始しました。 を返します。


  検証:


  - runtime validate --db-connection-name local-26ai-apexdev: pass

  - apexlang validate: 既存の workspace-components / breadcrumb 関連 lint 指摘で fail


  APEX アプリ 116 への再インポートは今回は実行していません。


─ Worked for 1m 15s ──────────────────────────────────────────────────────────────────────────────────────────


 

Write tests for @filename

 

  gpt-5.5 medium · ~/Documents/apexapps


/newコマンドが追加されました。先ほどと同様にインポートはされていないので、同じプロンプトを渡して、APEXアプリケーションをインポートしたのち、動作を確認します。

/newコマンドを入力すると「新しいチャットが開始しました。」と印刷されます。デバッグ・ログから、今までの履歴がメッセージに含まれていないことが確認できました。


端末でのバックスペースの処理が実装されていないようです。Codexに依頼します。

「端末でバックスペースの入力が適切に行われていません。端末上でバックスペースを入力したときに、すでに入力されている直前の一文字を削除するようにしてください。日本語のかな漢字変換モードのときは、バックスペースは変換前の文字列に対して有効に働くため、バックスペースにより削除する文字は、かな漢字変換モードになっていない場合に限ります。1文字は英数字および漢字の場合があることを考慮すること。」

端末でバックスペースの入力が適切に行われていません。端末上でバックスペースを入力したときに、すでに入力され

  ている直前の一文字を削除するようにしてください。日本語のかな漢字変換モードのときは、バックスペースは変換前

  の文字列に対して有効に働くため、バックスペースにより削除する文字は、かな漢字変換モードになっていない場合に

  限ります。1文字は英数字および漢字の場合があることを考慮すること。



apex / apexlang スキルを使って、端末入力処理を修正します。今回はページ 1 の JavaScript 側で、バックスペース

  時のバッファ削除と画面上の消去を実装します。


Explored

  Read SKILL.md (apex skill), SKILL.md (apexlang skill), p00001-home.apx


──────────────────────────────────────────────────────────────────────────────────────────────────────────────


バックスペースは xterm の onData\x7f または \b として届く前提で処理します。IME 変換中は composition

  event を見て、サーバ送信用バッファにも端末表示にも手を入れないようにします。


Explored

  Read routing-catalog-main.json, domains-catalog.json, routing-load-policy.json, workspace-

  intelligence.json


──────────────────────────────────────────────────────────────────────────────────────────────────────────────


これから sample-terminal-emulator/pages/p00001-home.apx:18 の JavaScript を編集します。英数字 1 セルと漢字/

  全角 2 セルを区別して、最後の Unicode 文字単位をバッファと表示の両方から削除します。


「中略]


対応しました。


  sample-terminal-emulator/pages/p00001-home.apx:31 の端末入力処理に、バックスペース対応を追加しました。

  Backspace\x7f または \b として届いた場合、IME 変換中でなければ入力バッファの直前 1 文字を削除し、端末表

  示上も消去します。英数字は 1 セル、漢字などの全角文字は 2 セルとして扱います。


  IME 変換中は compositionstart / compositionend / compositioncancel で判定し、バックスペースによるバッファ削

  除は行いません。


  検証:


  - runtime validate --db-connection-name local-26ai-apexdev: pass

  - apexlang validate: 既存の workspace-components / breadcrumb 関連 lint 指摘で fail


  APEX アプリ 116 への再インポートはまだ実行していません。


─ Worked for 6m 58s ──────────────────────────────────────────────────────────────────────────────────────────


 

Write tests for @filename

 

  gpt-5.5 medium · ~/Documents/apexapps


対応されたようなので、先ほどと同様にAPEXアプリのインポートを指示し、動作確認を行います。

おおむね指示通りにバックスペースの処理が実装されているようです。


今回の記事は以上になります。

今回Codexが生成した機能を含むAPEXアプリケーションの、APEXlang形式のエクスポートを以下に置きました。


ダウンロードには、https://download-directory.github.io/を開き、上記のURLをダウンロードの対象とすると、APEXワークスペースにインポート可能なZIPファイルをダウンロードできます。


Oracle APEXのアプリケーション作成の参考になれば幸いです。