autoforge-ai 0.1.0
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/.claude/commands/check-code.md +32 -0
- package/.claude/commands/checkpoint.md +40 -0
- package/.claude/commands/create-spec.md +613 -0
- package/.claude/commands/expand-project.md +234 -0
- package/.claude/commands/gsd-to-autoforge-spec.md +10 -0
- package/.claude/commands/review-pr.md +75 -0
- package/.claude/templates/app_spec.template.txt +331 -0
- package/.claude/templates/coding_prompt.template.md +265 -0
- package/.claude/templates/initializer_prompt.template.md +354 -0
- package/.claude/templates/testing_prompt.template.md +146 -0
- package/.env.example +64 -0
- package/LICENSE.md +676 -0
- package/README.md +423 -0
- package/agent.py +444 -0
- package/api/__init__.py +10 -0
- package/api/database.py +536 -0
- package/api/dependency_resolver.py +449 -0
- package/api/migration.py +156 -0
- package/auth.py +83 -0
- package/autoforge_paths.py +315 -0
- package/autonomous_agent_demo.py +293 -0
- package/bin/autoforge.js +3 -0
- package/client.py +607 -0
- package/env_constants.py +27 -0
- package/examples/OPTIMIZE_CONFIG.md +230 -0
- package/examples/README.md +531 -0
- package/examples/org_config.yaml +172 -0
- package/examples/project_allowed_commands.yaml +139 -0
- package/lib/cli.js +791 -0
- package/mcp_server/__init__.py +1 -0
- package/mcp_server/feature_mcp.py +988 -0
- package/package.json +53 -0
- package/parallel_orchestrator.py +1800 -0
- package/progress.py +247 -0
- package/prompts.py +427 -0
- package/pyproject.toml +17 -0
- package/rate_limit_utils.py +132 -0
- package/registry.py +614 -0
- package/requirements-prod.txt +14 -0
- package/security.py +959 -0
- package/server/__init__.py +17 -0
- package/server/main.py +261 -0
- package/server/routers/__init__.py +32 -0
- package/server/routers/agent.py +177 -0
- package/server/routers/assistant_chat.py +327 -0
- package/server/routers/devserver.py +309 -0
- package/server/routers/expand_project.py +239 -0
- package/server/routers/features.py +746 -0
- package/server/routers/filesystem.py +514 -0
- package/server/routers/projects.py +524 -0
- package/server/routers/schedules.py +356 -0
- package/server/routers/settings.py +127 -0
- package/server/routers/spec_creation.py +357 -0
- package/server/routers/terminal.py +453 -0
- package/server/schemas.py +593 -0
- package/server/services/__init__.py +36 -0
- package/server/services/assistant_chat_session.py +496 -0
- package/server/services/assistant_database.py +304 -0
- package/server/services/chat_constants.py +57 -0
- package/server/services/dev_server_manager.py +557 -0
- package/server/services/expand_chat_session.py +399 -0
- package/server/services/process_manager.py +657 -0
- package/server/services/project_config.py +475 -0
- package/server/services/scheduler_service.py +683 -0
- package/server/services/spec_chat_session.py +502 -0
- package/server/services/terminal_manager.py +756 -0
- package/server/utils/__init__.py +1 -0
- package/server/utils/process_utils.py +134 -0
- package/server/utils/project_helpers.py +32 -0
- package/server/utils/validation.py +54 -0
- package/server/websocket.py +903 -0
- package/start.py +456 -0
- package/ui/dist/assets/index-8W_wmZzz.js +168 -0
- package/ui/dist/assets/index-B47Ubhox.css +1 -0
- package/ui/dist/assets/vendor-flow-CVNK-_lx.js +7 -0
- package/ui/dist/assets/vendor-query-BUABzP5o.js +1 -0
- package/ui/dist/assets/vendor-radix-DTNNCg2d.js +45 -0
- package/ui/dist/assets/vendor-react-qkC6yhPU.js +1 -0
- package/ui/dist/assets/vendor-utils-COeKbHgx.js +2 -0
- package/ui/dist/assets/vendor-xterm-DP_gxef0.js +16 -0
- package/ui/dist/index.html +23 -0
- package/ui/dist/ollama.png +0 -0
- package/ui/dist/vite.svg +6 -0
- package/ui/package.json +57 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rate Limit Utilities
|
|
3
|
+
====================
|
|
4
|
+
|
|
5
|
+
Shared utilities for detecting and handling API rate limits.
|
|
6
|
+
Used by both agent.py (production) and test_rate_limit_utils.py (tests).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import random
|
|
10
|
+
import re
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
# Regex patterns for rate limit detection (used in both exception messages and response text)
|
|
14
|
+
# These patterns use word boundaries to avoid false positives like "PR #429" or "please wait while I..."
|
|
15
|
+
RATE_LIMIT_REGEX_PATTERNS = [
|
|
16
|
+
r"\brate[_\s]?limit", # "rate limit", "rate_limit", "ratelimit"
|
|
17
|
+
r"\btoo\s+many\s+requests", # "too many requests"
|
|
18
|
+
r"\bhttp\s*429\b", # "http 429", "http429"
|
|
19
|
+
r"\bstatus\s*429\b", # "status 429", "status429"
|
|
20
|
+
r"\berror\s*429\b", # "error 429", "error429"
|
|
21
|
+
r"\b429\s+too\s+many", # "429 too many"
|
|
22
|
+
r"\b(?:server|api|system)\s+(?:is\s+)?overloaded\b", # "server is overloaded", "api overloaded"
|
|
23
|
+
r"\bquota\s*exceeded\b", # "quota exceeded"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
# Compiled regex for efficient matching
|
|
27
|
+
_RATE_LIMIT_REGEX = re.compile(
|
|
28
|
+
"|".join(RATE_LIMIT_REGEX_PATTERNS),
|
|
29
|
+
re.IGNORECASE
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def parse_retry_after(error_message: str) -> Optional[int]:
|
|
34
|
+
"""
|
|
35
|
+
Extract retry-after seconds from various error message formats.
|
|
36
|
+
|
|
37
|
+
Handles common formats:
|
|
38
|
+
- "Retry-After: 60"
|
|
39
|
+
- "retry after 60 seconds"
|
|
40
|
+
- "try again in 5 seconds"
|
|
41
|
+
- "30 seconds remaining"
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
error_message: The error message to parse
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Seconds to wait, or None if not parseable.
|
|
48
|
+
"""
|
|
49
|
+
# Patterns require explicit "seconds" or "s" unit, OR no unit at all (end of string/sentence)
|
|
50
|
+
# This prevents matching "30 minutes" or "1 hour" since those have non-seconds units
|
|
51
|
+
patterns = [
|
|
52
|
+
r"retry.?after[:\s]+(\d+)\s*(?:seconds?|s\b)", # Requires seconds unit
|
|
53
|
+
r"retry.?after[:\s]+(\d+)(?:\s*$|\s*[,.])", # Or end of string/sentence
|
|
54
|
+
r"try again in\s+(\d+)\s*(?:seconds?|s\b)", # Requires seconds unit
|
|
55
|
+
r"try again in\s+(\d+)(?:\s*$|\s*[,.])", # Or end of string/sentence
|
|
56
|
+
r"(\d+)\s*seconds?\s*(?:remaining|left|until)",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
for pattern in patterns:
|
|
60
|
+
match = re.search(pattern, error_message, re.IGNORECASE)
|
|
61
|
+
if match:
|
|
62
|
+
return int(match.group(1))
|
|
63
|
+
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def is_rate_limit_error(error_message: str) -> bool:
|
|
68
|
+
"""
|
|
69
|
+
Detect if an error message indicates a rate limit.
|
|
70
|
+
|
|
71
|
+
Uses regex patterns with word boundaries to avoid false positives
|
|
72
|
+
like "PR #429", "please wait while I...", or "Node v14.29.0".
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
error_message: The error message to check
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
True if the message indicates a rate limit, False otherwise.
|
|
79
|
+
"""
|
|
80
|
+
return bool(_RATE_LIMIT_REGEX.search(error_message))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def calculate_rate_limit_backoff(retries: int) -> int:
|
|
84
|
+
"""
|
|
85
|
+
Calculate exponential backoff with jitter for rate limits.
|
|
86
|
+
|
|
87
|
+
Base formula: min(15 * 2^retries, 3600)
|
|
88
|
+
Jitter: adds 0-30% random jitter to prevent thundering herd.
|
|
89
|
+
Base sequence: ~15-20s, ~30-40s, ~60-78s, ~120-156s, ...
|
|
90
|
+
|
|
91
|
+
The lower starting delay (15s vs 60s) allows faster recovery from
|
|
92
|
+
transient rate limits, while jitter prevents synchronized retries
|
|
93
|
+
when multiple agents hit limits simultaneously.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
retries: Number of consecutive rate limit retries (0-indexed)
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Delay in seconds (clamped to 1-3600 range, with jitter)
|
|
100
|
+
"""
|
|
101
|
+
base = int(min(max(15 * (2 ** retries), 1), 3600))
|
|
102
|
+
jitter = random.uniform(0, base * 0.3)
|
|
103
|
+
return int(base + jitter)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def calculate_error_backoff(retries: int) -> int:
|
|
107
|
+
"""
|
|
108
|
+
Calculate linear backoff for non-rate-limit errors.
|
|
109
|
+
|
|
110
|
+
Formula: min(30 * retries, 300) - caps at 5 minutes
|
|
111
|
+
Sequence: 30s, 60s, 90s, 120s, ... 300s
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
retries: Number of consecutive error retries (1-indexed)
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Delay in seconds (clamped to 1-300 range)
|
|
118
|
+
"""
|
|
119
|
+
return min(max(30 * retries, 1), 300)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def clamp_retry_delay(delay_seconds: int) -> int:
|
|
123
|
+
"""
|
|
124
|
+
Clamp a retry delay to a safe range (1-3600 seconds).
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
delay_seconds: The raw delay value
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Delay clamped to 1-3600 seconds
|
|
131
|
+
"""
|
|
132
|
+
return min(max(delay_seconds, 1), 3600)
|