solana-agent 31.1.1__tar.gz → 31.1.2__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.
Files changed (42) hide show
  1. {solana_agent-31.1.1 → solana_agent-31.1.2}/PKG-INFO +1 -1
  2. {solana_agent-31.1.1 → solana_agent-31.1.2}/pyproject.toml +1 -1
  3. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/services/query.py +56 -37
  4. {solana_agent-31.1.1 → solana_agent-31.1.2}/LICENSE +0 -0
  5. {solana_agent-31.1.1 → solana_agent-31.1.2}/README.md +0 -0
  6. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/__init__.py +0 -0
  7. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/adapters/__init__.py +0 -0
  8. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/adapters/mongodb_adapter.py +0 -0
  9. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/adapters/openai_adapter.py +0 -0
  10. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/adapters/pinecone_adapter.py +0 -0
  11. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/cli.py +0 -0
  12. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/client/__init__.py +0 -0
  13. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/client/solana_agent.py +0 -0
  14. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/domains/__init__.py +0 -0
  15. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/domains/agent.py +0 -0
  16. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/domains/routing.py +0 -0
  17. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/factories/__init__.py +0 -0
  18. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/factories/agent_factory.py +0 -0
  19. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/guardrails/pii.py +0 -0
  20. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/interfaces/__init__.py +0 -0
  21. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/interfaces/client/client.py +0 -0
  22. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/interfaces/guardrails/guardrails.py +0 -0
  23. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/interfaces/plugins/plugins.py +0 -0
  24. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/interfaces/providers/data_storage.py +0 -0
  25. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/interfaces/providers/llm.py +0 -0
  26. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/interfaces/providers/memory.py +0 -0
  27. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/interfaces/providers/vector_storage.py +0 -0
  28. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/interfaces/services/agent.py +0 -0
  29. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/interfaces/services/knowledge_base.py +0 -0
  30. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/interfaces/services/query.py +0 -0
  31. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/interfaces/services/routing.py +0 -0
  32. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/plugins/__init__.py +0 -0
  33. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/plugins/manager.py +0 -0
  34. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/plugins/registry.py +0 -0
  35. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/plugins/tools/__init__.py +0 -0
  36. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/plugins/tools/auto_tool.py +0 -0
  37. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/repositories/__init__.py +0 -0
  38. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/repositories/memory.py +0 -0
  39. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/services/__init__.py +0 -0
  40. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/services/agent.py +0 -0
  41. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/services/knowledge_base.py +0 -0
  42. {solana_agent-31.1.1 → solana_agent-31.1.2}/solana_agent/services/routing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: solana-agent
3
- Version: 31.1.1
3
+ Version: 31.1.2
4
4
  Summary: AI Agents for Solana
5
5
  License: MIT
6
6
  Keywords: solana,solana ai,solana agent,ai,ai agent,ai agents
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "solana-agent"
3
- version = "31.1.1"
3
+ version = "31.1.2"
4
4
  description = "AI Agents for Solana"
5
5
  authors = ["Bevan Hunt <bevan@bevanhunt.com>"]
6
6
  license = "MIT"
@@ -200,7 +200,9 @@ class QueryService(QueryServiceInterface):
200
200
 
201
201
  # 7) Captured data context + incremental save using previous assistant message
202
202
  capture_context = ""
203
- form_complete = False
203
+ # Two completion flags:
204
+ required_complete = False
205
+ form_complete = False # required + optional
204
206
 
205
207
  # Helpers
206
208
  def _non_empty(v: Any) -> bool:
@@ -215,7 +217,6 @@ class QueryService(QueryServiceInterface):
215
217
 
216
218
  def _parse_numbers_list(s: str) -> List[str]:
217
219
  nums = re.findall(r"\b(\d+)\b", s)
218
- # dedupe keep order
219
220
  seen, out = set(), []
220
221
  for n in nums:
221
222
  if n not in seen:
@@ -225,9 +226,7 @@ class QueryService(QueryServiceInterface):
225
226
 
226
227
  def _extract_numbered_options(text: str) -> Dict[str, str]:
