Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cliff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,4 @@ mapping = [

[tag]
# regex for matching and parsing the version from a tag
pattern = "^v([0-9]+\\.[0-9]+\\.[0-9]+)$"
pattern = "^v([0-9]+\\.[0-9]+\\.[0-9]+)$"
2 changes: 1 addition & 1 deletion .codenomad/background_processes/mnh8wrrb/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,4 @@
"exitCode": 1,
"stoppedAt": "2026-04-02T09:26:12.658Z"
}
]
]
2 changes: 1 addition & 1 deletion .codenomad/background_processes/mnh9cz26/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,4 @@
"startedAt": "2026-04-02T09:25:59.367Z",
"outputSizeBytes": 0
}
]
]
2 changes: 1 addition & 1 deletion .codenomad/background_processes/mnhbc25b/index.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[]
[]
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ show_missing = true

[html]
directory = htmlcov
title = fusionAIze Gate Coverage Report
title = fusionAIze Gate Coverage Report
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@
"onAutoForward": "notify"
}
}
}
}
6 changes: 3 additions & 3 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
## What changed

-
-

## Why

-
-

## How verified

Expand All @@ -16,4 +16,4 @@

## Risk / follow-up

-
-
2 changes: 1 addition & 1 deletion .mailmap
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
typelicious <andre.lange@typelicious.com> André Lange <andre.lange@typelicious.com>

# Note: AI model contributions (e.g., Claude, GPT) are not considered human contributors
# and should not appear in contributor lists. Their commits are considered automated tooling.
# and should not appear in contributor lists. Their commits are considered automated tooling.
5 changes: 3 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ repos:
rev: '1.8.0'
hooks:
- id: bandit
args: ['-c', 'pyproject.toml']
args: ['-c', 'pyproject.toml', '-ll', '-ii']
files: '^faigate/'
additional_dependencies: ['bandit[toml]']

# Conventional commits
- repo: https://github.com/qoomon/git-conventional-commits
rev: v2.6.3
hooks:
- id: conventional-commits
stages: [commit-msg]
stages: [commit-msg]
2 changes: 1 addition & 1 deletion .understand-anything/knowledge-graph.json
Original file line number Diff line number Diff line change
Expand Up @@ -897,4 +897,4 @@
]
}
]
}
}
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
- Dashboard links include matching filters for seamless CLI→dashboard navigation
- **Safe config workflows**: New `faigate-config` CLI for config management
- `preview`: Preview config changes before applying
- `diff`: Show detailed config differences
- `diff`: Show detailed config differences
- `apply`: Apply config changes with backup and confirmation
- `validate`: Validate config syntax and structure
- **Clipboard integration**: `--copy` flag copies dashboard URLs to clipboard (macOS/Linux/Windows)
Expand Down
85 changes: 0 additions & 85 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1156,91 +1156,6 @@ providers:
# timeout:
# connect_s: 10
# read_s: 90

client_profiles:
enabled: true
default: generic
presets: ["openclaw", "n8n", "cli"]
profiles:
generic: {}
cli:
routing_mode: auto
local-only:
capability_values:
local: true
n8n:
routing_mode: eco
prefer_tiers: ["cheap", "default"]
openclaw:
routing_mode: auto
prefer_tiers: ["default", "reasoning"]
opencode:
routing_mode: auto
prefer_tiers: ["default", "mid", "high", "reasoning"]
# ── faigrid CLI integrations ──────────────────────────────────────────
claude:
routing_mode: auto
prefer_tiers: ["default", "mid", "high", "reasoning"]
codex:
routing_mode: auto
prefer_providers: ["deepseek-chat", "anthropic-haiku", "gemini-flash"]
prefer_tiers: ["default", "mid"]
deepseek-cli:
routing_mode: auto
prefer_providers: ["deepseek-chat", "deepseek-reasoner", "anthropic-haiku"]
prefer_tiers: ["default", "reasoning"]
kilocode:
routing_mode: auto
prefer_tiers: ["default", "mid", "high", "reasoning"]
gemini-cli:
routing_mode: auto
prefer_providers: ["gemini-flash", "gemini-flash-lite", "gemini-pro-high"]
prefer_tiers: ["cheap", "default", "mid"]
antigravity:
routing_mode: eco
prefer_providers: ["gemini-flash-lite", "gemini-flash", "gemini-pro-high"]
prefer_tiers: ["cheap", "default"]
rules:
- profile: opencode
match:
header_contains:
x-faigate-client: ["opencode"]
- profile: claude
match:
header_contains:
x-faigate-client: ["claude", "claude-code"]
- profile: codex
match:
header_contains:
x-faigate-client: ["codex"]
- profile: deepseek-cli
match:
header_contains:
x-faigate-client: ["deepseek-cli"]
- profile: kilocode
match:
header_contains:
x-faigate-client: ["kilocode", "kilo"]
- profile: gemini-cli
match:
header_contains:
x-faigate-client: ["gemini-cli"]
- profile: antigravity
match:
header_contains:
x-faigate-client: ["antigravity"]
- profile: openclaw
match:
header_present: ["x-openclaw-source"]
- profile: n8n
match:
header_contains:
x-faigate-client: ["n8n"]
# - profile: local-only
# match:
# header_contains:
# x-faigate-profile: ["local-only", "private"]

