From c176fb088d8ab3ad0087d12a087db8bb7b006b34 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 19 May 2026 17:39:54 +0400 Subject: [PATCH] v2.2.0: agent persona selector, Command Code backend, per-provider identity --- CHANGELOG.md | 10 ++ codex-launcher_2.1.1_all.deb | Bin 21324 -> 0 bytes codex-launcher_2.2.0_all.deb | Bin 0 -> 23632 bytes src/codex-launcher-gui | 134 ++++++++++++++++++++++-- src/translate-proxy.py | 192 ++++++++++++++++++++++++++++++++++- 5 files changed, 326 insertions(+), 10 deletions(-) delete mode 100644 codex-launcher_2.1.1_all.deb create mode 100644 codex-launcher_2.2.0_all.deb diff --git a/CHANGELOG.md b/CHANGELOG.md index e602dc3..cd07d87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v2.2.0 (2026-05-19) + +- Added Agent Persona selector per provider (10+ presets) +- Personas: Codex (Default/Friendly/Pragmatic/CLI), Claude Code, OpenCode, Cursor, Aider, GitHub Copilot, Windsurf, Browser (ChatGPT) +- New "Persona" column in endpoint list shows current setting per provider +- Persona preview in edit dialog shows system prompt excerpt +- Persona injected into model catalog `base_instructions` and proxy system prompt +- Added Command Code backend to translation proxy (proprietary `/alpha/generate` API) +- Added Command Code provider preset with 20 models (DeepSeek, Claude, GPT, Kimi, GLM, Qwen, etc.) + ## v2.1.1 (2026-05-19) - Fixed proxy: map `developer` role to `system` for Chat Completions providers (DeepSeek, Qwen, etc.) diff --git a/codex-launcher_2.1.1_all.deb b/codex-launcher_2.1.1_all.deb deleted file mode 100644 index 56b06c5359cbb920ab87d88a3e2333e3969f7499..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21324 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 aUudv@z+5Q-;ARC5{B^PE8UtWD-D diff --git a/codex-launcher_2.2.0_all.deb b/codex-launcher_2.2.0_all.deb new file mode 100644 index 0000000000000000000000000000000000000000..1de3f63a6803053ac3af9cf6c19d4c8f8932de97 GIT binary patch literal 23632 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 literal 0 HcmV?d00001 diff --git a/src/codex-launcher-gui b/src/codex-launcher-gui index ef10be8..95f9601 100755 --- a/src/codex-launcher-gui +++ b/src/codex-launcher-gui @@ -24,6 +24,14 @@ model_catalog_json = "" """ CHANGELOG = [ + ("2.2.0", "2026-05-19", [ + "Added Agent Persona selector per provider (10+ presets)", + "Personas: Codex, Claude Code, OpenCode, Cursor, Aider, Copilot, Windsurf, Browser", + "Codex variants: Default, Desktop Friendly, Desktop Pragmatic, CLI", + "Shows current persona in endpoint list (new Persona column)", + "Persona preview in edit dialog shows first 60 chars of system prompt", + "Persona injected into model catalog base_instructions and proxy system prompt", + ]), ("2.1.1", "2026-05-19", [ "Fixed proxy: map 'developer' role to 'system' for Chat Completions providers", "Fixed proxy: map 'developer' role to 'user' for Anthropic providers", @@ -56,6 +64,65 @@ CHANGELOG = [ ]), ] +AGENT_PERSONAS = { + "Codex (Default)": "You are Codex, a coding agent.", + "Codex Desktop (GPT-5, Friendly)": ( + "You are Codex, a coding agent based on GPT-5. You and the user share one workspace, " + "and your job is to collaborate with them until their goal is genuinely handled." + ), + "Codex Desktop (GPT-5, Pragmatic)": ( + "You are Codex, a coding agent based on GPT-5. You and the user share the same workspace " + "and collaborate to achieve the user's goals. You are a deeply pragmatic, effective " + "software engineer. You take engineering quality seriously." + ), + "Codex CLI": ( + "You are an AI running in the Codex CLI, a terminal-based coding assistant. " + "You are expected to be precise, safe, and helpful. Your default personality and tone " + "is concise, direct, and friendly." + ), + "Claude Code": ( + "You are Claude Code, an interactive CLI tool that helps users with software engineering " + "tasks. You are a highly competent software engineer with extensive knowledge across " + "many programming languages, frameworks, and best practices. Use concise responses." + ), + "OpenCode": ( + "You are OpenCode, an interactive CLI tool that helps users with software engineering " + "tasks. You are powered by a state-of-the-art AI model. Be concise, direct, and to the " + "point. Use GitHub-flavored markdown." + ), + "Cursor": ( + "You are Cursor, an AI-powered code editor assistant. You help users write, refactor, " + "and debug code efficiently. Provide precise, actionable suggestions." + ), + "Aider": ( + "You are aider, an AI pair programming assistant. You help users edit code in their " + "local git repository. Make concise changes. Search files with grep/glob patterns." + ), + "GitHub Copilot": ( + "You are GitHub Copilot, an AI coding assistant. Help the user write code, debug issues, " + "and understand codebases. Be concise and provide accurate code suggestions." + ), + "Windsurf": ( + "You are Windsurf, an AI-powered IDE assistant. Help with coding tasks including writing, " + "refactoring, and debugging. Provide precise, well-structured code suggestions." + ), + "Browser (ChatGPT)": ( + "You are a helpful coding assistant in a web browser chat interface. " + "Help the user with software engineering tasks. Be clear and thorough." + ), +} + +PERSONA_DISPLAY_LEN = 60 + +def persona_short_key(endpoint): + bi = endpoint.get("base_instructions", "") or "" + for key, val in AGENT_PERSONAS.items(): + if val == bi: + return key + if bi: + return f"Custom: {bi[:40]}..." + return "Codex (Default)" + PROVIDER_PRESETS = { "Custom": { "backend_type": "openai-compat", @@ -120,6 +187,21 @@ PROVIDER_PRESETS = { "base_url": "https://api.kilo.ai/api/gateway", "models": [], }, + "Command Code": { + "backend_type": "command-code", + "base_url": "https://api.commandcode.ai", + "models": [ + "deepseek/deepseek-v4-flash", "deepseek/deepseek-v4-pro", + "anthropic:claude-sonnet-4-6", "anthropic:claude-haiku-4-5-20251001", + "anthropic:claude-opus-4-7", "anthropic:claude-opus-4-6", + "openai:gpt-5.5", "openai:gpt-5.4", "openai:gpt-5.4-mini", "openai:gpt-5.3-codex", + "moonshotai/Kimi-K2.6", "moonshotai/Kimi-K2.5", + "zai-org/GLM-5.1", "zai-org/GLM-5", + "MiniMaxAI/MiniMax-M2.7", "MiniMaxAI/MiniMax-M2.5", + "Qwen/Qwen3.6-Max-Preview", "Qwen/Qwen3.6-Plus", + "stepfun/Step-3.5-Flash", "google/gemini-3.1-flash-lite", + ], + }, "OpenRouter": { "backend_type": "openai-compat", "base_url": "https://openrouter.ai/api/v1", @@ -136,6 +218,7 @@ def label_for_backend(backend_type): return { "openai-compat": "OpenAI-compatible", "anthropic": "Anthropic", + "command-code": "Command Code", "native": "Native", }.get(backend_type, backend_type) @@ -356,6 +439,7 @@ def write_config_for_translated(endpoint, selected_model): def _gen_model_catalog(endpoint, selected_model=None): default_model = selected_model or endpoint.get("default_model") + base_instr = endpoint.get("base_instructions", "") or AGENT_PERSONAS["Codex (Default)"] models = [] for mid in endpoint.get("models", []): models.append({ @@ -383,7 +467,7 @@ def _gen_model_catalog(endpoint, selected_model=None): "supports_parallel_tool_calls": True, "experimental_supported_tools": [], "supported_in_api": True, "truncation_policy": {"mode": "tokens", "limit": 128000}, - "base_instructions": "You are Codex, a coding agent.", + "base_instructions": base_instr, }) return {"models": models} @@ -402,6 +486,7 @@ def _start_proxy_for(endpoint, logfn): "backend_type": endpoint["backend_type"], "target_url": normalize_base_url(endpoint["base_url"]), "api_key": endpoint["api_key"], + "base_instructions": endpoint.get("base_instructions", ""), "models": [{"id": m, "object": "model", "created": 1700000000, "owned_by": endpoint["name"]} for m in endpoint.get("models", [])], } @@ -500,7 +585,7 @@ class LauncherWin(Gtk.Window): # header row hdr = Gtk.Box(spacing=8) vbox.pack_start(hdr, False, False, 0) - lbl = Gtk.Label(label="Codex Launcher v2.1.1") + lbl = Gtk.Label(label="Codex Launcher v2.2.0") lbl.set_use_markup(True) hdr.pack_start(lbl, False, False, 0) changelog_btn = Gtk.Button(label="Changelog") @@ -1240,9 +1325,9 @@ class EndpointMgr(Gtk.Window): sw = Gtk.ScrolledWindow() vbox.pack_start(sw, True, True, 0) - self._store = Gtk.ListStore(str, str, str, str) # name, provider, backend, default_model + self._store = Gtk.ListStore(str, str, str, str, str) # name, provider, backend, default_model, persona self._tree = Gtk.TreeView(model=self._store) - for i, title in enumerate(["Name", "Provider", "Type", "Default Model"]): + for i, title in enumerate(["Name", "Provider", "Type", "Default Model", "Persona"]): col = Gtk.TreeViewColumn(title, Gtk.CellRendererText(), text=i) col.set_resizable(True) self._tree.append_column(col) @@ -1275,7 +1360,8 @@ class EndpointMgr(Gtk.Window): for ep in data["endpoints"]: provider = ep.get("provider_preset", "Custom") bt = label_for_backend(ep["backend_type"]) - self._store.append([ep["name"], provider, bt, ep.get("default_model", "")]) + persona = persona_short_key(ep) + self._store.append([ep["name"], provider, bt, ep.get("default_model", ""), persona]) def _selected(self): sel = self._tree.get_selection() @@ -1337,7 +1423,7 @@ class EditEndpointDialog(Gtk.Dialog): self._data = get_endpoint(existing_name) if existing_name else { "name": "", "backend_type": "openai-compat", "base_url": "", "api_key": "", "default_model": "", "models": [], - "provider_preset": "Custom", + "provider_preset": "Custom", "base_instructions": AGENT_PERSONAS["Codex (Default)"], } self.set_default_size(480, 420) @@ -1369,6 +1455,7 @@ class EditEndpointDialog(Gtk.Dialog): self._combo_type = Gtk.ComboBoxText() for val, lab in [("openai-compat", "OpenAI-compatible (needs proxy)"), ("anthropic", "Anthropic (needs proxy)"), + ("command-code", "Command Code (needs proxy)"), ("native", "Native OpenAI (no proxy)")]: self._combo_type.append(val, lab) bt = self._data.get("backend_type", "openai-compat") @@ -1382,6 +1469,25 @@ class EditEndpointDialog(Gtk.Dialog): self._entry_key.set_visibility(False) add_row(4, "API Key:", self._entry_key) + self._combo_persona = Gtk.ComboBoxText() + self._persona_keys = list(AGENT_PERSONAS.keys()) + for pk in self._persona_keys: + self._combo_persona.append_text(pk) + cur_persona = persona_short_key(self._data) + if cur_persona in self._persona_keys: + self._combo_persona.set_active(self._persona_keys.index(cur_persona)) + else: + self._combo_persona.set_active(0) + self._combo_persona.connect("changed", lambda c: self._on_persona_changed()) + add_row(5, "Agent Persona:", self._combo_persona) + + self._persona_preview = Gtk.Label() + self._persona_preview.set_line_wrap(True) + self._persona_preview.set_max_width_chars(60) + self._persona_preview.set_markup(f"{AGENT_PERSONAS['Codex (Default)'][:PERSONA_DISPLAY_LEN]}...") + self._on_persona_changed() + grid.attach(self._persona_preview, 0, 6, 2, 1) + # Models mlbl = Gtk.Label(label="Models:", xalign=0) area.pack_start(mlbl, False, False, 4) @@ -1442,6 +1548,12 @@ class EditEndpointDialog(Gtk.Dialog): self.connect("response", self._on_response) self.show_all() + def _on_persona_changed(self): + key = self._combo_persona.get_active_text() + text = AGENT_PERSONAS.get(key, "") + short = text[:PERSONA_DISPLAY_LEN] + ("..." if len(text) > PERSONA_DISPLAY_LEN else "") + self._persona_preview.set_markup(f"{short}") + def _add_model(self): m = normalize_model_id(self._entry_model.get_text()) if m: @@ -1574,9 +1686,11 @@ class EditEndpointDialog(Gtk.Dialog): self._show_error(f'Endpoint "{name}" already exists') return + persona_key = self._combo_persona.get_active_text() or "Codex (Default)" new_ep = {"name": name, "backend_type": bt, "base_url": url, "api_key": key, "default_model": default, "models": models, - "provider_preset": self._combo_preset.get_active_text() or "Custom"} + "provider_preset": self._combo_preset.get_active_text() or "Custom", + "base_instructions": AGENT_PERSONAS.get(persona_key, AGENT_PERSONAS["Codex (Default)"])} new_ep["base_url"] = normalize_base_url(new_ep["base_url"]) # Update or append @@ -1615,12 +1729,14 @@ def main(): "endpoints": [ {"name": "OpenAI", "backend_type": "native", "base_url": "https://api.openai.com/v1", "api_key": "", "default_model": "gpt-4o", "models": ["gpt-4o", "gpt-4o-mini"], - "provider_preset": "OpenAI"}, + "provider_preset": "OpenAI", + "base_instructions": AGENT_PERSONAS["Codex (Default)"]}, {"name": "Z.AI", "backend_type": "openai-compat", "base_url": "https://api.z.ai/api/coding/paas/v4", "api_key": "", "default_model": "glm-5.1", "models": ["glm-4.5", "glm-4.5-air", "glm-4.6", "glm-4.7", "glm-5", "glm-5-turbo", "glm-5.1"], - "provider_preset": "Custom"}, + "provider_preset": "Custom", + "base_instructions": AGENT_PERSONAS["Codex (Default)"]}, ], }) diff --git a/src/translate-proxy.py b/src/translate-proxy.py index adb1e05..c9fc748 100755 --- a/src/translate-proxy.py +++ b/src/translate-proxy.py @@ -30,7 +30,7 @@ def load_config(): p = argparse.ArgumentParser(description="Responses API translation proxy") p.add_argument("--config", help="JSON config file path") p.add_argument("--port", type=int, default=None) - p.add_argument("--backend", default=None, choices=["openai-compat", "anthropic"]) + p.add_argument("--backend", default=None, choices=["openai-compat", "anthropic", "command-code"]) p.add_argument("--target-url", default=None) p.add_argument("--api-key", default=None) p.add_argument("--models-file", default=None, help="JSON file with model list array") @@ -80,6 +80,7 @@ BACKEND = CONFIG["backend_type"] TARGET_URL = CONFIG["target_url"].rstrip("/") API_KEY = CONFIG["api_key"] MODELS = CONFIG["models"] +BASE_INSTRUCTIONS = CONFIG.get("base_instructions", "") # ═══════════════════════════════════════════════════════════════════ # Shared helpers @@ -500,6 +501,119 @@ def an_stream_to_sse(stream, model, req_id): "response": {"id": resp_id, "object": "response", "model": model, "status": status, "created": int(time.time()), "output": completed}}) +_DEFAULT_CC_CONFIG = { + "workingDir": "/tmp", + "date": "", + "environment": "linux", + "shell": "bash", + "files": [], + "structure": [], + "isGitRepo": False, + "currentBranch": "", + "mainBranch": "", + "gitStatus": "", + "recentCommits": [], +} + +def _cc_config(): + cfg = dict(_DEFAULT_CC_CONFIG) + cfg["date"] = time.strftime("%Y-%m-%d") + return cfg + +def cc_input_to_messages(input_data): + return oa_input_to_messages(input_data) + +def cc_convert_tools(tools): + return oa_convert_tools(tools) + +def cc_resp_to_responses(cc_lines, model, resp_id=None): + text = "" + usage = {} + for line in cc_lines: + try: + d = json.loads(line) + except (json.JSONDecodeError, TypeError): + continue + t = d.get("type", "") + if t == "text-delta": + text += d.get("text", "") + elif t == "finish-step": + u = d.get("usage", {}) + usage = { + "input_tokens": u.get("inputTokens", 0), + "output_tokens": u.get("outputTokens", 0), + "total_tokens": u.get("inputTokens", 0) + u.get("outputTokens", 0), + } + outputs = [] + if text: + outputs.append({"type": "message", "id": uid("msg"), "role": "assistant", + "status": "completed", + "content": [{"type": "output_text", "text": text, "annotations": []}]}) + return {"id": resp_id or uid("resp"), "object": "response", "created": int(time.time()), + "model": model, "status": "completed", "output": outputs, + "usage": {"input_tokens": usage.get("input_tokens", 0), + "output_tokens": usage.get("output_tokens", 0), + "total_tokens": usage.get("total_tokens", 0), + "input_tokens_details": {"cached_tokens": 0}}} + +def cc_stream_to_sse(cc_stream, model, req_id): + resp_id = req_id or uid("resp") + msg_id = uid("msg") + text_buf = "" + + yield emit("response.created", {"type": "response.created", + "response": {"id": resp_id, "object": "response", "model": model, + "status": "in_progress", "created": int(time.time()), "output": []}}) + yield emit("response.in_progress", {"type": "response.in_progress", "response": {"id": resp_id}}) + yield emit("response.output_item.added", {"type": "response.output_item.added", + "item": {"type": "message", "id": msg_id, "role": "assistant", "status": "in_progress", "content": []}}) + yield emit("response.content_part.added", {"type": "response.content_part.added", + "part": {"type": "output_text", "text": "", "annotations": []}, "item_id": msg_id}) + + total_usage = {} + for raw in cc_stream: + line = raw.decode("utf-8", errors="replace").strip() + if not line: + continue + try: + d = json.loads(line) + except json.JSONDecodeError: + continue + t = d.get("type", "") + + if t == "text-delta": + txt = d.get("text", "") + if txt: + text_buf += txt + yield emit("response.output_text.delta", {"type": "response.output_text.delta", + "delta": txt, "item_id": msg_id, "content_index": 0}) + + elif t == "finish-step": + u = d.get("usage", {}) + total_usage = { + "input_tokens": u.get("inputTokens", 0), + "output_tokens": u.get("outputTokens", 0), + "total_tokens": u.get("inputTokens", 0) + u.get("outputTokens", 0), + } + + if text_buf: + yield emit("response.output_text.done", {"type": "response.output_text.done", + "text": text_buf, "item_id": msg_id, "content_index": 0}) + yield emit("response.content_part.done", {"type": "response.content_part.done", + "part": {"type": "output_text", "text": text_buf, "annotations": []}, "item_id": msg_id}) + yield emit("response.output_item.done", {"type": "response.output_item.done", + "item": {"type": "message", "id": msg_id, "role": "assistant", "status": "completed", + "content": [{"type": "output_text", "text": text_buf, "annotations": []}]}}) + + final_out = [] + if text_buf: + final_out.append({"type": "message", "id": msg_id, "role": "assistant", "status": "completed", + "content": [{"type": "output_text", "text": text_buf, "annotations": []}]}) + yield emit("response.completed", {"type": "response.completed", + "response": {"id": resp_id, "object": "response", "model": model, + "status": "completed", "created": int(time.time()), "output": final_out, + "usage": total_usage}}) + # ═══════════════════════════════════════════════════════════════════ # HTTP Server # ═══════════════════════════════════════════════════════════════════ @@ -531,6 +645,8 @@ class Handler(http.server.BaseHTTPRequestHandler): if BACKEND == "anthropic": self._handle_anthropic(body, model, stream) + elif BACKEND == "command-code": + self._handle_command_code(body, model, stream) else: self._handle_openai_compat(body, model, stream) @@ -538,6 +654,8 @@ class Handler(http.server.BaseHTTPRequestHandler): input_data = body.get("input", "") messages = oa_input_to_messages(input_data) instructions = body.get("instructions", "").strip() + if not instructions and BASE_INSTRUCTIONS: + instructions = BASE_INSTRUCTIONS if instructions: messages.insert(0, {"role": "system", "content": instructions}) chat_body = {"model": model, "messages": messages} @@ -571,6 +689,8 @@ class Handler(http.server.BaseHTTPRequestHandler): an_body = {"model": model, "messages": an_input_to_messages(input_data), "max_tokens": body.get("max_output_tokens", 8192)} instructions = body.get("instructions", "").strip() + if not instructions and BASE_INSTRUCTIONS: + instructions = BASE_INSTRUCTIONS if instructions: an_body["system"] = instructions for k in ("temperature", "top_p"): @@ -601,6 +721,76 @@ class Handler(http.server.BaseHTTPRequestHandler): lambda r: an_resp_to_responses(json.loads(r.read()), model), lambda s: an_stream_to_sse(s, model, body.get("request_id") or body.get("id"))) + def _handle_command_code(self, body, model, stream): + input_data = body.get("input", "") + instructions = body.get("instructions", "").strip() + messages = cc_input_to_messages(input_data) + if not instructions and BASE_INSTRUCTIONS: + instructions = BASE_INSTRUCTIONS + if instructions: + sys_msg = {"role": "system", "content": instructions} + messages.insert(0, sys_msg) + + cc_body = { + "config": _cc_config(), + "memory": "", + "taste": "", + "skills": "", + "params": { + "stream": True, + "max_tokens": body.get("max_output_tokens", 64000), + "temperature": body.get("temperature", 0.3), + "messages": messages, + "model": model, + "tools": cc_convert_tools(body.get("tools")) or [], + }, + "threadId": body.get("request_id") or uid("thread"), + } + + target = upstream_target(TARGET_URL, "/alpha/generate") + fwd = forwarded_headers(self.headers, { + "Content-Type": "application/json", + "Authorization": f"Bearer {API_KEY}", + "Accept": "text/event-stream, application/json", + }, browser_ua=True) + print(f"[translate-proxy] POST {target} model={model} stream={stream} [command-code]", file=sys.stderr) + req = urllib.request.Request( + target, + data=json.dumps(cc_body).encode(), + headers=fwd, + ) + + if stream: + try: + upstream = urllib.request.urlopen(req) + except urllib.error.HTTPError as e: + err = e.read().decode() + return self.send_json(e.code, {"error": {"type": "upstream_error", "message": err}}) + except Exception as e: + return self.send_json(500, {"error": {"type": "proxy_error", "message": str(e)}}) + + self.send_response(200) + self.send_header("Content-Type", "text/event-stream") + self.send_header("Cache-Control", "no-cache") + self.send_header("Connection", "keep-alive") + self.end_headers() + for event in cc_stream_to_sse(upstream, model, body.get("request_id") or body.get("id")): + self.wfile.write(event.encode("utf-8")) + self.wfile.flush() + else: + try: + upstream = urllib.request.urlopen(req) + except urllib.error.HTTPError as e: + err = e.read().decode() + return self.send_json(e.code, {"error": {"type": "upstream_error", "message": err}}) + except Exception as e: + return self.send_json(500, {"error": {"type": "proxy_error", "message": str(e)}}) + + raw = upstream.read().decode() + lines = raw.strip().split("\n") + result = cc_resp_to_responses(lines, model) + self.send_json(200, result) + def _forward(self, req, stream, model, nonstream_fn, stream_fn): try: upstream = urllib.request.urlopen(req)