|
1 | 1 | """Agentfile parser module for parsing Agentfile configurations.""" |
2 | 2 |
|
3 | 3 | import json |
| 4 | +import os |
| 5 | +import re |
4 | 6 | from dataclasses import dataclass, field |
5 | 7 | from typing import Any, Dict, List, Optional, Union |
6 | 8 |
|
7 | 9 |
|
| 10 | +def expand_env_vars(value: str) -> str: |
| 11 | + """ |
| 12 | + Expand environment variables in a string. |
| 13 | +
|
| 14 | + Supports both ${VAR} and $VAR syntax. |
| 15 | + If environment variable is not found, returns the original placeholder. |
| 16 | +
|
| 17 | + Args: |
| 18 | + value: String that may contain environment variable references |
| 19 | +
|
| 20 | + Returns: |
| 21 | + String with environment variables expanded |
| 22 | + """ |
| 23 | + if not isinstance(value, str): |
| 24 | + return value |
| 25 | + |
| 26 | + # Pattern to match ${VAR} or $VAR (where VAR is alphanumeric + underscore) |
| 27 | + pattern = r'\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)' |
| 28 | + |
| 29 | + def replace_var(match): |
| 30 | + # Get the variable name from either group |
| 31 | + var_name = match.group(1) or match.group(2) |
| 32 | + env_value = os.environ.get(var_name) |
| 33 | + if env_value is not None: |
| 34 | + return env_value |
| 35 | + # Return the original placeholder if env var not found |
| 36 | + return match.group(0) |
| 37 | + |
| 38 | + return re.sub(pattern, replace_var, value) |
| 39 | + |
| 40 | + |
8 | 41 | @dataclass |
9 | 42 | class MCPServer: |
10 | 43 | """Represents an MCP server configuration.""" |
@@ -516,7 +549,8 @@ def _handle_secret(self, parts: List[str]): |
516 | 549 | # Check if it's an inline value: SECRET KEY value |
517 | 550 | if len(parts) >= 3: |
518 | 551 | value = ' '.join(parts[2:]) # Join all remaining parts as the value |
519 | | - secret = SecretValue(name=secret_name, value=self._unquote(value)) |
| 552 | + expanded_value = expand_env_vars(self._unquote(value)) |
| 553 | + secret = SecretValue(name=secret_name, value=expanded_value) |
520 | 554 | self.config.secrets.append(secret) |
521 | 555 | self.current_context = None |
522 | 556 | # Check if it's a context (no value, will be populated with sub-instructions) |
@@ -565,7 +599,8 @@ def _handle_secret_sub_instruction(self, instruction: str, parts: List[str]): |
565 | 599 | if len(parts) >= 2: |
566 | 600 | key = instruction.upper() |
567 | 601 | value = ' '.join(parts[1:]) |
568 | | - secret_context.values[key] = self._unquote(value) |
| 602 | + expanded_value = expand_env_vars(self._unquote(value)) |
| 603 | + secret_context.values[key] = expanded_value |
569 | 604 | else: |
570 | 605 | raise ValueError("SECRET context requires KEY VALUE format") |
571 | 606 |
|
@@ -680,14 +715,16 @@ def _handle_server_sub_instruction(self, instruction: str, parts: List[str]): |
680 | 715 | key, value = env_part.split('=', 1) # Split only on first = |
681 | 716 | key = self._unquote(key) |
682 | 717 | value = self._unquote(value) |
683 | | - server.env[key] = value |
| 718 | + expanded_value = expand_env_vars(value) |
| 719 | + server.env[key] = expanded_value |
684 | 720 | else: |
685 | 721 | raise ValueError("ENV requires KEY VALUE or KEY=VALUE") |
686 | 722 | elif len(parts) >= 3: |
687 | 723 | # Handle KEY VALUE format |
688 | 724 | key = self._unquote(parts[1]) |
689 | 725 | value = self._unquote(' '.join(parts[2:])) # Join remaining parts as value |
690 | | - server.env[key] = value |
| 726 | + expanded_value = expand_env_vars(value) |
| 727 | + server.env[key] = expanded_value |
691 | 728 | else: |
692 | 729 | raise ValueError("ENV requires KEY VALUE or KEY=VALUE") |
693 | 730 |
|
|
0 commit comments