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 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 lightweight LLM classifier already determined the user's intent
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 |
@@ -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.0-flash-lite by
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.0-flash-lite)
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.0-flash-lite"
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 classify_intent(message: str) -> str:
131
+ async def classify_intent_with_source(message: str) -> tuple[str, str]:
102
132
  """
103
- Classify a user message using a lightweight Gemini call.
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
- Classification is disabled when GEMCODE_INTENT_CLASSIFY_ENABLED=0.
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
- # A lightweight Gemini call (gemini-2.0-flash-lite) classifies the message
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
- classify_intent,
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 classify_intent(prompt)
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
- # Fast path: generate a warm reply with the lightweight model and skip
536
- # the main agent entirely — no tool calls, no spinner, instant UX.
537
- _reply = await generate_greeting_reply(prompt)
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
  Metadata-Version: 2.4
2
2
  Name: gemcode
3
- Version: 0.3.40
3
+ Version: 0.3.41
4
4
  Summary: Local-first coding agent on Google Gemini + ADK
5
5
  Author: GemCode Contributors
6
6
  License: Apache License
@@ -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=zxCaQuDcgl1Hvp4XUMamd6s8Cpo5xy4C2plrTk_ZZFc,41514
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=WumeEfVYmXcWw-QI4aRCRkPO_YD7olRw4KUCAmskGUk,7190
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=pedyJg4i18NeZ9gvw3JObhmuEBaHJMKwQ5a17Q6Y-6s,3176
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=t1XXPniIZ3WPyG9XuHV-QZJdPqmpmjuk0XWxopAO9PE,30454
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.40.dist-info/licenses/LICENSE,sha256=TD4524qn-W8Z07GTDnag-9jJPFutFZNB0a1WbMHPC54,8388
84
- gemcode-0.3.40.dist-info/METADATA,sha256=Iqf7nwiUzs_-EnIzzqo5nhz8fdWqSUiQhiEHdE92mL8,23695
85
- gemcode-0.3.40.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
86
- gemcode-0.3.40.dist-info/entry_points.txt,sha256=cZdLTLDiHbks7OSUCuxCh66dCWeQdpLR8BozoqfEjV4,45
87
- gemcode-0.3.40.dist-info/top_level.txt,sha256=UYrjULLBY2bcgK6KI6flomJWmsbDXu7n0rvW2SWFrbo,8
88
- gemcode-0.3.40.dist-info/RECORD,,
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,,