stravinsky 0.2.52__py3-none-any.whl → 0.4.18__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.
Potentially problematic release.
This version of stravinsky might be problematic. Click here for more details.
- mcp_bridge/__init__.py +1 -1
- mcp_bridge/auth/token_store.py +113 -11
- mcp_bridge/cli/__init__.py +6 -0
- mcp_bridge/cli/install_hooks.py +1265 -0
- mcp_bridge/cli/session_report.py +585 -0
- mcp_bridge/config/MANIFEST_SCHEMA.md +305 -0
- mcp_bridge/config/README.md +276 -0
- mcp_bridge/config/hook_config.py +249 -0
- mcp_bridge/config/hooks_manifest.json +138 -0
- mcp_bridge/config/rate_limits.py +222 -0
- mcp_bridge/config/skills_manifest.json +128 -0
- mcp_bridge/hooks/HOOKS_SETTINGS.json +175 -0
- mcp_bridge/hooks/README.md +215 -0
- mcp_bridge/hooks/__init__.py +119 -60
- mcp_bridge/hooks/edit_recovery.py +42 -37
- mcp_bridge/hooks/git_noninteractive.py +89 -0
- mcp_bridge/hooks/keyword_detector.py +30 -0
- mcp_bridge/hooks/manager.py +8 -0
- mcp_bridge/hooks/notification_hook.py +103 -0
- mcp_bridge/hooks/parallel_execution.py +111 -0
- mcp_bridge/hooks/pre_compact.py +82 -183
- mcp_bridge/hooks/rules_injector.py +507 -0
- mcp_bridge/hooks/session_notifier.py +125 -0
- mcp_bridge/{native_hooks → hooks}/stravinsky_mode.py +51 -16
- mcp_bridge/hooks/subagent_stop.py +98 -0
- mcp_bridge/hooks/task_validator.py +73 -0
- mcp_bridge/hooks/tmux_manager.py +141 -0
- mcp_bridge/hooks/todo_continuation.py +90 -0
- mcp_bridge/hooks/todo_delegation.py +88 -0
- mcp_bridge/hooks/tool_messaging.py +267 -0
- mcp_bridge/hooks/truncator.py +21 -17
- mcp_bridge/notifications.py +151 -0
- mcp_bridge/prompts/multimodal.py +24 -3
- mcp_bridge/server.py +214 -49
- mcp_bridge/server_tools.py +445 -0
- mcp_bridge/tools/__init__.py +22 -18
- mcp_bridge/tools/agent_manager.py +220 -32
- mcp_bridge/tools/code_search.py +97 -11
- mcp_bridge/tools/lsp/__init__.py +7 -0
- mcp_bridge/tools/lsp/manager.py +448 -0
- mcp_bridge/tools/lsp/tools.py +637 -150
- mcp_bridge/tools/model_invoke.py +208 -106
- mcp_bridge/tools/query_classifier.py +323 -0
- mcp_bridge/tools/semantic_search.py +3042 -0
- mcp_bridge/tools/templates.py +32 -18
- mcp_bridge/update_manager.py +589 -0
- mcp_bridge/update_manager_pypi.py +299 -0
- stravinsky-0.4.18.dist-info/METADATA +468 -0
- stravinsky-0.4.18.dist-info/RECORD +88 -0
- stravinsky-0.4.18.dist-info/entry_points.txt +5 -0
- mcp_bridge/native_hooks/edit_recovery.py +0 -46
- mcp_bridge/native_hooks/todo_delegation.py +0 -54
- mcp_bridge/native_hooks/truncator.py +0 -23
- stravinsky-0.2.52.dist-info/METADATA +0 -204
- stravinsky-0.2.52.dist-info/RECORD +0 -63
- stravinsky-0.2.52.dist-info/entry_points.txt +0 -3
- /mcp_bridge/{native_hooks → hooks}/context.py +0 -0
- {stravinsky-0.2.52.dist-info → stravinsky-0.4.18.dist-info}/WHEEL +0 -0
mcp_bridge/tools/model_invoke.py
CHANGED
|
@@ -5,6 +5,7 @@ These tools use OAuth tokens from the token store to authenticate
|
|
|
5
5
|
API requests to external model providers.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import asyncio
|
|
8
9
|
import logging
|
|
9
10
|
import os
|
|
10
11
|
import time
|
|
@@ -134,6 +135,9 @@ _SESSION_CACHE: dict[str, str] = {}
|
|
|
134
135
|
# Pooled HTTP client for connection reuse
|
|
135
136
|
_HTTP_CLIENT: httpx.AsyncClient | None = None
|
|
136
137
|
|
|
138
|
+
# Rate limiting: Max 5 concurrent Gemini requests to prevent burst rate limits
|
|
139
|
+
_GEMINI_SEMAPHORE: asyncio.Semaphore | None = None
|
|
140
|
+
|
|
137
141
|
|
|
138
142
|
def _get_session_id(conversation_key: str | None = None) -> str:
|
|
139
143
|
"""
|
|
@@ -174,6 +178,19 @@ async def _get_http_client() -> httpx.AsyncClient:
|
|
|
174
178
|
return _HTTP_CLIENT
|
|
175
179
|
|
|
176
180
|
|
|
181
|
+
def _get_gemini_semaphore() -> asyncio.Semaphore:
|
|
182
|
+
"""
|
|
183
|
+
Get or create semaphore for Gemini API rate limiting.
|
|
184
|
+
|
|
185
|
+
Limits concurrent Gemini requests to prevent burst rate limits (429 errors).
|
|
186
|
+
Max 5 concurrent requests balances throughput with API quota constraints.
|
|
187
|
+
"""
|
|
188
|
+
global _GEMINI_SEMAPHORE
|
|
189
|
+
if _GEMINI_SEMAPHORE is None:
|
|
190
|
+
_GEMINI_SEMAPHORE = asyncio.Semaphore(5)
|
|
191
|
+
return _GEMINI_SEMAPHORE
|
|
192
|
+
|
|
193
|
+
|
|
177
194
|
def _extract_gemini_response(data: dict) -> str:
|
|
178
195
|
"""
|
|
179
196
|
Extract text from Gemini response, handling thinking blocks.
|
|
@@ -284,18 +301,25 @@ async def _ensure_valid_token(token_store: TokenStore, provider: str) -> str:
|
|
|
284
301
|
|
|
285
302
|
|
|
286
303
|
def is_retryable_exception(e: Exception) -> bool:
|
|
287
|
-
"""
|
|
304
|
+
"""
|
|
305
|
+
Check if an exception is retryable (5xx only, NOT 429).
|
|
306
|
+
|
|
307
|
+
429 (Rate Limit) errors should fail fast - retrying makes the problem worse
|
|
308
|
+
by adding more requests to an already exhausted quota. The semaphore prevents
|
|
309
|
+
these in the first place, but if one slips through, we shouldn't retry.
|
|
310
|
+
"""
|
|
288
311
|
if isinstance(e, httpx.HTTPStatusError):
|
|
289
|
-
|
|
312
|
+
# Only retry server errors (5xx), not rate limits (429)
|
|
313
|
+
return 500 <= e.response.status_code < 600
|
|
290
314
|
return False
|
|
291
315
|
|
|
292
316
|
|
|
293
317
|
@retry(
|
|
294
|
-
stop=stop_after_attempt(
|
|
295
|
-
wait=wait_exponential(multiplier=
|
|
318
|
+
stop=stop_after_attempt(2), # Reduced from 5 to 2 attempts
|
|
319
|
+
wait=wait_exponential(multiplier=2, min=10, max=120), # Longer waits: 10s → 20s → 40s
|
|
296
320
|
retry=retry_if_exception(is_retryable_exception),
|
|
297
321
|
before_sleep=lambda retry_state: logger.info(
|
|
298
|
-
f"
|
|
322
|
+
f"Server error, retrying in {retry_state.next_action.sleep} seconds..."
|
|
299
323
|
),
|
|
300
324
|
)
|
|
301
325
|
async def invoke_gemini(
|
|
@@ -305,11 +329,13 @@ async def invoke_gemini(
|
|
|
305
329
|
temperature: float = 0.7,
|
|
306
330
|
max_tokens: int = 4096,
|
|
307
331
|
thinking_budget: int = 0,
|
|
332
|
+
image_path: str | None = None,
|
|
308
333
|
) -> str:
|
|
309
334
|
"""
|
|
310
335
|
Invoke a Gemini model with the given prompt.
|
|
311
336
|
|
|
312
337
|
Uses OAuth authentication with Antigravity credentials.
|
|
338
|
+
Supports vision API for image/PDF analysis when image_path is provided.
|
|
313
339
|
|
|
314
340
|
Args:
|
|
315
341
|
token_store: Token store for OAuth credentials
|
|
@@ -317,6 +343,8 @@ async def invoke_gemini(
|
|
|
317
343
|
model: Gemini model to use
|
|
318
344
|
temperature: Sampling temperature (0.0-2.0)
|
|
319
345
|
max_tokens: Maximum tokens in response
|
|
346
|
+
thinking_budget: Tokens reserved for internal reasoning
|
|
347
|
+
image_path: Optional path to image/PDF for vision analysis (token optimization)
|
|
320
348
|
|
|
321
349
|
Returns:
|
|
322
350
|
The model's response text.
|
|
@@ -349,132 +377,198 @@ async def invoke_gemini(
|
|
|
349
377
|
# Extract agent context for logging (may be passed via params or original call)
|
|
350
378
|
agent_context = params.get("agent_context", {})
|
|
351
379
|
agent_type = agent_context.get("agent_type", "direct")
|
|
380
|
+
task_id = agent_context.get("task_id", "")
|
|
381
|
+
description = agent_context.get("description", "")
|
|
352
382
|
prompt_summary = _summarize_prompt(prompt)
|
|
353
383
|
|
|
354
384
|
# Log with agent context and prompt summary
|
|
355
385
|
logger.info(f"[{agent_type}] → {model}: {prompt_summary}")
|
|
356
386
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
387
|
+
# USER-VISIBLE NOTIFICATION (stderr) - Shows when Gemini is invoked
|
|
388
|
+
import sys
|
|
389
|
+
task_info = f" task={task_id}" if task_id else ""
|
|
390
|
+
desc_info = f" | {description}" if description else ""
|
|
391
|
+
print(f"🔮 GEMINI: {model} | agent={agent_type}{task_info}{desc_info}", file=sys.stderr)
|
|
361
392
|
|
|
362
|
-
#
|
|
363
|
-
|
|
364
|
-
|
|
393
|
+
# Acquire semaphore to limit concurrent Gemini requests (prevents 429 rate limits)
|
|
394
|
+
semaphore = _get_gemini_semaphore()
|
|
395
|
+
async with semaphore:
|
|
396
|
+
access_token = await _ensure_valid_token(token_store, "gemini")
|
|
365
397
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
"Content-Type": "application/json",
|
|
369
|
-
**ANTIGRAVITY_HEADERS, # Include Antigravity headers
|
|
370
|
-
}
|
|
398
|
+
# Resolve user-friendly model name to actual API model ID
|
|
399
|
+
api_model = resolve_gemini_model(model)
|
|
371
400
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
"contents": [{"role": "user", "parts": [{"text": prompt}]}],
|
|
376
|
-
"generationConfig": {
|
|
377
|
-
"temperature": temperature,
|
|
378
|
-
"maxOutputTokens": max_tokens,
|
|
379
|
-
},
|
|
380
|
-
"sessionId": session_id,
|
|
381
|
-
}
|
|
401
|
+
# Use persistent session ID for thinking signature caching
|
|
402
|
+
session_id = _get_session_id()
|
|
403
|
+
project_id = os.getenv("STRAVINSKY_ANTIGRAVITY_PROJECT_ID", ANTIGRAVITY_DEFAULT_PROJECT_ID)
|
|
382
404
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
inner_payload["generationConfig"]["thinkingConfig"] = {
|
|
388
|
-
"includeThoughts": True,
|
|
389
|
-
"thinkingBudget": thinking_budget,
|
|
405
|
+
headers = {
|
|
406
|
+
"Authorization": f"Bearer {access_token}",
|
|
407
|
+
"Content-Type": "application/json",
|
|
408
|
+
**ANTIGRAVITY_HEADERS, # Include Antigravity headers
|
|
390
409
|
}
|
|
391
410
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
411
|
+
# Build inner request payload
|
|
412
|
+
# Per API spec: contents must include role ("user" or "model")
|
|
413
|
+
|
|
414
|
+
# Build parts list - text prompt plus optional image
|
|
415
|
+
parts = [{"text": prompt}]
|
|
416
|
+
|
|
417
|
+
# Add image data for vision analysis (token optimization for multimodal)
|
|
418
|
+
if image_path:
|
|
419
|
+
import base64
|
|
420
|
+
from pathlib import Path
|
|
421
|
+
|
|
422
|
+
image_file = Path(image_path)
|
|
423
|
+
if image_file.exists():
|
|
424
|
+
# Determine MIME type
|
|
425
|
+
suffix = image_file.suffix.lower()
|
|
426
|
+
mime_types = {
|
|
427
|
+
".png": "image/png",
|
|
428
|
+
".jpg": "image/jpeg",
|
|
429
|
+
".jpeg": "image/jpeg",
|
|
430
|
+
".gif": "image/gif",
|
|
431
|
+
".webp": "image/webp",
|
|
432
|
+
".pdf": "application/pdf",
|
|
433
|
+
}
|
|
434
|
+
mime_type = mime_types.get(suffix, "image/png")
|
|
395
435
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
logger.error(f"UUID IMPORT FAILED: {e}")
|
|
399
|
-
raise RuntimeError(f"CUSTOM ERROR: UUID import failed: {e}")
|
|
400
|
-
|
|
401
|
-
wrapped_payload = {
|
|
402
|
-
"project": project_id,
|
|
403
|
-
"model": api_model,
|
|
404
|
-
"userAgent": "antigravity",
|
|
405
|
-
"requestId": request_id,
|
|
406
|
-
"request": inner_payload,
|
|
407
|
-
}
|
|
436
|
+
# Read and base64 encode
|
|
437
|
+
image_data = base64.b64encode(image_file.read_bytes()).decode("utf-8")
|
|
408
438
|
|
|
409
|
-
|
|
410
|
-
|
|
439
|
+
# Add inline image data for Gemini Vision API
|
|
440
|
+
parts.append({
|
|
441
|
+
"inlineData": {
|
|
442
|
+
"mimeType": mime_type,
|
|
443
|
+
"data": image_data,
|
|
444
|
+
}
|
|
445
|
+
})
|
|
446
|
+
logger.info(f"[multimodal] Added vision data: {image_path} ({mime_type})")
|
|
411
447
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
448
|
+
inner_payload = {
|
|
449
|
+
"contents": [{"role": "user", "parts": parts}],
|
|
450
|
+
"generationConfig": {
|
|
451
|
+
"temperature": temperature,
|
|
452
|
+
"maxOutputTokens": max_tokens,
|
|
453
|
+
},
|
|
454
|
+
"sessionId": session_id,
|
|
455
|
+
}
|
|
416
456
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
#
|
|
420
|
-
|
|
457
|
+
# Add thinking budget if supported by model/API
|
|
458
|
+
if thinking_budget > 0:
|
|
459
|
+
# For Gemini 2.0+ Thinking models
|
|
460
|
+
# Per Antigravity API: use "thinkingBudget", NOT "tokenLimit"
|
|
461
|
+
inner_payload["generationConfig"]["thinkingConfig"] = {
|
|
462
|
+
"includeThoughts": True,
|
|
463
|
+
"thinkingBudget": thinking_budget,
|
|
464
|
+
}
|
|
421
465
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
headers=headers,
|
|
426
|
-
json=wrapped_payload,
|
|
427
|
-
timeout=120.0,
|
|
428
|
-
)
|
|
466
|
+
# Wrap request body per reference implementation
|
|
467
|
+
try:
|
|
468
|
+
import uuid as uuid_module # Local import workaround for MCP context issue
|
|
429
469
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
470
|
+
request_id = f"invoke-{uuid_module.uuid4()}"
|
|
471
|
+
except Exception as e:
|
|
472
|
+
logger.error(f"UUID IMPORT FAILED: {e}")
|
|
473
|
+
raise RuntimeError(f"CUSTOM ERROR: UUID import failed: {e}")
|
|
474
|
+
|
|
475
|
+
wrapped_payload = {
|
|
476
|
+
"project": project_id,
|
|
477
|
+
"model": api_model,
|
|
478
|
+
"userAgent": "antigravity",
|
|
479
|
+
"requestId": request_id,
|
|
480
|
+
"request": inner_payload,
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
# Get pooled HTTP client for connection reuse
|
|
484
|
+
client = await _get_http_client()
|
|
485
|
+
|
|
486
|
+
# Try endpoints in fallback order with thinking recovery
|
|
487
|
+
response = None
|
|
488
|
+
last_error = None
|
|
489
|
+
max_retries = 2 # For thinking recovery
|
|
490
|
+
|
|
491
|
+
for retry_attempt in range(max_retries):
|
|
492
|
+
for endpoint in ANTIGRAVITY_ENDPOINTS:
|
|
493
|
+
# Reference uses: {endpoint}/v1internal:generateContent (NOT /models/{model})
|
|
494
|
+
api_url = f"{endpoint}/v1internal:generateContent"
|
|
495
|
+
|
|
496
|
+
try:
|
|
497
|
+
response = await client.post(
|
|
498
|
+
api_url,
|
|
499
|
+
headers=headers,
|
|
500
|
+
json=wrapped_payload,
|
|
501
|
+
timeout=120.0,
|
|
434
502
|
)
|
|
435
|
-
last_error = Exception(f"{response.status_code} from {endpoint}")
|
|
436
|
-
continue
|
|
437
503
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
error_text = response.text.lower()
|
|
441
|
-
if "thinking" in error_text or "signature" in error_text:
|
|
504
|
+
# 401/403 might be endpoint-specific, try next endpoint
|
|
505
|
+
if response.status_code in (401, 403):
|
|
442
506
|
logger.warning(
|
|
443
|
-
f"[Gemini]
|
|
507
|
+
f"[Gemini] Endpoint {endpoint} returned {response.status_code}, trying next"
|
|
444
508
|
)
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
509
|
+
last_error = Exception(f"{response.status_code} from {endpoint}")
|
|
510
|
+
continue
|
|
511
|
+
|
|
512
|
+
# Check for thinking-related errors that need recovery
|
|
513
|
+
if response.status_code in (400, 500):
|
|
514
|
+
error_text = response.text.lower()
|
|
515
|
+
if "thinking" in error_text or "signature" in error_text:
|
|
516
|
+
logger.warning(
|
|
517
|
+
f"[Gemini] Thinking error detected, clearing session cache and retrying"
|
|
518
|
+
)
|
|
519
|
+
clear_session_cache()
|
|
520
|
+
# Update session ID for retry
|
|
521
|
+
wrapped_payload["request"]["sessionId"] = _get_session_id()
|
|
522
|
+
last_error = Exception(f"Thinking error: {response.text[:200]}")
|
|
523
|
+
break # Break inner loop to retry with new session
|
|
524
|
+
|
|
525
|
+
# If we got a non-retryable response (success or 4xx client error), use it
|
|
526
|
+
if response.status_code < 500 and response.status_code != 429:
|
|
527
|
+
break
|
|
528
|
+
|
|
529
|
+
except httpx.TimeoutException as e:
|
|
530
|
+
last_error = e
|
|
531
|
+
continue
|
|
532
|
+
except Exception as e:
|
|
533
|
+
last_error = e
|
|
534
|
+
continue
|
|
535
|
+
else:
|
|
536
|
+
# Inner loop completed without break - no thinking recovery needed
|
|
537
|
+
break
|
|
454
538
|
|
|
455
|
-
|
|
456
|
-
|
|
539
|
+
# If we broke out of inner loop for thinking recovery, continue outer retry loop
|
|
540
|
+
if response and response.status_code in (400, 500):
|
|
457
541
|
continue
|
|
458
|
-
except Exception as e:
|
|
459
|
-
last_error = e
|
|
460
|
-
continue
|
|
461
|
-
else:
|
|
462
|
-
# Inner loop completed without break - no thinking recovery needed
|
|
463
542
|
break
|
|
464
543
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
544
|
+
if response is None:
|
|
545
|
+
# FALLBACK: Try Claude sonnet-4.5 for agents that support it
|
|
546
|
+
agent_context = params.get("agent_context", {})
|
|
547
|
+
agent_type = agent_context.get("agent_type", "unknown")
|
|
548
|
+
|
|
549
|
+
if agent_type in ("dewey", "explore", "document_writer", "multimodal"):
|
|
550
|
+
logger.warning(f"[{agent_type}] Gemini failed, falling back to Claude sonnet-4.5")
|
|
551
|
+
try:
|
|
552
|
+
import subprocess
|
|
553
|
+
fallback_result = subprocess.run(
|
|
554
|
+
["claude", "-p", prompt, "--model", "sonnet", "--output-format", "text"],
|
|
555
|
+
capture_output=True,
|
|
556
|
+
text=True,
|
|
557
|
+
timeout=120,
|
|
558
|
+
cwd=os.getcwd(),
|
|
559
|
+
)
|
|
560
|
+
if fallback_result.returncode == 0 and fallback_result.stdout.strip():
|
|
561
|
+
return fallback_result.stdout.strip()
|
|
562
|
+
except Exception as fallback_error:
|
|
563
|
+
logger.error(f"Fallback to Claude also failed: {fallback_error}")
|
|
469
564
|
|
|
470
|
-
|
|
471
|
-
raise ValueError(f"All Antigravity endpoints failed: {last_error}")
|
|
565
|
+
raise ValueError(f"All Antigravity endpoints failed: {last_error}")
|
|
472
566
|
|
|
473
|
-
|
|
474
|
-
|
|
567
|
+
response.raise_for_status()
|
|
568
|
+
data = response.json()
|
|
475
569
|
|
|
476
|
-
|
|
477
|
-
|
|
570
|
+
# Extract text from response using thinking-aware parser
|
|
571
|
+
return _extract_gemini_response(data)
|
|
478
572
|
|
|
479
573
|
|
|
480
574
|
# ========================
|
|
@@ -761,11 +855,11 @@ async def invoke_gemini_agentic(
|
|
|
761
855
|
|
|
762
856
|
|
|
763
857
|
@retry(
|
|
764
|
-
stop=stop_after_attempt(
|
|
765
|
-
wait=wait_exponential(multiplier=
|
|
858
|
+
stop=stop_after_attempt(2), # Reduced from 5 to 2 attempts
|
|
859
|
+
wait=wait_exponential(multiplier=2, min=10, max=120), # Longer waits: 10s → 20s → 40s
|
|
766
860
|
retry=retry_if_exception(is_retryable_exception),
|
|
767
861
|
before_sleep=lambda retry_state: logger.info(
|
|
768
|
-
f"
|
|
862
|
+
f"Server error, retrying in {retry_state.next_action.sleep} seconds..."
|
|
769
863
|
),
|
|
770
864
|
)
|
|
771
865
|
async def invoke_openai(
|
|
@@ -816,11 +910,19 @@ async def invoke_openai(
|
|
|
816
910
|
# Extract agent context for logging (may be passed via params or original call)
|
|
817
911
|
agent_context = params.get("agent_context", {})
|
|
818
912
|
agent_type = agent_context.get("agent_type", "direct")
|
|
913
|
+
task_id = agent_context.get("task_id", "")
|
|
914
|
+
description = agent_context.get("description", "")
|
|
819
915
|
prompt_summary = _summarize_prompt(prompt)
|
|
820
916
|
|
|
821
917
|
# Log with agent context and prompt summary
|
|
822
918
|
logger.info(f"[{agent_type}] → {model}: {prompt_summary}")
|
|
823
919
|
|
|
920
|
+
# USER-VISIBLE NOTIFICATION (stderr) - Shows when OpenAI is invoked
|
|
921
|
+
import sys
|
|
922
|
+
task_info = f" task={task_id}" if task_id else ""
|
|
923
|
+
desc_info = f" | {description}" if description else ""
|
|
924
|
+
print(f"🧠 OPENAI: {model} | agent={agent_type}{task_info}{desc_info}", file=sys.stderr)
|
|
925
|
+
|
|
824
926
|
access_token = await _ensure_valid_token(token_store, "openai")
|
|
825
927
|
logger.info(f"[invoke_openai] Got access token")
|
|
826
928
|
|