Parcourir la source

feat: 接入 Codex(codex app-server)

引擎
- 新增 CodexProvider:单个常驻 codex app-server(JSON-RPC over stdio),
  按 threadId 分发通知与服务端请求;thread/start、thread/resume(失败则新建)、
  turn/start、turn/interrupt;进程退出时运行中的 Run 失败,下次自动重启
- @openai/codex@0.164.0 作为服务端依赖锁定版本,随部署发布;协议类型由该版本
  `codex app-server generate-ts` 生成(providers/codex/protocol)
- 服务专用 CODEX_HOME(生产 /srv/agent-server/home/.codex,开发 .dev-data),
  不加载个人 ~/.codex 的插件与 MCP;auth.json 列入全局禁止项
- 事件映射:文本与思考增量、命令执行、文件修改、MCP 工具、网页搜索、用量
  (按线程累计换算为本次 Run 的用量);命令标题去掉 bash -lc 包装

安全
- Profile 的文件访问策略转换为 Codex 权限配置,随 thread/start 的 config 动态
  传入(受限模式 ":root" = "deny"),并开放沙箱辅助程序与二进制目录;
  shell 环境只继承基础变量,TMPDIR 指向工作区
- 命令联网请求一律拒绝并审计;文件变更与额外权限申请先按访问策略检查,
  越界自动拒绝;需审批模式下其余请求走审批卡片
- 提问(item/tool/requestUserInput)映射为提问卡片

登录与后台
- 设备码登录(account/login/start chatgptDeviceCode)、退出、账户与模型列表;
  后台概览页新增 Codex 登录对话框,设置页可启用 Codex 并配置模型
- 引擎动态发现的模型参与会话模型校验
- 新增 agent-codex 命令:以 agentsvc 身份运行自带的 Codex
- 提示词按引擎列出可用工具(Codex 暂无文档转换与公式重算)

测试:48 个通过(新增模拟 app-server 的 Codex 适配测试:审批、联网拒绝、
提问、续聊、中断、用量、设备码登录、模型列表;命令标题)
验收:真实 Codex 完成数据分析与中文图表,越权读取、联网、写 /tmp 均被阻止;
服务重启后续聊正常

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Jerry Liang il y a 1 semaine
Parent
commit
007cc9e24a
100 fichiers modifiés avec 1821 ajouts et 13 suppressions
  1. 101 0
      apps/admin/src/components/CodexLoginDialog.vue
  2. 8 0
      apps/admin/src/lib/api.ts
  3. 15 10
      apps/admin/src/views/OverviewView.vue
  4. 11 2
      apps/admin/src/views/SettingsView.vue
  5. 1 0
      apps/server/package.json
  6. 3 1
      apps/server/src/app.ts
  7. 22 0
      apps/server/src/http/routes/admin.ts
  8. 480 0
      apps/server/src/providers/codex/adapter.ts
  9. 36 0
      apps/server/src/providers/codex/cli.ts
  10. 77 0
      apps/server/src/providers/codex/permissions.ts
  11. 14 0
      apps/server/src/providers/codex/protocol/AbsolutePathBuf.ts
  12. 5 0
      apps/server/src/providers/codex/protocol/AgentMessageInputContent.ts
  13. 5 0
      apps/server/src/providers/codex/protocol/AgentPath.ts
  14. 21 0
      apps/server/src/providers/codex/protocol/ApplyPatchApprovalParams.ts
  15. 6 0
      apps/server/src/providers/codex/protocol/ApplyPatchApprovalResponse.ts
  16. 8 0
      apps/server/src/providers/codex/protocol/AuthMode.ts
  17. 9 0
      apps/server/src/providers/codex/protocol/AutoCompactTokenLimitScope.ts
  18. 5 0
      apps/server/src/providers/codex/protocol/ClientInfo.ts
  19. 5 0
      apps/server/src/providers/codex/protocol/ClientNotification.ts
  20. 108 0
      apps/server/src/providers/codex/protocol/ClientRequest.ts
  21. 5 0
      apps/server/src/providers/codex/protocol/CodexResponseHandoffMode.ts
  22. 10 0
      apps/server/src/providers/codex/protocol/CollaborationMode.ts
  23. 9 0
      apps/server/src/providers/codex/protocol/ConfigurationReasoning.ts
  24. 6 0
      apps/server/src/providers/codex/protocol/ContentItem.ts
  25. 5 0
      apps/server/src/providers/codex/protocol/ConversationGitInfo.ts
  26. 8 0
      apps/server/src/providers/codex/protocol/ConversationSummary.ts
  27. 5 0
      apps/server/src/providers/codex/protocol/ConversationTextRole.ts
  28. 16 0
      apps/server/src/providers/codex/protocol/ExecCommandApprovalParams.ts
  29. 6 0
      apps/server/src/providers/codex/protocol/ExecCommandApprovalResponse.ts
  30. 12 0
      apps/server/src/providers/codex/protocol/ExecPolicyAmendment.ts
  31. 5 0
      apps/server/src/providers/codex/protocol/FileChange.ts
  32. 5 0
      apps/server/src/providers/codex/protocol/ForcedLoginMethod.ts
  33. 6 0
      apps/server/src/providers/codex/protocol/FunctionCallOutputBody.ts
  34. 10 0
      apps/server/src/providers/codex/protocol/FunctionCallOutputContentItem.ts
  35. 5 0
      apps/server/src/providers/codex/protocol/FuzzyFileSearchMatchType.ts
  36. 5 0
      apps/server/src/providers/codex/protocol/FuzzyFileSearchParams.ts
  37. 6 0
      apps/server/src/providers/codex/protocol/FuzzyFileSearchResponse.ts
  38. 9 0
      apps/server/src/providers/codex/protocol/FuzzyFileSearchResult.ts
  39. 5 0
      apps/server/src/providers/codex/protocol/FuzzyFileSearchSessionCompletedNotification.ts
  40. 6 0
      apps/server/src/providers/codex/protocol/FuzzyFileSearchSessionUpdatedNotification.ts
  41. 5 0
      apps/server/src/providers/codex/protocol/GetAuthStatusParams.ts
  42. 6 0
      apps/server/src/providers/codex/protocol/GetAuthStatusResponse.ts
  43. 6 0
      apps/server/src/providers/codex/protocol/GetConversationSummaryParams.ts
  44. 6 0
      apps/server/src/providers/codex/protocol/GetConversationSummaryResponse.ts
  45. 5 0
      apps/server/src/providers/codex/protocol/GitDiffToRemoteParams.ts
  46. 6 0
      apps/server/src/providers/codex/protocol/GitDiffToRemoteResponse.ts
  47. 5 0
      apps/server/src/providers/codex/protocol/GitSha.ts
  48. 5 0
      apps/server/src/providers/codex/protocol/ImageDetail.ts
  49. 5 0
      apps/server/src/providers/codex/protocol/ImageGenerationFailure.ts
  50. 13 0
      apps/server/src/providers/codex/protocol/ImageGenerationInputImage.ts
  51. 12 0
      apps/server/src/providers/codex/protocol/ImageGenerationItem.ts
  52. 37 0
      apps/server/src/providers/codex/protocol/InitializeCapabilities.ts
  53. 7 0
      apps/server/src/providers/codex/protocol/InitializeParams.ts
  54. 20 0
      apps/server/src/providers/codex/protocol/InitializeResponse.ts
  55. 8 0
      apps/server/src/providers/codex/protocol/InputModality.ts
  56. 11 0
      apps/server/src/providers/codex/protocol/InternalChatMessageMetadataPassthrough.ts
  57. 5 0
      apps/server/src/providers/codex/protocol/InternalSessionSource.ts
  58. 27 0
      apps/server/src/providers/codex/protocol/LegacyAppPathString.ts
  59. 6 0
      apps/server/src/providers/codex/protocol/LocalShellAction.ts
  60. 5 0
      apps/server/src/providers/codex/protocol/LocalShellExecAction.ts
  61. 5 0
      apps/server/src/providers/codex/protocol/LocalShellStatus.ts
  62. 9 0
      apps/server/src/providers/codex/protocol/McpServerInfo.ts
  63. 11 0
      apps/server/src/providers/codex/protocol/MessagePhase.ts
  64. 8 0
      apps/server/src/providers/codex/protocol/ModeKind.ts
  65. 9 0
      apps/server/src/providers/codex/protocol/MultiAgentMode.ts
  66. 6 0
      apps/server/src/providers/codex/protocol/NetworkPolicyAmendment.ts
  67. 5 0
      apps/server/src/providers/codex/protocol/NetworkPolicyRuleAction.ts
  68. 12 0
      apps/server/src/providers/codex/protocol/ParsedCommand.ts
  69. 32 0
      apps/server/src/providers/codex/protocol/PathUri.ts
  70. 8 0
      apps/server/src/providers/codex/protocol/Personality.ts
  71. 5 0
      apps/server/src/providers/codex/protocol/PlanType.ts
  72. 5 0
      apps/server/src/providers/codex/protocol/RealtimeConversationVersion.ts
  73. 5 0
      apps/server/src/providers/codex/protocol/RealtimeOutputModality.ts
  74. 5 0
      apps/server/src/providers/codex/protocol/RealtimeVoice.ts
  75. 6 0
      apps/server/src/providers/codex/protocol/RealtimeVoicesList.ts
  76. 8 0
      apps/server/src/providers/codex/protocol/ReasoningEffort.ts
  77. 5 0
      apps/server/src/providers/codex/protocol/ReasoningItemContent.ts
  78. 5 0
      apps/server/src/providers/codex/protocol/ReasoningItemReasoningSummary.ts
  79. 10 0
      apps/server/src/providers/codex/protocol/ReasoningSummary.ts
  80. 5 0
      apps/server/src/providers/codex/protocol/RequestId.ts
  81. 9 0
      apps/server/src/providers/codex/protocol/Resource.ts
  82. 17 0
      apps/server/src/providers/codex/protocol/ResourceContent.ts
  83. 9 0
      apps/server/src/providers/codex/protocol/ResourceTemplate.ts
  84. 24 0
      apps/server/src/providers/codex/protocol/ResponseItem.ts
  85. 9 0
      apps/server/src/providers/codex/protocol/ResponseItemId.ts
  86. 10 0
      apps/server/src/providers/codex/protocol/ReviewDecision.ts
  87. 92 0
      apps/server/src/providers/codex/protocol/ServerNotification.ts
  88. 102 0
      apps/server/src/providers/codex/protocol/ServerNotificationEnvelope.ts
  89. 19 0
      apps/server/src/providers/codex/protocol/ServerRequest.ts
  90. 7 0
      apps/server/src/providers/codex/protocol/SessionSource.ts
  91. 9 0
      apps/server/src/providers/codex/protocol/Settings.ts
  92. 8 0
      apps/server/src/providers/codex/protocol/SleepItem.ts
  93. 7 0
      apps/server/src/providers/codex/protocol/SubAgentSource.ts
  94. 10 0
      apps/server/src/providers/codex/protocol/ThreadId.ts
  95. 5 0
      apps/server/src/providers/codex/protocol/ThreadMemoryMode.ts
  96. 9 0
      apps/server/src/providers/codex/protocol/Tool.ts
  97. 8 0
      apps/server/src/providers/codex/protocol/ToolExposureSurface.ts
  98. 9 0
      apps/server/src/providers/codex/protocol/Verbosity.ts
  99. 5 0
      apps/server/src/providers/codex/protocol/WebSearchAction.ts
  100. 5 0
      apps/server/src/providers/codex/protocol/WebSearchContextSize.ts