request_hooks:
enabled: true
hooks:
Expand Down
2 changes: 1 addition & 1 deletion docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ faigate-config discover --no-scan

The discovery command checks:
- **Ollama** (localhost:11434)
- **vLLM** (localhost:8000)
- **vLLM** (localhost:8000)
- **LM Studio** (localhost:1234)
- **LiteLLM** proxy (localhost:4000)
- **Grid** integration (if available)
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/faigate-metadata-sync.cron
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Example cron entry for fusionAIze Gate metadata synchronization.
# Runs every 3 hours to pull latest provider, offering, and package metadata.
0 */3 * * * faigate /opt/faigate/scripts/sync-metadata.sh >>/var/log/faigate-metadata-sync.log 2>&1
0 */3 * * * faigate /opt/faigate/scripts/sync-metadata.sh >>/var/log/faigate-metadata-sync.log 2>&1
2 changes: 1 addition & 1 deletion docs/examples/faigate-metadata-sync.service
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ ReadWritePaths=/var/lib/faigate /opt/faigate /home/faigate/.faigate
PrivateTmp=true

[Install]
WantedBy=multi-user.target
WantedBy=multi-user.target
2 changes: 1 addition & 1 deletion docs/examples/faigate-metadata-sync.timer
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ Persistent=true
Unit=faigate-metadata-sync.service

[Install]
WantedBy=timers.target
WantedBy=timers.target
2 changes: 1 addition & 1 deletion docs/fusionAIze-project-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,4 @@ fusionAIze projects are licensed under the Apache‑2.0 license unless otherwise

---

*This template is derived from the fusionAIze Gate project and serves as the benchmark for all fusionAIze repositories.*
*This template is derived from the fusionAIze Gate project and serves as the benchmark for all fusionAIze repositories.*
4 changes: 2 additions & 2 deletions docs/process/git-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ To prevent branch sprawl and maintain repository hygiene, follow these cleanup g
```bash
# List merged branches
git branch --merged main | grep -E "^(feature|review|hotfix)/"

# List branches older than 30 days
git for-each-ref --sort=committerdate refs/heads/ \
--format='%(committerdate:short) %(refname:short)' | \
Expand All @@ -134,7 +134,7 @@ To prevent branch sprawl and maintain repository hygiene, follow these cleanup g
```bash
# Safe deletion of merged branches
git branch --merged main | grep -E "^(feature|review|hotfix)/" | xargs -n1 git branch -d

