gemcode 0.3.40__py3-none-any.whl → 0.3.41__py3-none-any.whl
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.
- gemcode/agent.py +1 -1
- gemcode/intent_classifier.py +58 -17
- gemcode/pricing.py +1 -0
- gemcode/tui/scrollback.py +15 -12
- {gemcode-0.3.40.dist-info → gemcode-0.3.41.dist-info}/METADATA +1 -1
- {gemcode-0.3.40.dist-info → gemcode-0.3.41.dist-info}/RECORD +10 -10
- {gemcode-0.3.40.dist-info → gemcode-0.3.41.dist-info}/WHEEL +0 -0
- {gemcode-0.3.40.dist-info → gemcode-0.3.41.dist-info}/entry_points.txt +0 -0
- {gemcode-0.3.40.dist-info → gemcode-0.3.41.dist-info}/licenses/LICENSE +0 -0
- {gemcode-0.3.40.dist-info → gemcode-0.3.41.dist-info}/top_level.txt +0 -0
gemcode/agent.py
CHANGED
|
@@ -400,7 +400,7 @@ You run locally via the GemCode CLI. You are the same agent the user launched
|
|
|
400
400
|
|
|
401
401
|
### Intent-aware workflow
|
|
402
402
|
|
|
403
|
-
Before you see this message, a
|
|
403
|
+
Before you see this message, a **gemini-2.5-flash-lite** intent classifier already determined the user's intent
|
|
404
404
|
and stored it in session state as `_gemcode_intent`. Read it and adapt your behaviour:
|
|
405
405
|
|
|
406
406
|
| `_gemcode_intent` | Meaning | What to do |
|
gemcode/intent_classifier.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
LLM-based intent pre-classifier.
|
|
3
3
|
|
|
4
|
-
Before each user turn a lightweight Gemini call (gemini-2.
|
|
4
|
+
Before each user turn a lightweight Gemini call (gemini-2.5-flash-lite by
|
|
5
5
|
default) classifies the message into one of five intents. The TUI / CLI
|
|
6
6
|
can then:
|
|
7
7
|
|
|
@@ -10,7 +10,7 @@ can then:
|
|
|
10
10
|
workflow automatically without re-classifying
|
|
11
11
|
|
|
12
12
|
Configure:
|
|
13
|
-
GEMCODE_INTENT_MODEL Override the classifier model (default: gemini-2.
|
|
13
|
+
GEMCODE_INTENT_MODEL Override the classifier model (default: gemini-2.5-flash-lite)
|
|
14
14
|
GEMCODE_INTENT_CLASSIFY_ENABLED Set "0" to disable (falls back to main agent)
|
|
15
15
|
"""
|
|
16
16
|
from __future__ import annotations
|
|
@@ -41,6 +41,36 @@ INTENT_DESCRIPTIONS: dict[str, str] = {
|
|
|
41
41
|
INTENT_ANALYSIS: "Systematic audit or summarisation — thorough tool sweep, then synthesise.",
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
# One-line summaries for the TUI — same visual lane as ∴ Thinking (collapsed)
|
|
45
|
+
INTENT_THINKING_SUMMARY: dict[str, str] = {
|
|
46
|
+
INTENT_GREETING: "Greeting / chitchat — no tools",
|
|
47
|
+
INTENT_CONCEPT: "General knowledge — answer without repo reads if possible",
|
|
48
|
+
INTENT_PROJECT_QUESTION: "About this repo — a few read-only tools, then answer",
|
|
49
|
+
INTENT_ENGINEERING_TASK: "Engineering task — orient → plan → execute → verify",
|
|
50
|
+
INTENT_ANALYSIS: "Deep analysis — systematic read / grep / synthesise",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# How the intent was determined (for TUI suffix)
|
|
54
|
+
SOURCE_LOCAL = "local" # obvious greeting / heuristic, no classifier API call
|
|
55
|
+
SOURCE_LLM = "llm" # gemini-2.5-flash-lite classifier
|
|
56
|
+
SOURCE_OFF = "off" # classifier disabled
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def format_intent_thinking_line(intent: str, source: str) -> str | None:
|
|
60
|
+
"""
|
|
61
|
+
Single line of text after ``∴ Intent`` in the TUI (same visual lane as
|
|
62
|
+
collapsed ``∴ Thinking``). Returns None when the classifier is off and
|
|
63
|
+
nothing should be shown.
|
|
64
|
+
"""
|
|
65
|
+
if source == SOURCE_OFF:
|
|
66
|
+
return None
|
|
67
|
+
summary = INTENT_THINKING_SUMMARY.get(intent, intent)
|
|
68
|
+
if source == SOURCE_LOCAL:
|
|
69
|
+
tag = "instant"
|
|
70
|
+
else:
|
|
71
|
+
tag = "flash-lite classifier"
|
|
72
|
+
return f"{intent} — {summary} · {tag}"
|
|
73
|
+
|
|
44
74
|
# ── Prompts ───────────────────────────────────────────────────────────────────
|
|
45
75
|
_CLASSIFY_PROMPT = """\
|
|
46
76
|
Classify the user message into exactly ONE intent label from the list below.
|
|
@@ -67,7 +97,7 @@ _GREETING_SYSTEM = (
|
|
|
67
97
|
)
|
|
68
98
|
|
|
69
99
|
_CLASSIFIER_MODEL_ENV = "GEMCODE_INTENT_MODEL"
|
|
70
|
-
_DEFAULT_CLASSIFIER_MODEL = "gemini-2.
|
|
100
|
+
_DEFAULT_CLASSIFIER_MODEL = "gemini-2.5-flash-lite"
|
|
71
101
|
|
|
72
102
|
# Single-word / very-short messages that are unambiguously greetings —
|
|
73
103
|
# checked locally before spending an API call on the classifier.
|
|
@@ -98,27 +128,24 @@ def _get_api_key() -> str:
|
|
|
98
128
|
)
|
|
99
129
|
|
|
100
130
|
|
|
101
|
-
async def
|
|
131
|
+
async def classify_intent_with_source(message: str) -> tuple[str, str]:
|
|
102
132
|
"""
|
|
103
|
-
Classify a user message
|
|
104
|
-
|
|
105
|
-
Returns one of: GREETING, CONCEPT, PROJECT_QUESTION, ENGINEERING_TASK, ANALYSIS.
|
|
106
|
-
Falls back to ENGINEERING_TASK on any error (the main agent handles all real
|
|
107
|
-
tasks safely with that default).
|
|
133
|
+
Classify a user message; return (intent, source).
|
|
108
134
|
|
|
109
|
-
|
|
135
|
+
``source`` is one of: ``SOURCE_LOCAL`` (heuristic, no API call),
|
|
136
|
+
``SOURCE_LLM`` (classifier model), ``SOURCE_OFF`` (classifier disabled).
|
|
110
137
|
"""
|
|
111
138
|
if not _classifier_enabled():
|
|
112
|
-
return INTENT_ENGINEERING_TASK
|
|
139
|
+
return INTENT_ENGINEERING_TASK, SOURCE_OFF
|
|
113
140
|
|
|
114
141
|
stripped = (message or "").strip()
|
|
115
142
|
if not stripped:
|
|
116
|
-
return INTENT_GREETING
|
|
143
|
+
return INTENT_GREETING, SOURCE_LOCAL
|
|
117
144
|
|
|
118
145
|
# Fast local check for unambiguously short greetings — saves an API round-trip.
|
|
119
146
|
lower = stripped.lower()
|
|
120
147
|
if lower in _OBVIOUS_GREETINGS or (len(lower) <= 3 and lower.isalpha()):
|
|
121
|
-
return INTENT_GREETING
|
|
148
|
+
return INTENT_GREETING, SOURCE_LOCAL
|
|
122
149
|
|
|
123
150
|
try:
|
|
124
151
|
import google.genai as genai
|
|
@@ -136,14 +163,28 @@ async def classify_intent(message: str) -> str:
|
|
|
136
163
|
label = (resp.text or "").strip().upper()
|
|
137
164
|
# Exact match first
|
|
138
165
|
if label in _VALID_INTENTS:
|
|
139
|
-
return label
|
|
166
|
+
return label, SOURCE_LLM
|
|
140
167
|
# Partial match (model may return extra punctuation or lowercase)
|
|
141
168
|
for key in _VALID_INTENTS:
|
|
142
169
|
if key in label:
|
|
143
|
-
return key
|
|
144
|
-
return INTENT_ENGINEERING_TASK
|
|
170
|
+
return key, SOURCE_LLM
|
|
171
|
+
return INTENT_ENGINEERING_TASK, SOURCE_LLM
|
|
145
172
|
except Exception:
|
|
146
|
-
return INTENT_ENGINEERING_TASK
|
|
173
|
+
return INTENT_ENGINEERING_TASK, SOURCE_LLM
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def classify_intent(message: str) -> str:
|
|
177
|
+
"""
|
|
178
|
+
Classify a user message using a lightweight Gemini call.
|
|
179
|
+
|
|
180
|
+
Returns one of: GREETING, CONCEPT, PROJECT_QUESTION, ENGINEERING_TASK, ANALYSIS.
|
|
181
|
+
Falls back to ENGINEERING_TASK on any error (the main agent handles all real
|
|
182
|
+
tasks safely with that default).
|
|
183
|
+
|
|
184
|
+
Classification is disabled when GEMCODE_INTENT_CLASSIFY_ENABLED=0.
|
|
185
|
+
"""
|
|
186
|
+
intent, _ = await classify_intent_with_source(message)
|
|
187
|
+
return intent
|
|
147
188
|
|
|
148
189
|
|
|
149
190
|
async def generate_greeting_reply(message: str) -> str:
|
gemcode/pricing.py
CHANGED
|
@@ -16,6 +16,7 @@ from __future__ import annotations
|
|
|
16
16
|
_PRICING_TABLE: dict[str, tuple[float, float]] = {
|
|
17
17
|
# ── Gemini 2.5 ──────────────────────────────────────────────────────────
|
|
18
18
|
"gemini-2.5-pro": (3.50, 10.50), # standard context ≤200k
|
|
19
|
+
"gemini-2.5-flash-lite": (0.10, 0.40), # lowest-cost 2.5 (must precede flash)
|
|
19
20
|
"gemini-2.5-flash": (0.15, 0.60), # standard context
|
|
20
21
|
"gemini-2.5-flash-8b": (0.037, 0.15), # 8B lite variant
|
|
21
22
|
# ── Gemini 2.0 ──────────────────────────────────────────────────────────
|
gemcode/tui/scrollback.py
CHANGED
|
@@ -516,28 +516,31 @@ async def run_gemcode_scrollback_tui(
|
|
|
516
516
|
prompt = slash.model_prompt or prompt
|
|
517
517
|
|
|
518
518
|
# ── LLM intent pre-classifier ────────────────────────────────────────────
|
|
519
|
-
#
|
|
520
|
-
# before the main agent runs. Greetings are short-circuited entirely:
|
|
521
|
-
# we generate a natural reply directly and skip the main agent, so the
|
|
522
|
-
# user gets an instant response with zero unnecessary tool calls.
|
|
523
|
-
# For all other intents the classified label is stored in session state
|
|
524
|
-
# so the main agent can adapt its workflow without re-classifying.
|
|
519
|
+
# gemini-2.5-flash-lite classifies the message (same lane as Thinking)
|
|
525
520
|
try:
|
|
526
521
|
from gemcode.intent_classifier import (
|
|
527
|
-
|
|
522
|
+
classify_intent_with_source,
|
|
528
523
|
generate_greeting_reply,
|
|
524
|
+
format_intent_thinking_line,
|
|
529
525
|
INTENT_GREETING,
|
|
530
526
|
INTENT_DESCRIPTIONS,
|
|
531
527
|
)
|
|
532
|
-
_intent = await
|
|
528
|
+
_intent, _intent_src = await classify_intent_with_source(prompt)
|
|
529
|
+
_intent_line = format_intent_thinking_line(_intent, _intent_src)
|
|
530
|
+
if _intent_line:
|
|
531
|
+
print(
|
|
532
|
+
f" \u23bf {ansi.dim}\u2234 Intent {_intent_line}{ansi.reset}"
|
|
533
|
+
)
|
|
534
|
+
print("")
|
|
533
535
|
|
|
534
536
|
if _intent == INTENT_GREETING:
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
537
|
+
_start_anim("Replying\u2026")
|
|
538
|
+
try:
|
|
539
|
+
_reply = await generate_greeting_reply(prompt)
|
|
540
|
+
finally:
|
|
541
|
+
_stop_anim()
|
|
538
542
|
print(f" \u23bf {ansi.bold}GemCode{ansi.reset}:")
|
|
539
543
|
console.print(_RichPadding(_RichMarkdown(_reply), (0, 0, 0, 4)))
|
|
540
|
-
# Print minimal footer (no token stats since we bypassed the main model)
|
|
541
544
|
print("")
|
|
542
545
|
if os.environ.get("GEMCODE_TUI_TURN_RULE", "1").lower() in (
|
|
543
546
|
"1", "true", "yes", "on"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
gemcode/__init__.py,sha256=l0DCRYqK7KM7Fb7u49fqh-5_SlpeIL7r3LjMeJWMgSg,112
|
|
2
2
|
gemcode/__main__.py,sha256=EX2s1hxq2Yvli_-tnBN3w5Qv4bOjsBBbjyISF0pDIQw,37
|
|
3
|
-
gemcode/agent.py,sha256=
|
|
3
|
+
gemcode/agent.py,sha256=p5BtoqBKQdFjziO6ZHIpNxHbP8_KxKFXcZaQVoVxjUM,41531
|
|
4
4
|
gemcode/audit.py,sha256=bh9uhXaeh8wqxqoZtz3ZAowd8Ndk1ss-mw9993Vlrgo,469
|
|
5
5
|
gemcode/autocompact.py,sha256=77h5tgFzJ2rjrhlCL2oIc28IHwLbP4Pqlo7cSNgDwiA,6727
|
|
6
6
|
gemcode/callbacks.py,sha256=qgbxyBMOlgzVkJr9lI55En4EpJOwjo83U3dvNrl5I4s,24229
|
|
@@ -13,7 +13,7 @@ gemcode/context_warning.py,sha256=Q8mg5Vojj7EglPhsGAVL7vb8ROLuHVPgdzw25yw-Q2c,42
|
|
|
13
13
|
gemcode/credentials.py,sha256=04v-rLD8_Ams69FQdof2FwcL3ZgsroGUnMcHNQFuBZo,1296
|
|
14
14
|
gemcode/hitl_session.py,sha256=oNiI7odFJGUcmqPavjKLJOEumZKrgklLvwjjrIG9GPg,281
|
|
15
15
|
gemcode/hooks.py,sha256=FHz175d_18j-4ByZZVdEIagmdOvLHcjDjo7HD2Cikf4,6339
|
|
16
|
-
gemcode/intent_classifier.py,sha256=
|
|
16
|
+
gemcode/intent_classifier.py,sha256=7nwjb69kJV-Z8AmQ-4sKni7ASExeh06-BDrZVqLQ41s,8967
|
|
17
17
|
gemcode/interactions.py,sha256=B0b3QNE_I2i5_HtiebX4ehhjlc4Nbqjf_XbvcTLyJT0,641
|
|
18
18
|
gemcode/invoke.py,sha256=qnIBAZeFJQhRYmx-IoaXdDGLTSXYMRn6r6CvKNAWBbA,7553
|
|
19
19
|
gemcode/kairos_daemon.py,sha256=giINipslAIhBtdbqA0o4RYwt4fBsZjtkiKDjp4zSEvw,6980
|
|
@@ -27,7 +27,7 @@ gemcode/model_routing.py,sha256=Q42HZtXQa6rao2O2vYMHxohrTgD-wq4t7qGxU4_38Jg,4881
|
|
|
27
27
|
gemcode/openapi_loader.py,sha256=g_NZD8YL9_9iIJJ9qykhdbBrylJ1195A4FyHGC0mroc,4157
|
|
28
28
|
gemcode/paths.py,sha256=U6cEH9jfIcSc4NO8Ke0jniZSiJTfCIJPvSMue3hR0ZU,768
|
|
29
29
|
gemcode/permissions.py,sha256=0gQ63Ll-KPlZVU6KigIpwSwKL5-OWqYMB6a0x2wpc28,6766
|
|
30
|
-
gemcode/pricing.py,sha256=
|
|
30
|
+
gemcode/pricing.py,sha256=G8rH_JuB-7JeHpTbwJJz1TIvN22vuHUsWSbDjYwTjr8,3262
|
|
31
31
|
gemcode/prompt_suggestions.py,sha256=h-W_9LlfagS91PyoMEjEjsCqoG4XmIh3QBypA59HyGw,2553
|
|
32
32
|
gemcode/refine.py,sha256=BijEZ4Z32wGa9aK_WottyAhZF-j0xEqRg5UpjedNv2A,7653
|
|
33
33
|
gemcode/repl_commands.py,sha256=qPUVSjNLR0QnABmURoYP58quu2pAcz3F0wUvDd_1v3o,11340
|
|
@@ -73,16 +73,16 @@ gemcode/tools/think.py,sha256=WrNATR-bi97aLkbSsOFOYYAGxbzihe9AnPDZfw3z5-Q,1704
|
|
|
73
73
|
gemcode/tools/todo.py,sha256=d9aXiyT04r1RFZIk6qdVif17-_Oc3oi4ymDnsPBRg68,3143
|
|
74
74
|
gemcode/tools/web.py,sha256=ULg1e3inG4FjPSUCYI8dVBzTrcCHINNRo76SIU9qw-A,4489
|
|
75
75
|
gemcode/tui/input_handler.py,sha256=VWF92Fe3WkD3QP9kBDY5zrx333Vj2eShO3UO0x2n6eo,10079
|
|
76
|
-
gemcode/tui/scrollback.py,sha256=
|
|
76
|
+
gemcode/tui/scrollback.py,sha256=xia_FN66p-OrLh3r0s9orBysma5MiogayibFMGfHRqE,30229
|
|
77
77
|
gemcode/tui/spinner.py,sha256=AJrApG5od-Sh40-5uWcNM9RHb5ax7gr-NbgAZmTbIYY,4848
|
|
78
78
|
gemcode/tui/welcome_banner.py,sha256=aocl1lnoyLIM6RN4f65g3i0wRA71RqUlgPrGsXeVLW4,4387
|
|
79
79
|
gemcode/tui/welcome_rich.py,sha256=8FEZzLXrzqly5JWiDgV9ooRV1LNXDk-CXV1a7K6ua-U,4048
|
|
80
80
|
gemcode/web/__init__.py,sha256=EysmUAWs6g-lmMk4VFljKfaHVrEgb_FiIzwQmBdORJc,40
|
|
81
81
|
gemcode/web/claude_sse_adapter.py,sha256=HcNp0Lh4DdBZBLOpstsqa-VzfqAUrRngZ6FSuJ-mIMg,8609
|
|
82
82
|
gemcode/web/terminal_repl.py,sha256=k2irvFGbCY8gDm_pbirR7b_cakaeafcctoTIvnJkVXk,3902
|
|
83
|
-
gemcode-0.3.
|
|
84
|
-
gemcode-0.3.
|
|
85
|
-
gemcode-0.3.
|
|
86
|
-
gemcode-0.3.
|
|
87
|
-
gemcode-0.3.
|
|
88
|
-
gemcode-0.3.
|
|
83
|
+
gemcode-0.3.41.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
|
|
84
|
+
gemcode-0.3.41.dist-info/METADATA,sha256=CGtENkiu0yb2-sOTv63EcyXeNzJp61VLDzeUqv6EmhI,23695
|
|
85
|
+
gemcode-0.3.41.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
86
|
+
gemcode-0.3.41.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
|
|
87
|
+
gemcode-0.3.41.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
|
|
88
|
+
gemcode-0.3.41.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|