iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
AI Engineering

30天拆Agent:從Repo看設計系列 第 10 篇

Day 10|Codex App Server:把 Codex Core 接到真正的產品介面

  • 分享至 

  • xImage
  •  

上一篇介紹 Codex Core 時,看到它用 Submission / Event 跟 Client 互動。

Core 負責真正的 Agent loop:

收到任務
  ↓
呼叫 Model
  ↓
執行 Tool / Command
  ↓
修改檔案
  ↓
產生 Event
  ↓
繼續下一步

但這裡留下一個問題:

如果 Client 不再是 Codex 自己的 TUI,而是 Web、Slack、LINE 或 IDE,這些產品要怎麼操作同一套 Codex Runtime?

這就是 codex app-server 要解決的事情。

它是在 Codex Core 上面建立一條穩定的產品介面:

Slack / LINE / Web / IDE
          │
          ▼
     Codex App Server
          │
          ▼
       Codex Core

1. App Server 先定義一套「Agent Interaction Model」

假設我要做一個 Slack Agent。

使用者輸入:

@codex 幫我跑 pytest,
看看是哪裡壞掉。

如果 Slack Backend 直接接 Codex Core,它就必須知道:

ThreadManager
Submission
EventMsg
Approval
Turn lifecycle
Tool execution
Agent resume

等於 Slack Adapter 同時也要變成 Codex Runtime Client。

App Server 做的事情,是在中間建立一套比較穩定的模型:

Slack Adapter
│
├─ Slack app_mention
├─ Slack button
│
└─ Codex App Server Protocol
      ├─ thread/start
      ├─ turn/start
      ├─ turn/completed
      └─ requestApproval

所以 Slack、LINE、Web 不必理解 Core 裡到底是哪一個 Rust EventMsg。

它們只需要理解:

turn/start
item/started
item/completed
turn/completed

2. 一個正常 Turn:Input 進來,Item 持續出去

假設 LINE 使用者傳:

[error.png]

這是什麼問題?

LINE 本身可能只透過 Webhook 告訴我們:

message.type = image
message.id = 123456

Codex 當然不知道 LINE 的 message.id 是什麼。

所以 LINE Adapter 先轉成 App Server 定義的 UserInput:

{
  "method": "turn/start",
  "params": {
    "threadId": "thr_001",
    "input": [
      {
        "type": "text",
        "text": "這是什麼問題?"
      },
      {
        "type": "localImage",
        "path": "/workspace/error.png"
      }
    ]
  }
}

目前 App Server 的 UserInput 已定義文字、圖片、本地圖片、audio、skill、mention 等輸入型別。

所以不同平台最後可以變成:

LINE image ──┐
             │
Slack image ─┼── Adapter ──► localImage
             │
Web upload ──┘

到這裡,Agent 開始執行。

App Server 不會只等最後答案。它會把這些 Runtime activity 持續轉成 Item:

turn/started

item/started
type = commandExecution

item/completed

item/started
type = fileChange

item/completed

item/started
type = commandExecution

item/completed

turn/completed

目前 ThreadItem 也已經有許多明確類型,例如:

agentMessage
reasoning
commandExecution
fileChange
mcpToolCall
webSearch
imageView
...

於是 Slack 可以看到:

🔧 Running pytest...
📝 Editing foo.py...
🔧 Running pytest...
✅ Completed

再傳最終答案。

這裡可以看出 App Server 做的第一件核心工作:

把一次 Agent Turn 的輸入與執行過程,整理成 Client 可以穩定理解的 lifecycle。

外面的 Interface 可以不同,但對 Codex 來說都是:

UserInput
   ↓
Turn
   ↓
Item
   ↓
Turn Complete

3. Turn 做到一半不能繼續:Request / Response

假設 Agent 執行到一半想做:

rm old.db

但這個操作需要使用者批准。

這時就不能只是:

item/started
item/completed

因為 Agent 現在缺一個外部答案。

所以流程會從單向 Event:

Agent
  ↓
Client

變成雙向:

Agent
  ↓
Request
  ↓
Client
  ↓
Response
  ↓
Agent 繼續

例如 App Server 發出:

request #77

是否允許執行:
rm old.db

Slack 可以把它顯示成:

Codex 想執行:

rm old.db

[Approve] [Decline]

LINE 則可能是:

Codex 想刪除 old.db

[同意] [拒絕]

這兩個 UI 完全不同。

App Server 不需要知道:

Approve
同意
Yes
✅

各自的 Adapter 只需要轉成共同語意:

Slack Approve ─┐
               │
LINE 同意 ─────┼──► accept
               │
Web ✅ ─────────┘

最後送回:

{
  "id": 77,
  "result": {
    "decision": "accept"
  }
}

這裡 77 代表這個 accept 是在回答剛才第 77 個 Request。

所以完整流程是:

Codex Core
    │
    │ Need Approval
    ▼
App Server
    │
    │ Request #77
    ▼
LINE / Slack
    │
    │ 使用者按「同意」
    ▼
Adapter
    │
    │ #77 → accept
    ▼
App Server
    │
    ▼
Codex Core
    │
    └─ 繼續原本的 Turn

4. Request 不只能問「人」,也可以問「外部系統」

理解 Approval 之後,Tool 就很好理解了。

因為同一種模式延伸成:

Agent → 問 Application

假設 Slack 使用者說:

@codex
幫我找台北車站附近三間咖啡廳。

有時 Google Maps API Key 不想放在 Codex Container。

公司的 Backend 本來就有:

Google Maps API

這時可以反過來讓 Agent 向 Client 要能力。

例如 Agent 判斷:

我需要 search_google_maps

App Server 可以發出 Tool Request:

Codex
  ↓
App Server
  ↓
「幫我執行 search_google_maps」
  ↓
Product Backend
  ↓
Google Maps API

Backend 查完:

A Cafe
B Coffee
C Cafe

再把結果回去:

Google Maps
  ↓
Product Backend
  ↓
Response
  ↓
App Server
  ↓
Codex

Agent 拿到結果後,再繼續原本的 Turn。

目前 Dynamic Tool 相關介面仍屬較新的/experimental API,但這個設計很適合用來理解 App Server 想建立的產品邊界。


5. JSON-RPC、stdio、WebSocket只是傳輸方式

App Server是一個 Server / Runtime;它對外暴露的是 Codex App Server Protocol,目前本質上是雙向 JSON-RPC 訊息。

而 JSON-RPC 訊息還需要透過某種傳輸方式送出去。

例如 Backend 收到:

{
  "type": "message",
  "text": "幫我檢查專案"
}

Backend 會先轉成 Codex App Server 的訊息:

{
  "id": 10,
  "method": "turn/start",
  "params": {
    "threadId": "xxx",
    "input": [...]
  }
}

再透過 App Server 支援的 Transport 傳進去。

目前程式裡的 AppServerTransport 定義了:

Transport 適合情境
stdio:// Backend 直接啟動 codex app-server,用 stdin/stdout 溝通
unix:// 同一台 Linux 主機上的不同 Process
ws://IP:PORT 不同 Process、Container 或遠端服務之間的長連線
off 不開放外部 Transport

References


上一篇
Day 9|Codex Agent 架構:OpenAI 怎麼把 Model + Tool 做成一套 Runtime?
下一篇
Day 11|用 Codex 做長期 Agent:Memory 要怎麼設定、調整與多人隔離?
系列文
30天拆Agent:從Repo看設計 共 13 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言