# Force deletion of stale unmerged branches (with caution)
git branch | grep -E "^(feature|review|hotfix)/" | \
while read branch; do
Expand Down
2 changes: 1 addition & 1 deletion docs/process/issue-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,4 @@ Consider creating helper scripts:
## Related Documents
- [Git Workflow](./git-workflow.md) - Branch management and cleanup
- [Roadmap](../FAIGATE-ROADMAP.md) - Product direction and release sequence
- [RELEASES.md](../../RELEASES.md) - Release process and versioning
- [RELEASES.md](../../RELEASES.md) - Release process and versioning
2 changes: 1 addition & 1 deletion faigate/assets/brand/fusionaize-app-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion faigate/assets/brand/fusionaize-logo-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion faigate/assets/brand/fusionaize-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions faigate/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@ def _normalize_policy_select(
raw_limit = normalized.get(limit_field)
if raw_limit is None:
normalized[limit_field] = None
elif isinstance(raw_limit, (int, float)) and raw_limit > 0:
elif isinstance(raw_limit, int | float) and raw_limit > 0:
normalized[limit_field] = float(raw_limit)
else:
raise ConfigError(f"Policy '{name}' field '{limit_field}' must be a positive number (USD)")
Expand Down Expand Up @@ -1555,7 +1555,7 @@ def _normalize_update_check(data: dict[str, Any]) -> dict[str, Any]:
raise ConfigError("'update_check.api_base' must be a non-empty string")

timeout_seconds = raw.get("timeout_seconds", 5)
if isinstance(timeout_seconds, bool) or not isinstance(timeout_seconds, (int, float)):
if isinstance(timeout_seconds, bool) or not isinstance(timeout_seconds, int | float):
raise ConfigError("'update_check.timeout_seconds' must be a positive number")
if timeout_seconds <= 0:
raise ConfigError("'update_check.timeout_seconds' must be positive")
Expand Down Expand Up @@ -1810,7 +1810,7 @@ def _normalize_provider_source_refresh(data: dict[str, Any]) -> dict[str, Any]:
raise ConfigError("'provider_source_refresh' must be a mapping")

timeout_seconds = raw.get("timeout_seconds", 10.0)
if isinstance(timeout_seconds, bool) or not isinstance(timeout_seconds, (int, float)):
if isinstance(timeout_seconds, bool) or not isinstance(timeout_seconds, int | float):
raise ConfigError("'provider_source_refresh.timeout_seconds' must be a number")
if float(timeout_seconds) <= 0:
raise ConfigError("'provider_source_refresh.timeout_seconds' must be positive")
Expand Down
4 changes: 2 additions & 2 deletions faigate/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def _sanitize_body_updates(updates: dict[str, Any]) -> tuple[dict[str, Any], lis
if key in {"model", "tool_choice", "user"} and not isinstance(value, str):
warnings.append(f"Ignored hook body update for '{key}' because it was not a string")
continue
if key in {"temperature"} and not isinstance(value, (int, float)):
if key in {"temperature"} and not isinstance(value, int | float):
warnings.append(f"Ignored hook body update for '{key}' because it was not numeric")
continue
if key in {"max_tokens"} and (isinstance(value, bool) or not isinstance(value, int) or value <= 0):
Expand Down Expand Up @@ -226,7 +226,7 @@ def _sanitize_routing_hints(hints: dict[str, Any]) -> tuple[dict[str, Any], list
normalized_values = [
value
for value in values
if isinstance(value, (str, bool)) and (not isinstance(value, str) or value.strip())
if isinstance(value, str | bool) and (not isinstance(value, str) or value.strip())
]
if normalized_values:
cap_values[capability.strip()] = normalized_values
Expand Down
2 changes: 1 addition & 1 deletion faigate/provider_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ def _get_provider_pricing(provider_name: str) -> dict[str, Any]:
if field in registry_pricing and registry_pricing[field]:
# Convert to float if not already
value = registry_pricing[field]
if isinstance(value, (int, float)) and value > 0 and field not in pricing: # noqa: E501
if isinstance(value, int | float) and value > 0 and field not in pricing: # noqa: E501
pricing[field] = float(value)

return pricing
Expand Down
51 changes: 23 additions & 28 deletions faigate/provider_catalog_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,26 +332,24 @@ def get_latest_availability(
where_clauses.append("source_name=?")
params.append(source_name)
where_sql = f"WHERE {' AND '.join(where_clauses)}" if where_clauses else ""
cur = self._conn.execute(
f"""
SELECT snap.provider_id, snap.route_name, snap.source_name, snap.checked_at,
snap.model_id, snap.available_for_key, snap.request_ready,
snap.verified_via, snap.last_issue_type, snap.metadata_json
FROM provider_availability_snapshots AS snap
INNER JOIN (
SELECT provider_id, route_name, source_name, MAX(checked_at) AS checked_at
FROM provider_availability_snapshots
{where_sql}
GROUP BY provider_id, route_name, source_name
) AS latest
ON snap.provider_id = latest.provider_id
AND snap.route_name = latest.route_name
AND snap.source_name = latest.source_name
AND snap.checked_at = latest.checked_at
ORDER BY snap.provider_id, snap.route_name, snap.source_name
""",
params,
_q = ( # nosec B608 – where_sql built from hardcoded column names only
f"SELECT snap.provider_id, snap.route_name, snap.source_name, snap.checked_at,"
" snap.model_id, snap.available_for_key, snap.request_ready,"
" snap.verified_via, snap.last_issue_type, snap.metadata_json"
" FROM provider_availability_snapshots AS snap"
" INNER JOIN ("
" SELECT provider_id, route_name, source_name, MAX(checked_at) AS checked_at"
" FROM provider_availability_snapshots"
f" {where_sql}"
" GROUP BY provider_id, route_name, source_name"
") AS latest"
" ON snap.provider_id = latest.provider_id"
" AND snap.route_name = latest.route_name"
" AND snap.source_name = latest.source_name"
" AND snap.checked_at = latest.checked_at"
" ORDER BY snap.provider_id, snap.route_name, snap.source_name"
)
cur = self._conn.execute(_q, params)
cols = [item[0] for item in cur.description]
rows = [dict(zip(cols, row)) for row in cur.fetchall()]
for row in rows:
Expand Down Expand Up @@ -465,16 +463,13 @@ def get_recent_change_events(
if provider_id:
where_sql = " WHERE provider_id=?"
params.append(provider_id)
cur = self._conn.execute(
f"""
SELECT provider_id, detected_at, source_kind, change_type, severity,
model_id, field_name, old_value, new_value, message
FROM provider_change_events{where_sql}
ORDER BY detected_at DESC
LIMIT ?
""",
(*params, limit),
_q = ( # nosec B608 – where_sql built from hardcoded column names only
"SELECT provider_id, detected_at, source_kind, change_type, severity,"
" model_id, field_name, old_value, new_value, message"
f" FROM provider_change_events{where_sql}"
" ORDER BY detected_at DESC LIMIT ?"
)
cur = self._conn.execute(_q, (*params, limit))
cols = [item[0] for item in cur.description]
return [dict(zip(cols, row)) for row in cur.fetchall()]

Expand Down
2 changes: 1 addition & 1 deletion faigate/vendor/uPlot.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading