diff --git a/opencode.jsonc b/opencode.jsonc new file mode 100644 index 0000000..5f4948c --- /dev/null +++ b/opencode.jsonc @@ -0,0 +1,149 @@ +{ + "$schema": "https://opencode.ai/config.json", + "plugin": [ + "@openontology/opencode-palantir@0.1.5" + ], + "model": "openai/gpt-5.3-codex", + "tools": { + "palantir-mcp_*": true + }, + "mcp": { + "palantir-mcp": { + "type": "local", + "command": [ + "npx", + "-y", + "palantir-mcp", + "--foundry-api-url", + "{env:FOUNDRY_URL}" + ], + "environment": { + "FOUNDRY_TOKEN": "{env:FOUNDRY_TOKEN}" + } + } + }, + "agent": { + "foundry-librarian": { + "mode": "subagent", + "hidden": false, + "description": "Foundry exploration and context gathering (parallel-friendly) | Generated by opencode-palantir /setup-palantir-mcp. Profile: unknown. Edit palantir-mcp_* tool flags below to reject tools.", + "prompt": "You are the Foundry librarian.\n\n- Focus on exploration and context gathering.\n- Split independent exploration tasks and run them in parallel when possible.\n- Return compact summaries and cite the tool calls you ran.\n- Avoid dumping massive schemas unless explicitly asked.", + "tools": { + "get_doc_page": true, + "list_all_docs": true, + "palantir-mcp_aggregate_ontology_objects": false, + "palantir-mcp_build_datasets": false, + "palantir-mcp_clone_code_repository_locally": false, + "palantir-mcp_close_foundry_branch": false, + "palantir-mcp_close_foundry_proposal": false, + "palantir-mcp_connect_to_dev_console_app": false, + "palantir-mcp_convert_to_osdk_react": false, + "palantir-mcp_create_and_write_to_foundry_dataset": false, + "palantir-mcp_create_code_repository_pull_request": false, + "palantir-mcp_create_foundry_branch": false, + "palantir-mcp_create_foundry_proposal": false, + "palantir-mcp_create_foundry_rest_api_data_source": false, + "palantir-mcp_create_foundry_rest_api_data_source_webhook": false, + "palantir-mcp_create_or_update_foundry_link_type": false, + "palantir-mcp_create_or_update_foundry_object_type": false, + "palantir-mcp_create_python_transforms_code_repository": false, + "palantir-mcp_delete_foundry_link_type": false, + "palantir-mcp_delete_foundry_object_type": false, + "palantir-mcp_generate_new_ontology_sdk_version": false, + "palantir-mcp_get_build_status": true, + "palantir-mcp_get_compute_modules_documentation": true, + "palantir-mcp_get_custom_widget_documentation": true, + "palantir-mcp_get_foundry_dataset_schema": true, + "palantir-mcp_get_foundry_ontology_rid": true, + "palantir-mcp_get_job_status": true, + "palantir-mcp_get_ml_documentation": true, + "palantir-mcp_get_ontology_sdk_context": true, + "palantir-mcp_get_ontology_sdk_examples": true, + "palantir-mcp_get_or_create_network_egress_policy": true, + "palantir-mcp_get_platform_sdk_api_reference": true, + "palantir-mcp_get_project_imports": true, + "palantir-mcp_get_python_transforms_documentation": true, + "palantir-mcp_get_repository_context": true, + "palantir-mcp_get_resource_graph": true, + "palantir-mcp_get_typescript_v1_functions_documentation": true, + "palantir-mcp_get_typescript_v2_functions_documentation": true, + "palantir-mcp_install_sdk_package": false, + "palantir-mcp_list_platform_sdk_apis": true, + "palantir-mcp_list_resources_in_foundry_folder": true, + "palantir-mcp_query_ontology_objects": true, + "palantir-mcp_run_sql_query_on_foundry_dataset": false, + "palantir-mcp_search_dataset_builds": true, + "palantir-mcp_search_foundry_documentation": true, + "palantir-mcp_search_foundry_functions": true, + "palantir-mcp_search_foundry_ontology": true, + "palantir-mcp_view_foundry_action_type": false, + "palantir-mcp_view_foundry_branch": false, + "palantir-mcp_view_foundry_link_type": false, + "palantir-mcp_view_foundry_object_type": false, + "palantir-mcp_view_foundry_proposal": false, + "palantir-mcp_view_osdk_definition": false + } + }, + "foundry": { + "mode": "all", + "hidden": false, + "description": "Foundry execution agent (uses only enabled palantir-mcp tools) | Generated by opencode-palantir /setup-palantir-mcp. Profile: unknown. Edit palantir-mcp_* tool flags below to reject tools.", + "prompt": "You are the Foundry execution agent.\n\n- Use only enabled palantir-mcp tools.\n- Prefer working from summaries produced by @foundry-librarian.\n- Keep operations focused and deterministic.", + "tools": { + "get_doc_page": false, + "list_all_docs": false, + "palantir-mcp_aggregate_ontology_objects": false, + "palantir-mcp_build_datasets": false, + "palantir-mcp_clone_code_repository_locally": false, + "palantir-mcp_close_foundry_branch": false, + "palantir-mcp_close_foundry_proposal": false, + "palantir-mcp_connect_to_dev_console_app": true, + "palantir-mcp_convert_to_osdk_react": false, + "palantir-mcp_create_and_write_to_foundry_dataset": false, + "palantir-mcp_create_code_repository_pull_request": false, + "palantir-mcp_create_foundry_branch": false, + "palantir-mcp_create_foundry_proposal": false, + "palantir-mcp_create_foundry_rest_api_data_source": true, + "palantir-mcp_create_foundry_rest_api_data_source_webhook": true, + "palantir-mcp_create_or_update_foundry_link_type": false, + "palantir-mcp_create_or_update_foundry_object_type": false, + "palantir-mcp_create_python_transforms_code_repository": false, + "palantir-mcp_delete_foundry_link_type": false, + "palantir-mcp_delete_foundry_object_type": false, + "palantir-mcp_generate_new_ontology_sdk_version": false, + "palantir-mcp_get_build_status": true, + "palantir-mcp_get_compute_modules_documentation": true, + "palantir-mcp_get_custom_widget_documentation": true, + "palantir-mcp_get_foundry_dataset_schema": true, + "palantir-mcp_get_foundry_ontology_rid": true, + "palantir-mcp_get_job_status": true, + "palantir-mcp_get_ml_documentation": true, + "palantir-mcp_get_ontology_sdk_context": true, + "palantir-mcp_get_ontology_sdk_examples": true, + "palantir-mcp_get_or_create_network_egress_policy": true, + "palantir-mcp_get_platform_sdk_api_reference": true, + "palantir-mcp_get_project_imports": true, + "palantir-mcp_get_python_transforms_documentation": true, + "palantir-mcp_get_repository_context": true, + "palantir-mcp_get_resource_graph": true, + "palantir-mcp_get_typescript_v1_functions_documentation": true, + "palantir-mcp_get_typescript_v2_functions_documentation": true, + "palantir-mcp_install_sdk_package": false, + "palantir-mcp_list_platform_sdk_apis": true, + "palantir-mcp_list_resources_in_foundry_folder": true, + "palantir-mcp_query_ontology_objects": true, + "palantir-mcp_run_sql_query_on_foundry_dataset": false, + "palantir-mcp_search_dataset_builds": true, + "palantir-mcp_search_foundry_documentation": true, + "palantir-mcp_search_foundry_functions": true, + "palantir-mcp_search_foundry_ontology": true, + "palantir-mcp_view_foundry_action_type": false, + "palantir-mcp_view_foundry_branch": false, + "palantir-mcp_view_foundry_link_type": false, + "palantir-mcp_view_foundry_object_type": false, + "palantir-mcp_view_foundry_proposal": false, + "palantir-mcp_view_osdk_definition": false + } + } + } +} diff --git a/src/common/config.py b/src/common/config.py index 7ca43bc..8416954 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -73,6 +73,64 @@ def _load_extra_headers(env: EnvironmentSettings) -> dict[str, str]: return headers +def _load_source_credentials() -> dict[str, object]: + """Load compute module source credentials JSON, if present.""" + + path = os.environ.get("SOURCE_CREDENTIALS") + if not path: + return {} + + try: + with open(path, "r", encoding="utf-8") as handle: + data = json.load(handle) + except FileNotFoundError: + return {} + except json.JSONDecodeError as exc: + raise RuntimeError( + "Invalid JSON in SOURCE_CREDENTIALS file. Check compute module Source configuration." + ) from exc + + if not isinstance(data, dict): + return {} + + return data + + +def _get_openrouter_api_key_from_sources() -> str | None: + """Try to resolve OpenRouter API key from compute module Sources.""" + + credentials = _load_source_credentials() + + preferred_source = os.environ.get("OPENROUTER_SOURCE_API_NAME") or "OpenRouterAPIService" + preferred_field = os.environ.get("OPENROUTER_SOURCE_SECRET_FIELD") or "additionalSecretOpenRouterApiKey" + + keys = [ + preferred_field, + "additionalSecretOpenRouterApiKey", + "apiKey", + "OPENROUTER_API_KEY", + ] + + source_names: list[str] = [] + if preferred_source in credentials: + source_names.append(preferred_source) + source_names.extend([name for name in sorted(credentials.keys()) if name != preferred_source]) + + for source_name in source_names: + entry = credentials.get(source_name) + if not isinstance(entry, dict): + continue + for field in keys: + value = entry.get(field) + if isinstance(value, str): + value = value.strip() + if value: + os.environ.setdefault("OPENROUTER_API_KEY", value) + return value + + return None + + def load_llm_config() -> LLMConfig: """Load LM configuration from environment variables.""" @@ -92,8 +150,12 @@ def load_llm_config() -> LLMConfig: ) # OpenRouter provider (default) - if not env.openrouter_api_key: - raise RuntimeError("No API key found. Set OPENROUTER_API_KEY or use DSPY_PROVIDER=local.") + openrouter_api_key = env.openrouter_api_key or _get_openrouter_api_key_from_sources() + if not openrouter_api_key: + raise RuntimeError( + "No API key found. Set OPENROUTER_API_KEY, or bind a compute module Source containing " + "additionalSecretOpenRouterApiKey, or use DSPY_PROVIDER=local." + ) model_name = env.model_name or DEFAULT_MODEL # Ensure model has openrouter/ prefix for litellm provider routing @@ -102,7 +164,7 @@ def load_llm_config() -> LLMConfig: return LLMConfig( model=model_name, - api_key=env.openrouter_api_key, + api_key=openrouter_api_key, api_base=DEFAULT_OPENROUTER_BASE, headers=_load_extra_headers(env), )