+ 101 - 0
apps/admin/src/components/CodexLoginDialog.vue

@@ -0,0 +1,101 @@
+<script setup lang="ts">
+import { onBeforeUnmount, onMounted, ref } from 'vue'
+import { admin, errorMessage, type CodexLoginState } from '../lib/api'
+import { toast } from '../lib/ui'
+
+const emit = defineEmits<{ close: [loggedIn: boolean] }>()
+const login = ref<CodexLoginState | null>(null)
+const busy = ref(false)
+const error = ref('')
+let timer: ReturnType<typeof setInterval> | undefined
+
+async function start() {
+  busy.value = true
+  error.value = ''
+  try {
+    login.value = await admin.codexLogin.start()
+    poll()
+  } catch (err) {
+    error.value = errorMessage(err)
+  } finally {
+    busy.value = false
+  }
+}
+
+// 登录在 OpenAI 页面完成,这里轮询结果
+function poll() {
+  clearInterval(timer)
+  timer = setInterval(async () => {
+    const s = await admin.codexLogin.current().catch(() => null)
+    if (!s) return
+    login.value = s
+    if (s.status !== 'pending') clearInterval(timer)
+  }, 2000)
+}
+
+async function copyCode() {
+  if (!login.value) return
+  try {
+    await navigator.clipboard.writeText(login.value.userCode)
+    toast('已复制', 'success')
+  } catch {
+    toast('复制失败,请手动输入', 'error')
+  }
+}
+
+async function close() {
+  clearInterval(timer)
+  if (login.value?.status === 'pending') await admin.codexLogin.cancel().catch(() => {})
+  emit('close', login.value?.status === 'success')
+}
+
+onMounted(start)
+onBeforeUnmount(() => clearInterval(timer))
+</script>
+
+<template>
+  <div class="fixed inset-0 z-40 flex items-center justify-center bg-[var(--app-overlay)]" @click.self="close">
+    <div class="w-[520px] card bg-input-alt p-5 flex flex-col gap-4 shadow-xl text-[12.5px]">
+      <div class="flex items-center gap-2">
+        <span class="codicon codicon-account text-accent text-[16px]" />
+        <span class="font-600 text-[14px] flex-1">登录 Codex(ChatGPT 账号)</span>
+        <button type="button" class="icon-btn" aria-label="关闭" @click="close"><span class="codicon codicon-close" /></button>
+      </div>
+
+      <div v-if="busy" class="hint flex items-center gap-2"><span class="codicon codicon-loading codicon-modifier-spin" />正在获取设备码…</div>
+      <div v-else-if="error" class="text-error">{{ error }}</div>
+
+      <template v-else-if="login && login.status === 'pending'">
+        <ol class="m-0 pl-5 flex flex-col gap-3">
+          <li>
+            打开 OpenAI 登录页:
+            <a :href="login.verificationUrl" target="_blank" rel="noopener noreferrer" class="btn-primary ml-2 no-underline"><span class="codicon codicon-link-external" />打开登录页</a>
+          </li>
+          <li>
+            登录 ChatGPT 账号后,输入下面的设备码:
+            <div class="flex items-center gap-2 mt-2">
+              <code class="font-mono text-[22px] tracking-widest px-3 py-1 rounded-md bg-code select-all">{{ login.userCode }}</code>
+              <button type="button" class="icon-btn" title="复制" @click="copyCode"><span class="codicon codicon-copy" /></button>
+            </div>
+          </li>
+        </ol>
+        <div class="flex items-center gap-2 hint"><span class="codicon codicon-loading codicon-modifier-spin" />等待你在 OpenAI 页面完成登录…</div>
+        <div class="hint">登录由 Codex 官方流程完成,AgentServer 不接触你的密码和凭证。</div>
+      </template>
+
+      <template v-else-if="login?.status === 'success'">
+        <div class="flex items-center gap-2 text-success"><span class="codicon codicon-pass-filled text-[16px]" />登录成功</div>
+        <div class="hint">如果还没有启用 Codex,请到"设置 → 引擎"中勾选启用。</div>
+        <div class="flex justify-end"><button type="button" class="btn-primary" @click="close">完成</button></div>
+      </template>
+
+      <template v-else-if="login">
+        <div class="text-error">登录{{ login.status === 'cancelled' ? '已取消' : '失败' }}{{ login.error ? `:${login.error}` : '' }}</div>
+        <div class="flex justify-end gap-2">
+          <button type="button" class="btn-secondary" @click="close">关闭</button>
+          <button type="button" class="btn-primary" @click="start">重试</button>
+        </div>
+      </template>
+    </div>
+  </div>
+</template>

+ 8 - 0
apps/admin/src/lib/api.ts

@@ -23,6 +23,12 @@ export const admin = {
     cancel: (id: string) => req<void>('DELETE', `/providers/claude/login/${id}`),
     logout: () => req<{ ok: true }>('POST', '/providers/claude/logout'),
   },
+  codexLogin: {
+    current: () => req<CodexLoginState | null>('GET', '/providers/codex/login'),
+    start: () => req<CodexLoginState>('POST', '/providers/codex/login'),
+    cancel: () => req<void>('DELETE', '/providers/codex/login'),
+    logout: () => req<{ ok: true }>('POST', '/providers/codex/logout'),
+  },
   overview: () => req<{ providers: ProviderStatusDto[]; runs: { running: number; queued: number; active: number }; version: string }>('GET', '/overview'),
   settings: () => req<{ settings: AppSettings; defaults: AppSettings; builtinGlobalDeny: string[] }>('GET', '/settings'),
   updateSettings: (patch: DeepPartial<AppSettings>) => req<{ settings: AppSettings }>('PATCH', '/settings', patch),
@@ -50,4 +56,6 @@ export const admin = {
   },
 }
 
+export interface CodexLoginState { loginId: string; verificationUrl: string; userCode: string; status: 'pending' | 'success' | 'failed' | 'cancelled'; error: string | null }
+
 export const errorMessage = (err: unknown) => (err instanceof Error ? err.message : String(err))

+ 15 - 10
apps/admin/src/views/OverviewView.vue

@@ -2,6 +2,7 @@
 import { onMounted, ref } from 'vue'
 import type { ProviderStatusDto } from '@agent-server/shared'
 import ClaudeLoginDialog from '../components/ClaudeLoginDialog.vue'
+import CodexLoginDialog from '../components/CodexLoginDialog.vue'
 import PageHeader from '../components/PageHeader.vue'
 import { admin, errorMessage } from '../lib/api'
 import { toast } from '../lib/ui'
@@ -10,13 +11,15 @@ const providers = ref<ProviderStatusDto[]>([])
 const runs = ref({ running: 0, queued: 0, active: 0 })
 const loading = ref(false)
 const showLogin = ref(false)
+const showCodexLogin = ref(false)
 const loggingOut = ref(false)
 
