codexa 0.4.0__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.
- codexa-0.4.0.dist-info/METADATA +650 -0
- codexa-0.4.0.dist-info/RECORD +189 -0
- codexa-0.4.0.dist-info/WHEEL +5 -0
- codexa-0.4.0.dist-info/entry_points.txt +2 -0
- codexa-0.4.0.dist-info/licenses/LICENSE +21 -0
- codexa-0.4.0.dist-info/top_level.txt +1 -0
- semantic_code_intelligence/__init__.py +5 -0
- semantic_code_intelligence/analysis/__init__.py +21 -0
- semantic_code_intelligence/analysis/ai_features.py +351 -0
- semantic_code_intelligence/bridge/__init__.py +28 -0
- semantic_code_intelligence/bridge/context_provider.py +245 -0
- semantic_code_intelligence/bridge/protocol.py +167 -0
- semantic_code_intelligence/bridge/server.py +348 -0
- semantic_code_intelligence/bridge/vscode.py +271 -0
- semantic_code_intelligence/ci/__init__.py +13 -0
- semantic_code_intelligence/ci/hooks.py +98 -0
- semantic_code_intelligence/ci/hotspots.py +272 -0
- semantic_code_intelligence/ci/impact.py +246 -0
- semantic_code_intelligence/ci/metrics.py +591 -0
- semantic_code_intelligence/ci/pr.py +412 -0
- semantic_code_intelligence/ci/quality.py +557 -0
- semantic_code_intelligence/ci/templates.py +164 -0
- semantic_code_intelligence/ci/trace.py +224 -0
- semantic_code_intelligence/cli/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/__init__.py +0 -0
- semantic_code_intelligence/cli/commands/ask_cmd.py +153 -0
- semantic_code_intelligence/cli/commands/benchmark_cmd.py +303 -0
- semantic_code_intelligence/cli/commands/chat_cmd.py +252 -0
- semantic_code_intelligence/cli/commands/ci_gen_cmd.py +74 -0
- semantic_code_intelligence/cli/commands/context_cmd.py +120 -0
- semantic_code_intelligence/cli/commands/cross_refactor_cmd.py +113 -0
- semantic_code_intelligence/cli/commands/deps_cmd.py +91 -0
- semantic_code_intelligence/cli/commands/docs_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/doctor_cmd.py +147 -0
- semantic_code_intelligence/cli/commands/evolve_cmd.py +171 -0
- semantic_code_intelligence/cli/commands/explain_cmd.py +112 -0
- semantic_code_intelligence/cli/commands/gate_cmd.py +135 -0
- semantic_code_intelligence/cli/commands/grep_cmd.py +234 -0
- semantic_code_intelligence/cli/commands/hotspots_cmd.py +119 -0
- semantic_code_intelligence/cli/commands/impact_cmd.py +131 -0
- semantic_code_intelligence/cli/commands/index_cmd.py +138 -0
- semantic_code_intelligence/cli/commands/init_cmd.py +152 -0
- semantic_code_intelligence/cli/commands/investigate_cmd.py +163 -0
- semantic_code_intelligence/cli/commands/languages_cmd.py +101 -0
- semantic_code_intelligence/cli/commands/lsp_cmd.py +49 -0
- semantic_code_intelligence/cli/commands/mcp_cmd.py +50 -0
- semantic_code_intelligence/cli/commands/metrics_cmd.py +264 -0
- semantic_code_intelligence/cli/commands/models_cmd.py +157 -0
- semantic_code_intelligence/cli/commands/plugin_cmd.py +275 -0
- semantic_code_intelligence/cli/commands/pr_summary_cmd.py +178 -0
- semantic_code_intelligence/cli/commands/quality_cmd.py +208 -0
- semantic_code_intelligence/cli/commands/refactor_cmd.py +103 -0
- semantic_code_intelligence/cli/commands/review_cmd.py +88 -0
- semantic_code_intelligence/cli/commands/search_cmd.py +236 -0
- semantic_code_intelligence/cli/commands/serve_cmd.py +117 -0
- semantic_code_intelligence/cli/commands/suggest_cmd.py +100 -0
- semantic_code_intelligence/cli/commands/summary_cmd.py +78 -0
- semantic_code_intelligence/cli/commands/tool_cmd.py +282 -0
- semantic_code_intelligence/cli/commands/trace_cmd.py +123 -0
- semantic_code_intelligence/cli/commands/tui_cmd.py +58 -0
- semantic_code_intelligence/cli/commands/viz_cmd.py +127 -0
- semantic_code_intelligence/cli/commands/watch_cmd.py +72 -0
- semantic_code_intelligence/cli/commands/web_cmd.py +61 -0
- semantic_code_intelligence/cli/commands/workspace_cmd.py +250 -0
- semantic_code_intelligence/cli/main.py +65 -0
- semantic_code_intelligence/cli/router.py +92 -0
- semantic_code_intelligence/config/__init__.py +0 -0
- semantic_code_intelligence/config/settings.py +260 -0
- semantic_code_intelligence/context/__init__.py +19 -0
- semantic_code_intelligence/context/engine.py +429 -0
- semantic_code_intelligence/context/memory.py +253 -0
- semantic_code_intelligence/daemon/__init__.py +1 -0
- semantic_code_intelligence/daemon/watcher.py +515 -0
- semantic_code_intelligence/docs/__init__.py +1080 -0
- semantic_code_intelligence/embeddings/__init__.py +0 -0
- semantic_code_intelligence/embeddings/enhanced.py +131 -0
- semantic_code_intelligence/embeddings/generator.py +149 -0
- semantic_code_intelligence/embeddings/model_registry.py +100 -0
- semantic_code_intelligence/evolution/__init__.py +1 -0
- semantic_code_intelligence/evolution/budget_guard.py +111 -0
- semantic_code_intelligence/evolution/commit_manager.py +88 -0
- semantic_code_intelligence/evolution/context_builder.py +131 -0
- semantic_code_intelligence/evolution/engine.py +249 -0
- semantic_code_intelligence/evolution/patch_generator.py +229 -0
- semantic_code_intelligence/evolution/task_selector.py +214 -0
- semantic_code_intelligence/evolution/test_runner.py +111 -0
- semantic_code_intelligence/indexing/__init__.py +0 -0
- semantic_code_intelligence/indexing/chunker.py +174 -0
- semantic_code_intelligence/indexing/parallel.py +86 -0
- semantic_code_intelligence/indexing/scanner.py +146 -0
- semantic_code_intelligence/indexing/semantic_chunker.py +337 -0
- semantic_code_intelligence/llm/__init__.py +62 -0
- semantic_code_intelligence/llm/cache.py +219 -0
- semantic_code_intelligence/llm/cached_provider.py +145 -0
- semantic_code_intelligence/llm/conversation.py +190 -0
- semantic_code_intelligence/llm/cross_refactor.py +272 -0
- semantic_code_intelligence/llm/investigation.py +274 -0
- semantic_code_intelligence/llm/mock_provider.py +77 -0
- semantic_code_intelligence/llm/ollama_provider.py +122 -0
- semantic_code_intelligence/llm/openai_provider.py +100 -0
- semantic_code_intelligence/llm/provider.py +92 -0
- semantic_code_intelligence/llm/rate_limiter.py +164 -0
- semantic_code_intelligence/llm/reasoning.py +438 -0
- semantic_code_intelligence/llm/safety.py +110 -0
- semantic_code_intelligence/llm/streaming.py +251 -0
- semantic_code_intelligence/lsp/__init__.py +609 -0
- semantic_code_intelligence/mcp/__init__.py +393 -0
- semantic_code_intelligence/parsing/__init__.py +19 -0
- semantic_code_intelligence/parsing/parser.py +375 -0
- semantic_code_intelligence/plugins/__init__.py +255 -0
- semantic_code_intelligence/plugins/examples/__init__.py +1 -0
- semantic_code_intelligence/plugins/examples/code_quality.py +73 -0
- semantic_code_intelligence/plugins/examples/search_annotator.py +56 -0
- semantic_code_intelligence/scalability/__init__.py +205 -0
- semantic_code_intelligence/search/__init__.py +0 -0
- semantic_code_intelligence/search/formatter.py +123 -0
- semantic_code_intelligence/search/grep.py +361 -0
- semantic_code_intelligence/search/hybrid_search.py +170 -0
- semantic_code_intelligence/search/keyword_search.py +311 -0
- semantic_code_intelligence/search/section_expander.py +103 -0
- semantic_code_intelligence/services/__init__.py +0 -0
- semantic_code_intelligence/services/indexing_service.py +630 -0
- semantic_code_intelligence/services/search_service.py +269 -0
- semantic_code_intelligence/storage/__init__.py +0 -0
- semantic_code_intelligence/storage/chunk_hash_store.py +86 -0
- semantic_code_intelligence/storage/hash_store.py +66 -0
- semantic_code_intelligence/storage/index_manifest.py +85 -0
- semantic_code_intelligence/storage/index_stats.py +138 -0
- semantic_code_intelligence/storage/query_history.py +160 -0
- semantic_code_intelligence/storage/symbol_registry.py +209 -0
- semantic_code_intelligence/storage/vector_store.py +297 -0
- semantic_code_intelligence/tests/__init__.py +0 -0
- semantic_code_intelligence/tests/test_ai_features.py +351 -0
- semantic_code_intelligence/tests/test_chunker.py +119 -0
- semantic_code_intelligence/tests/test_cli.py +188 -0
- semantic_code_intelligence/tests/test_config.py +154 -0
- semantic_code_intelligence/tests/test_context.py +381 -0
- semantic_code_intelligence/tests/test_embeddings.py +73 -0
- semantic_code_intelligence/tests/test_endtoend.py +1142 -0
- semantic_code_intelligence/tests/test_enhanced_embeddings.py +92 -0
- semantic_code_intelligence/tests/test_hash_store.py +79 -0
- semantic_code_intelligence/tests/test_logging.py +55 -0
- semantic_code_intelligence/tests/test_new_cli.py +138 -0
- semantic_code_intelligence/tests/test_parser.py +495 -0
- semantic_code_intelligence/tests/test_phase10.py +355 -0
- semantic_code_intelligence/tests/test_phase11.py +593 -0
- semantic_code_intelligence/tests/test_phase12.py +375 -0
- semantic_code_intelligence/tests/test_phase13.py +663 -0
- semantic_code_intelligence/tests/test_phase14.py +568 -0
- semantic_code_intelligence/tests/test_phase15.py +814 -0
- semantic_code_intelligence/tests/test_phase16.py +792 -0
- semantic_code_intelligence/tests/test_phase17.py +815 -0
- semantic_code_intelligence/tests/test_phase18.py +934 -0
- semantic_code_intelligence/tests/test_phase19.py +986 -0
- semantic_code_intelligence/tests/test_phase20.py +2753 -0
- semantic_code_intelligence/tests/test_phase20b.py +2058 -0
- semantic_code_intelligence/tests/test_phase20c.py +962 -0
- semantic_code_intelligence/tests/test_phase21.py +428 -0
- semantic_code_intelligence/tests/test_phase22.py +799 -0
- semantic_code_intelligence/tests/test_phase23.py +783 -0
- semantic_code_intelligence/tests/test_phase24.py +715 -0
- semantic_code_intelligence/tests/test_phase25.py +496 -0
- semantic_code_intelligence/tests/test_phase26.py +251 -0
- semantic_code_intelligence/tests/test_phase27.py +531 -0
- semantic_code_intelligence/tests/test_phase8.py +592 -0
- semantic_code_intelligence/tests/test_phase9.py +643 -0
- semantic_code_intelligence/tests/test_plugins.py +293 -0
- semantic_code_intelligence/tests/test_priority_features.py +727 -0
- semantic_code_intelligence/tests/test_router.py +41 -0
- semantic_code_intelligence/tests/test_scalability.py +138 -0
- semantic_code_intelligence/tests/test_scanner.py +125 -0
- semantic_code_intelligence/tests/test_search.py +160 -0
- semantic_code_intelligence/tests/test_semantic_chunker.py +255 -0
- semantic_code_intelligence/tests/test_tools.py +182 -0
- semantic_code_intelligence/tests/test_vector_store.py +151 -0
- semantic_code_intelligence/tests/test_watcher.py +211 -0
- semantic_code_intelligence/tools/__init__.py +442 -0
- semantic_code_intelligence/tools/executor.py +232 -0
- semantic_code_intelligence/tools/protocol.py +200 -0
- semantic_code_intelligence/tui/__init__.py +454 -0
- semantic_code_intelligence/utils/__init__.py +0 -0
- semantic_code_intelligence/utils/logging.py +112 -0
- semantic_code_intelligence/version.py +3 -0
- semantic_code_intelligence/web/__init__.py +11 -0
- semantic_code_intelligence/web/api.py +289 -0
- semantic_code_intelligence/web/server.py +397 -0
- semantic_code_intelligence/web/ui.py +659 -0
- semantic_code_intelligence/web/visualize.py +226 -0
- semantic_code_intelligence/workspace/__init__.py +427 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Streaming LLM support — delivers tokens incrementally with plugin hooks.
|
|
2
|
+
|
|
3
|
+
Provides:
|
|
4
|
+
- ``stream_chat()`` wrapper that works with any LLMProvider
|
|
5
|
+
- ``StreamEvent`` lightweight token container
|
|
6
|
+
- Plugin dispatch through ``PluginHook.ON_STREAM`` for each token
|
|
7
|
+
- Accumulator helpers for building final ``LLMResponse`` from a stream
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any, Generator
|
|
14
|
+
|
|
15
|
+
from semantic_code_intelligence.llm.provider import (
|
|
16
|
+
LLMMessage,
|
|
17
|
+
LLMProvider,
|
|
18
|
+
LLMResponse,
|
|
19
|
+
)
|
|
20
|
+
from semantic_code_intelligence.utils.logging import get_logger
|
|
21
|
+
|
|
22
|
+
logger = get_logger("llm.streaming")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Data types
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class StreamEvent:
|
|
31
|
+
"""A single streaming event (token, status change, or error)."""
|
|
32
|
+
|
|
33
|
+
kind: str # "token" | "start" | "done" | "error"
|
|
34
|
+
content: str = ""
|
|
35
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
36
|
+
|
|
37
|
+
def to_dict(self) -> dict[str, Any]:
|
|
38
|
+
return {"kind": self.kind, "content": self.content, "metadata": self.metadata}
|
|
39
|
+
|
|
40
|
+
def to_sse(self) -> str:
|
|
41
|
+
"""Format as a Server-Sent Event line."""
|
|
42
|
+
import json
|
|
43
|
+
|
|
44
|
+
return f"data: {json.dumps(self.to_dict())}\n\n"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Streaming wrappers for each provider
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
def _stream_ollama(
|
|
52
|
+
provider: Any,
|
|
53
|
+
messages: list[LLMMessage],
|
|
54
|
+
**kwargs: Any,
|
|
55
|
+
) -> Generator[StreamEvent, None, LLMResponse | None]:
|
|
56
|
+
"""Stream tokens from an Ollama provider."""
|
|
57
|
+
import json as json_mod
|
|
58
|
+
from urllib.request import Request, urlopen
|
|
59
|
+
|
|
60
|
+
temperature = kwargs.get("temperature", provider._temperature)
|
|
61
|
+
payload: dict[str, Any] = {
|
|
62
|
+
"model": provider._model,
|
|
63
|
+
"messages": [m.to_dict() for m in messages],
|
|
64
|
+
"stream": True,
|
|
65
|
+
"options": {
|
|
66
|
+
"temperature": temperature,
|
|
67
|
+
"num_predict": kwargs.get("max_tokens", provider._max_tokens),
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
url = f"{provider._base_url}/api/chat"
|
|
72
|
+
data = json_mod.dumps(payload).encode("utf-8")
|
|
73
|
+
req = Request(url, data=data, headers={"Content-Type": "application/json"})
|
|
74
|
+
|
|
75
|
+
yield StreamEvent(kind="start", metadata={"model": provider._model, "provider": "ollama"})
|
|
76
|
+
|
|
77
|
+
accumulated = ""
|
|
78
|
+
prompt_tokens = 0
|
|
79
|
+
completion_tokens = 0
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
with urlopen(req, timeout=120) as resp: # noqa: S310 — localhost only
|
|
83
|
+
for raw_line in resp:
|
|
84
|
+
line = raw_line.decode("utf-8", errors="replace").strip()
|
|
85
|
+
if not line:
|
|
86
|
+
continue
|
|
87
|
+
try:
|
|
88
|
+
chunk = json_mod.loads(line)
|
|
89
|
+
except json_mod.JSONDecodeError:
|
|
90
|
+
continue
|
|
91
|
+
msg = chunk.get("message", {})
|
|
92
|
+
token = msg.get("content", "")
|
|
93
|
+
if token:
|
|
94
|
+
accumulated += token
|
|
95
|
+
yield StreamEvent(kind="token", content=token)
|
|
96
|
+
|
|
97
|
+
if chunk.get("done"):
|
|
98
|
+
prompt_tokens = chunk.get("prompt_eval_count", 0)
|
|
99
|
+
completion_tokens = chunk.get("eval_count", 0)
|
|
100
|
+
except Exception as exc:
|
|
101
|
+
yield StreamEvent(kind="error", content=str(exc))
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
yield StreamEvent(kind="done")
|
|
105
|
+
return LLMResponse(
|
|
106
|
+
content=accumulated,
|
|
107
|
+
model=provider._model,
|
|
108
|
+
provider="ollama",
|
|
109
|
+
usage={
|
|
110
|
+
"prompt_tokens": prompt_tokens,
|
|
111
|
+
"completion_tokens": completion_tokens,
|
|
112
|
+
"total_tokens": prompt_tokens + completion_tokens,
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _stream_openai(
|
|
118
|
+
provider: Any,
|
|
119
|
+
messages: list[LLMMessage],
|
|
120
|
+
**kwargs: Any,
|
|
121
|
+
) -> Generator[StreamEvent, None, LLMResponse | None]:
|
|
122
|
+
"""Stream tokens from an OpenAI provider."""
|
|
123
|
+
client = provider._get_client()
|
|
124
|
+
temperature = kwargs.get("temperature", provider._temperature)
|
|
125
|
+
max_tokens = kwargs.get("max_tokens", provider._max_tokens)
|
|
126
|
+
|
|
127
|
+
api_messages = [m.to_dict() for m in messages]
|
|
128
|
+
|
|
129
|
+
yield StreamEvent(kind="start", metadata={"model": provider._model, "provider": "openai"})
|
|
130
|
+
|
|
131
|
+
accumulated = ""
|
|
132
|
+
try:
|
|
133
|
+
response = client.chat.completions.create(
|
|
134
|
+
model=provider._model,
|
|
135
|
+
messages=api_messages,
|
|
136
|
+
temperature=temperature,
|
|
137
|
+
max_tokens=max_tokens,
|
|
138
|
+
stream=True,
|
|
139
|
+
)
|
|
140
|
+
for chunk in response:
|
|
141
|
+
delta = chunk.choices[0].delta if chunk.choices else None
|
|
142
|
+
if delta and delta.content:
|
|
143
|
+
accumulated += delta.content
|
|
144
|
+
yield StreamEvent(kind="token", content=delta.content)
|
|
145
|
+
except Exception as exc:
|
|
146
|
+
yield StreamEvent(kind="error", content=str(exc))
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
yield StreamEvent(kind="done")
|
|
150
|
+
return LLMResponse(
|
|
151
|
+
content=accumulated,
|
|
152
|
+
model=provider._model,
|
|
153
|
+
provider="openai",
|
|
154
|
+
usage={"completion_tokens": len(accumulated) // 4},
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _stream_mock(
|
|
159
|
+
provider: Any,
|
|
160
|
+
messages: list[LLMMessage],
|
|
161
|
+
**kwargs: Any,
|
|
162
|
+
) -> Generator[StreamEvent, None, LLMResponse | None]:
|
|
163
|
+
"""Simulate streaming from a MockProvider by yielding word-by-word."""
|
|
164
|
+
yield StreamEvent(kind="start", metadata={"model": provider._model, "provider": "mock"})
|
|
165
|
+
|
|
166
|
+
content = provider._next_response()
|
|
167
|
+
provider._call_history.append({
|
|
168
|
+
"method": "stream_chat",
|
|
169
|
+
"messages": [m.to_dict() for m in messages],
|
|
170
|
+
"response": content,
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
words = content.split(" ")
|
|
174
|
+
for i, word in enumerate(words):
|
|
175
|
+
token = word if i == 0 else " " + word
|
|
176
|
+
yield StreamEvent(kind="token", content=token)
|
|
177
|
+
|
|
178
|
+
yield StreamEvent(kind="done")
|
|
179
|
+
return LLMResponse(
|
|
180
|
+
content=content,
|
|
181
|
+
model=provider._model,
|
|
182
|
+
provider="mock",
|
|
183
|
+
usage={"completion_tokens": len(content) // 4},
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
# Unified streaming API
|
|
189
|
+
# ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
def stream_chat(
|
|
192
|
+
provider: LLMProvider,
|
|
193
|
+
messages: list[LLMMessage],
|
|
194
|
+
*,
|
|
195
|
+
plugin_manager: Any | None = None,
|
|
196
|
+
**kwargs: Any,
|
|
197
|
+
) -> Generator[StreamEvent, None, LLMResponse | None]:
|
|
198
|
+
"""Stream chat tokens from any supported LLM provider.
|
|
199
|
+
|
|
200
|
+
Yields ``StreamEvent`` objects for each token. If a *plugin_manager*
|
|
201
|
+
is provided, dispatches ``PluginHook.ON_STREAM`` for each token event.
|
|
202
|
+
|
|
203
|
+
Usage::
|
|
204
|
+
|
|
205
|
+
gen = stream_chat(provider, messages)
|
|
206
|
+
for event in gen:
|
|
207
|
+
print(event.content, end="", flush=True)
|
|
208
|
+
|
|
209
|
+
The generator's return value (accessible via StopIteration.value) is
|
|
210
|
+
the accumulated ``LLMResponse``.
|
|
211
|
+
"""
|
|
212
|
+
from semantic_code_intelligence.llm.ollama_provider import OllamaProvider
|
|
213
|
+
from semantic_code_intelligence.llm.openai_provider import OpenAIProvider
|
|
214
|
+
from semantic_code_intelligence.llm.mock_provider import MockProvider
|
|
215
|
+
|
|
216
|
+
# Select the appropriate streaming implementation
|
|
217
|
+
if isinstance(provider, OllamaProvider):
|
|
218
|
+
inner = _stream_ollama(provider, messages, **kwargs)
|
|
219
|
+
elif isinstance(provider, OpenAIProvider):
|
|
220
|
+
inner = _stream_openai(provider, messages, **kwargs)
|
|
221
|
+
elif isinstance(provider, MockProvider):
|
|
222
|
+
inner = _stream_mock(provider, messages, **kwargs)
|
|
223
|
+
else:
|
|
224
|
+
# Fallback — non-streaming: call chat() and emit as single token
|
|
225
|
+
resp = provider.chat(messages, **kwargs)
|
|
226
|
+
yield StreamEvent(kind="start", metadata={"provider": provider.name})
|
|
227
|
+
yield StreamEvent(kind="token", content=resp.content)
|
|
228
|
+
yield StreamEvent(kind="done")
|
|
229
|
+
return resp
|
|
230
|
+
|
|
231
|
+
# Iterate inner generator, dispatching plugin hooks
|
|
232
|
+
result: LLMResponse | None = None
|
|
233
|
+
try:
|
|
234
|
+
while True:
|
|
235
|
+
event = next(inner)
|
|
236
|
+
# Dispatch ON_STREAM plugin hook for token events
|
|
237
|
+
if plugin_manager and event.kind == "token":
|
|
238
|
+
try:
|
|
239
|
+
from semantic_code_intelligence.plugins import PluginHook
|
|
240
|
+
|
|
241
|
+
plugin_manager.dispatch(
|
|
242
|
+
PluginHook.ON_STREAM,
|
|
243
|
+
{"event": event.to_dict(), "accumulated": ""},
|
|
244
|
+
)
|
|
245
|
+
except Exception:
|
|
246
|
+
logger.debug("Plugin streaming hook failed")
|
|
247
|
+
yield event
|
|
248
|
+
except StopIteration as stop:
|
|
249
|
+
result = stop.value
|
|
250
|
+
|
|
251
|
+
return result
|