227
228
  """Parse previous assistant message for lines like:
228
- '1) Foo', '2. Bar', '- 3) Baz', '* 4. Buzz'
229
- Returns mapping '1' -> 'Foo', etc.
230
- """
229
+ '1) Foo', '1. Foo', '- 1) Foo', '* 1. Foo' -> {'1': 'Foo'}"""
231
230
  options: Dict[str, str] = {}
232
231
  if not text:
233
232
  return options
@@ -235,13 +234,9 @@ class QueryService(QueryServiceInterface):
235
234
  line = raw.strip()
236
235
  if not line:
237
236
  continue
238
- # Common Markdown patterns: "1. Label", "1) Label", "- 1) Label", "* 1. Label"
239
237
  m = re.match(r"^(?:[-*]\s*)?(\d+)[\.)]?\s+(.*)$", line)
240
238
  if m:
241
- idx, label = m.group(1), m.group(2).strip()
242
- # Strip trailing markdown soft-break spaces
243
- label = label.rstrip()
244
- # Ignore labels that are too short or look like continuations
239
+ idx, label = m.group(1), m.group(2).strip().rstrip()
245
240
  if len(label) >= 1:
246
241
  options[idx] = label
247
242
  return options
@@ -252,7 +247,6 @@ class QueryService(QueryServiceInterface):
252
247
  if not prev_text or not isinstance(schema, dict):
253
248
  return None
254
249
  t = prev_text.lower()
255
- # Heuristic synonyms for your onboarding schema
256
250
  patterns = [
257
251
  ("ideas", ["which ideas attract you", "ideas"]),
258
252
  ("description", ["please describe yourself", "describe yourself"]),
@@ -269,7 +263,6 @@ class QueryService(QueryServiceInterface):
269
263
  for field, keys in patterns:
270
264
  if field in candidates and any(key in t for key in keys):
271
265
  return field
272
- # Fallback: property name appears directly
273
266
  for field in candidates:
274
267
  if field in t:
275
268
  return field
@@ -322,33 +315,41 @@ class QueryService(QueryServiceInterface):
322
315
  required_fields = list(
323
316
  (active_capture_schema or {}).get("required", []) or []
324
317
  )
325
- # Prefer a field detected from prev assistant; else if exactly one required missing, use it
326
- target_field: Optional[str] = _detect_field_from_prev_question(
327
- prev_assistant, active_capture_schema
328
- )
318
+ all_fields = list(props.keys())
319
+ optional_fields = [
320
+ f for f in all_fields if f not in set(required_fields)
321
+ ]
322
+
329
323
  active_data_existing = (
330
324
  latest_by_name.get(active_capture_name, {}) or {}
331
325
  ).get("data", {}) or {}
332
326
 
333
- def _missing_required() -> List[str]:
327
+ def _missing(fields: List[str]) -> List[str]:
334
328
  return [
335
329
  f
336
- for f in required_fields
330
+ for f in fields
337
331
  if not _non_empty(active_data_existing.get(f))
338
332
  ]
339
333
 
334
+ missing_required = _missing(required_fields)
335
+ missing_optional = _missing(optional_fields)
336
+
337
+ target_field: Optional[str] = _detect_field_from_prev_question(
338
+ prev_assistant, active_capture_schema
339
+ )
340
340
  if not target_field:
341
- missing = _missing_required()
342
- if len(missing) == 1:
343
- target_field = missing[0]
341
+ # If exactly one required missing, target it; else if none required missing and exactly one optional missing, target it.
342
+ if len(missing_required) == 1:
343
+ target_field = missing_required[0]
344
+ elif len(missing_required) == 0 and len(missing_optional) == 1:
345
+ target_field = missing_optional[0]
344
346
 
345
- if target_field:
347
+ if target_field and target_field in props:
346
348
  f_schema = props.get(target_field, {}) or {}
347
349
  f_type = f_schema.get("type")
348
350
  number_to_label = _extract_numbered_options(prev_assistant)
349
351
 
350
352
  if number_to_label:
351
- # Map any numbers in user's reply to their labels
352
353
  nums = _parse_numbers_list(user_text)
353
354
  labels = [
354
355
  number_to_label[n] for n in nums if n in number_to_label
@@ -359,7 +360,6 @@ class QueryService(QueryServiceInterface):
359
360
  else:
360
361
  incremental[target_field] = labels[0]
361
362
 
362
- # If we didn't map via options, fallback to type-based parse
363
363
  if target_field not in incremental:
364
364
  if f_type == "number":
365
365
  m = re.search(r"\b([0-9]+(?:\.[0-9]+)?)\b", user_text)
@@ -369,7 +369,6 @@ class QueryService(QueryServiceInterface):
369
369
  except Exception:
370
370
  pass
371
371
  elif f_type == "array":
372
- # Accept CSV-style input as array of strings
373
372
  parts = [
374
373
  p.strip()
375
374
  for p in re.split(r"[,\n;]+", user_text)
@@ -377,11 +376,10 @@ class QueryService(QueryServiceInterface):
377
376
  ]
378
377
  if parts:
379
378
  incremental[target_field] = parts
380
- else: # string/default
379
+ else:
381
380
  if user_text.strip():
382
381
  incremental[target_field] = user_text.strip()
383
382
 
384
- # Filter out empty junk and save
385
383
  if incremental:
386
384
  cleaned = {
387
385
  k: v for k, v in incremental.items() if _non_empty(v)
@@ -397,6 +395,7 @@ class QueryService(QueryServiceInterface):
397
395
  )
398
396
  except Exception as se:
399
397
  logger.error(f"Error saving incremental capture: {se}")
398
+
400
399
  except Exception as e:
401
400
  logger.debug(f"Incremental extraction skipped: {e}")
402
401
 
@@ -411,19 +410,33 @@ class QueryService(QueryServiceInterface):
411
410
 
412
411
  lines: List[str] = []
413
412
  if active_capture_name and isinstance(active_capture_schema, dict):
414
- active_data = _get_active_data(active_capture_name)
413
+ props = (active_capture_schema or {}).get("properties", {})
415
414
  required_fields = list(
416
415
  (active_capture_schema or {}).get("required", []) or []
417
416
  )
418
- missing = [
419
- f for f in required_fields if not _non_empty(active_data.get(f))
417
+ all_fields = list(props.keys())
418
+ optional_fields = [
419
+ f for f in all_fields if f not in set(required_fields)
420
420
  ]
421
- form_complete = len(missing) == 0 and len(required_fields) > 0
421
+
422
+ active_data = _get_active_data(active_capture_name)
423
+
424
+ def _missing_from(data: Dict[str, Any], fields: List[str]) -> List[str]:
425
+ return [f for f in fields if not _non_empty(data.get(f))]
426
+
427
+ missing_required = _missing_from(active_data, required_fields)
428
+ missing_optional = _missing_from(active_data, optional_fields)
429
+
430
+ required_complete = (
431
+ len(missing_required) == 0 and len(required_fields) > 0
432
+ )
433
+ form_complete = required_complete and len(missing_optional) == 0
422
434
 
423
435
  lines.append(
424
436
  "CAPTURED FORM STATE (Authoritative; do not re-ask filled values):"
425
437
  )
426
438
  lines.append(f"- form_name: {active_capture_name}")
439
+
427
440
  if active_data:
428
441
  pairs = [
429
442
  f"{k}: {v}" for k, v in active_data.items() if _non_empty(v)
@@ -433,8 +446,12 @@ class QueryService(QueryServiceInterface):
433
446
  )
434
447
  else:
435
448
  lines.append("- filled_fields: (none)")
449
+
450
+ lines.append(
451
+ f"- missing_required_fields: {', '.join(missing_required) if missing_required else '(none)'}"
452
+ )
436
453
  lines.append(
437
- f"- missing_required_fields: {', '.join(missing) if missing else '(none)'}"
454
+ f"- missing_optional_fields: {', '.join(missing_optional) if missing_optional else '(none)'}"
438
455
  )
439
456
  lines.append("")
440
457
 
@@ -455,7 +472,7 @@ class QueryService(QueryServiceInterface):
455
472
  if lines:
456
473
  capture_context = "\n".join(lines) + "\n\n"
457
474
 
458
- # Merge contexts
475
+ # Merge contexts + flow rules
459
476
  combined_context = ""
460
477
  if capture_context:
461
478
  combined_context += capture_context
@@ -470,9 +487,11 @@ class QueryService(QueryServiceInterface):
470
487
  "- Prefer KB/tools for facts.\n"
471
488
  "- History is for tone and continuity.\n\n"
472
489
  "FORM FLOW RULES:\n"
473
- "- Ask exactly one missing required field per turn.\n"
490
+ "- Ask exactly one field per turn.\n"
491
+ "- If any required fields are missing, ask the next missing required field.\n"
492
+ "- If all required fields are filled but optional fields are missing, ask the next missing optional field.\n"
474
493
  "- Do NOT re-ask or verify values present in Captured User Data (auto-saved, authoritative).\n"
475
- "- If no required fields are missing, proceed without further capture questions.\n\n"
494
+ "- Do NOT provide summaries until no required or optional fields are missing.\n\n"
476
495
  )
477
496
 
478
497
  # 8) Generate response
@@ -510,7 +529,7 @@ class QueryService(QueryServiceInterface):
510
529
  except Exception:
511
530
  pass
512
531
 
513
- # If form is complete, ask for structured output JSON
532
+ # Only run final structured output when no required or optional fields are missing
514
533
  if capture_schema and capture_name and form_complete:
515
534
  try:
516
535
  DynamicModel = self._build_model_from_json_schema(
@@ -739,5 +758,5 @@ class QueryService(QueryServiceInterface):
739
758
  else:
740
759
  fields[field_name] = (typ, default)
741
760
 
742
- Model = create_model(name, **fields) # type: ignore
761
+ Model = create_model(name, **fields)
743
762
  return Model
File without changes
File without changes