-async function logout() {
-  if (!confirm('退出 Claude 登录?退出后 AgentServer 将无法运行 Claude,直到重新登录。')) return
+async function logout(provider: 'claude' | 'codex' = 'claude') {
+  const name = provider === 'claude' ? 'Claude' : 'Codex'
+  if (!confirm(`退出 ${name} 登录?退出后 AgentServer 将无法运行 ${name},直到重新登录。`)) return
   loggingOut.value = true
   try {
-    await admin.claudeLogin.logout()
+    await (provider === 'claude' ? admin.claudeLogin.logout() : admin.codexLogin.logout())
     toast('已退出登录', 'success')
     await load()
   } catch (err) {
@@ -26,9 +29,10 @@ async function logout() {
   }
 }
 
-function onLoginClosed(loggedIn: boolean) {
+function onLoginClosed(loggedIn: boolean, name = 'Claude') {
   showLogin.value = false
-  if (loggedIn) toast('Claude 登录成功', 'success')
+  showCodexLogin.value = false
+  if (loggedIn) toast(`${name} 登录成功`, 'success')
   void load()
 }
 
@@ -70,11 +74,11 @@ onMounted(load)
                 {{ p.available ? '可用' : p.enabled ? '不可用' : '未启用' }}
               </span>
               <span class="flex-1" />
-              <template v-if="p.id === 'claude' && p.loggedIn !== null">
-                <button v-if="!p.loggedIn" type="button" class="btn-primary" @click="showLogin = true"><span class="codicon codicon-account" />登录</button>
+              <template v-if="p.loggedIn !== null">
+                <button v-if="!p.loggedIn" type="button" class="btn-primary" @click="p.id === 'claude' ? (showLogin = true) : (showCodexLogin = true)"><span class="codicon codicon-account" />登录</button>
                 <template v-else>
-                  <button type="button" class="btn-ghost" @click="showLogin = true">重新登录</button>
-                  <button type="button" class="btn-ghost" :disabled="loggingOut" @click="logout">退出登录</button>
+                  <button type="button" class="btn-ghost" @click="p.id === 'claude' ? (showLogin = true) : (showCodexLogin = true)">重新登录</button>
+                  <button type="button" class="btn-ghost" :disabled="loggingOut" @click="logout(p.id)">退出登录</button>
                 </template>
               </template>
             </div>
@@ -85,9 +89,10 @@ onMounted(load)
             <template v-if="p.message"><span class="text-fg-muted">说明</span><span class="text-warning">{{ p.message }}</span></template>
           </div>
         </div>
-        <div class="hint">服务以专用账号 agentsvc 运行,登录的是你自己的 Claude 账号。可以点上方的"登录"按钮在后台完成,也可以在服务器终端执行 <code class="font-mono">agent-claude auth login</code>。无需重启服务。</div>
+        <div class="hint">服务以专用账号 agentsvc 运行,登录的是你自己的账号:Claude 粘贴授权码完成,Codex 用设备码完成(在 OpenAI 页面输入短码)。也可以在服务器终端执行 <code class="font-mono">agent-claude auth login</code> / <code class="font-mono">agent-codex login --device-auth</code>。无需重启服务。</div>
       </section>
     </div>
     <ClaudeLoginDialog v-if="showLogin" @close="onLoginClosed" />
+    <CodexLoginDialog v-if="showCodexLogin" @close="ok => onLoginClosed(ok, 'Codex')" />
   </div>
 </template>

+ 11 - 2
apps/admin/src/views/SettingsView.vue

@@ -152,8 +152,17 @@ const LABELS: Record<(typeof PATH_KEYS)[number], string> = {
               </div>
             </div>
           </div>
-          <div class="card p-4 flex items-center gap-2 opacity-70">
-            <span class="font-600 flex-1">Codex</span><span class="hint">M2 接入(app-server),当前不可启用</span>
+          <div class="card p-4 flex flex-col gap-3">
+            <div class="flex items-center gap-2"><span class="font-600 flex-1">Codex</span><label class="flex items-center gap-1.5 cursor-pointer"><input v-model="draft.providers.codex.enabled" type="checkbox">启用</label></div>
+            <div><label class="label" for="cx-path">CLI 路径</label><input id="cx-path" v-model="draft.providers.codex.cliPath" class="field font-mono"></div>
+            <div class="grid grid-cols-2 gap-3">
+              <div><label class="label" for="cx-def">默认模型(留空则用账号可用的第一个模型)</label><input id="cx-def" v-model="draft.providers.codex.defaultModel" class="field"></div>
+              <div>
+                <label class="label" for="cx-models">可选模型(逗号分隔;留空则自动使用账号可用的模型)</label>
+                <input id="cx-models" class="field" :value="draft.providers.codex.models.join(', ')" @change="draft.providers.codex.models = ($event.target as HTMLInputElement).value.split(',').map(s => s.trim()).filter(Boolean)">
+              </div>
+            </div>
+            <div class="hint">Codex 使用服务专用的配置目录,不会加载你个人 ~/.codex 中的插件和 MCP。登录在"概览"页完成。</div>
           </div>
         </section>
 

+ 1 - 0
apps/server/package.json

@@ -17,6 +17,7 @@
     "@fastify/cookie": "^11.1.3",
     "@fastify/multipart": "^10.1.5",
     "@fastify/static": "^10.1.7",
+    "@openai/codex": "0.164.0",
     "better-sqlite3": "^13.0.3",
     "drizzle-orm": "^0.45.4",
     "fastify": "^5.12.7",

+ 3 - 1
apps/server/src/app.ts

@@ -3,6 +3,7 @@ import type { ProviderId } from '@agent-server/shared'
 import { config, dbPath } from './config'
 import { createDb, migrateDb, type DB } from './db'
 import { ClaudeProvider } from './providers/claude/adapter'
+import { CodexProvider } from './providers/codex/adapter'
 import type { AgentProvider } from './providers/types'
 import { AdminAuthService } from './services/admin-auth'
 import { ApiKeyService, AuditService, KnowledgeService, ProfileService } from './services/catalog'
@@ -38,7 +39,8 @@ export function createApp(log: AppLogger, opts: { db?: DB; providers?: Partial<R
   const adminAuth = new AdminAuthService(db, log, config.adminPassword)
   const plugins = new PluginService(settings, audit)
   const claudeLogin = new ClaudeLoginManager(() => settings.get().providers.claude.cliPath, summary => audit.log({ actor: 'admin', kind: 'admin', summary }))
-  const providers: Partial<Record<ProviderId, AgentProvider>> = opts.providers ?? { claude: new ClaudeProvider() }
+  const providers: Partial<Record<ProviderId, AgentProvider>> = opts.providers ?? { claude: new ClaudeProvider(), codex: new CodexProvider(log) }
+  sessions.extraModels = p => providers[p]?.knownModels?.() ?? []
   const runs = new RunManager({ db, log, settings, sessions, profiles, knowledge, files, events, interactions, audit, plugins, providers })
 
   return { db, log, settings, events, files, profiles, knowledge, apiKeys, audit, interactions, sessions, adminAuth, plugins, claudeLogin, providers, runs }

+ 22 - 0
apps/server/src/http/routes/admin.ts

@@ -5,6 +5,7 @@ import { BUILTIN_GLOBAL_DENY, type AppSettings, type DeepPartial, type FileAcces
 import type { App } from '../../app'
 import { AppError } from '../../lib/errors'
 import { runClaudeCli } from '../../providers/claude/cli'
+import { CodexProvider } from '../../providers/codex/adapter'
 import { checkKnowledgePath, checkPathBasic, resolveAccess, toResolvedDto } from '../../services/access-policy'
 import { parse, type AuditKind, type KnowledgeInput } from '../../services/catalog'
 import { defaultSettings } from '../../services/settings'
@@ -81,6 +82,27 @@ export default async function adminRoutes(f: FastifyInstance, { app }: { app: Ap
       app.claudeLogin.cancel(req.params.id)
       reply.code(204)
     })
+    // Codex 登录:设备码方式,在 OpenAI 官方页面输入短码完成
+    const codex = () => {
+      const p = app.providers.codex
+      if (!(p instanceof CodexProvider)) throw new AppError(400, 'provider_unavailable', 'Codex 未接入')
+      return p
+    }
+    g.get('/providers/codex/login', async () => codex().loginState())
+    g.post('/providers/codex/login', async () => {
+      const r = await codex().startLogin(app.settings.get())
+      app.audit.log({ actor: 'admin', kind: 'admin', summary: '后台发起 Codex 登录(设备码)' })
+      return r
+    })
+    g.delete('/providers/codex/login', async (_req, reply) => {
+      await codex().cancelLogin(app.settings.get())
+      reply.code(204)
+    })
+    g.post('/providers/codex/logout', async () => {
+      await codex().logout(app.settings.get())
+      app.audit.log({ actor: 'admin', kind: 'admin', summary: 'Codex 退出登录' })
+      return { ok: true }
+    })
     g.post('/providers/claude/logout', async () => {
       const r = await runClaudeCli(app.settings.get().providers.claude.cliPath, ['auth', 'logout'], { timeoutMs: 30_000 })
       app.audit.log({ actor: 'admin', kind: 'admin', summary: `Claude 退出登录${r.code === 0 ? '' : '失败'}` })

+ 480 - 0
apps/server/src/providers/codex/adapter.ts

@@ -0,0 +1,480 @@
+// Codex 适配器:codex app-server(JSON-RPC)→ 统一事件(docs/design/03-providers.md §3,ADR-0003)
+import fs from 'node:fs'
+import path from 'node:path'
+import type { AppSettings, ProviderStatusDto, UsagePayload } from '@agent-server/shared'
+import { checkAccess } from '../../services/access-policy'
+import { ProviderRunError, type AgentProvider, type RunContext, type RunResult } from '../types'
+import { bundledCodexPath, codexHome } from './cli'
+import { buildThreadConfig } from './permissions'
+import type {
+  AskForApproval, CommandExecutionRequestApprovalParams, FileChangeRequestApprovalParams, GetAccountResponse,
+  ItemCompletedNotification, ItemStartedNotification, LoginAccountResponse, ModelListResponse,
+  PermissionsRequestApprovalParams, ThreadItem, ThreadStartResponse, ThreadTokenUsageUpdatedNotification,
+  ToolRequestUserInputParams, TurnCompletedNotification, TurnStartResponse,
+} from './protocol/v2'
+import { AppServerClient, RpcRequestError } from './rpc'
+
+const MAX_TOOL_OUTPUT = 64 * 1024
+const CLIENT_INFO = { name: 'agent_server', title: 'AgentServer', version: '0.1.0' }
+
+interface Logger { info(o: object, m?: string): void; warn(o: object, m?: string): void; error(o: object, m?: string): void }
+
+export interface CodexLoginState {
+  loginId: string
+  verificationUrl: string
+  userCode: string
+  status: 'pending' | 'success' | 'failed' | 'cancelled'
+  error: string | null
+}
+
+export class CodexProvider implements AgentProvider {
+  readonly id = 'codex' as const
+  private client: AppServerClient | null = null
+  private starting: Promise<AppServerClient> | null = null
+  private clientKey = ''
+  private runs = new Map<string, ThreadRun>()
+  private models: string[] = []
+  private login: CodexLoginState | null = null
+  private version: string | null = null
+
+  constructor(private log: Logger) {}
+
+  knownModels(): string[] {
+    return this.models
+  }
+
+  private bin(settings: AppSettings) {
+    return settings.providers.codex.cliPath
+  }
+
+  /** 启动(或复用)app-server;CLI 路径或配置覆盖变化时重启 */
+  private async ensure(settings: AppSettings): Promise<AppServerClient> {
+    const s = settings.providers.codex
+    const key = JSON.stringify([s.cliPath, s.configOverrides])
+    if (this.client?.alive && this.clientKey === key) return this.client
+    if (this.starting && this.clientKey === key) return this.starting
+    this.client?.stop()
+    this.clientKey = key
+    this.starting = (async () => {
+      const home = codexHome()
+      fs.mkdirSync(home, { recursive: true, mode: 0o700 })
+      const env: NodeJS.ProcessEnv = { ...process.env, CODEX_HOME: home }
+      delete env.OPENAI_API_KEY
+      delete env.CODEX_API_KEY
+      const client = new AppServerClient({ bin: s.cliPath, env, args: s.configOverrides.flatMap(o => ['-c', o]) })
+      client.on('notification', (method, params) => this.onNotification(method, params))
+      client.onServerRequest = (method, params) => this.onServerRequest(method, params)
+      client.on('stderr', line => {
+        if (!/WARN|INFO|DEBUG/.test(line)) this.log.warn({ stderr: line.trim().slice(0, 500) }, 'codex stderr')
+      })
+      client.on('exit', code => {
+        this.log.warn({ code }, 'codex app-server 已退出')
+        if (this.client === client) this.client = null
+        for (const run of this.runs.values()) run.fail(new ProviderRunError('codex_exited', `codex app-server 意外退出(${code})`))
+      })
+      const init = await client.start(CLIENT_INFO) as { userAgent?: string }
+      // userAgent 形如 "agent_server/0.164.0 (...)",斜杠后是 codex 版本
+      this.version = /\/([\d.]+)/.exec(init.userAgent ?? '')?.[1] ?? null
+      this.client = client
+      return client
+    })()
+    try {
+      return await this.starting
+    } finally {
+      this.starting = null
+    }
+  }
+
+  async run(ctx: RunContext): Promise<RunResult> {
+    const client = await this.ensure(ctx.settings)
+    fs.mkdirSync(path.join(ctx.workspaceDir, '.agent', 'tmp'), { recursive: true })
+    const config = buildThreadConfig({
+      access: ctx.access, permissionMode: ctx.permissionMode, settings: ctx.settings,
+      codexBin: this.bin(ctx.settings), codexHome: codexHome(),
+    })
+    // 需审批:沙箱内的命令自动执行,越过沙箱边界时请求审批;自动/只读:从不请求,越界直接失败
+    const approvalPolicy: AskForApproval = ctx.permissionMode === 'ask' ? 'on-request' : 'never'
+    const threadParams = {
+      cwd: ctx.workspaceDir, model: ctx.model, approvalPolicy, config,
+      developerInstructions: ctx.systemPromptAppend,
+    }
+
+    let threadId = ctx.providerSessionId
+    if (threadId) {
+      try {
+        await client.request('thread/resume', { ...threadParams, threadId, excludeTurns: true })
+      } catch (err) {
+        ctx.log.warn({ err: (err as Error).message, threadId }, 'codex 会话恢复失败,以新会话继续')
+        threadId = null
+      }
+    }
+    if (!threadId) {
+      const r = await client.request<ThreadStartResponse>('thread/start', { ...threadParams, serviceName: 'agent-server' })
+      threadId = r.thread.id
+      ctx.setProviderSessionId(threadId)
+    }
+
+    const run = new ThreadRun(ctx, threadId)
+    this.runs.set(threadId, run)
+    const onAbort = () => {
+      if (run.turnId) void client.request('turn/interrupt', { threadId, turnId: run.turnId }).catch(() => {})
+      run.fail(new ProviderRunError('interrupted', '已中断'))
+    }
+    ctx.signal.addEventListener('abort', onAbort, { once: true })
+    try {
+      const r = await client.request<TurnStartResponse>('turn/start', { threadId, model: ctx.model, input: buildInput(ctx) })
+      run.turnId = r.turn.id
+      return await run.done
+    } finally {
+      ctx.signal.removeEventListener('abort', onAbort)
+      this.runs.delete(threadId)
+    }
+  }
+
+  private onNotification(method: string, params: unknown) {
+    if (method === 'account/login/completed') {
+      const p = params as { loginId: string | null; success: boolean; error: string | null }
+      if (this.login && (!p.loginId || p.loginId === this.login.loginId)) {
+        Object.assign(this.login, { status: p.success ? 'success' : 'failed', error: p.error })
+      }
+      return
+    }
+    const threadId = (params as { threadId?: string } | null)?.threadId
+    if (threadId) this.runs.get(threadId)?.onNotification(method, params)
+  }
+
+  private async onServerRequest(method: string, params: unknown): Promise<unknown> {
+    const threadId = (params as { threadId?: string } | null)?.threadId
+    const run = threadId ? this.runs.get(threadId) : undefined
+    if (!run) throw new Error(`没有对应的运行:${method}`)
+    return run.onServerRequest(method, params)
+  }
+
+  // ---- 账户与登录(设备码方式:在 OpenAI 官方页面输入短码完成登录,AgentServer 不接触凭证) ----
+
+  async startLogin(settings: AppSettings): Promise<CodexLoginState> {
+    const client = await this.ensure(settings)
+    if (this.login?.status === 'pending') await this.cancelLogin(settings).catch(() => {})
+    const r = await client.request<LoginAccountResponse>('account/login/start', { type: 'chatgptDeviceCode' })
+    if (r.type !== 'chatgptDeviceCode') throw new Error('codex 没有返回设备码')
+    this.login = { loginId: r.loginId, verificationUrl: r.verificationUrl, userCode: r.userCode, status: 'pending', error: null }
+    return this.login
+  }
+
+  loginState(): CodexLoginState | null {
+    return this.login
+  }
+
+  async cancelLogin(settings: AppSettings) {
+    if (!this.login || this.login.status !== 'pending') return
+    const client = await this.ensure(settings)
+    await client.request('account/login/cancel', { loginId: this.login.loginId }).catch(() => {})
+    this.login.status = 'cancelled'
+  }
+
+  async logout(settings: AppSettings) {
+    const client = await this.ensure(settings)
+    await client.request('account/logout', {})
+  }
+
+  async status(settings: AppSettings): Promise<ProviderStatusDto> {
+    const s = settings.providers.codex
+    const base: ProviderStatusDto = {
+      id: 'codex', enabled: s.enabled, available: false, loggedIn: null, account: null,
+      cliPath: s.cliPath, version: null, models: s.models, defaultModel: s.defaultModel, message: null,
+    }
+    try {
+      const client = await this.ensure(settings)
+      const acc = await client.request<GetAccountResponse>('account/read', {}, 30_000)
+      const loggedIn = !!acc.account
+      let models = s.models
+      if (loggedIn) {
+        const list = await client.request<ModelListResponse>('model/list', {}, 30_000).catch(() => null)
+        if (list) {
+          this.models = list.data.filter(m => !m.hidden).map(m => m.model)
+          if (!models.length) models = this.models
+        }
+      }
+      const account = acc.account?.type === 'chatgpt'
+        ? [acc.account.email, `(${acc.account.planType})`].filter(Boolean).join(' ')
+        : acc.account?.type ?? null
+      return {
+        ...base, version: this.version, loggedIn, account, models,
+        defaultModel: s.defaultModel || models[0] || '',
+        available: s.enabled && loggedIn,
+        message: loggedIn ? (s.enabled ? null : '已登录,尚未在"设置 → 引擎"中启用') : '未登录:点击"登录"按钮,用 ChatGPT 账号完成设备码登录',
+      }
+    } catch (err) {
+      return { ...base, message: `无法启动 codex app-server:${(err as Error).message}` }
+    }
+  }
+}
+
+export function defaultCodexPath() {
+  return bundledCodexPath()
+}
+
+function buildInput(ctx: RunContext) {
+  let text = ctx.input.text
+  if (ctx.input.attachments.length) {
+    const list = ctx.input.attachments.map(a => `- ${a.path}(${a.mime})`).join('\n')
+    text += `\n\n<attachments>\n用户上传了以下文件,位于工作区中:\n${list}\n</attachments>`
+  }
+  const images = ctx.input.attachments
+    .filter(a => ctx.input.images.some(i => i.name === a.name))
+    .map(a => ({ type: 'localImage' as const, path: path.join(ctx.workspaceDir, a.path) }))
+  return [{ type: 'text' as const, text, text_elements: [] }, ...images]
+}
+
+/** 一次 Run 对应的一个 turn:把通知映射为统一事件,处理审批与提问 */
+class ThreadRun {
+  turnId: string | null = null
+  readonly done: Promise<RunResult>
+  private resolve!: (r: RunResult) => void
+  private reject!: (e: Error) => void
+  private settled = false
+  private items = new Map<string, ThreadItem>()
+  private denied = new Set<string>()
+  private usageBase: UsagePayload | null = null
+  private usageLatest: UsagePayload | null = null
+
+  constructor(private ctx: RunContext, private threadId: string) {
+    this.done = new Promise((res, rej) => {
+      this.resolve = res
+      this.reject = rej
+    })
+  }
+
+  fail(err: Error) {
+    if (this.settled) return
+    this.settled = true
+    this.reject(err)
+  }
+
+  onNotification(method: string, params: unknown) {
+    const emit = this.ctx.emit
+    switch (method) {
+      case 'item/started': {
+        const { item } = params as ItemStartedNotification
+        this.items.set(item.id, item)
+        const tool = describeTool(item, this.ctx.workspaceDir)
+        if (tool) emit('tool.started', { toolCallId: item.id, ...tool })
+        if (item.type === 'commandExecution') this.ctx.audit('command', item.command.slice(0, 500), { itemId: item.id })
+        break
+      }
+      case 'item/agentMessage/delta': {
+        const p = params as { itemId: string; delta: string }
+        emit('message.delta', { messageId: p.itemId, text: p.delta })
+        break
+      }
+      case 'item/reasoning/summaryTextDelta': {
+        const p = params as { itemId: string; delta: string }
+        emit('reasoning.delta', { blockId: p.itemId, text: p.delta })
+        break
+      }
+      case 'item/completed': {
+        const { item } = params as ItemCompletedNotification
+        this.items.set(item.id, item)
+        this.onItemCompleted(item)
+        break
+      }
+      case 'thread/tokenUsage/updated': {
+        const { tokenUsage } = params as ThreadTokenUsageUpdatedNotification
+        const toUsage = (b: typeof tokenUsage.total): UsagePayload => ({
+          inputTokens: b.inputTokens - b.cachedInputTokens, outputTokens: b.outputTokens,
+          cacheReadTokens: b.cachedInputTokens, cacheWriteTokens: b.cacheWriteInputTokens,
+        })
+        // 本次 Run 的用量 = 最新累计 −(第一次通知时的累计 − 那一次的增量)
+        if (!this.usageBase) {
+          const t = toUsage(tokenUsage.total), l = toUsage(tokenUsage.last)
+          this.usageBase = {
+            inputTokens: t.inputTokens - l.inputTokens, outputTokens: t.outputTokens - l.outputTokens,
+            cacheReadTokens: t.cacheReadTokens - l.cacheReadTokens, cacheWriteTokens: t.cacheWriteTokens - l.cacheWriteTokens,
+          }
+        }
+        this.usageLatest = toUsage(tokenUsage.total)
+        break
+      }
+      case 'turn/completed': {
+        const { turn } = params as TurnCompletedNotification
+        if (this.settled) return
+        if (turn.status === 'completed') {
+          this.settled = true
+          this.resolve({ usage: this.runUsage() })
+        } else if (turn.status === 'interrupted') {
+          this.fail(new ProviderRunError('interrupted', '已中断'))
+        } else {
+          this.fail(new ProviderRunError('codex_failed', turn.error?.message ?? '运行失败'))
+        }
+        break
+      }
+      case 'error': {
+        const p = params as { error: { message: string }; willRetry: boolean }
+        this.ctx.log.warn({ error: p.error.message, willRetry: p.willRetry }, 'codex error')
+        break
+      }
+    }
+  }
+
+  private runUsage(): UsagePayload | null {
+    if (!this.usageLatest || !this.usageBase) return null
+    const b = this.usageBase, l = this.usageLatest
+    return {
+      inputTokens: l.inputTokens - b.inputTokens, outputTokens: l.outputTokens - b.outputTokens,
+      cacheReadTokens: l.cacheReadTokens - b.cacheReadTokens, cacheWriteTokens: l.cacheWriteTokens - b.cacheWriteTokens,
+    }
+  }
+
+  private onItemCompleted(item: ThreadItem) {
+    const emit = this.ctx.emit
+    const done = (status: 'ok' | 'error' | 'denied', output: string) => emit('tool.completed', {
+      toolCallId: item.id, status: this.denied.has(item.id) ? 'denied' : status,
+      output: output.slice(0, MAX_TOOL_OUTPUT), truncated: output.length > MAX_TOOL_OUTPUT,
+    })
+    switch (item.type) {
+      case 'agentMessage':
+        if (item.text) emit('message.completed', { messageId: item.id, text: item.text })
+        break
+      case 'reasoning': {
+        const text = item.summary.join('\n').trim()
+        if (text) emit('reasoning.completed', { blockId: item.id, text })
+        break
+      }
+      case 'commandExecution':
+        done(item.status === 'declined' ? 'denied' : item.status === 'completed' && (item.exitCode ?? 0) === 0 ? 'ok' : 'error',
+          `${item.aggregatedOutput ?? ''}${item.exitCode ? `\n(exit ${item.exitCode})` : ''}`.trim())
+        break
+      case 'fileChange':
+        done(item.status === 'declined' ? 'denied' : item.status === 'completed' ? 'ok' : 'error',
+          item.changes.map(c => `${c.kind} ${c.path}`).join('\n'))
+        break
+      case 'mcpToolCall':
+        done(item.status === 'completed' ? 'ok' : 'error',
+          item.error ? JSON.stringify(item.error) : (item.result?.content ?? []).map(c => (c as { text?: string }).text ?? '').join('\n'))
+        break
+      case 'webSearch':
+      case 'imageView':
+        done('ok', '')
+        break
+    }
+  }
+
+  async onServerRequest(method: string, params: unknown): Promise<unknown> {
+    const ctx = this.ctx
+    switch (method) {
+      case 'item/commandExecution/requestApproval': {
+        const p = params as CommandExecutionRequestApprovalParams
+        // 网络访问不放开:Codex 的命令一旦联网就是无限制的(可访问内网)
+        if (p.networkApprovalContext) {
+          this.denied.add(p.itemId)
+          ctx.audit('network_denied', `拒绝命令联网:${p.command ?? ''}`.slice(0, 500), { networkApprovalContext: p.networkApprovalContext })
+          return { decision: 'decline' }
+        }
+        if (ctx.permissionMode === 'readonly') return { decision: 'decline' }
+        const command = p.command ?? (this.items.get(p.itemId) as { command?: string } | undefined)?.command ?? ''
+        const outcome = await ctx.interact({
+          kind: 'approval', tool: 'Bash', title: commandTitle(command) || '执行命令',
+          input: { command, cwd: p.cwd }, detail: { command, reason: p.reason ?? undefined },
+          options: ['allow_once', 'allow_session', 'deny'],
+        })
+        ctx.audit('approval', `codex 命令:${outcome.resolution}`, { command })
+        if (outcome.resolution === 'allowed') return { decision: outcome.scope === 'session' ? 'acceptForSession' : 'accept' }
+        this.denied.add(p.itemId)
+        return { decision: outcome.resolution === 'denied' ? 'decline' : 'cancel' }
+      }
+      case 'item/fileChange/requestApproval': {
+        const p = params as FileChangeRequestApprovalParams
+        const item = this.items.get(p.itemId)
+        const paths = item?.type === 'fileChange' ? item.changes.map(c => path.resolve(ctx.workspaceDir, c.path)) : []
+        if (p.grantRoot) paths.push(path.resolve(ctx.workspaceDir, p.grantRoot))
+        const blocked = paths.map(f => ({ f, d: checkAccess(ctx.access, f, 'write') })).find(x => !x.d.allowed)
+        if (ctx.permissionMode === 'readonly' || blocked || !paths.length) {
+          this.denied.add(p.itemId)
+          if (blocked && !blocked.d.allowed) ctx.audit('file_access_denied', blocked.d.reason, { tool: 'codex fileChange', paths })
+          return { decision: 'decline' }
+        }
+        const outcome = await ctx.interact({
+          kind: 'approval', tool: 'Edit', title: paths.map(f => path.relative(ctx.workspaceDir, f)).join('、'),
+          input: { paths }, detail: { path: paths[0], reason: p.reason ?? undefined }, options: ['allow_once', 'allow_session', 'deny'],
+        })
+        if (outcome.resolution === 'allowed') return { decision: outcome.scope === 'session' ? 'acceptForSession' : 'accept' }
+        this.denied.add(p.itemId)
+        return { decision: outcome.resolution === 'denied' ? 'decline' : 'cancel' }
+      }
+      case 'item/permissions/requestApproval': {
+        // 申请额外权限:联网一律拒绝;文件路径必须在 Profile 的访问策略之内,然后由人审批
+        const p = params as PermissionsRequestApprovalParams
+        const reqFs = p.permissions.fileSystem
+        const reads = (reqFs?.read ?? []).map(f => path.resolve(p.cwd, f))
+        const writes = (reqFs?.write ?? []).map(f => path.resolve(p.cwd, f))
+        const outside = [...reads.map(f => checkAccess(ctx.access, f, 'read')), ...writes.map(f => checkAccess(ctx.access, f, 'write'))].find(d => !d.allowed)
+        const wantsNetwork = !!p.permissions.network?.enabled
+        if (wantsNetwork) ctx.audit('network_denied', '拒绝 codex 申请联网权限', { reason: p.reason })
+        if (outside && !outside.allowed) ctx.audit('file_access_denied', outside.reason, { reads, writes })
+        if (wantsNetwork || outside || (!reads.length && !writes.length) || ctx.permissionMode !== 'ask') {
+          return { permissions: {}, scope: 'turn' }
+        }
+        const outcome = await ctx.interact({
+          kind: 'approval', tool: 'Permissions', title: `申请访问:${[...reads, ...writes].join('、')}`.slice(0, 300),
+          input: p.permissions, detail: { reason: p.reason ?? undefined }, options: ['allow_once', 'allow_session', 'deny'],
+        })
+        if (outcome.resolution !== 'allowed') return { permissions: {}, scope: 'turn' }
+        return { permissions: { fileSystem: { read: reads, write: writes } }, scope: outcome.scope === 'session' ? 'session' : 'turn' }
+      }
+      case 'item/tool/requestUserInput': {
+        const p = params as ToolRequestUserInputParams
+        const outcome = await ctx.interact({
+          kind: 'question',
+          questions: p.questions.map(q => ({
+            id: q.id, question: q.question, header: q.header, multiSelect: false,
+            options: (q.options ?? []).map(o => ({ label: o.label, description: o.description })),
+          })),
+        })
+        const answers: Record<string, { answers: string[] }> = {}
+        if (outcome.resolution === 'answered') {
+          for (const q of p.questions) {
+            const a = outcome.answers[q.id]
+            if (a !== undefined) answers[q.id] = { answers: Array.isArray(a) ? a : [a] }
+          }
+        }
+        return { answers }
+      }
+      case 'mcpServer/elicitation/request':
+        return { action: 'decline', content: null, _meta: null }
+      default:
+        throw new RpcRequestError(method, { code: -32601, message: `AgentServer 不支持:${method}` })
+    }
+  }
+}
+
+/** Codex 把命令包成 `/usr/bin/bash -lc "..."`,显示时去掉外层,只保留第一行 */
+export function commandTitle(command: string): string {
+  let c = command.trim()
+  const m = /^(?:\/usr)?(?:\/bin\/)?(?:ba|z)?sh\s+-l?c\s+([\s\S]*)$/.exec(c)
+  if (m) {
+    c = m[1]!.trim()
+    if ((c.startsWith('"') && c.endsWith('"')) || (c.startsWith("'") && c.endsWith("'"))) c = c.slice(1, -1)
+  }
+  return c.split('\n')[0]!.slice(0, 200)
+}
+
+function describeTool(item: ThreadItem, cwd: string): { name: string; title: string; input: unknown } | null {
+  const rel = (p: string) => {
+    const r = path.relative(cwd, path.resolve(cwd, p))
+    return r.startsWith('..') ? p : r
+  }
+  switch (item.type) {
+    case 'commandExecution':
+      return { name: 'Bash', title: commandTitle(item.command), input: { command: item.command, cwd: item.cwd } }
+    case 'fileChange':
+      return { name: 'Edit', title: item.changes.map(c => rel(c.path)).join('、').slice(0, 200), input: { changes: item.changes.map(c => ({ path: c.path, kind: c.kind, diff: c.diff })) } }
+    case 'mcpToolCall':
+      return { name: `mcp__${item.server}__${item.tool}`, title: '', input: item.arguments }
+    case 'webSearch':
+      return { name: 'WebSearch', title: (item as { query?: string }).query ?? '', input: { query: (item as { query?: string }).query } }
+    case 'imageView':
+      return { name: 'Read', title: rel(item.path), input: { path: item.path } }
+    default:
+      return null
+  }
+}

+ 36 - 0
apps/server/src/providers/codex/cli.ts

@@ -0,0 +1,36 @@
+// 定位随服务锁定版本安装的 Codex CLI(@openai/codex 的平台包),以及服务专用的 CODEX_HOME
+import fs from 'node:fs'
+import { createRequire } from 'node:module'
+import path from 'node:path'
+import { config } from '../../config'
+
+const require = createRequire(import.meta.url)
+
+const TRIPLES: Record<string, string> = {
+  'linux-x64': 'x86_64-unknown-linux-musl',
+  'linux-arm64': 'aarch64-unknown-linux-musl',
+  'darwin-x64': 'x86_64-apple-darwin',
+  'darwin-arm64': 'aarch64-apple-darwin',
+}
+
+export function bundledCodexPath(): string | null {
+  const key = `${process.platform}-${process.arch}`
+  const triple = TRIPLES[key]
+  if (!triple) return null
+  try {
+    const codexDir = path.dirname(require.resolve('@openai/codex/package.json'))
+    const platformPkg = createRequire(path.join(codexDir, 'package.json')).resolve(`@openai/codex-${key}/package.json`)
+    const bin = path.join(path.dirname(platformPkg), 'vendor', triple, 'bin', 'codex')
+    return fs.existsSync(bin) ? bin : null
+  } catch {
+    return null
+  }
+}
+
+/**
+ * 服务专用的 CODEX_HOME:生产为 agentsvc 的 ~/.codex(/srv/agent-server/home/.codex),
+ * 开发时在 .dev-data/home/.codex。不使用个人的 ~/.codex,避免加载个人配置中的插件与 MCP。
+ */
+export function codexHome(): string {
+  return path.join(config.dataDir, 'home', '.codex')
+}

+ 77 - 0
apps/server/src/providers/codex/permissions.ts

@@ -0,0 +1,77 @@
+// 把合并后的文件访问策略映射为 Codex 的权限配置(permission profile),随 thread/start 的 config 传入。
+// 已验证(2026-10-23,codex 0.164):":root" = "deny" 可以做默认拒绝;Codex 的沙箱辅助程序是指向 codex 二进制的
+// 链接,位于 CODEX_HOME/tmp/arg0-linux,这两处必须可读,否则沙箱无法启动(docs/design/03-providers.md §6)
+import path from 'node:path'
+import type { AppSettings, PermissionMode } from '@agent-server/shared'
+import type { ResolvedAccess } from '../../services/access-policy'
+import { hasGlob, realpathLoose } from '../../lib/paths'
+
+export const PROFILE_NAME = 'agent_server'
+
+type Access = 'read' | 'write' | 'deny'
+type FsTable = Record<string, Access | Record<string, Access> | number>
+
+export interface CodexThreadConfig {
+  default_permissions: string
+  permissions: Record<string, { filesystem: FsTable; network: { enabled: boolean } }>
+  shell_environment_policy: { inherit: 'core'; set: Record<string, string> }
+  [key: string]: unknown
+}
+
+export function buildThreadConfig(opts: {
+  access: ResolvedAccess
+  permissionMode: PermissionMode
+  settings: AppSettings
+  codexBin: string
+  codexHome: string
+}): CodexThreadConfig {
+  const { access } = opts
+  const fs: FsTable = {
+    ':root': access.mode === 'broad' ? 'read' : 'deny',
+    ':minimal': 'read',
+    // Codex 沙箱辅助程序及其指向的二进制
+    [realpathLoose(path.join(opts.codexHome, 'tmp', 'arg0-linux'))]: 'read',
+    [path.dirname(realpathLoose(opts.codexBin))]: 'read',
+  }
+  const workspace = realpathLoose(access.workspaceDir)
+  for (const p of access.readable) if (p !== '/' && p !== workspace) fs[p] = 'read'
+  for (const p of access.writable) if (p !== workspace) fs[p] = opts.permissionMode === 'readonly' ? 'read' : 'write'
+  // 宽松模式下屏蔽其他会话的工作区与服务数据目录(当前工作区更具体,仍然可写)
+  if (access.mode === 'broad') for (const p of access.softDeny) fs[p] = 'deny'
+
+  const workspaceRules: Record<string, Access> = { '.': opts.permissionMode === 'readonly' ? 'read' : 'write' }
+  let hasGlobRule = false
+  for (const entry of access.hardDeny) {
+    if (path.isAbsolute(entry) && !hasGlob(entry)) {
+      fs[entry] = 'deny'
+    } else if (!path.isAbsolute(entry)) {
+      // 相对通配(如 **/*.env)作用于工作区
+      workspaceRules[entry] = 'deny'
+      hasGlobRule = true
+    } else {
+      fs[entry] = 'deny'
+      hasGlobRule = true
+    }
+  }
+  fs[':workspace_roots'] = workspaceRules
+  if (hasGlobRule) fs.glob_scan_max_depth = 6
+
+  const py = opts.settings.paths.pythonRuntime
+  const agentDir = path.join(access.workspaceDir, '.agent')
+  return {
+    default_permissions: PROFILE_NAME,
+    permissions: { [PROFILE_NAME]: { filesystem: fs, network: { enabled: false } } },
+    // 只继承基础环境变量(PATH、HOME 等),不把服务进程中的密钥带进命令
+    shell_environment_policy: {
+      inherit: 'core',
+      set: {
+        PATH: `${path.join(py, 'bin')}:/usr/local/bin:/usr/bin:/bin`,
+        VIRTUAL_ENV: py,
+        MATPLOTLIBRC: opts.settings.paths.matplotlibrc,
+        MPLCONFIGDIR: path.join(agentDir, 'mpl'),
+        NODE_PATH: opts.settings.paths.nodeModules,
+        TMPDIR: path.join(agentDir, 'tmp'),
+      },
+    },
+  }
+}

+ 14 - 0
apps/server/src/providers/codex/protocol/AbsolutePathBuf.ts

@@ -0,0 +1,14 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * A path that is guaranteed to be absolute and normalized (though it is not
+ * guaranteed to be canonicalized or exist on the filesystem).
+ *
+ * IMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set
+ * using [AbsolutePathBufGuard::new]. If no base path is set, the
+ * deserialization will fail unless the path being deserialized is already
+ * absolute.
+ */
+export type AbsolutePathBuf = string;

+ 5 - 0
apps/server/src/providers/codex/protocol/AgentMessageInputContent.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type AgentMessageInputContent = { "type": "input_text", text: string, } | { "type": "encrypted_content", encrypted_content: string, };

+ 5 - 0
apps/server/src/providers/codex/protocol/AgentPath.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type AgentPath = string;

+ 21 - 0
apps/server/src/providers/codex/protocol/ApplyPatchApprovalParams.ts

@@ -0,0 +1,21 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { FileChange } from "./FileChange";
+import type { ThreadId } from "./ThreadId";
+
+export type ApplyPatchApprovalParams = { conversationId: ThreadId,
+/**
+ * Use to correlate this with [codex_protocol::protocol::PatchApplyBeginEvent]
+ * and [codex_protocol::protocol::PatchApplyEndEvent].
+ */
+callId: string, fileChanges: { [key in string]?: FileChange },
+/**
+ * Optional explanatory reason (e.g. request for extra write access).
+ */
+reason: string | null,
+/**
+ * When set, the agent is asking the user to allow writes under this root
+ * for the remainder of the session (unclear if this is honored today).
+ */
+grantRoot: string | null, };

+ 6 - 0
apps/server/src/providers/codex/protocol/ApplyPatchApprovalResponse.ts

@@ -0,0 +1,6 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ReviewDecision } from "./ReviewDecision";
+
+export type ApplyPatchApprovalResponse = { decision: ReviewDecision, };

+ 8 - 0
apps/server/src/providers/codex/protocol/AuthMode.ts

@@ -0,0 +1,8 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Authentication mode for OpenAI-backed providers.
+ */
+export type AuthMode = "apikey" | "chatgpt" | "chatgptAuthTokens" | "headers" | "agentIdentity" | "personalAccessToken" | "bedrockApiKey" | "bedrockAccessKeys";

+ 9 - 0
apps/server/src/providers/codex/protocol/AutoCompactTokenLimitScope.ts

@@ -0,0 +1,9 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Selects which part of the active context is charged against
+ * `model_auto_compact_token_limit`.
+ */
+export type AutoCompactTokenLimitScope = "total" | "body_after_prefix";

+ 5 - 0
apps/server/src/providers/codex/protocol/ClientInfo.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type ClientInfo = { name: string, title: string | null, version: string, };

+ 5 - 0
apps/server/src/providers/codex/protocol/ClientNotification.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type ClientNotification = { "method": "initialized" };

Fichier diff supprimé car celui-ci est trop grand
+ 108 - 0
apps/server/src/providers/codex/protocol/ClientRequest.ts


+ 5 - 0
apps/server/src/providers/codex/protocol/CodexResponseHandoffMode.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type CodexResponseHandoffMode = "thinking" | "commentary" | "bemTags";

+ 10 - 0
apps/server/src/providers/codex/protocol/CollaborationMode.ts

@@ -0,0 +1,10 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ModeKind } from "./ModeKind";
+import type { Settings } from "./Settings";
+
+/**
+ * Collaboration mode for a Codex session.
+ */
+export type CollaborationMode = { mode: ModeKind, settings: Settings, };

+ 9 - 0
apps/server/src/providers/codex/protocol/ConfigurationReasoning.ts

@@ -0,0 +1,9 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ReasoningEffort } from "./ReasoningEffort";
+
+/**
+ * Reasoning settings interpreted by the backend for the routed model.
+ */
+export type ConfigurationReasoning = { effort: ReasoningEffort, };

+ 6 - 0
apps/server/src/providers/codex/protocol/ContentItem.ts

@@ -0,0 +1,6 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ImageDetail } from "./ImageDetail";
+
+export type ContentItem = { "type": "input_text", text: string, } | { "type": "input_image", detail?: ImageDetail, } & ({ image_url: string, } | { file_id: string, }) | { "type": "input_audio", audio_url: string, } | { "type": "output_text", text: string, annotations?: unknown[], logprobs?: unknown[], };

+ 5 - 0
apps/server/src/providers/codex/protocol/ConversationGitInfo.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type ConversationGitInfo = { sha: string | null, branch: string | null, origin_url: string | null, };

+ 8 - 0
apps/server/src/providers/codex/protocol/ConversationSummary.ts

@@ -0,0 +1,8 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ConversationGitInfo } from "./ConversationGitInfo";
+import type { SessionSource } from "./SessionSource";
+import type { ThreadId } from "./ThreadId";
+
+export type ConversationSummary = { conversationId: ThreadId, path: string, preview: string, timestamp: string | null, updatedAt: string | null, modelProvider: string, cwd: string, cliVersion: string, source: SessionSource, gitInfo: ConversationGitInfo | null, };

+ 5 - 0
apps/server/src/providers/codex/protocol/ConversationTextRole.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type ConversationTextRole = "user" | "developer" | "assistant";

+ 16 - 0
apps/server/src/providers/codex/protocol/ExecCommandApprovalParams.ts

@@ -0,0 +1,16 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ParsedCommand } from "./ParsedCommand";
+import type { ThreadId } from "./ThreadId";
+
+export type ExecCommandApprovalParams = { conversationId: ThreadId,
+/**
+ * Use to correlate this with [codex_protocol::protocol::ExecCommandBeginEvent]
+ * and [codex_protocol::protocol::ExecCommandEndEvent].
+ */
+callId: string,
+/**
+ * Identifier for this specific approval callback.
+ */
+approvalId: string | null, command: Array<string>, cwd: string, reason: string | null, parsedCmd: Array<ParsedCommand>, };

+ 6 - 0
apps/server/src/providers/codex/protocol/ExecCommandApprovalResponse.ts

@@ -0,0 +1,6 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ReviewDecision } from "./ReviewDecision";
+
+export type ExecCommandApprovalResponse = { decision: ReviewDecision, };

+ 12 - 0
apps/server/src/providers/codex/protocol/ExecPolicyAmendment.ts

@@ -0,0 +1,12 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Proposed execpolicy change to allow commands starting with this prefix.
+ *
+ * The `command` tokens form the prefix that would be added as an execpolicy
+ * `prefix_rule(..., decision="allow")`, letting the agent bypass approval for
+ * commands that start with this token sequence.
+ */
+export type ExecPolicyAmendment = Array<string>;

+ 5 - 0
apps/server/src/providers/codex/protocol/FileChange.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type FileChange = { "type": "add", content: string, } | { "type": "delete", content: string, } | { "type": "update", unified_diff: string, move_path: string | null, };

+ 5 - 0
apps/server/src/providers/codex/protocol/ForcedLoginMethod.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type ForcedLoginMethod = "chatgpt" | "api";

+ 6 - 0
apps/server/src/providers/codex/protocol/FunctionCallOutputBody.ts

@@ -0,0 +1,6 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { FunctionCallOutputContentItem } from "./FunctionCallOutputContentItem";
+
+export type FunctionCallOutputBody = string | Array<FunctionCallOutputContentItem>;

+ 10 - 0
apps/server/src/providers/codex/protocol/FunctionCallOutputContentItem.ts

@@ -0,0 +1,10 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ImageDetail } from "./ImageDetail";
+
+/**
+ * Responses API compatible content items that can be returned by a tool call.
+ * This is a subset of ContentItem with the types we support as function call outputs.
+ */
+export type FunctionCallOutputContentItem = { "type": "input_text", text: string, } | { "type": "input_image", detail?: ImageDetail, } & ({ image_url: string, } | { file_id: string, }) | { "type": "input_audio", audio_url: string, } | { "type": "encrypted_content", encrypted_content: string, };

+ 5 - 0
apps/server/src/providers/codex/protocol/FuzzyFileSearchMatchType.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type FuzzyFileSearchMatchType = "file" | "directory";

+ 5 - 0
apps/server/src/providers/codex/protocol/FuzzyFileSearchParams.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type FuzzyFileSearchParams = { query: string, roots: Array<string>, cancellationToken: string | null, };

+ 6 - 0
apps/server/src/providers/codex/protocol/FuzzyFileSearchResponse.ts

@@ -0,0 +1,6 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { FuzzyFileSearchResult } from "./FuzzyFileSearchResult";
+
+export type FuzzyFileSearchResponse = { files: Array<FuzzyFileSearchResult>, };

+ 9 - 0
apps/server/src/providers/codex/protocol/FuzzyFileSearchResult.ts

@@ -0,0 +1,9 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { FuzzyFileSearchMatchType } from "./FuzzyFileSearchMatchType";
+
+/**
+ * Superset of [`codex_file_search::FileMatch`]
+ */
+export type FuzzyFileSearchResult = { root: string, path: string, match_type: FuzzyFileSearchMatchType, file_name: string, score: number, indices: Array<number> | null, };

+ 5 - 0
apps/server/src/providers/codex/protocol/FuzzyFileSearchSessionCompletedNotification.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type FuzzyFileSearchSessionCompletedNotification = { sessionId: string, };

+ 6 - 0
apps/server/src/providers/codex/protocol/FuzzyFileSearchSessionUpdatedNotification.ts

@@ -0,0 +1,6 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { FuzzyFileSearchResult } from "./FuzzyFileSearchResult";
+
+export type FuzzyFileSearchSessionUpdatedNotification = { sessionId: string, query: string, files: Array<FuzzyFileSearchResult>, };

+ 5 - 0
apps/server/src/providers/codex/protocol/GetAuthStatusParams.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type GetAuthStatusParams = { includeToken: boolean | null, refreshToken: boolean | null, };

+ 6 - 0
apps/server/src/providers/codex/protocol/GetAuthStatusResponse.ts

@@ -0,0 +1,6 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { AuthMode } from "./AuthMode";
+
+export type GetAuthStatusResponse = { authMethod: AuthMode | null, authToken: string | null, requiresOpenaiAuth: boolean | null, };

+ 6 - 0
apps/server/src/providers/codex/protocol/GetConversationSummaryParams.ts

@@ -0,0 +1,6 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ThreadId } from "./ThreadId";
+
+export type GetConversationSummaryParams = { rolloutPath: string, } | { conversationId: ThreadId, };

