jac-coder 0.2.5__tar.gz → 0.2.6__tar.gz
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.
- {jac_coder-0.2.5 → jac_coder-0.2.6}/PKG-INFO +1 -1
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/api.impl.jac +14 -1
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/api.jac +2 -1
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/cli.jac +56 -8
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/core/nodes.impl.jac +1 -1
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/infra/config.jac +33 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/cost_tracker.impl.jac +41 -8
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/cost_tracker.jac +11 -3
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/file_logger.jac +1 -1
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/prompt.jac +20 -18
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/server.jac +24 -3
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/meta/clarify_intent.impl.jac +1 -1
- jac_coder-0.2.6/jac_coder/tool/net/preview.impl.jac +597 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/net/preview.jac +16 -11
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/run/guarded.impl.jac +37 -3
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/run/guarded.jac +7 -1
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/write/checked.impl.jac +31 -1
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/write/checked.jac +2 -2
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder.egg-info/PKG-INFO +1 -1
- {jac_coder-0.2.5 → jac_coder-0.2.6}/pyproject.toml +1 -1
- jac_coder-0.2.5/jac_coder/tool/net/preview.impl.jac +0 -626
- {jac_coder-0.2.5 → jac_coder-0.2.6}/README.md +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/__init__.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/__init__.py +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/cli.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/cli_entry.py +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/core/__init__.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/core/nodes.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/core/walkers.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/core/walkers.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/infra/__init__.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/infra/config.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/infra/events.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/infra/kv.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/infra/mcp_manager.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/infra/mcp_manager.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/lib/__init__.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/lib/coder.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/lib/coder.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/__init__.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/compaction.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/context.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/context.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/events.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/memory.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/memory.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/permission.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/permission.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/prompt.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/skills.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/runtime/skills.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/serve_entry.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/__init__.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/git.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/git.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/mcp.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/mcp.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/meta/__init__.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/meta/clarify_intent.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/meta/delegation.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/meta/delegation.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/meta/task.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/meta/task.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/meta/think.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/meta/think.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/meta/todo.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/meta/todo.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/meta/validate.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/meta/validate.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/net/__init__.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/net/web.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/net/web.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/read/__init__.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/read/filesystem.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/read/filesystem.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/read/jac_analyzer.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/read/jac_analyzer.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/read/load_jac_skill.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/read/load_jac_skill.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/read/search.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/read/search.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/run/__init__.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/run/jac_tools.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/run/jac_tools.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/run/shell.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/run/shell.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/tool/write/__init__.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/util/__init__.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/util/banner.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/util/colors.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/util/md_render.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/util/sandbox.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/util/sandbox.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/util/tool_output.impl.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder/util/tool_output.jac +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder.egg-info/SOURCES.txt +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder.egg-info/dependency_links.txt +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder.egg-info/entry_points.txt +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder.egg-info/requires.txt +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/jac_coder.egg-info/top_level.txt +0 -0
- {jac_coder-0.2.5 → jac_coder-0.2.6}/setup.cfg +0 -0
|
@@ -624,14 +624,27 @@ impl clarification_response(
|
|
|
624
624
|
if not session_id {
|
|
625
625
|
return {"ok": False, "error": "session_id required"};
|
|
626
626
|
}
|
|
627
|
-
|
|
627
|
+
decision = {
|
|
628
628
|
"request_id": request_id,
|
|
629
629
|
"selected_id": selected_id,
|
|
630
630
|
"custom_text": custom_text,
|
|
631
631
|
"skipped": skipped
|
|
632
632
|
};
|
|
633
|
+
# Cross-pod delivery: write to Redis so the waiting pod finds it on its next 250ms poll.
|
|
634
|
+
# Key scoped by request_id — a late response for a timed-out question can't
|
|
635
|
+
# pollute the next clarification on the same session.
|
|
636
|
+
# TTL 130s covers the 120s gate timeout + buffer; waiting pod deletes key on pick-up.
|
|
637
|
+
if os.environ.get("JACCODER_WEB_MODE", "") {
|
|
638
|
+
import from jac_coder.infra.kv { get_kv }
|
|
639
|
+
try {
|
|
640
|
+
get_kv().set_with_ttl(_CLRFY_KEY_PREFIX + session_id + ":" + request_id, decision, 130);
|
|
641
|
+
} except Exception { ; }
|
|
642
|
+
}
|
|
643
|
+
# Same-pod delivery: only store locally if the waiter gate exists on this pod.
|
|
644
|
+
# Avoids a permanent dict entry on cross-pod responder pods (no one ever pops it there).
|
|
633
645
|
gate = _clarification_events.get(session_id);
|
|
634
646
|
if gate {
|
|
647
|
+
_clarification_decisions[session_id] = decision;
|
|
635
648
|
gate.set();
|
|
636
649
|
}
|
|
637
650
|
return {"ok": True};
|
|
@@ -43,7 +43,8 @@ import from jac_coder.tool.run.guarded {
|
|
|
43
43
|
_clear_chat_session,
|
|
44
44
|
_chat_contexts,
|
|
45
45
|
_clarification_events,
|
|
46
|
-
_clarification_decisions
|
|
46
|
+
_clarification_decisions,
|
|
47
|
+
_CLRFY_KEY_PREFIX
|
|
47
48
|
}
|
|
48
49
|
import from jac_coder.runtime.events {
|
|
49
50
|
register_event_callback,
|
|
@@ -32,7 +32,13 @@ import from jac_coder.util.colors {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
import from jac_coder.runtime.cost_tracker {
|
|
35
|
+
import from jac_coder.runtime.cost_tracker {
|
|
36
|
+
record_cost,
|
|
37
|
+
is_cost_tracking_enabled,
|
|
38
|
+
reset_request_cost,
|
|
39
|
+
mark_call_started as _ct_mark_call_started,
|
|
40
|
+
mark_call_finished as _ct_mark_call_finished
|
|
41
|
+
}
|
|
36
42
|
|
|
37
43
|
# Session-wide accumulated cost totals for the CLI /cost command.
|
|
38
44
|
# Accumulates across all turns in a single CLI session; reset by /cost reset.
|
|
@@ -87,6 +93,21 @@ with entry {
|
|
|
87
93
|
import from litellm.integrations.custom_logger { CustomLogger as _CL }
|
|
88
94
|
|
|
89
95
|
class _CostLogger(_CL) {
|
|
96
|
+
def log_pre_api_call(
|
|
97
|
+
self: _CostLogger,
|
|
98
|
+
model: object,
|
|
99
|
+
messages: object,
|
|
100
|
+
kwargs: dict
|
|
101
|
+
) {
|
|
102
|
+
if is_cost_tracking_enabled() {
|
|
103
|
+
try {
|
|
104
|
+
_cid = str(kwargs.get("litellm_call_id", "") or "");
|
|
105
|
+
_ct_mark_call_started(_cid);
|
|
106
|
+
} except Exception {
|
|
107
|
+
_ = 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
90
111
|
def log_success_event(
|
|
91
112
|
self: _CostLogger,
|
|
92
113
|
kwargs: dict,
|
|
@@ -94,16 +115,41 @@ with entry {
|
|
|
94
115
|
start_time: object,
|
|
95
116
|
end_time: object
|
|
96
117
|
) {
|
|
97
|
-
|
|
98
|
-
|
|
118
|
+
_cid = str(kwargs.get("litellm_call_id", "") or "");
|
|
119
|
+
try {
|
|
120
|
+
if is_cost_tracking_enabled() {
|
|
99
121
|
slp = kwargs.get("standard_logging_object") or {};
|
|
100
122
|
cost = float(slp.get("response_cost") or 0.0);
|
|
101
123
|
if cost > 0 {
|
|
102
|
-
record_cost(cost);
|
|
124
|
+
record_cost(cost, _cid);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} except Exception {
|
|
128
|
+
_ = 0;
|
|
129
|
+
} finally {
|
|
130
|
+
try { _ct_mark_call_finished(_cid); } except Exception { ; }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async def async_log_success_event(
|
|
134
|
+
self: _CostLogger,
|
|
135
|
+
kwargs: dict,
|
|
136
|
+
response_obj: object,
|
|
137
|
+
start_time: object,
|
|
138
|
+
end_time: object
|
|
139
|
+
) {
|
|
140
|
+
_cid = str(kwargs.get("litellm_call_id", "") or "");
|
|
141
|
+
try {
|
|
142
|
+
if is_cost_tracking_enabled() {
|
|
143
|
+
slp = kwargs.get("standard_logging_object") or {};
|
|
144
|
+
cost = float(slp.get("response_cost") or 0.0);
|
|
145
|
+
if cost > 0 {
|
|
146
|
+
record_cost(cost, _cid);
|
|
103
147
|
}
|
|
104
|
-
} except Exception {
|
|
105
|
-
_ = 0;
|
|
106
148
|
}
|
|
149
|
+
} except Exception {
|
|
150
|
+
_ = 0;
|
|
151
|
+
} finally {
|
|
152
|
+
try { _ct_mark_call_finished(_cid); } except Exception { ; }
|
|
107
153
|
}
|
|
108
154
|
}
|
|
109
155
|
|
|
@@ -114,11 +160,13 @@ with entry {
|
|
|
114
160
|
start_time: object,
|
|
115
161
|
end_time: object
|
|
116
162
|
) {
|
|
117
|
-
|
|
163
|
+
_cid = str(kwargs.get("litellm_call_id", "") or "");
|
|
164
|
+
try { _ct_mark_call_finished(_cid); } except Exception { ; }
|
|
118
165
|
}
|
|
119
166
|
}
|
|
120
167
|
|
|
121
|
-
|
|
168
|
+
_cli_cost_logger = _CostLogger();
|
|
169
|
+
litellm.callbacks.append(_cli_cost_logger);
|
|
122
170
|
} except Exception {
|
|
123
171
|
_ = 0;
|
|
124
172
|
}
|
|
@@ -152,7 +152,7 @@ Build responsive, mobile-first UIs: fluid flex/grid layouts, Tailwind responsive
|
|
|
152
152
|
## Workflow
|
|
153
153
|
1. Read .jaccoder/progress.md if it exists, update it as you go.
|
|
154
154
|
2. analyze_project(dir) for existing projects.
|
|
155
|
-
3.
|
|
155
|
+
3. Minimal main.jac rendering first, then leaf-first: services → hooks → components, wiring each into the layout as it's built. Never import a file you haven't created yet.
|
|
156
156
|
4. **If `components/ui/` exists AND jac.toml has `[jac-shadcn]` (jac-shadcn project):**
|
|
157
157
|
- Load `jac-shadcn-components` skill BEFORE writing any UI.
|
|
158
158
|
- Load `jac-cl-styling` for conditional class and cn() patterns.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os;
|
|
2
2
|
import json;
|
|
3
3
|
import threading;
|
|
4
|
+
import from contextlib { suppress }
|
|
4
5
|
import from pathlib { Path }
|
|
5
6
|
import from dotenv { load_dotenv }
|
|
6
7
|
import from byllm.lib { Model }
|
|
@@ -47,6 +48,33 @@ glob llm = SessionAwareModel(
|
|
|
47
48
|
call_params={"max_tool_result_length": 20000}
|
|
48
49
|
);
|
|
49
50
|
|
|
51
|
+
"""Warn (once per model name) when the active model lacks vision support."""
|
|
52
|
+
glob _vision_warned_models: set[str] = `set();
|
|
53
|
+
|
|
54
|
+
def _warn_if_no_vision(model_name_to_check: str) -> None {
|
|
55
|
+
if not model_name_to_check or model_name_to_check in _vision_warned_models {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
_vision_warned_models.add(model_name_to_check);
|
|
59
|
+
with suppress(Exception) {
|
|
60
|
+
import litellm;
|
|
61
|
+
import sys;
|
|
62
|
+
# Only warn for models litellm knows about — unknown models are
|
|
63
|
+
# trusted (supports_vision returns False for unknowns too).
|
|
64
|
+
known = model_name_to_check in litellm.model_cost;
|
|
65
|
+
if known and not litellm.supports_vision(model_name_to_check) {
|
|
66
|
+
sys.stderr.write(
|
|
67
|
+
f"[jac-coder] Note: model '{model_name_to_check}' does not support vision. "
|
|
68
|
+
"Browser tools will return text only — the agent cannot verify "
|
|
69
|
+
"visual outcomes (theme colors, layout, rendering). For full "
|
|
70
|
+
"visual verification, use a vision-capable model "
|
|
71
|
+
"(e.g. gpt-5.x, claude-sonnet-4.x, gemini-2.x).\n"
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
50
78
|
"""Set a thread-local model override. Fully concurrent — no locks, no global mutation."""
|
|
51
79
|
def set_session_llm(model_name_override: str, api_key_override: str = "") {
|
|
52
80
|
import logging;
|
|
@@ -55,6 +83,7 @@ def set_session_llm(model_name_override: str, api_key_override: str = "") {
|
|
|
55
83
|
logging.getLogger("jac_coder.infra.config").info(
|
|
56
84
|
f"Session LLM set: thread={threading.current_thread().name} model={model_name_override} has_key={bool(api_key_override)}"
|
|
57
85
|
);
|
|
86
|
+
_warn_if_no_vision(model_name_override);
|
|
58
87
|
}
|
|
59
88
|
|
|
60
89
|
"""Clear the thread-local model override."""
|
|
@@ -92,4 +121,8 @@ with entry {
|
|
|
92
121
|
llm.call_params = {"max_tool_result_length": 3000};
|
|
93
122
|
}
|
|
94
123
|
llm.postinit();
|
|
124
|
+
# Surface a vision-capability warning at startup if the default
|
|
125
|
+
# model can't handle the image content blocks that browser tools
|
|
126
|
+
# now return. Session-level overrides re-check via set_session_llm.
|
|
127
|
+
_warn_if_no_vision(model_name);
|
|
95
128
|
}
|
|
@@ -43,14 +43,20 @@ impl record_usage(usage_data: dict, agent: str = "main") -> None {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
impl record_cost(cost: float) -> None {
|
|
47
|
-
# Accumulate USD cost from a litellm CustomLogger callback into the
|
|
48
|
-
# per-request ContextVar state. The ContextVar propagates across threads
|
|
49
|
-
# via litellm's `executor.submit(ctx.run, success_handler)`.
|
|
46
|
+
impl record_cost(cost: float, call_id: str = "") -> None {
|
|
50
47
|
if not _enabled {
|
|
51
48
|
return;
|
|
52
49
|
}
|
|
53
|
-
state =
|
|
50
|
+
state = None;
|
|
51
|
+
if call_id {
|
|
52
|
+
with _active_calls_lock {
|
|
53
|
+
_entry = _active_calls.get(call_id);
|
|
54
|
+
state = _entry[1] if _entry is not None else None;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if state is None {
|
|
58
|
+
state = _request_state_cv.get();
|
|
59
|
+
}
|
|
54
60
|
if state is not None {
|
|
55
61
|
with _lock {
|
|
56
62
|
state["cost"] = float(state.get("cost", 0.0)) + float(cost);
|
|
@@ -93,7 +99,7 @@ impl reset_request_cost() -> None {
|
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
|
|
96
|
-
impl mark_call_started() -> None {
|
|
102
|
+
impl mark_call_started(call_id: str = "") -> None {
|
|
97
103
|
state = _request_state_cv.get();
|
|
98
104
|
if state is None {
|
|
99
105
|
return;
|
|
@@ -107,11 +113,38 @@ impl mark_call_started() -> None {
|
|
|
107
113
|
ev.clear();
|
|
108
114
|
}
|
|
109
115
|
}
|
|
116
|
+
if call_id {
|
|
117
|
+
with _active_calls_lock {
|
|
118
|
+
# Prune stale entries when map is at capacity to bound memory.
|
|
119
|
+
if len(_active_calls) >= _ACTIVE_CALLS_MAX {
|
|
120
|
+
_now = time.monotonic();
|
|
121
|
+
_stale = [];
|
|
122
|
+
for (_k, _ent) in _active_calls.items() {
|
|
123
|
+
if _now - _ent[0] > _ACTIVE_CALLS_TTL_SECONDS {
|
|
124
|
+
_stale.append(_k);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
for _k in _stale {
|
|
128
|
+
_active_calls.pop(_k, None);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
_active_calls[call_id] = (time.monotonic(), state);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
110
134
|
}
|
|
111
135
|
|
|
112
136
|
|
|
113
|
-
impl mark_call_finished() -> None {
|
|
114
|
-
state =
|
|
137
|
+
impl mark_call_finished(call_id: str = "") -> None {
|
|
138
|
+
state = None;
|
|
139
|
+
if call_id {
|
|
140
|
+
with _active_calls_lock {
|
|
141
|
+
_entry = _active_calls.pop(call_id, None);
|
|
142
|
+
state = _entry[1] if _entry is not None else None;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if state is None {
|
|
146
|
+
state = _request_state_cv.get();
|
|
147
|
+
}
|
|
115
148
|
if state is None {
|
|
116
149
|
return;
|
|
117
150
|
}
|
|
@@ -7,6 +7,7 @@ import os;
|
|
|
7
7
|
import json;
|
|
8
8
|
import threading;
|
|
9
9
|
import contextvars;
|
|
10
|
+
import time;
|
|
10
11
|
|
|
11
12
|
glob _enabled: bool = os.environ.get("JACCODER_TRACK_COST", "") in ("1", "true", "yes");
|
|
12
13
|
glob _lock = threading.Lock();
|
|
@@ -19,6 +20,13 @@ glob _request_state_cv: contextvars.ContextVar = contextvars.ContextVar(
|
|
|
19
20
|
"jaccoder_request_state", default=None
|
|
20
21
|
);
|
|
21
22
|
|
|
23
|
+
# Each entry is (insert_timestamp: float, state: dict) so stale entries
|
|
24
|
+
# can be reaped when the map approaches the size cap.
|
|
25
|
+
glob _active_calls: dict = {};
|
|
26
|
+
glob _active_calls_lock = threading.Lock();
|
|
27
|
+
glob _ACTIVE_CALLS_MAX: int = 2048;
|
|
28
|
+
glob _ACTIVE_CALLS_TTL_SECONDS: float = 300.0;
|
|
29
|
+
|
|
22
30
|
|
|
23
31
|
def is_cost_tracking_enabled() -> bool {
|
|
24
32
|
return _enabled;
|
|
@@ -35,11 +43,11 @@ def disable_cost_tracking() -> None {
|
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
def record_usage(usage_data: dict, agent: str = "main") -> None;
|
|
38
|
-
def record_cost(cost: float) -> None;
|
|
46
|
+
def record_cost(cost: float, call_id: str = "") -> None;
|
|
39
47
|
def get_request_cost() -> float;
|
|
40
48
|
def reset_request_cost() -> None;
|
|
41
|
-
def mark_call_started() -> None;
|
|
42
|
-
def mark_call_finished() -> None;
|
|
49
|
+
def mark_call_started(call_id: str = "") -> None;
|
|
50
|
+
def mark_call_finished(call_id: str = "") -> None;
|
|
43
51
|
def wait_for_pending_costs(timeout: float = 2.0) -> bool;
|
|
44
52
|
def save_to_file(filepath: str = "") -> str;
|
|
45
53
|
def get_per_call_data() -> list;
|
|
@@ -198,7 +198,7 @@ def file_log_event(session_id: str, event_type: str, data: dict) -> None {
|
|
|
198
198
|
return;
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
_log_file_handle.write(json.dumps(record) + "\n");
|
|
201
|
+
_log_file_handle.write(json.dumps(record, default=vars) + "\n");
|
|
202
202
|
|
|
203
203
|
} except Exception as e {
|
|
204
204
|
sys.stderr.write(f"[JacCoder] log write error: {e}\n");
|
|
@@ -46,26 +46,26 @@ Use absolute paths. Load the `jac-scaffold` skill for the full template list and
|
|
|
46
46
|
|
|
47
47
|
## Iteration Budget
|
|
48
48
|
You have limited iterations. Prioritize a WORKING app over a perfect one:
|
|
49
|
-
- Build breadth-first: get
|
|
49
|
+
- Build breadth-first: get a working end-to-end skeleton rendering before polishing any single file.
|
|
50
50
|
- If a component has errors after 2 fix attempts, write a minimal working version and move on.
|
|
51
51
|
- Target: app starts and renders by iteration ~50. Remaining iterations for testing and fixes.
|
|
52
52
|
- Never spend 10+ iterations debugging one file while the rest of the app is unbuilt.
|
|
53
53
|
|
|
54
54
|
## Build Workflow
|
|
55
55
|
0. **Inspect the workspace** (per Step 0 above) — decide EXTEND vs CREATE before anything else.
|
|
56
|
-
1. Load
|
|
56
|
+
1. Load skills from `<jac-skills-available>` via `load_jac_skill(name)`: **always start with `jac-core-cheatsheet`**, then load the task-specific skills (fullstack + anything the task touches).
|
|
57
57
|
2. Create or update `.jaccoder/progress.md` with the plan.
|
|
58
|
-
3. Backend services (.sv.jac)
|
|
58
|
+
3. Backend services (.sv.jac), then a minimal main.jac that renders a real layout (not a stub) — your live-preview shell; it grows as you wire in components.
|
|
59
59
|
4. Tailwind v4 — jac.toml MUST have ALL four of these (missing any one breaks the build silently):
|
|
60
60
|
[dependencies.npm.dev]: `tailwindcss = "latest"` and `"@tailwindcss/vite" = "latest"`
|
|
61
61
|
[plugins.client.vite]: `plugins = ["tailwindcss()"]` AND `lib_imports = ["import tailwindcss from '@tailwindcss/vite'"]`
|
|
62
62
|
5. CSS entry point `styles/global.css`: first line `@import "tailwindcss";`
|
|
63
63
|
Import in main.jac using `cl import`: `cl import ".styles.global.css";`
|
|
64
64
|
(Use `cl import`, NOT plain `import` — plain import causes E5001 if placed before `to cl:`)
|
|
65
|
-
6. Frontend
|
|
65
|
+
6. Frontend (.cl.jac) with Tailwind: build LEAF-FIRST — write each hook/component fully, THEN wire it into its parent. Never import a file that doesn't exist yet — it flashes "undefined component" in the live preview.
|
|
66
66
|
7. Run `jac install`— after all jac.toml changes are final.
|
|
67
67
|
8. `run_command("jac start --dev main.jac", background=True)`.
|
|
68
|
-
9. browser_validate(url) → PASS/FAIL. Follow FAIL action instructions.
|
|
68
|
+
9. browser_validate(url) → PASS/FAIL. Follow FAIL action instructions. Validate at milestones, not after every component.
|
|
69
69
|
10. If PASS → one interactive test (browser_do + browser_state), then browser_close.
|
|
70
70
|
11. Update progress.md Status: DONE.
|
|
71
71
|
|
|
@@ -88,19 +88,21 @@ Unstyled OR non-responsive app = bug.""";
|
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
glob SEM_BROWSER_TESTING: str = """## Browser Testing
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
91
|
+
Browser tools return observations — snapshot, annotated screenshot, JS errors, console errors, optional server-log diagnosis. They do not judge PASS/FAIL. You decide by looking at the screenshot.
|
|
92
|
+
|
|
93
|
+
## Visual verification is the contract
|
|
94
|
+
After ANY edit that affects rendering (CSS, classNames, layout, component structure), the very next validate MUST confirm your change is visible in the screenshot. A successful file write means nothing if the pixels don't reflect it. Never scrape CSS or curl the page — the screenshot is ground truth.
|
|
95
|
+
|
|
96
|
+
## When the screenshot shows something wrong
|
|
97
|
+
- Blank or near-blank page → likely auth-gated or routing issue; try the app's known entry path before declaring failure.
|
|
98
|
+
- Placeholder / unfinished UI → not done; restore the real content.
|
|
99
|
+
- JS or console error names a file → read it, fix it, re-validate.
|
|
100
|
+
- 5xx with server-log diagnosis → the diagnosis names the compile error or missing dep; act on it.
|
|
101
|
+
|
|
102
|
+
## Anti-loop
|
|
103
|
+
3 validates on the same URL without convergence → stop. Reduce the page to a known-good shell, add pieces back one at a time, validate after each. The first addition that breaks is the bug.
|
|
104
|
+
|
|
105
|
+
Never retry the same failing browser action twice.""";
|
|
104
106
|
|
|
105
107
|
|
|
106
108
|
glob SEM_PROGRESS_TEMPLATE: str = """## Progress Tracking
|
|
@@ -541,9 +541,26 @@ with entry {
|
|
|
541
541
|
try {
|
|
542
542
|
import litellm;
|
|
543
543
|
import from litellm.integrations.custom_logger { CustomLogger as _CL }
|
|
544
|
-
import from jac_coder.runtime.cost_tracker {
|
|
544
|
+
import from jac_coder.runtime.cost_tracker {
|
|
545
|
+
record_cost,
|
|
546
|
+
mark_call_started as _ct_mark_call_started,
|
|
547
|
+
mark_call_finished as _ct_mark_call_finished
|
|
548
|
+
}
|
|
545
549
|
|
|
546
550
|
class _CostLogger(_CL) {
|
|
551
|
+
def log_pre_api_call(
|
|
552
|
+
self: _CostLogger,
|
|
553
|
+
model: object,
|
|
554
|
+
messages: object,
|
|
555
|
+
kwargs: dict
|
|
556
|
+
) {
|
|
557
|
+
try {
|
|
558
|
+
_cid = str(kwargs.get("litellm_call_id", "") or "");
|
|
559
|
+
_ct_mark_call_started(_cid);
|
|
560
|
+
} except Exception {
|
|
561
|
+
_ = 0;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
547
564
|
def log_success_event(
|
|
548
565
|
self: _CostLogger,
|
|
549
566
|
kwargs: dict,
|
|
@@ -551,15 +568,18 @@ with entry {
|
|
|
551
568
|
start_time: object,
|
|
552
569
|
end_time: object
|
|
553
570
|
) {
|
|
571
|
+
_cid = str(kwargs.get("litellm_call_id", "") or "");
|
|
554
572
|
try {
|
|
555
573
|
slp = kwargs.get("standard_logging_object") or {};
|
|
556
574
|
raw = slp.get("response_cost") or 0;
|
|
557
575
|
cost = float(str(raw));
|
|
558
576
|
if cost > 0 {
|
|
559
|
-
record_cost(cost);
|
|
577
|
+
record_cost(cost, _cid);
|
|
560
578
|
}
|
|
561
579
|
} except Exception {
|
|
562
580
|
_ = 0;
|
|
581
|
+
} finally {
|
|
582
|
+
try { _ct_mark_call_finished(_cid); } except Exception { ; }
|
|
563
583
|
}
|
|
564
584
|
}
|
|
565
585
|
|
|
@@ -570,7 +590,8 @@ with entry {
|
|
|
570
590
|
start_time: object,
|
|
571
591
|
end_time: object
|
|
572
592
|
) {
|
|
573
|
-
|
|
593
|
+
_cid = str(kwargs.get("litellm_call_id", "") or "");
|
|
594
|
+
try { _ct_mark_call_finished(_cid); } except Exception { ; }
|
|
574
595
|
}
|
|
575
596
|
}
|
|
576
597
|
|
|
@@ -34,7 +34,7 @@ impl clarify_intent(
|
|
|
34
34
|
"allow_custom": allow_custom,
|
|
35
35
|
"skippable": skippable
|
|
36
36
|
});
|
|
37
|
-
result = str(_await_clarification(session_id, options_payload));
|
|
37
|
+
result = str(_await_clarification(session_id, request_id, options_payload));
|
|
38
38
|
} else {
|
|
39
39
|
# 3. CLI branch — colored numbered menu
|
|
40
40
|
print(f"\n{YELLOW}[Clarification]{RESET} {question}\n");
|