machinaos 0.0.76 → 0.0.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -107
- package/client/dist/assets/ActionBar-Du2MSFSz.js +1 -0
- package/client/dist/assets/ApiKeyInput-k2LBmBjb.js +1 -0
- package/client/dist/assets/ApiKeyPanel-C_bV9U0X.js +1 -0
- package/client/dist/assets/ApiUsageSection-CmVfwZzL.js +1 -0
- package/client/dist/assets/EmailPanel-CeKIMGu-.js +1 -0
- package/client/dist/assets/OAuthPanel-KA3t3Q2K.js +1 -0
- package/client/dist/assets/QrPairingPanel-NgNpJNuk.js +1 -0
- package/client/dist/assets/RateLimitSection-Du5YNVIA.js +1 -0
- package/client/dist/assets/StatusCard-DNLyayXc.js +1 -0
- package/client/dist/assets/index-DQ0nwhec.js +257 -0
- package/client/dist/assets/index-DxmbVskS.css +1 -0
- package/client/dist/assets/vendor-flow-CZmBvHRo.js +1 -0
- package/client/dist/assets/vendor-icons-CVrPjN2Q.js +22 -0
- package/client/dist/assets/vendor-markdown-CRou3yQ5.js +62 -0
- package/client/dist/assets/vendor-misc-C4VxKHs5.js +1 -0
- package/client/dist/assets/vendor-query-SzWcOU0G.js +1 -0
- package/client/dist/assets/vendor-radix-Dnos29jG.js +56 -0
- package/client/dist/assets/vendor-react-DvWIbVx0.js +1 -0
- package/client/dist/index.html +37 -3
- package/client/index.html +28 -1
- package/client/package.json +44 -40
- package/client/src/App.tsx +2 -0
- package/client/src/Dashboard.tsx +157 -45
- package/client/src/ParameterPanel.tsx +3 -5
- package/client/src/adapters/nodeSpecToDescription.ts +1 -0
- package/client/src/assets/icons/NodeIcon.tsx +32 -0
- package/client/src/assets/icons/index.ts +4 -0
- package/client/src/assets/icons/stripe.svg +1 -0
- package/client/src/assets/icons/themedGlyphs.ts +404 -0
- package/client/src/components/AIAgentNode.tsx +77 -53
- package/client/src/components/GenericNode.tsx +34 -52
- package/client/src/components/OutputPanel.tsx +64 -147
- package/client/src/components/ParameterRenderer.tsx +5 -3
- package/client/src/components/SkillEditorModal.tsx +9 -18
- package/client/src/components/SquareNode.tsx +97 -115
- package/client/src/components/StartNode.tsx +32 -42
- package/client/src/components/SvgFilterDefs.tsx +54 -0
- package/client/src/components/TeamMonitorNode.tsx +12 -14
- package/client/src/components/ToolkitNode.tsx +35 -60
- package/client/src/components/TriggerNode.tsx +43 -77
- package/client/src/components/__tests__/CredentialsModal.test.tsx +49 -45
- package/client/src/components/credentials/CredentialsModal.tsx +98 -30
- package/client/src/components/credentials/CredentialsPalette.tsx +73 -5
- package/client/src/components/credentials/catalogueAdapter.ts +17 -1
- package/client/src/components/credentials/panels/ApiKeyPanel.tsx +102 -37
- package/client/src/components/credentials/panels/EmailPanel.tsx +7 -19
- package/client/src/components/credentials/panels/OAuthPanel.tsx +5 -1
- package/client/src/components/credentials/panels/QrPairingPanel.tsx +1 -3
- package/client/src/components/credentials/primitives/ActionBar.tsx +7 -11
- package/client/src/components/credentials/primitives/OAuthConnect.tsx +19 -28
- package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +24 -3
- package/client/src/components/credentials/types.ts +12 -2
- package/client/src/components/credentials/useCredentialPanel.ts +43 -19
- package/client/src/components/icons/AIProviderIcons.tsx +16 -0
- package/client/src/components/onboarding/OnboardingWizard.tsx +23 -63
- package/client/src/components/onboarding/nodeRoleClasses.ts +23 -0
- package/client/src/components/onboarding/steps/CanvasStep.tsx +15 -21
- package/client/src/components/onboarding/steps/ConceptsStep.tsx +2 -11
- package/client/src/components/onboarding/steps/GetStartedStep.tsx +2 -10
- package/client/src/components/parameterPanel/InputSection.tsx +9 -7
- package/client/src/components/parameterPanel/MasterSkillEditor.tsx +84 -198
- package/client/src/components/parameterPanel/MiddleSection.tsx +57 -80
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +31 -25
- package/client/src/components/parameterPanel/__tests__/InputSection.test.tsx +7 -2
- package/client/src/components/ui/AIResultModal.tsx +1 -1
- package/client/src/components/ui/CollapsibleSection.tsx +9 -5
- package/client/src/components/ui/CommandPalette.tsx +147 -0
- package/client/src/components/ui/CommandPaletteHost.tsx +189 -0
- package/client/src/components/ui/ComponentItem.tsx +13 -7
- package/client/src/components/ui/ComponentPalette.tsx +24 -13
- package/client/src/components/ui/ConsolePanel.tsx +19 -11
- package/client/src/components/ui/DropCap.tsx +28 -0
- package/client/src/components/ui/EditableNodeLabel.tsx +10 -2
- package/client/src/components/ui/InputNodesPanel.tsx +1 -1
- package/client/src/components/ui/Modal.tsx +38 -6
- package/client/src/components/ui/OutputDisplayPanel.tsx +1 -1
- package/client/src/components/ui/SettingsPanel.tsx +42 -13
- package/client/src/components/ui/StatusBar.tsx +108 -0
- package/client/src/components/ui/ThemeSwitcher.tsx +109 -0
- package/client/src/components/ui/TopToolbar.tsx +42 -25
- package/client/src/components/ui/WorkflowSidebar.tsx +32 -16
- package/client/src/components/ui/action-button.tsx +40 -15
- package/client/src/components/ui/button.tsx +24 -1
- package/client/src/components/ui/dropdown-menu.tsx +24 -2
- package/client/src/components/ui/input.tsx +19 -2
- package/client/src/components/ui/select.tsx +15 -0
- package/client/src/components/ui/textarea.tsx +15 -2
- package/client/src/contexts/AuthContext.tsx +148 -109
- package/client/src/contexts/ThemeContext.tsx +93 -17
- package/client/src/contexts/WebSocketContext.tsx +373 -206
- package/client/src/contexts/__tests__/AuthContext.test.tsx +221 -0
- package/client/src/hooks/__tests__/useDragVariable.test.ts +7 -1
- package/client/src/hooks/__tests__/useWorkflowOpsListener.test.ts +142 -0
- package/client/src/hooks/useAppTheme.ts +209 -7
- package/client/src/hooks/useAutoSkillEdges.ts +7 -2
- package/client/src/hooks/useCatalogueQuery.ts +67 -1
- package/client/src/hooks/useDragVariable.ts +1 -1
- package/client/src/hooks/useNodeAllowlist.ts +115 -8
- package/client/src/hooks/useOnboarding.ts +20 -8
- package/client/src/hooks/useParameterPanel.ts +2 -1
- package/client/src/hooks/useReactFlowNodes.ts +2 -1
- package/client/src/hooks/useSound.ts +185 -0
- package/client/src/hooks/useWorkflowManagement.ts +6 -8
- package/client/src/hooks/useWorkflowOpsListener.ts +90 -0
- package/client/src/index.css +65 -3
- package/client/src/lib/__tests__/connectionConfig.test.ts +91 -0
- package/client/src/lib/aiModelProviders.ts +8 -0
- package/client/src/lib/connectionConfig.ts +107 -0
- package/client/src/lib/queryPersist.ts +13 -5
- package/client/src/lib/sound.ts +393 -0
- package/client/src/main.tsx +20 -0
- package/client/src/store/useAppStore.ts +26 -0
- package/client/src/styles/canvasAnimations.ts +37 -36
- package/client/src/styles/theme.ts +36 -20
- package/client/src/test/setup.ts +1 -0
- package/client/src/themes/atomic.css +253 -0
- package/client/src/themes/base.css +373 -0
- package/client/src/themes/cyber.css +890 -0
- package/client/src/themes/dark.css +70 -0
- package/client/src/themes/edo.css +246 -0
- package/client/src/themes/greek.css +293 -0
- package/client/src/themes/light.css +78 -0
- package/client/src/themes/plague.css +253 -0
- package/client/src/themes/renaissance.css +727 -0
- package/client/src/themes/rot.css +249 -0
- package/client/src/themes/steampunk.css +272 -0
- package/client/src/themes/surveillance.css +289 -0
- package/client/src/themes/wasteland.css +250 -0
- package/client/src/types/INodeProperties.ts +5 -0
- package/client/src/types/NodeTypes.ts +11 -1
- package/client/src/types/__tests__/cloudEvents.test.ts +99 -0
- package/client/src/types/cloudEvents.ts +78 -0
- package/client/src/vite-env.d.ts +7 -0
- package/client/tsconfig.json +1 -1
- package/client/vite.config.js +62 -2
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/machina/commands/build.py +51 -7
- package/machina/pyproject.toml +4 -0
- package/machina/supervisor.py +12 -2
- package/machina/tree.py +71 -21
- package/package.json +4 -4
- package/scripts/install.js +16 -1
- package/server/config/ai_cli_providers.json +54 -0
- package/server/config/credential_providers.json +109 -2
- package/server/config/llm_defaults.json +24 -0
- package/server/config/model_registry.json +338 -499
- package/server/config/node_allowlist.json +16 -1
- package/server/config/pricing.json +8 -0
- package/server/constants.py +38 -15
- package/server/core/container.py +2 -2
- package/server/core/credentials_database.py +35 -2
- package/server/core/logging.py +4 -3
- package/server/main.py +99 -13
- package/server/models/node_metadata.py +1 -0
- package/server/nodejs/package.json +8 -6
- package/server/nodejs/src/index.ts +22 -5
- package/server/nodes/README.md +31 -4
- package/server/nodes/agent/_inline.py +2 -0
- package/server/nodes/agent/_specialized.py +6 -3
- package/server/nodes/agent/ai_agent.py +13 -3
- package/server/nodes/agent/chat_agent.py +6 -3
- package/server/nodes/agent/claude_code_agent.py +287 -75
- package/server/nodes/agent/codex_agent.py +239 -0
- package/server/nodes/agent/deep_agent.py +3 -3
- package/server/nodes/agent/rlm_agent.py +3 -3
- package/server/nodes/android/__init__.py +31 -1
- package/server/nodes/android/_base.py +9 -5
- package/server/{services/android_service.py → nodes/android/_dispatcher.py} +2 -2
- package/server/nodes/android/_handlers.py +154 -0
- package/server/nodes/android/_option_loaders.py +44 -0
- package/server/nodes/android/_refresh.py +127 -0
- package/server/{services/android → nodes/android/_relay}/client.py +4 -4
- package/server/{routers/android.py → nodes/android/_router.py} +27 -8
- package/server/nodes/browser/browser.py +2 -2
- package/server/nodes/code/_base.py +6 -2
- package/server/nodes/code/_claude_code.py +134 -0
- package/server/nodes/document/embedding_generator.py +3 -3
- package/server/nodes/document/http_scraper.py +3 -3
- package/server/nodes/document/vector_store.py +5 -5
- package/server/nodes/email/__init__.py +11 -1
- package/server/nodes/email/_filters.py +21 -0
- package/server/{services/himalaya_service.py → nodes/email/_himalaya.py} +6 -10
- package/server/{services/email_service.py → nodes/email/_service.py} +9 -13
- package/server/nodes/email/email_read.py +1 -1
- package/server/nodes/email/email_receive.py +54 -5
- package/server/nodes/email/email_send.py +1 -1
- package/server/nodes/filesystem/shell.py +24 -1
- package/server/nodes/google/__init__.py +55 -1
- package/server/{services/handlers/google_auth.py → nodes/google/_auth_helper.py} +8 -5
- package/server/nodes/google/_base.py +2 -2
- package/server/nodes/google/_credentials.py +5 -5
- package/server/nodes/google/_filters.py +25 -0
- package/server/nodes/google/_handlers.py +57 -0
- package/server/{services/google_oauth.py → nodes/google/_oauth.py} +195 -162
- package/server/nodes/google/_option_loaders.py +107 -0
- package/server/nodes/google/_refresh.py +66 -0
- package/server/nodes/google/_router.py +131 -0
- package/server/nodes/google/gmail_receive.py +41 -4
- package/server/nodes/groups.py +1 -0
- package/server/nodes/location/_credentials.py +45 -1
- package/server/{services/maps.py → nodes/location/_service.py} +18 -3
- package/server/nodes/location/gmaps_create.py +4 -4
- package/server/nodes/location/gmaps_locations.py +4 -4
- package/server/nodes/location/gmaps_nearby_places.py +4 -4
- package/server/nodes/model/_base.py +8 -3
- package/server/nodes/model/_credentials.py +96 -8
- package/server/nodes/model/_local_validator.py +345 -0
- package/server/nodes/model/lmstudio_chat_model.py +23 -0
- package/server/nodes/model/ollama_chat_model.py +25 -0
- package/server/nodes/proxy/_usage.py +2 -2
- package/server/nodes/proxy/proxy_config.py +14 -14
- package/server/nodes/proxy/proxy_request.py +4 -4
- package/server/nodes/scraper/_credentials.py +29 -1
- package/server/nodes/scraper/apify_actor.py +9 -9
- package/server/nodes/scraper/crawlee_scraper.py +5 -5
- package/server/nodes/search/brave_search.py +4 -0
- package/server/nodes/search/perplexity_search.py +9 -0
- package/server/nodes/search/serper_search.py +3 -0
- package/server/nodes/skill/simple_memory.py +12 -0
- package/server/nodes/social/_base.py +2 -2
- package/server/nodes/stripe/__init__.py +46 -0
- package/server/nodes/stripe/_credentials.py +33 -0
- package/server/nodes/stripe/_handlers.py +270 -0
- package/server/nodes/stripe/_install.py +127 -0
- package/server/nodes/stripe/_source.py +174 -0
- package/server/nodes/stripe/stripe_action.py +81 -0
- package/server/nodes/stripe/stripe_receive.py +92 -0
- package/server/nodes/telegram/_credentials.py +52 -1
- package/server/nodes/telegram/_handlers.py +19 -18
- package/server/nodes/telegram/_service.py +134 -32
- package/server/nodes/telegram/telegram_send.py +5 -6
- package/server/nodes/text/file_handler.py +2 -2
- package/server/nodes/text/text_generator.py +2 -2
- package/server/nodes/tool/agent_builder.py +630 -0
- package/server/nodes/tool/task_manager.py +144 -2
- package/server/nodes/twitter/__init__.py +38 -1
- package/server/nodes/twitter/_base.py +7 -7
- package/server/nodes/twitter/_credentials.py +1 -1
- package/server/nodes/twitter/_filters.py +37 -0
- package/server/nodes/twitter/_handlers.py +77 -0
- package/server/nodes/twitter/_oauth.py +124 -0
- package/server/nodes/twitter/_refresh.py +78 -0
- package/server/nodes/twitter/_router.py +29 -0
- package/server/nodes/twitter/twitter_receive.py +4 -0
- package/server/nodes/visuals.json +64 -19
- package/server/nodes/whatsapp/__init__.py +45 -5
- package/server/nodes/whatsapp/_base.py +3 -3
- package/server/nodes/whatsapp/_filters.py +137 -0
- package/server/nodes/whatsapp/_handlers.py +167 -0
- package/server/nodes/whatsapp/_option_loaders.py +68 -0
- package/server/nodes/whatsapp/_refresh.py +62 -0
- package/server/nodes/whatsapp/_runtime.py +1 -1
- package/server/pyproject.toml +29 -7
- package/server/routers/schemas.py +2 -2
- package/server/routers/webhook.py +26 -9
- package/server/routers/websocket.py +149 -810
- package/server/services/ai.py +89 -8
- package/server/services/auth.py +220 -43
- package/server/services/claude_oauth.py +126 -100
- package/server/services/cli_agent/__init__.py +78 -0
- package/server/services/cli_agent/_handlers.py +237 -0
- package/server/services/cli_agent/config.py +112 -0
- package/server/services/cli_agent/factory.py +48 -0
- package/server/services/cli_agent/lockfile.py +141 -0
- package/server/services/cli_agent/mcp_server.py +482 -0
- package/server/services/cli_agent/protocol.py +173 -0
- package/server/services/cli_agent/providers/__init__.py +9 -0
- package/server/services/cli_agent/providers/anthropic_claude.py +419 -0
- package/server/services/cli_agent/providers/google_gemini.py +80 -0
- package/server/services/cli_agent/providers/openai_codex.py +310 -0
- package/server/services/cli_agent/service.py +607 -0
- package/server/services/cli_agent/session.py +618 -0
- package/server/services/cli_agent/types.py +227 -0
- package/server/services/cli_agent/workflow_tools.py +233 -0
- package/server/services/credential_registry.py +26 -1
- package/server/services/deployment/manager.py +26 -145
- package/server/services/deployment/poll_registry.py +59 -0
- package/server/services/event_waiter.py +76 -246
- package/server/services/events/__init__.py +54 -0
- package/server/services/events/cli.py +78 -0
- package/server/services/events/daemon.py +163 -0
- package/server/services/events/envelope.py +281 -0
- package/server/services/events/lifecycle.py +99 -0
- package/server/services/events/oauth_lifecycle.py +534 -0
- package/server/services/events/polling.py +60 -0
- package/server/services/events/push.py +36 -0
- package/server/services/events/source.py +63 -0
- package/server/services/events/triggers.py +118 -0
- package/server/services/events/verifiers/__init__.py +25 -0
- package/server/services/events/verifiers/base.py +28 -0
- package/server/services/events/verifiers/github.py +25 -0
- package/server/services/events/verifiers/hmac_basic.py +32 -0
- package/server/services/events/verifiers/standard_webhooks.py +47 -0
- package/server/services/events/verifiers/stripe.py +42 -0
- package/server/services/events/webhook.py +105 -0
- package/server/services/handlers/tools.py +28 -186
- package/server/services/llm/config.py +7 -0
- package/server/services/llm/factory.py +8 -2
- package/server/services/memory/__init__.py +52 -0
- package/server/services/memory/jsonl.py +80 -0
- package/server/services/memory/markdown.py +65 -0
- package/server/services/memory/state.py +112 -0
- package/server/services/memory/vector_store.py +40 -0
- package/server/services/model_registry.py +76 -0
- package/server/services/node_allowlist.py +71 -15
- package/server/services/node_executor.py +2 -2
- package/server/services/node_output_schemas.py +21 -10
- package/server/services/node_spec.py +1 -1
- package/server/services/oauth_utils.py +1 -1
- package/server/services/plugin/__init__.py +2 -0
- package/server/services/plugin/base.py +44 -2
- package/server/services/plugin/credential.py +288 -1
- package/server/services/plugin/deps.py +105 -0
- package/server/services/plugin/edge_walker.py +12 -4
- package/server/services/plugin/oauth.py +381 -0
- package/server/services/plugin/polling.py +247 -0
- package/server/services/plugin/registry.py +145 -0
- package/server/services/plugin/singleton.py +65 -0
- package/server/services/plugin/ws.py +81 -0
- package/server/services/process_service.py +31 -2
- package/server/services/status_broadcaster.py +155 -238
- package/server/services/temporal/workflow.py +7 -7
- package/server/services/workflow.py +21 -3
- package/server/services/ws_handler_registry.py +111 -28
- package/server/skills/GUIDE.md +16 -1
- package/server/skills/assistant/agent-builder-skill/SKILL.md +166 -0
- package/server/skills/payments_agent/stripe-skill/SKILL.md +306 -0
- package/server/tests/credentials/test_auth_service.py +16 -9
- package/server/tests/credentials/test_credential_broadcasts.py +219 -0
- package/server/tests/credentials/test_google_oauth.py +6 -6
- package/server/tests/credentials/test_oauth_utils.py +1 -1
- package/server/tests/credentials/test_twitter_oauth.py +2 -2
- package/server/tests/credentials/test_websocket_handlers.py +44 -20
- package/server/tests/llm/test_factory.py +1 -0
- package/server/tests/llm/test_wiring.py +5 -1
- package/server/tests/nodes/_compat.py +24 -24
- package/server/tests/nodes/test_agent_builder.py +439 -0
- package/server/tests/nodes/test_ai_tools.py +18 -14
- package/server/tests/nodes/test_code_fs_process.py +17 -8
- package/server/tests/nodes/test_email.py +10 -9
- package/server/tests/nodes/test_google_workspace.py +2 -2
- package/server/tests/nodes/test_specialized_agents.py +100 -53
- package/server/tests/nodes/test_stripe_plugin.py +293 -0
- package/server/tests/nodes/test_telegram_social.py +4 -4
- package/server/tests/nodes/test_twitter.py +1 -1
- package/server/tests/nodes/test_web_automation.py +2 -2
- package/server/tests/nodes/test_whatsapp.py +9 -9
- package/server/tests/services/cli_agent/__init__.py +0 -0
- package/server/tests/services/cli_agent/test_mcp_server.py +432 -0
- package/server/tests/services/cli_agent/test_providers.py +358 -0
- package/server/tests/services/cli_agent/test_service.py +298 -0
- package/server/tests/services/memory/__init__.py +0 -0
- package/server/tests/services/memory/test_jsonl.py +188 -0
- package/server/tests/services/test_events.py +333 -0
- package/server/tests/test_node_spec.py +56 -16
- package/server/tests/test_plugin_helpers.py +116 -0
- package/server/tests/test_plugin_self_containment.py +486 -0
- package/server/tests/test_status_broadcasts.py +425 -0
- package/workflows/{AI Assistant_workflow-1777421105154-0m4snkzjf.json → AI Assistant_workflow-1778504793388-ou1m1tz2x.json } +70 -266
- package/workflows/{AI Employee_workflow-1777720598005-u4cm858dv.json → AI Employee_example_workflow-1777720598005-u4cm858dv.json } +112 -112
- package/workflows/Claude Assistant_workflow-1778380124051-mdibn807c.json +709 -0
- package/client/dist/assets/ActionBar-vzPpSR77.js +0 -1
- package/client/dist/assets/ApiKeyInput-Ds7AKFe8.js +0 -1
- package/client/dist/assets/ApiKeyPanel-gfblELep.js +0 -1
- package/client/dist/assets/ApiUsageSection-BMNWTe2r.js +0 -1
- package/client/dist/assets/EmailPanel-B1Om64p5.js +0 -1
- package/client/dist/assets/OAuthPanel-CXyQYGBz.js +0 -1
- package/client/dist/assets/QrPairingPanel-BgNuI1we.js +0 -1
- package/client/dist/assets/RateLimitSection-YYK8sx1T.js +0 -1
- package/client/dist/assets/StatusCard-DuYA5hJR.js +0 -1
- package/client/dist/assets/index-D9tZfgvi.js +0 -363
- package/client/dist/assets/index-al7snTkG.css +0 -1
- package/client/src/components/credentials/providers.tsx +0 -177
- package/server/routers/google.py +0 -277
- package/server/routers/maps.py +0 -142
- package/server/routers/twitter.py +0 -365
- package/server/services/claude_code_service.py +0 -106
- package/server/services/memory.py +0 -159
- package/server/services/node_option_loaders/__init__.py +0 -77
- package/server/services/node_option_loaders/android_loaders.py +0 -55
- package/server/services/node_option_loaders/google_loaders.py +0 -97
- package/server/services/node_option_loaders/whatsapp_loaders.py +0 -69
- package/server/services/twitter_oauth.py +0 -411
- package/server/services/websocket_client.py +0 -29
- /package/server/{services/android → nodes/android/_relay}/__init__.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/broadcaster.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/manager.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/protocol.py +0 -0
- /package/server/{services/browser_service.py → nodes/browser/_service.py} +0 -0
- /package/server/{services/whatsapp_service.py → nodes/whatsapp/_service.py} +0 -0
- /package/server/skills/{task_agent → assistant}/write-todos-skill/SKILL.md +0 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""OpenAI Codex CLI provider.
|
|
2
|
+
|
|
3
|
+
Sandbox-first companion to Claude. Codex is a Rust binary
|
|
4
|
+
(``codex-rs/cli``); the npm package wraps it for distribution.
|
|
5
|
+
|
|
6
|
+
Subprocess: ``codex exec --json --model <m> --sandbox <mode>
|
|
7
|
+
--ask-for-approval <when> <prompt>``
|
|
8
|
+
|
|
9
|
+
Auth: native — ``codex login`` writes ``~/.codex/auth.json``. We do NOT
|
|
10
|
+
inject ``OPENAI_API_KEY`` ourselves (the CLI reads its own auth file).
|
|
11
|
+
If the user prefers env-based auth they can set the env in their shell;
|
|
12
|
+
we inherit it via ``os.environ`` in the session.
|
|
13
|
+
|
|
14
|
+
Feature surface (v1):
|
|
15
|
+
- sandbox: read-only / workspace-write / danger-full-access
|
|
16
|
+
- ask_for_approval: untrusted / on-request / never
|
|
17
|
+
- NO sessions, NO resume, NO budget cap, NO max-turns
|
|
18
|
+
- NO IDE lockfile (Codex CLI doesn't honor it yet); session writes
|
|
19
|
+
no lockfile env var
|
|
20
|
+
|
|
21
|
+
Final event detection: best-effort by event-type matching ("complete",
|
|
22
|
+
"task_complete", "done") with stream-end fallback. The Codex CLI's JSON
|
|
23
|
+
schema isn't publicly documented, so this is fragile by design — pin
|
|
24
|
+
tests against vendored fixtures.
|
|
25
|
+
|
|
26
|
+
Cost: not exposed in JSON output. ``cost_usd=None`` always; aggregate
|
|
27
|
+
``summary.total_cost_usd`` becomes ``None`` if any task is Codex.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import json
|
|
33
|
+
import shutil
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
from typing import Any, Dict, List, Optional
|
|
36
|
+
|
|
37
|
+
from core.logging import get_logger
|
|
38
|
+
|
|
39
|
+
from services.cli_agent.config import get_provider_config
|
|
40
|
+
from services.cli_agent.protocol import CanonicalUsage
|
|
41
|
+
from services.cli_agent.types import CodexTaskSpec
|
|
42
|
+
|
|
43
|
+
logger = get_logger(__name__)
|
|
44
|
+
|
|
45
|
+
NAME = "codex"
|
|
46
|
+
|
|
47
|
+
# Best-effort markers for Codex's "stream-end" / "task complete" event.
|
|
48
|
+
# Update if/when OpenAI publishes a stable schema.
|
|
49
|
+
_FINAL_EVENT_TYPES = frozenset({
|
|
50
|
+
"complete", "task_complete", "done", "finished", "result",
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
_AUTH_ERROR_MARKERS = (
|
|
54
|
+
"OPENAI_API_KEY not set",
|
|
55
|
+
"401 Unauthorized",
|
|
56
|
+
"Authentication required",
|
|
57
|
+
"Not authenticated",
|
|
58
|
+
"Please run 'codex login'",
|
|
59
|
+
"Please run `codex login`",
|
|
60
|
+
"Invalid API key",
|
|
61
|
+
"auth/missing",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class OpenAICodexProvider:
|
|
66
|
+
"""`AICliProvider` for OpenAI's Codex CLI."""
|
|
67
|
+
|
|
68
|
+
def __init__(self) -> None:
|
|
69
|
+
cfg = get_provider_config(NAME)
|
|
70
|
+
if cfg is None:
|
|
71
|
+
raise RuntimeError(
|
|
72
|
+
f"Provider config missing for {NAME!r}. Check ai_cli_providers.json."
|
|
73
|
+
)
|
|
74
|
+
self.name = NAME
|
|
75
|
+
self.package_name = cfg.package_name
|
|
76
|
+
self.binary_name = cfg.binary_name
|
|
77
|
+
self.ide_lock_env_var = cfg.ide_lock_env_var
|
|
78
|
+
self.ide_lockfile_dir = cfg.ide_lockfile_dir
|
|
79
|
+
self._defaults = cfg.defaults
|
|
80
|
+
self._supports = cfg.supports
|
|
81
|
+
self._login_argv = cfg.login_argv
|
|
82
|
+
self._auth_status_argv = cfg.auth_status_argv
|
|
83
|
+
|
|
84
|
+
# ---- spawn surface ---------------------------------------------------
|
|
85
|
+
|
|
86
|
+
def binary_path(self) -> Path:
|
|
87
|
+
which_codex = shutil.which(self.binary_name)
|
|
88
|
+
if which_codex:
|
|
89
|
+
return Path(which_codex)
|
|
90
|
+
|
|
91
|
+
npx = shutil.which("npx")
|
|
92
|
+
if npx:
|
|
93
|
+
return Path(npx)
|
|
94
|
+
|
|
95
|
+
raise FileNotFoundError(
|
|
96
|
+
f"Neither {self.binary_name!r} nor 'npx' found in PATH. "
|
|
97
|
+
f"Install with: npm install -g {self.package_name}"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def headless_argv(
|
|
101
|
+
self,
|
|
102
|
+
task: Any, # CodexTaskSpec
|
|
103
|
+
*,
|
|
104
|
+
defaults: Dict[str, Any],
|
|
105
|
+
) -> List[str]:
|
|
106
|
+
if not isinstance(task, CodexTaskSpec):
|
|
107
|
+
raise TypeError(
|
|
108
|
+
"OpenAICodexProvider.headless_argv requires CodexTaskSpec, "
|
|
109
|
+
f"got {type(task).__name__}"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
which_codex = shutil.which(self.binary_name)
|
|
113
|
+
if which_codex:
|
|
114
|
+
argv: List[str] = [which_codex]
|
|
115
|
+
else:
|
|
116
|
+
npx = shutil.which("npx")
|
|
117
|
+
if not npx:
|
|
118
|
+
raise FileNotFoundError(
|
|
119
|
+
f"Neither {self.binary_name!r} nor 'npx' found in PATH"
|
|
120
|
+
)
|
|
121
|
+
argv = [npx, "--yes", self.package_name]
|
|
122
|
+
|
|
123
|
+
argv += ["exec", "--json"]
|
|
124
|
+
|
|
125
|
+
model = (
|
|
126
|
+
task.model
|
|
127
|
+
or defaults.get("default_model")
|
|
128
|
+
or self._defaults.get("default_model", "gpt-5.2-codex")
|
|
129
|
+
)
|
|
130
|
+
argv += ["--model", model]
|
|
131
|
+
|
|
132
|
+
sandbox = task.sandbox or defaults.get(
|
|
133
|
+
"default_sandbox",
|
|
134
|
+
self._defaults.get("default_sandbox", "workspace-write"),
|
|
135
|
+
)
|
|
136
|
+
argv += ["--sandbox", sandbox]
|
|
137
|
+
|
|
138
|
+
ask = task.ask_for_approval or defaults.get(
|
|
139
|
+
"default_ask_for_approval",
|
|
140
|
+
self._defaults.get("default_ask_for_approval", "never"),
|
|
141
|
+
)
|
|
142
|
+
argv += ["--ask-for-approval", ask]
|
|
143
|
+
|
|
144
|
+
# System prompt — Codex doesn't have a dedicated --system-prompt
|
|
145
|
+
# flag in `exec`. Prepend it to the user prompt with a clear
|
|
146
|
+
# divider when present.
|
|
147
|
+
prompt = task.prompt
|
|
148
|
+
if task.system_prompt:
|
|
149
|
+
prompt = f"<system>\n{task.system_prompt}\n</system>\n\n{task.prompt}"
|
|
150
|
+
|
|
151
|
+
argv += [prompt]
|
|
152
|
+
return argv
|
|
153
|
+
|
|
154
|
+
# ---- native auth -----------------------------------------------------
|
|
155
|
+
|
|
156
|
+
def login_argv(self) -> List[str]:
|
|
157
|
+
return list(self._login_argv) or ["codex", "login"]
|
|
158
|
+
|
|
159
|
+
def auth_status_argv(self) -> Optional[List[str]]:
|
|
160
|
+
return list(self._auth_status_argv) if self._auth_status_argv else None
|
|
161
|
+
|
|
162
|
+
def detect_auth_error(self, stderr: str, exit_code: int) -> bool:
|
|
163
|
+
if not stderr and exit_code == 0:
|
|
164
|
+
return False
|
|
165
|
+
return any(m in stderr for m in _AUTH_ERROR_MARKERS)
|
|
166
|
+
|
|
167
|
+
# ---- streaming output parsing ---------------------------------------
|
|
168
|
+
|
|
169
|
+
def parse_event(self, line: str) -> Optional[Dict[str, Any]]:
|
|
170
|
+
line = line.strip()
|
|
171
|
+
if not line:
|
|
172
|
+
return None
|
|
173
|
+
try:
|
|
174
|
+
return json.loads(line)
|
|
175
|
+
except json.JSONDecodeError:
|
|
176
|
+
return None
|
|
177
|
+
|
|
178
|
+
def is_final_event(self, event: Dict[str, Any]) -> bool:
|
|
179
|
+
return event.get("type") in _FINAL_EVENT_TYPES
|
|
180
|
+
|
|
181
|
+
def event_to_session_result(
|
|
182
|
+
self,
|
|
183
|
+
events: List[Dict[str, Any]],
|
|
184
|
+
stderr: str,
|
|
185
|
+
exit_code: int,
|
|
186
|
+
) -> Dict[str, Any]:
|
|
187
|
+
# Codex doesn't have a single canonical "result" event; treat the
|
|
188
|
+
# last assistant message as the response, with the final event
|
|
189
|
+
# (if any) for metadata.
|
|
190
|
+
final = next(
|
|
191
|
+
(e for e in reversed(events) if e.get("type") in _FINAL_EVENT_TYPES),
|
|
192
|
+
None,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
response = self._extract_response(events)
|
|
196
|
+
provider_data = self._extract_provider_data(events)
|
|
197
|
+
|
|
198
|
+
tool_calls = sum(
|
|
199
|
+
1 for e in events
|
|
200
|
+
if e.get("type") in ("tool_call", "tool_use", "function_call")
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
success = exit_code == 0
|
|
204
|
+
error: Optional[str] = None
|
|
205
|
+
if exit_code != 0:
|
|
206
|
+
error = stderr.strip()[-2000:] or f"codex exited with code {exit_code}"
|
|
207
|
+
elif not response:
|
|
208
|
+
# Codex sometimes emits no terminal event; only fail if there's
|
|
209
|
+
# nothing useful in the stream at all.
|
|
210
|
+
if not events:
|
|
211
|
+
success = False
|
|
212
|
+
error = "no events received from codex"
|
|
213
|
+
|
|
214
|
+
# session_id: Codex doesn't expose one; leave None.
|
|
215
|
+
session_id: Optional[str] = None
|
|
216
|
+
if final and isinstance(final.get("session_id"), str):
|
|
217
|
+
session_id = final["session_id"]
|
|
218
|
+
|
|
219
|
+
# duration_ms / num_turns: best-effort lookups
|
|
220
|
+
duration_ms = None
|
|
221
|
+
num_turns = None
|
|
222
|
+
if final:
|
|
223
|
+
stats = final.get("stats") or {}
|
|
224
|
+
duration_ms = stats.get("duration_ms") or final.get("duration_ms")
|
|
225
|
+
num_turns = stats.get("turns") or final.get("turns")
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
"session_id": session_id,
|
|
229
|
+
"response": response,
|
|
230
|
+
"cost_usd": None, # Codex doesn't expose cost in --json
|
|
231
|
+
"duration_ms": duration_ms,
|
|
232
|
+
"num_turns": num_turns,
|
|
233
|
+
"tool_calls": tool_calls,
|
|
234
|
+
"canonical_usage": self.canonical_usage(events),
|
|
235
|
+
"provider_data": provider_data,
|
|
236
|
+
"success": success,
|
|
237
|
+
"error": error,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
def canonical_usage(self, events: List[Dict[str, Any]]) -> CanonicalUsage:
|
|
241
|
+
# Codex doesn't expose token counts in `--json` output. Return zeros.
|
|
242
|
+
# If a future Codex schema adds usage, extract here.
|
|
243
|
+
request_count = sum(
|
|
244
|
+
1 for e in events
|
|
245
|
+
if e.get("type") in ("assistant", "message", "response")
|
|
246
|
+
)
|
|
247
|
+
return CanonicalUsage(request_count=request_count)
|
|
248
|
+
|
|
249
|
+
# ---- feature gating --------------------------------------------------
|
|
250
|
+
|
|
251
|
+
def supports(self, feature: str) -> bool:
|
|
252
|
+
return feature in self._supports
|
|
253
|
+
|
|
254
|
+
# ---- internals -------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
@staticmethod
|
|
257
|
+
def _extract_response(events: List[Dict[str, Any]]) -> str:
|
|
258
|
+
"""Best-effort: walk the event stream looking for the final
|
|
259
|
+
assistant message body."""
|
|
260
|
+
# Try in priority order: explicit `result` field on a final event,
|
|
261
|
+
# then last assistant `message`/`text`/`content`, then concatenated
|
|
262
|
+
# delta text.
|
|
263
|
+
for evt in reversed(events):
|
|
264
|
+
if evt.get("type") in _FINAL_EVENT_TYPES:
|
|
265
|
+
for key in ("result", "response", "text", "content"):
|
|
266
|
+
val = evt.get(key)
|
|
267
|
+
if isinstance(val, str) and val:
|
|
268
|
+
return val
|
|
269
|
+
msg = evt.get("message")
|
|
270
|
+
if isinstance(msg, dict):
|
|
271
|
+
val = msg.get("content") or msg.get("text")
|
|
272
|
+
if isinstance(val, str):
|
|
273
|
+
return val
|
|
274
|
+
break # one final event is enough
|
|
275
|
+
|
|
276
|
+
# Last assistant message
|
|
277
|
+
for evt in reversed(events):
|
|
278
|
+
if evt.get("type") not in ("assistant", "message"):
|
|
279
|
+
continue
|
|
280
|
+
for key in ("text", "content", "response"):
|
|
281
|
+
val = evt.get(key)
|
|
282
|
+
if isinstance(val, str) and val:
|
|
283
|
+
return val
|
|
284
|
+
msg = evt.get("message")
|
|
285
|
+
if isinstance(msg, dict):
|
|
286
|
+
val = msg.get("content") or msg.get("text")
|
|
287
|
+
if isinstance(val, str) and val:
|
|
288
|
+
return val
|
|
289
|
+
|
|
290
|
+
# Concatenate delta-style text events
|
|
291
|
+
parts: List[str] = []
|
|
292
|
+
for evt in events:
|
|
293
|
+
if evt.get("type") in ("delta", "text", "content"):
|
|
294
|
+
val = evt.get("text") or evt.get("content")
|
|
295
|
+
if isinstance(val, str):
|
|
296
|
+
parts.append(val)
|
|
297
|
+
return "".join(parts)
|
|
298
|
+
|
|
299
|
+
@staticmethod
|
|
300
|
+
def _extract_provider_data(events: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
301
|
+
"""Pull Codex-specific metadata into provider_data."""
|
|
302
|
+
data: Dict[str, Any] = {}
|
|
303
|
+
# Last assistant message — capture call_id / response_item_id
|
|
304
|
+
for evt in reversed(events):
|
|
305
|
+
if evt.get("type") in ("assistant", "message", "response"):
|
|
306
|
+
for key in ("call_id", "response_item_id", "id", "model"):
|
|
307
|
+
if key in evt and evt[key] is not None:
|
|
308
|
+
data.setdefault(key, evt[key])
|
|
309
|
+
break
|
|
310
|
+
return data
|