+ 6 - 0
apps/server/src/providers/codex/protocol/GetConversationSummaryResponse.ts

@@ -0,0 +1,6 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ConversationSummary } from "./ConversationSummary";
+
+export type GetConversationSummaryResponse = { summary: ConversationSummary, };

+ 5 - 0
apps/server/src/providers/codex/protocol/GitDiffToRemoteParams.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type GitDiffToRemoteParams = { cwd: string, };

+ 6 - 0
apps/server/src/providers/codex/protocol/GitDiffToRemoteResponse.ts

@@ -0,0 +1,6 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { GitSha } from "./GitSha";
+
+export type GitDiffToRemoteResponse = { sha: GitSha, diff: string, };

+ 5 - 0
apps/server/src/providers/codex/protocol/GitSha.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type GitSha = string;

+ 5 - 0
apps/server/src/providers/codex/protocol/ImageDetail.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type ImageDetail = "auto" | "low" | "high" | "original";

+ 5 - 0
apps/server/src/providers/codex/protocol/ImageGenerationFailure.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type ImageGenerationFailure = { "type": "usageLimitExceeded", limitId: string, resetsAt: number | null, };

+ 13 - 0
apps/server/src/providers/codex/protocol/ImageGenerationInputImage.ts

@@ -0,0 +1,13 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { AbsolutePathBuf } from "./AbsolutePathBuf";
+
+/**
+ * A local image file successfully read for this exact image-generation request.
+ */
+export type ImageGenerationInputImage = { path: AbsolutePathBuf,
+/**
+ * Lowercase SHA-256 of the file bytes read before image normalization.
+ */
+sha256: string, };

+ 12 - 0
apps/server/src/providers/codex/protocol/ImageGenerationItem.ts

@@ -0,0 +1,12 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { AbsolutePathBuf } from "./AbsolutePathBuf";
+import type { ImageGenerationFailure } from "./ImageGenerationFailure";
+import type { ImageGenerationInputImage } from "./ImageGenerationInputImage";
+
+export type ImageGenerationItem = { id: string, status: string, revisedPrompt: string | null, transparentBackground?: boolean, failure: ImageGenerationFailure | null, savedPath?: AbsolutePathBuf,
+/**
+ * Observed local inputs for a completed generation, absent on older items.
+ */
+inputImages?: Array<ImageGenerationInputImage>, } & ({ fileId: string, } | { result: string, });

+ 37 - 0
apps/server/src/providers/codex/protocol/InitializeCapabilities.ts

@@ -0,0 +1,37 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { JsonValue } from "./serde_json/JsonValue";
+
+/**
+ * Client-declared capabilities negotiated during initialize.
+ */
+export type InitializeCapabilities = {
+/**
+ * Use explicit gateway OAuth login instead of automatic browser authorization.
+ * Applies to this app-server's gateway runtime; later connections cannot undo it.
+ */
+explicitGatewayOauth?: boolean,
+/**
+ * Opt into receiving experimental API methods and fields.
+ */
+experimentalApi: boolean,
+/**
+ * Opt into `attestation/generate` requests for upstream `x-oai-attestation`.
+ */
+requestAttestation: boolean,
+/**
+ * Legacy opt-in for the `openai/form` MCP extension.
+ *
+ * New clients should declare `openai/form` in [`Self::extensions`].
+ */
+mcpServerOpenaiFormElicitation?: boolean,
+/**
+ * Exact notification method names that should be suppressed for this
+ * connection (for example `thread/started`).
+ */
+optOutNotificationMethods?: Array<string> | null,
+/**
+ * MCP extension settings declared by the app-server client.
+ */
+extensions?: { [key in string]?: JsonValue } | null, };

