mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-08-17 10:22:01 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9e0928aef | ||
|
|
652a522e50 | ||
|
|
ce1a9ce133 | ||
|
|
ae45a4f67c | ||
|
|
697eed23d4 | ||
|
|
100595f8aa | ||
|
|
dd03a55028 | ||
|
|
a72218f99f | ||
|
|
eaa76032d5 | ||
|
|
ed01ab8c8d |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "Git Clones",
|
||||
"message": "176,576",
|
||||
"message": "181,942",
|
||||
"color": "green",
|
||||
"namedLogo": "git"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"total_clones": 176576,
|
||||
"last_updated": "2026-07-29T08:35:42Z",
|
||||
"total_clones": 181942,
|
||||
"last_updated": "2026-08-05T08:31:05Z",
|
||||
"daily": {
|
||||
"2026-03-27": 2189,
|
||||
"2026-03-28": 1874,
|
||||
@@ -125,6 +125,13 @@
|
||||
"2026-07-25": 928,
|
||||
"2026-07-26": 740,
|
||||
"2026-07-27": 799,
|
||||
"2026-07-28": 665
|
||||
"2026-07-28": 665,
|
||||
"2026-07-29": 745,
|
||||
"2026-07-30": 591,
|
||||
"2026-07-31": 783,
|
||||
"2026-08-01": 567,
|
||||
"2026-08-02": 1248,
|
||||
"2026-08-03": 724,
|
||||
"2026-08-04": 708
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,15 @@ export default defineConfig({
|
||||
server: {
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/v1': process.env.VITE_API_URL || 'http://localhost:8000',
|
||||
// ws: true is required for the /v1/agents/events WebSocket. Without it
|
||||
// Vite proxies the HTTP request but not the upgrade, so the socket never
|
||||
// opens — no error, no close event, just silence — and every live agent
|
||||
// view sits empty in dev while working in a production build.
|
||||
'/v1': {
|
||||
target: process.env.VITE_API_URL || 'http://localhost:8000',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
},
|
||||
'/health': process.env.VITE_API_URL || 'http://localhost:8000',
|
||||
'/api': process.env.VITE_API_URL || 'http://localhost:8000',
|
||||
},
|
||||
|
||||
@@ -12,9 +12,18 @@ import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass, field, is_dataclass
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
get_args,
|
||||
get_origin,
|
||||
get_type_hints,
|
||||
)
|
||||
|
||||
from openjarvis.core.paths import (
|
||||
ConfigurationError,
|
||||
@@ -1710,10 +1719,16 @@ def _apply_toml_section(target: Any, section: Dict[str, Any]) -> None:
|
||||
"""Overlay TOML key/value pairs onto a dataclass instance.
|
||||
|
||||
Recursively handles nested dicts when the target attribute is itself
|
||||
a dataclass. Normalises TOML arrays to comma-separated strings — both
|
||||
for dataclass fields annotated as ``str`` and for backward-compat
|
||||
property setters that expect string input.
|
||||
a dataclass, including dict entries in lists of dataclasses. Normalises
|
||||
TOML arrays to comma-separated strings — both for dataclass fields annotated
|
||||
as ``str`` and for backward-compat property setters that expect string input.
|
||||
"""
|
||||
try:
|
||||
type_hints = get_type_hints(type(target))
|
||||
except (NameError, TypeError):
|
||||
# Some config types contain optional runtime-only forward references.
|
||||
type_hints = {}
|
||||
|
||||
for key, value in section.items():
|
||||
if hasattr(target, key):
|
||||
if isinstance(value, dict):
|
||||
@@ -1728,14 +1743,35 @@ def _apply_toml_section(target: Any, section: Dict[str, Any]) -> None:
|
||||
# property setters (e.g. reward_weights, default_tools).
|
||||
if isinstance(value, list):
|
||||
is_str_field = False
|
||||
item_dataclass = None
|
||||
if hasattr(target, "__dataclass_fields__"):
|
||||
field_obj = target.__dataclass_fields__.get(key)
|
||||
if field_obj is not None and field_obj.type in ("str", str):
|
||||
is_str_field = True
|
||||
elif field_obj is None:
|
||||
if field_obj is not None:
|
||||
field_type = type_hints.get(key, field_obj.type)
|
||||
type_args = get_args(field_type)
|
||||
if (
|
||||
get_origin(field_type) is list
|
||||
and len(type_args) == 1
|
||||
and is_dataclass(type_args[0])
|
||||
):
|
||||
item_dataclass = type_args[0]
|
||||
elif field_obj.type in ("str", str):
|
||||
is_str_field = True
|
||||
else:
|
||||
# Property, not a real field — normalise to string
|
||||
is_str_field = True
|
||||
if is_str_field:
|
||||
|
||||
if item_dataclass is not None:
|
||||
converted = []
|
||||
for item in value:
|
||||
if isinstance(item, dict):
|
||||
nested = item_dataclass()
|
||||
_apply_toml_section(nested, item)
|
||||
converted.append(nested)
|
||||
else:
|
||||
converted.append(item)
|
||||
value = converted
|
||||
elif is_str_field:
|
||||
value = ",".join(str(v) for v in value)
|
||||
setattr(target, key, value)
|
||||
|
||||
|
||||
@@ -212,6 +212,7 @@ class InstrumentedEngine(InferenceEngine):
|
||||
completion_tokens=completion_tokens,
|
||||
total_tokens=prompt_tok + completion_tokens,
|
||||
latency_seconds=latency,
|
||||
cost_usd=result.get("cost_usd", 0.0),
|
||||
ttft=ttft,
|
||||
throughput_tok_per_sec=throughput,
|
||||
energy_per_output_token_joules=energy_per_output_token,
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from openjarvis.core.config import SkillsConfig, SkillSourceConfig
|
||||
from pathlib import Path
|
||||
|
||||
from openjarvis.core.config import SkillsConfig, SkillSourceConfig, load_config
|
||||
|
||||
|
||||
class TestSkillSourceConfig:
|
||||
@@ -41,3 +43,36 @@ class TestSkillsConfigWithSources:
|
||||
)
|
||||
assert len(cfg.sources) == 2
|
||||
assert cfg.sources[0].source == "hermes"
|
||||
|
||||
def test_loads_source_tables_as_config_objects(
|
||||
self, tmp_path: Path, monkeypatch
|
||||
) -> None:
|
||||
monkeypatch.setenv("OPENJARVIS_HOME", str(tmp_path / "home"))
|
||||
toml_file = tmp_path / "config.toml"
|
||||
toml_file.write_text(
|
||||
"[[skills.sources]]\n"
|
||||
'source = "hermes"\n'
|
||||
'filter = { category = ["productivity"] }\n\n'
|
||||
"[[skills.sources]]\n"
|
||||
'source = "github"\n'
|
||||
'url = "https://github.com/example/skill-library"\n'
|
||||
"auto_update = true\n"
|
||||
)
|
||||
|
||||
load_config.cache_clear()
|
||||
try:
|
||||
cfg = load_config(toml_file)
|
||||
finally:
|
||||
load_config.cache_clear()
|
||||
|
||||
assert cfg.skills.sources == [
|
||||
SkillSourceConfig(
|
||||
source="hermes",
|
||||
filter={"category": ["productivity"]},
|
||||
),
|
||||
SkillSourceConfig(
|
||||
source="github",
|
||||
url="https://github.com/example/skill-library",
|
||||
auto_update=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -71,6 +71,17 @@ class TestInstrumentedEngine:
|
||||
assert record.prompt_tokens == 10
|
||||
assert record.completion_tokens == 5
|
||||
|
||||
def test_generate_records_cost(self, mock_engine, bus):
|
||||
mock_engine.generate.return_value["cost_usd"] = 0.0015
|
||||
ie = InstrumentedEngine(mock_engine, bus)
|
||||
messages = [Message(role=Role.USER, content="Hi")]
|
||||
ie.generate(messages, model="test")
|
||||
|
||||
event = next(
|
||||
e for e in bus.history if e.event_type == EventType.TELEMETRY_RECORD
|
||||
)
|
||||
assert event.data["record"].cost_usd == pytest.approx(0.0015)
|
||||
|
||||
def test_list_models_delegates(self, mock_engine, bus):
|
||||
ie = InstrumentedEngine(mock_engine, bus)
|
||||
assert ie.list_models() == ["test-model"]
|
||||
|
||||
Reference in New Issue
Block a user