diff --git a/package-lock.json b/package-lock.json index 12fafde14c..59b1726b69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "omniroute", - "version": "3.7.7", + "version": "3.7.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "omniroute", - "version": "3.7.7", + "version": "3.7.8", "hasInstallScript": true, "license": "MIT", "workspaces": [ diff --git a/package.json b/package.json index 18e9704d0d..41cec7a351 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "omniroute", - "version": "3.7.7", + "version": "3.7.8", "description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.", "type": "module", "bin": { diff --git a/scripts/scratch/categorize_ideas.py b/scripts/scratch/categorize_ideas.py new file mode 100644 index 0000000000..943939d630 --- /dev/null +++ b/scripts/scratch/categorize_ideas.py @@ -0,0 +1,39 @@ +import os +import shutil +import subprocess + +categories = { + "viable": [1718, 1731, 1764], + "need_details": [1765, 1679, 1594, 1584], + "defer": [1845, 1833, 1814, 1786, 1737, 1736, 1735, 1716, 1591, 1590, 1589, 1588, 1587], + "notfit": [1826, 1788, 1529, 1586] # 1586 is already exists, goes to notfit +} + +# Create dirs +os.makedirs("_ideia/viable/need_details", exist_ok=True) +os.makedirs("_ideia/defer", exist_ok=True) +os.makedirs("_ideia/notfit", exist_ok=True) + +files = os.listdir("_ideia") + +for f in files: + if not f.endswith(".md"): continue + num_str = f.split('-')[0] + if not num_str.isdigit(): continue + num = int(num_str) + + src = os.path.join("_ideia", f) + + if num in categories["viable"]: + dst = os.path.join("_ideia/viable", f) + elif num in categories["need_details"]: + dst = os.path.join("_ideia/viable/need_details", f) + elif num in categories["defer"]: + dst = os.path.join("_ideia/defer", f) + elif num in categories["notfit"]: + dst = os.path.join("_ideia/notfit", f) + else: + continue + + shutil.move(src, dst) + print(f"Moved {f} to {dst}") diff --git a/scripts/scratch/harvest_issues.py b/scripts/scratch/harvest_issues.py new file mode 100644 index 0000000000..649e75f511 --- /dev/null +++ b/scripts/scratch/harvest_issues.py @@ -0,0 +1,96 @@ +import json +import os +import subprocess +import re + +issues = [1845, 1833, 1826, 1814, 1788, 1786, 1765, 1764, 1737, 1736, 1735, 1731, 1718, 1716, 1679, 1594, 1591, 1590, 1589, 1588, 1587, 1586, 1584, 1529] + +os.makedirs('_ideia', exist_ok=True) + +def slugify(value): + value = re.sub(r'\[Feature\]', '', value, flags=re.IGNORECASE) + value = re.sub(r'[^\w\s-]', '', value).strip().lower() + return re.sub(r'[-\s]+', '-', value) + +for num in issues: + print(f"Processing #{num}...") + try: + res = subprocess.run( + ['gh', 'issue', 'view', str(num), '--repo', 'diegosouzapw/OmniRoute', '--json', 'number,title,labels,body,comments,createdAt,author,assignees'], + capture_output=True, text=True, check=True + ) + data = json.loads(res.stdout) + + slug = slugify(data['title']) + filename = f"_ideia/{data['number']}-{slug}.md" + + author = data['author']['login'] + created_at = data['createdAt'] + body = data['body'] + comments = data['comments'] + + participants = set([author]) + comments_str = "" + if comments: + for c in comments: + c_author = c['author']['login'] + participants.add(c_author) + comments_str += f"**@{c_author}** ({c['createdAt']}):\n{c['body']}\n---\n" + else: + comments_str = "*No comments.*" + + participants_list = "\n".join([f"- @{p}" for p in participants]) + + content = f"""# Feature: {data['title']} + +> GitHub Issue: #{data['number']} — opened by @{author} on {created_at} +> Status: 📋 Cataloged | Priority: TBD + +## 📝 Original Request + +{body} + +## 💬 Community Discussion + +{comments_str} + +### Participants + +{participants_list} + +### Key Points + +- Needs detailed analysis + +## 🎯 Refined Feature Description + +Feature needs manual refinement and interpretation to fill logical gaps and outline high-level technical scope. + +### What it solves + +- TBD + +### How it should work (high level) + +1. TBD +2. TBD + +### Affected areas + +- TBD + +## 📎 Attachments & References + +- Check issue body for references + +## 🔗 Related Ideas + +- None yet +""" + with open(filename, 'w', encoding='utf-8') as f: + f.write(content) + + except Exception as e: + print(f"Error processing {num}: {e}") + +print("Done.") diff --git a/scripts/scratch/refine_viable.py b/scripts/scratch/refine_viable.py new file mode 100644 index 0000000000..abdf86ed5d --- /dev/null +++ b/scripts/scratch/refine_viable.py @@ -0,0 +1,91 @@ +import os + +viable_data = { + 1718: { + "title": "expose upstream error details in client-facing error responses", + "solves": "Debugging upstream errors is difficult because they are hidden behind generic '[400] Error from provider' wrappers.", + "how": "1. Modify `buildErrorBody` to accept `upstream_details`.\n2. In `BaseExecutor` or specific handlers, parse the raw upstream response on failure.\n3. Propagate the parsed body to the client response inside `upstream_details`.", + "areas": "open-sse/executors/base.ts, open-sse/utils/errors.ts, src/app/api/v1/chat/completions/route.ts", + "req_summary": "Expose the upstream error body (e.g. context_length_exceeded) directly in the error response under an `upstream_details` key, without breaking OpenAI compatibility.", + "req_approach": "Modify the central error generation functions (like `buildErrorBody`) to optionally accept an `upstreamDetails` object. Update the request executors to pass the JSON parsed error from the upstream response into this new parameter when a request fails.", + "req_files": "| File | Changes |\n|---|---|\n| `open-sse/utils/errors.ts` | Update `buildErrorBody` to include `upstream_details`. |\n| `open-sse/executors/base.ts` | Extract response body on failure and pass to error builder. |", + "req_effort": "Low. A few files changed. No breaking changes." + }, + 1731: { + "title": "(combo): provider-level exhaustion tracking to skip same-provider targets", + "solves": "Combo routing wastes significant time retrying multiple targets from the same provider when the entire provider is rate-limited or quota-exhausted.", + "how": "1. Track 429 quota exhaustion errors at the provider level.\n2. In `combo.ts`, before attempting a target, check if its provider is currently marked as exhausted.\n3. If exhausted, skip the target and move to the next provider.", + "areas": "open-sse/services/combo.ts, open-sse/services/accountFallback.ts", + "req_summary": "Implement provider-level 429 exhaustion tracking in the combo router so it skips remaining targets of a provider if a 429 quota exhaustion occurs.", + "req_approach": "Add a temporary exclusion set in `handleComboChat` that tracks providers that have returned a hard 429. Before evaluating the next target in the combo, check if its provider is in the exclusion set and skip it if true.", + "req_files": "| File | Changes |\n|---|---|\n| `open-sse/services/combo.ts` | Add logic to track provider failures and skip matching targets. |\n| `open-sse/services/accountFallback.ts` | Properly bubble up the 429 status. |", + "req_effort": "Medium. Needs careful state tracking across the combo loop. No breaking changes." + }, + 1764: { + "title": "Make installation script detect termux", + "solves": "OmniRoute fails to start or install properly on Termux because `wreq-js` attempts to load a native `libgcc` arm64 module which is incompatible.", + "how": "1. Update the `postinstall` script to check `process.env.PREFIX` for termux.\n2. If termux is detected, gracefully skip or patch the wreq-js installation/loading.", + "areas": "scripts/postinstall.mjs, open-sse/executors/wreq.ts", + "req_summary": "Detect termux environments during installation or runtime and gracefully handle the `wreq-js` native module failure, allowing the rest of OmniRoute to function.", + "req_approach": "Modify `scripts/postinstall.mjs` or the wreq-js loader logic. If `process.env.PREFIX && process.env.PREFIX.includes('termux')` is true, avoid hard crashing on wreq-js load failures.", + "req_files": "| File | Changes |\n|---|---|\n| `scripts/postinstall.mjs` | Add termux detection and warning. |\n| `open-sse/utils/env.ts` (or similar) | Graceful downgrade. |", + "req_effort": "Low. Very localized fix." + } +} + +for root, _, files in os.walk("_ideia/viable"): + for f in files: + if not f.endswith(".md") or "requirements" in f: continue + num = int(f.split('-')[0]) + if num in viable_data: + path = os.path.join(root, f) + with open(path, 'r') as file: + content = file.read() + + d = viable_data[num] + + # replace TBDs + content = content.replace("### What it solves\n\n- TBD", f"### What it solves\n\n- {d['solves']}") + content = content.replace("### How it should work (high level)\n\n1. TBD\n2. TBD", f"### How it should work (high level)\n\n{d['how']}") + content = content.replace("### Affected areas\n\n- TBD", f"### Affected areas\n\n- {d['areas']}") + content = content.replace("Feature needs manual refinement and interpretation to fill logical gaps and outline high-level technical scope.", "Refined and scoped for implementation.") + + with open(path, 'w') as file: + file.write(content) + + req_content = f"""# Requirements: {d['title']} + +> Feature Idea: [#{num}](./{f}) +> Research Date: 2026-05-01 +> Verdict: ✅ VIABLE + +## 🔍 Research Summary + +{d['req_summary']} + +## 📚 Reference Implementations + +| # | Repository | Stars | Last Updated | Approach | Relevance | +| --- | ---------------- | ----- | ------------ | -------- | ------------ | +| 1 | OmniRoute Source | - | 2026-05-01 | Internal | High | + +## 📐 Proposed Solution Architecture + +### Approach + +{d['req_approach']} + +### Modified Files + +{d['req_files']} + +## ⚙️ Implementation Effort + +- **Estimated complexity**: {d['req_effort']} +- **Breaking changes**: No +""" + req_path = os.path.join(root, f.replace(".md", ".requirements.md")) + with open(req_path, 'w') as req_file: + req_file.write(req_content) + + print(f"Refined {num} and created requirements.")