+ 7 - 0
apps/server/src/providers/codex/protocol/InitializeParams.ts

@@ -0,0 +1,7 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ClientInfo } from "./ClientInfo";
+import type { InitializeCapabilities } from "./InitializeCapabilities";
+
+export type InitializeParams = { clientInfo: ClientInfo, capabilities: InitializeCapabilities | null, };

+ 20 - 0
apps/server/src/providers/codex/protocol/InitializeResponse.ts

@@ -0,0 +1,20 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { AbsolutePathBuf } from "./AbsolutePathBuf";
+
+export type InitializeResponse = { userAgent: string,
+/**
+ * Absolute path to the server's $CODEX_HOME directory.
+ */
+codexHome: AbsolutePathBuf,
+/**
+ * Platform family for the running app-server target, for example
+ * `"unix"` or `"windows"`.
+ */
+platformFamily: string,
+/**
+ * Operating system for the running app-server target, for example
+ * `"macos"`, `"linux"`, or `"windows"`.
+ */
+platformOs: string, };

+ 8 - 0
apps/server/src/providers/codex/protocol/InputModality.ts

@@ -0,0 +1,8 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Canonical user-input modality tags advertised by a model.
+ */
+export type InputModality = "text" | "image" | "audio";

+ 11 - 0
apps/server/src/providers/codex/protocol/InternalChatMessageMetadataPassthrough.ts

@@ -0,0 +1,11 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Internal Responses API passthrough metadata copied into underlying chat messages.
+ *
+ * Responses API strongly types this payload. Do not modify it without first getting API
+ * approval and making the corresponding Responses API change.
+ */
+export type InternalChatMessageMetadataPassthrough = { turn_id?: string, };

+ 5 - 0
apps/server/src/providers/codex/protocol/InternalSessionSource.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type InternalSessionSource = "memory_consolidation" | "guardian";

+ 27 - 0
apps/server/src/providers/codex/protocol/LegacyAppPathString.ts

@@ -0,0 +1,27 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * A UTF-8 path for preserving raw path compatibility at the app-server API
+ * boundary while Codex migrates to [`PathUri`].
+ *
+ * Supports storing arbitrary strings read from the API and converting to and
+ * from [`PathUri`] using an explicitly selected native path convention.
+ *
+ * When converting from [`PathUri`], "native" refers to the supplied
+ * [`PathConvention`], which may be foreign to the operating system running
+ * this process. The inner string is private so path-producing code must use a
+ * path conversion method instead of bypassing the intended conversion
+ * boundary. Non-UTF-8 paths are converted to UTF-8 lossily because this API
+ * value is serialized as a JSON string.
+ *
+ * Deserialization and [`Self::from_string`] accept any UTF-8 string without
+ * interpreting or validating it. Use [`Self::from_string`] when a caller
+ * already owns legacy app-server path text and needs to preserve its wire
+ * spelling; use [`Self::from_path`], [`Self::from_abs_path`], or
+ * [`Self::from_path_uri`] when converting an actual path value. Relative
+ * path text remains valid until an operation such as [`Self::to_path_uri`]
+ * requires an absolute path.
+ */
+export type LegacyAppPathString = string;

+ 6 - 0
apps/server/src/providers/codex/protocol/LocalShellAction.ts

@@ -0,0 +1,6 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { LocalShellExecAction } from "./LocalShellExecAction";
+
+export type LocalShellAction = { "type": "exec" } & LocalShellExecAction;

+ 5 - 0
apps/server/src/providers/codex/protocol/LocalShellExecAction.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type LocalShellExecAction = { command: Array<string>, timeout_ms: bigint | null, working_directory: string | null, env: { [key in string]?: string } | null, user: string | null, };

+ 5 - 0
apps/server/src/providers/codex/protocol/LocalShellStatus.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type LocalShellStatus = "completed" | "in_progress" | "incomplete";

+ 9 - 0
apps/server/src/providers/codex/protocol/McpServerInfo.ts

@@ -0,0 +1,9 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { JsonValue } from "./serde_json/JsonValue";
+
+/**
+ * Presentation metadata advertised by an initialized MCP server.
+ */
+export type McpServerInfo = { name: string, title: string | null, version: string, description: string | null, icons: Array<JsonValue> | null, websiteUrl: string | null, };

+ 11 - 0
apps/server/src/providers/codex/protocol/MessagePhase.ts

@@ -0,0 +1,11 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Classifies assistant text as commentary, a partial answer, or a terminal answer.
+ *
+ * Providers do not emit this consistently, so callers must treat `None` as
+ * "phase unknown" and keep compatibility behavior for legacy models.
+ */
+export type MessagePhase = "commentary" | "partial_answer" | "final_answer";

+ 8 - 0
apps/server/src/providers/codex/protocol/ModeKind.ts

@@ -0,0 +1,8 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Initial collaboration mode to use when the TUI starts.
+ */
+export type ModeKind = "plan" | "default";

+ 9 - 0
apps/server/src/providers/codex/protocol/MultiAgentMode.ts

@@ -0,0 +1,9 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Controls the effective multi-agent delegation instructions for a turn. `custom` means the
+ * configured mode hint defines the policy instead of a built-in policy.
+ */
+export type MultiAgentMode = { "custom": string } | "explicitRequestOnly" | "proactive";

+ 6 - 0
apps/server/src/providers/codex/protocol/NetworkPolicyAmendment.ts

@@ -0,0 +1,6 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { NetworkPolicyRuleAction } from "./NetworkPolicyRuleAction";
+
+export type NetworkPolicyAmendment = { host: string, action: NetworkPolicyRuleAction, };

+ 5 - 0
apps/server/src/providers/codex/protocol/NetworkPolicyRuleAction.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type NetworkPolicyRuleAction = "allow" | "deny";

+ 12 - 0
apps/server/src/providers/codex/protocol/ParsedCommand.ts

@@ -0,0 +1,12 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type ParsedCommand = { "type": "read", cmd: string, name: string,
+/**
+ * (Best effort) Path to the file being read by the command. When
+ * possible, this is an absolute path, though when relative, it should
+ * be resolved against the `cwd`` that will be used to run the command
+ * to derive the absolute path.
+ */
+path: string, } | { "type": "list_files", cmd: string, path: string | null, } | { "type": "search", cmd: string, query: string | null, path: string | null, } | { "type": "unknown", cmd: string, };

+ 32 - 0
apps/server/src/providers/codex/protocol/PathUri.ts

@@ -0,0 +1,32 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * An immutable, cross-platform representation of a `file:` URI.
+ *
+ * Only the `file:` scheme is currently accepted. Construction validates the
+ * URL, and the URI cannot be mutated after construction. [`Self::basename`],
+ * [`Self::parent`], and [`Self::join`] operate on URI path segments without
+ * interpreting them using the operating system running Codex. Fallback URIs
+ * created by [`Self::from_abs_path`] are opaque to these lexical operations.
+ *
+ * `file:` paths retain their URI spelling so they can be parsed independently
+ * of the current host, except that Windows drive letters are canonicalized to
+ * uppercase. A local POSIX `file:` URI can also retain percent-encoded non-UTF-8
+ * bytes for lossless native round trips.
+ *
+ * Like [VS Code resources], path operations use `/` URI separators on every
+ * host. Lexical path operations preserve a URL authority without interpreting
+ * Windows drive or UNC roots from path text. Windows path equality and hashing
+ * ignore ASCII case, while POSIX paths remain case-sensitive. Native path
+ * normalization, filesystem aliases, symlinks, and Unicode normalization are
+ * not resolved.
+ *
+ * Serde represents a `PathUri` as its canonical URI string. Deserialization
+ * accepts only valid `file:` URI strings. These strings round-trip through
+ * their canonical URL form, including encoded non-UTF-8 path bytes.
+ *
+ * [VS Code resources]: https://github.com/microsoft/vscode/blob/main/src/vs/base/common/resources.ts
+ */
+export type PathUri = string;

+ 8 - 0
apps/server/src/providers/codex/protocol/Personality.ts

@@ -0,0 +1,8 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Deprecated: `friendly` and `pragmatic` no longer select a style.
+ */
+export type Personality = "none" | "friendly" | "pragmatic";

