codex app-server همان چیزی است که افزونهی VS Code کدکس پشتش مینشیند: یک سرور JSON-RPC که تاریخچهی گفتگو، تاییدها و رویدادهای جریانی ایجنت را به هر کلاینتی میدهد. در این نوشته روی نسخهی 0.158.0 یک کلاینت کوچک نوشتم که بدون کلید API بالا میآید، دست initialize را کامل میکند، فهرست مدلها را میگیرد و دروازهی experimentalApi را میسنجد. لحظهی خواندن داده: ۱۲ مهر ۱۴۰۵، برابر با ۴ اکتبر ۲۰۲۶.
مستندات app-server آن را رابطی میخواند که کدکس برای تغذیهی کلاینتهای غنی از آن استفاده میکند، و پیادهسازیاش متنباز در پوشهی codex-rs/app-server مخزن کدکس است.
مستندات سه واحد درونیتر را نام میبرد و هر کدام نامی جدا در متدها دارد: یک گفتگو، یک درخواست کاربر با کاری که پس از آن میآید، و واحدهای ورودی و خروجی مثل پیام ایجنت یا اجرای فرمان.
دستدادن و اینکه چه چیزی را به کلاینت میدهد
روی هر اتصال فقط یک بار initialize میفرستید و بعد یک اعلان initialized. هر چیزی پیش از آن با خطا رد میشود. این را اندازه گرفتم، نه از روی مستندات: یک درخواست thread/list پیش از دستدادن فرستادم و سرور پاسخ داد Not initialized.
$ python3 app.py
1 before-initialize -> {"error": {"code": -32600, "message": "Not initialized"}, "id": 1}
2 initialize -> {"id": 2, "result": {"userAgent": "post-demo/0.158.0 (Ubuntu 24.4.0; x86_64) unknown (post-demo; 0.1.0)", "codexHome": "/root/.codex", "platformFamily": "unix", "platformOs": "linux"}}
3 model/list -> {"id": 3, "result": {"data": [{"id": "gpt-6-astra", "model": "gpt-6-astra", "upgrade": null, "upgradeInfo": null, "availabilityNux": null, "displayName": "GPT-6-Astra", "description": "Frontier intelligence for the most demanding work.", "modelSpecialty":
4 diagnostics -> {"error": {"code": -32600, "message": "server/diagnostics requires experimentalApi capability"}, "id": 4}
پاسخ initialize سه چیز به شما میدهد: رشتهی userAgent که به سرویسهای بالادستی فرستاده میشود، و platformFamily و platformOs که در اجرای من unix و linux بودند.
کل چهار خط پاسخ بالا از یک فایل کوچک آمده که تنها کارش نوشتن یک خط JSON روی ورودی استاندارد و خواندن یک خط از خروجی است:
import json
import subprocess
proc = subprocess.Popen(
["codex", "app-server"], # پیشفرض روی stdio بالا میآید
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, text=True, bufsize=1,
)
def send(msg):
# یک پیام JSON-RPC در یک خط روی ورودی استاندارد
proc.stdin.write(json.dumps(msg) + "\n")
proc.stdin.flush()
def read_until(request_id):
# سرور اعلان هم میفرستد، پس تا پاسخ همین شناسه جلو میرویم
for _ in range(40):
line = proc.stdout.readline()
if not line:
return None
msg = json.loads(line)
if msg.get("id") == request_id:
return msg
return None
send({"method": "initialize", "id": 2, "params": {"clientInfo": {
"name": "post-demo", "title": "Post Demo", "version": "0.1.0"}}})
print("2 initialize ->", json.dumps(read_until(2)))
send({"method": "initialized", "params": {}})
نکتهی عملی این است که یک readline ساده کافی نیست. سرور بین پاسخ شما اعلان میفرستد و اگر فقط یک خط بخوانید ممکن است بهجای پاسخ، اعلان را بگیرید. در اولین اجرای خودم همین اتفاق افتاد: بهجای پاسخ thread/start یک پیام remoteControl/status/changed گرفتم.
بدون کلید API چه چیزهایی واقعا کار میکنند
مهمترین یافتهی این نوشته همینجاست. سرور بدون هیچ اعتباری بالا میآید، چون فقط خط لوله را میبندد و به سرویسی وصل نمیشود. یک codex doctor روی همین ماشین میگوید no Codex credentials were found، و در عین حال درخواستهای زیر جواب واقعی برمیگردانند:
| متد | وضعیت بدون کلید API | آنچه برگشت |
|---|---|---|
initialize | پاسخ داد | userAgent و platform |
model/list | پاسخ داد | فهرست مدلها با نام نمایشی |
skills/list | پاسخ داد | فهرست اسکیلهای همین پوشه |
config/read | پاسخ داد | کل پیکربندی با فیلدهای null |
hooks/list | پاسخ داد | آرایهی خالی با دو کلید بیخطا |
app/installed | پاسخ داد | فهرست خالی |
thread/list | پاسخ داد | آرایهی خالی با نشانگر صفحه |
server/diagnostics | رد شد | پیام نیاز به experimentalApi |
آن config/read با همهی فیلدهای null نکتهی ریزی دارد که در جدول گم میشود. فیلدهایی مثل model و sandbox_mode همه null برگشتند، یعنی این سرور تنظیمات مؤثر را از فایل میخواند نه از دل کلاینت. اگر پیکربندی مؤثر را میخواهید، config/read ابزار درست است.
جدول را با یک اسکریپت ساختم که هفت متد را روی یک اتصال پشت سر هم میفرستد. نتیجهی واقعی روی همین ماشین این بود:
$ python3 probe6.py
config/read -> {"id": 2, "result": {"config": {"model": null, "review_model": null, "model_context_window": null, ...
model/list -> {"id": 3, "result": {"data": [{"id": "gpt-6-astra", "model": "gpt-6-astra", "displayName": "GPT-6-Astra", ...
skills/list -> {"id": 4, "result": {"data": [{"cwd": "/root/.hermes/hoosh-blog", "skills": [{"name": "plugin-eval:evaluate-plugin", ...
hooks/list -> {"id": 5, "result": {"data": [{"cwd": "/root/.hermes/hoosh-blog", "hooks": [], "warnings": [], "errors": []}]}}
app/installed -> {"id": 7, "result": {"apps": []}}
thread/loaded/list -> {"id": 8, "result": {"data": [], "nextCursor": null}}
server/diagnostics -> {"error": {"code": -32600, "message": "server/diagnostics requires experimentalApi capability"}, "id": 6}
تفاوت hooks/list با بقیه این است که فقط یک آرایه نیست. هر عنصر سه کلید دارد: hooks، warnings و errors. یعنی میتوانید بدون باز کردن فایلها بفهمید قلابهای یک پوشه چه خطایی دادهاند.
دروازهی experimentalApi و اینکه چه چیزی واقعا پشتش است
مستندات میگویند بعضی متدها و فیلدها پشت capabilities.experimentalApi قفل شدهاند و بدون آن خطای requires experimentalApi capability میگیرید. این را روی پنج متد سنجیدم و نتیجه یکدست نبود:
=========== experimentalApi: off
mock/experimentalMethod -> {"error": {"code": -32600, "message": "mock/experimentalMethod requires experimentalApi capability"}, "id": 2}
thread/backgroundTerminals/list -> {"error": {"code": -32600, "message": "thread/backgroundTerminals/list requires experimentalApi capability"}, "id": 2}
thread/items/list -> {"error": {"code": -32601, "message": "thread/items/list is not supported yet"}, "id": 2}
thread/loaded/list -> {"id": 2, "result": {"data": [], "nextCursor": null}}
=========== experimentalApi: on
mock/experimentalMethod -> {"id": 2, "result": {"echoed": null}}
thread/backgroundTerminals/list -> {"error": {"code": -32600, "message": "thread not found: 11111111-2222-3333-4444-555555555555"}, "id": 2}
دو چیز اینجا ارزش دانستن دارد. اول اینکه thread/items/list در هر دو حالت خطا داد ولی خطایش فرق کرد: بدون اجازه دروازه را زد و با اجازه گفت هنوز پیاده نشده است. یعنی اجازه دادن همهچیز را آزاد نمیکند.
دوم اینکه با اجازه، thread/backgroundTerminals/list از requires experimentalApi capability به thread not found رفت. یعنی دروازه باز شد و سرور واقعا به مرحلهی بعدی رفت، یعنی دنبال ترد میگشت. برای تست دروازه، همین جابهجایی پیام خطا را نگاه کنید، نه وجود یا نبود خطا.
نکتهی دومی هم هست که سرگردانکننده است: مستندات thread/turns/list را آزمایشی میخواند، ولی در اجرای من با هر سه حالت experimentalApi پاسخ thread not loaded داد و هرگز پیام نیاز به اجازه نداد. یعنی مستندات و رفتار این نسخه برای این یک متد یکی نیستند.
کد را از خود کلاینت بگیرید، نه از مستندات
بهجای نوشتن فیلدها از روی خواندن مستندات، میتوانید schema دقیق همان نسخهای را که نصب دارید تولید کنید. مستندات این را صریح مینویسد: هر خروجی مخصوص نسخهی کدکسی است که آن را اجرا کردهاید.
$ codex app-server generate-json-schema --out ./schema
$ ls ./schema | wc -l
39
$ codex app-server generate-ts --out ./ts
$ ls ./ts | wc -l
96
$ codex app-server generate-json-schema --out ./schema-exp --experimental
$ ls ./schema-exp | wc -l
47
$ codex app-server generate-ts --out ./ts-exp --experimental
$ ls ./ts-exp | wc -l
102
کلید --experimental روی schema تولیدشده ۸ فایل JSON Schema و ۶ فایل TypeScript اضافه میکند. این تفاوت را خودم شمردم، نه از دل مستندات.
از همین schema میتوانید فهرست کامل متدها را بیرون بکشید. الگوی نامگذاری را روی فایل ClientRequest.json اجرا کردم و ۱۲۵ نام متد بیرون آمد:
$ python3 schema_scan.py
methods found: 125
account/read
account/rateLimits/read
account/usage/read
command/exec
config/read
config/mcpServer/reload
fs/readFile
memory/status
mock/experimentalMethod
model/list
plugin/install
process/spawn
skills/list
thread/archive
thread/fork
thread/list
thread/resume
turn/interrupt
turn/start
...
آن ۱۲۵ عدد فقط در یک نسخه معتبر است. با هر بهروزرسانی کدکس میتواند عوض شود، پس schema را در مخزن پروژهی خودتان نگه دارید و diff بگیرید.
همان سرور روی سوکت یونیکس و وبسوکت
تا اینجا همه چیز روی stdio بود. اگر میخواهید کلاینت از ماشین دیگر وصل شود، مستندات چهار حملونقل را نام میبرند: stdio بهعنوان پیشفرض، سوکت یونیکس، وبسوکت و off. این را اجرا کردم و رفتار واقعی سه نقطهی کنترلی را دیدم:
$ codex app-server --listen ws://127.0.0.1:4517 \
--ws-auth capability-token --ws-token-file ./token.txt &
pid=1135437
--- listening socket:
LISTEN 0 128 127.0.0.1:4517 0.0.0.0:*
$ curl -s -o /dev/null -w "code=%{http_code}\n" http://127.0.0.1:4517/readyz
code=200
$ curl -s -o /dev/null -w "code=%{http_code}\n" http://127.0.0.1:4517/healthz
code=200
$ curl -s -o /dev/null -w "code=%{http_code}\n" -H "Origin: http://evil.example" http://127.0.0.1:4517/healthz
code=403
عدد ۴۰۳ با هدر Origin یعنی همان قاعدهای که مستندات نوشتهاند: درخواستهای دارای Origin رد میشوند. خود سرور هم در خروجی استانداردش این را تکرار میکند و میگوید فقط لوکالهاست باید گوش دهد.
=== upgrade WITHOUT token
code=401
missing websocket bearer token
=== upgrade WITH wrong token
code=401
invalid websocket bearer token
=== upgrade WITH right token
code=101
دو پیام خطا جدا از هم هستند: نبودن توکن و توکن نادرست. اگر لاگ احراز هویت خودتان را میخوانید، این تمایز به شما میگوید کلاینت چه اشتباهی کرده.
در خروجی استاندارد سرور، نشانیهای سلامت را چاپ میکند و هشدار میدهد:
codex app-server (WebSockets)
listening on: ws://127.0.0.1:4517
readyz: http://127.0.0.1:4517/readyz
healthz: http://127.0.0.1:4517/healthz
note: binds localhost only (use SSH port-forwarding for remote access)
همان خط آخر دلیل قفل بودن روی لوکالهاست را میگوید. برای دسترسی از بیرون تونل SSH بزنید، نه اینکه پورت را باز کنید.
قدم بعدی شما
اگر تا اینجا آمدهاید، یک کلاینت دارید که دستدادن میکند و بدون کلید API جواب واقعی میگیرد. قدم بعدی thread/start و بعد turn/start است، ولی هر دو به اعتبار واقعی نیاز دارند و در اجرای من به همین دلیل جلو نرفتم.
همین خط لولهی محلی بدون کلید API یک کاربرد روشن دارد: تست دود روی کلاینت خودتان. پیش از آنکه اعتبار اضافه کنید، میتوانید مطمئن شوید که handshake و پاسخخوانی درست کار میکند. برای دیدن شکل دقیق پیامها، نمونهی Node.js در مستندات را کنار همین کلاینت پایتونی بگذارید.
دو پرچم را با احتیاط مصرف کنید. --listen ws:// و --ws-auth هر دو آزمایشیاند و خود مستندات میگویند برای بار کاری تولید پشتیبانی نمیشوند. اگر میخواهید بدانید در نسخهی شما چه چیزی پایدار است، بدوی codex app-server daemon version را نگاه کنید.
برای دیدن اینکه چه نسخهای روی ماشینتان است و چه چیزی کهنه است، چطور خرابی کدکس را با doctor پیدا کنیم را بخوانید. عدد 0.158.0 در هر دو نوشته از یک ماشین آمده است.
منابع
- مستندات Codex App Server: پروتکل، چهار حملونقل، دروازهی experimentalApi و نمونهی Node.js
- متن همان مستندات در مخزن کدکس، همراه با رفتار فشار بازگشت و کد خطای -32001
- مرجع خط فرمان کدکس و روش نصب با install.sh
- مرجع پیکربندی پیشرفته، از جمله محل CODEX_HOME و پروفایلها
- انتشارهای کدکس در گیتهاب
- کدکس doctor چیست و چطور خرابی کدکس را پیدا کنیم
دیدگاهها
۰ موردهنوز دیدگاهی ثبت نشده. اولین نفر باشید.