+ 5 - 0
apps/server/src/providers/codex/protocol/PlanType.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type PlanType = "free" | "go" | "plus" | "pro" | "prolite" | "promax" | "team" | "self_serve_business_prolite" | "self_serve_business_usage_based" | "business" | "ent26" | "enterprise_cbp_automation" | "enterprise_cbp_usage_based" | "enterprise" | "edu" | "edu_plus" | "edu_pro" | "sci" | "sci_plus" | "unknown";

+ 5 - 0
apps/server/src/providers/codex/protocol/RealtimeConversationVersion.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type RealtimeConversationVersion = "v1" | "v2" | "v3";

+ 5 - 0
apps/server/src/providers/codex/protocol/RealtimeOutputModality.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type RealtimeOutputModality = "text" | "audio";

+ 5 - 0
apps/server/src/providers/codex/protocol/RealtimeVoice.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type RealtimeVoice = "alloy" | "arbor" | "ash" | "aube" | "ballad" | "breeze" | "bubbie" | "cedar" | "coral" | "cove" | "echo" | "ember" | "haetsal" | "hanul" | "himari" | "juniper" | "leher" | "maple" | "marin" | "miko" | "neer" | "porto" | "rindo" | "rio" | "rivage" | "sage" | "selva" | "shimmer" | "sol" | "sonna" | "spruce" | "tinta" | "vale" | "verse" | "viola";

+ 6 - 0
apps/server/src/providers/codex/protocol/RealtimeVoicesList.ts

@@ -0,0 +1,6 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { RealtimeVoice } from "./RealtimeVoice";
+
+export type RealtimeVoicesList = { v1: Array<RealtimeVoice>, v2: Array<RealtimeVoice>, v3?: Array<RealtimeVoice>, defaultV1: RealtimeVoice, defaultV2: RealtimeVoice, };

+ 8 - 0
apps/server/src/providers/codex/protocol/ReasoningEffort.ts

@@ -0,0 +1,8 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning
+ */
+export type ReasoningEffort = string;

+ 5 - 0
apps/server/src/providers/codex/protocol/ReasoningItemContent.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type ReasoningItemContent = { "type": "reasoning_text", text: string, } | { "type": "text", text: string, };

+ 5 - 0
apps/server/src/providers/codex/protocol/ReasoningItemReasoningSummary.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type ReasoningItemReasoningSummary = { "type": "summary_text", text: string, };

+ 10 - 0
apps/server/src/providers/codex/protocol/ReasoningSummary.ts

@@ -0,0 +1,10 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * A summary of the reasoning performed by the model. This can be useful for
+ * debugging and understanding the model's reasoning process.
+ * See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries
+ */
+export type ReasoningSummary = "auto" | "concise" | "detailed" | "none";

+ 5 - 0
apps/server/src/providers/codex/protocol/RequestId.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type RequestId = string | number;

+ 9 - 0
apps/server/src/providers/codex/protocol/Resource.ts

@@ -0,0 +1,9 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { JsonValue } from "./serde_json/JsonValue";
+
+/**
+ * A known resource that the server is capable of reading.
+ */
+export type Resource = { annotations?: JsonValue, description?: string, mimeType?: string, name: string, size?: number, title?: string, uri: string, icons?: Array<JsonValue>, _meta?: JsonValue, };

+ 17 - 0
apps/server/src/providers/codex/protocol/ResourceContent.ts

@@ -0,0 +1,17 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { JsonValue } from "./serde_json/JsonValue";
+
+/**
+ * Contents returned when reading a resource from an MCP server.
+ */
+export type ResourceContent = {
+/**
+ * The URI of this resource.
+ */
+uri: string, mimeType?: string, text: string, _meta?: JsonValue, } | {
+/**
+ * The URI of this resource.
+ */
+uri: string, mimeType?: string, blob: string, _meta?: JsonValue, };

+ 9 - 0
apps/server/src/providers/codex/protocol/ResourceTemplate.ts

@@ -0,0 +1,9 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { JsonValue } from "./serde_json/JsonValue";
+
+/**
+ * A template description for resources available on the server.
+ */
+export type ResourceTemplate = { annotations?: JsonValue, uriTemplate: string, name: string, title?: string, description?: string, mimeType?: string, };

Fichier diff supprimé car celui-ci est trop grand
+ 24 - 0
apps/server/src/providers/codex/protocol/ResponseItem.ts


+ 9 - 0
apps/server/src/providers/codex/protocol/ResponseItemId.ts

@@ -0,0 +1,9 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * A Responses API item ID. New IDs require an explicit prefix; deserialization
+ * remains permissive so legacy rollouts can still be read.
+ */
+export type ResponseItemId = string;

+ 10 - 0
apps/server/src/providers/codex/protocol/ReviewDecision.ts

@@ -0,0 +1,10 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ExecPolicyAmendment } from "./ExecPolicyAmendment";
+import type { NetworkPolicyAmendment } from "./NetworkPolicyAmendment";
+
+/**
+ * User's decision in response to an ExecApprovalRequest.
+ */
+export type ReviewDecision = "approved" | { "approved_execpolicy_amendment": { proposed_execpolicy_amendment: ExecPolicyAmendment, } } | "approved_for_session" | "approved_mcp_policy_amendment" | { "network_policy_amendment": { network_policy_amendment: NetworkPolicyAmendment, } } | { "denied": { rejection: string, } } | "timed_out" | "abort";

Fichier diff supprimé car celui-ci est trop grand
+ 92 - 0
apps/server/src/providers/codex/protocol/ServerNotification.ts


Fichier diff supprimé car celui-ci est trop grand
+ 102 - 0
apps/server/src/providers/codex/protocol/ServerNotificationEnvelope.ts


+ 19 - 0
apps/server/src/providers/codex/protocol/ServerRequest.ts

@@ -0,0 +1,19 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams";
+import type { ExecCommandApprovalParams } from "./ExecCommandApprovalParams";
+import type { RequestId } from "./RequestId";
+import type { AttestationGenerateParams } from "./v2/AttestationGenerateParams";
+import type { ChatgptAuthTokensRefreshParams } from "./v2/ChatgptAuthTokensRefreshParams";
+import type { CommandExecutionRequestApprovalParams } from "./v2/CommandExecutionRequestApprovalParams";
+import type { DynamicToolCallParams } from "./v2/DynamicToolCallParams";
+import type { FileChangeRequestApprovalParams } from "./v2/FileChangeRequestApprovalParams";
+import type { McpServerElicitationRequestParams } from "./v2/McpServerElicitationRequestParams";
+import type { PermissionsRequestApprovalParams } from "./v2/PermissionsRequestApprovalParams";
+import type { ToolRequestUserInputParams } from "./v2/ToolRequestUserInputParams";
+
+/**
+ * Request initiated from the server and sent to the client.
+ */
+export type ServerRequest ={ "method": "item/commandExecution/requestApproval", id: RequestId, params: CommandExecutionRequestApprovalParams, } | { "method": "item/fileChange/requestApproval", id: RequestId, params: FileChangeRequestApprovalParams, } | { "method": "item/tool/requestUserInput", id: RequestId, params: ToolRequestUserInputParams, } | { "method": "mcpServer/elicitation/request", id: RequestId, params: McpServerElicitationRequestParams, } | { "method": "item/permissions/requestApproval", id: RequestId, params: PermissionsRequestApprovalParams, } | { "method": "item/tool/call", id: RequestId, params: DynamicToolCallParams, } | { "method": "account/chatgptAuthTokens/refresh", id: RequestId, params: ChatgptAuthTokensRefreshParams, } | { "method": "attestation/generate", id: RequestId, params: AttestationGenerateParams, } | { "method": "applyPatchApproval", id: RequestId, params: ApplyPatchApprovalParams, } | { "method": "execCommandApproval", id: RequestId, params: ExecCommandApprovalParams, };

+ 7 - 0
apps/server/src/providers/codex/protocol/SessionSource.ts

@@ -0,0 +1,7 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { InternalSessionSource } from "./InternalSessionSource";
+import type { SubAgentSource } from "./SubAgentSource";
+
+export type SessionSource = "cli" | "vscode" | "exec" | "mcp" | { "custom": string } | { "internal": InternalSessionSource } | { "subagent": SubAgentSource } | "unknown";

+ 9 - 0
apps/server/src/providers/codex/protocol/Settings.ts

@@ -0,0 +1,9 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { ReasoningEffort } from "./ReasoningEffort";
+
+/**
+ * Settings for a collaboration mode.
+ */
+export type Settings = { model: string, reasoning_effort: ReasoningEffort | null, developer_instructions: string | null, };

+ 8 - 0
apps/server/src/providers/codex/protocol/SleepItem.ts

@@ -0,0 +1,8 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Display item emitted by the interruptible `clock.sleep` tool.
+ */
+export type SleepItem = { id: string, durationMs: number, };

+ 7 - 0
apps/server/src/providers/codex/protocol/SubAgentSource.ts

@@ -0,0 +1,7 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { AgentPath } from "./AgentPath";
+import type { ThreadId } from "./ThreadId";
+
+export type SubAgentSource = "review" | "compact" | { "thread_spawn": { parent_thread_id: ThreadId, depth: number, agent_path: AgentPath | null, agent_nickname: string | null, agent_role: string | null, } } | "memory_consolidation" | { "other": string };

+ 10 - 0
apps/server/src/providers/codex/protocol/ThreadId.ts

@@ -0,0 +1,10 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Identifier for a Codex thread.
+ *
+ * Codex-generated thread IDs are UUIDv7, and some use cases rely on that.
+ */
+export type ThreadId = string;

+ 5 - 0
apps/server/src/providers/codex/protocol/ThreadMemoryMode.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type ThreadMemoryMode = "enabled" | "disabled";

+ 9 - 0
apps/server/src/providers/codex/protocol/Tool.ts

@@ -0,0 +1,9 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { JsonValue } from "./serde_json/JsonValue";
+
+/**
+ * Definition for a tool the client can call.
+ */
+export type Tool = { name: string, title?: string, description?: string, inputSchema: JsonValue, outputSchema?: JsonValue, annotations?: JsonValue, icons?: Array<JsonValue>, _meta?: JsonValue, };

+ 8 - 0
apps/server/src/providers/codex/protocol/ToolExposureSurface.ts

@@ -0,0 +1,8 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * A model-facing surface on which a tool can be exposed.
+ */
+export type ToolExposureSurface = "code_mode" | "deferred" | "direct";

+ 9 - 0
apps/server/src/providers/codex/protocol/Verbosity.ts

@@ -0,0 +1,9 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+/**
+ * Controls output length/detail on GPT-5 models via the Responses API.
+ * Serialized with lowercase values to match the OpenAI API.
+ */
+export type Verbosity = "low" | "medium" | "high";

+ 5 - 0
apps/server/src/providers/codex/protocol/WebSearchAction.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type WebSearchAction = { "type": "search", query?: string, queries?: Array<string>, } | { "type": "open_page", url?: string, } | { "type": "find_in_page", url?: string, pattern?: string, } | { "type": "other" };

+ 5 - 0
apps/server/src/providers/codex/protocol/WebSearchContextSize.ts

@@ -0,0 +1,5 @@
+// GENERATED CODE! DO NOT MODIFY BY HAND!
+
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type WebSearchContextSize = "low" | "medium" | "high";

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff