hyperforge 1.0.0.post56__tar.gz → 1.0.0.post60__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 (106) hide show
  1. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/PKG-INFO +1 -1
  2. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/pyproject.toml +1 -1
  3. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/v1/mcp_content.py +14 -0
  4. hyperforge-1.0.0.post60/src/hyperforge/api/v1/oauth.py +163 -0
  5. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/interaction.py +1 -0
  6. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/memory/memory.py +5 -0
  7. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/models.py +21 -0
  8. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/server/session.py +5 -1
  9. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/server/utils.py +2 -0
  10. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/settings.py +6 -0
  11. hyperforge-1.0.0.post56/src/hyperforge/api/v1/oauth.py +0 -60
  12. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/README.md +0 -0
  13. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/VERSION +0 -0
  14. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/__init__.py +0 -0
  15. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/agent.py +0 -0
  16. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/alembic/README +0 -0
  17. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/alembic/env.py +0 -0
  18. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/alembic/script.py.mako +0 -0
  19. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/alembic/versions/049a4aed19e1_create_generation_table.py +0 -0
  20. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/alembic/versions/1416cb41bd49_add_downloads_table.py +0 -0
  21. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/alembic/versions/2847934e2d59_unique_driver.py +0 -0
  22. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/alembic/versions/4d89a36c8bda_memory.py +0 -0
  23. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/alembic/versions/72b997edb05d_identifier_for_drivers.py +0 -0
  24. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/alembic/versions/9c6f6a1b4e7f_workflows_default_fk.py +0 -0
  25. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/alembic/versions/b8edb72295e0_.py +0 -0
  26. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/alembic/versions/f3d2c1b0a9e8_soft_delete_workflows.py +0 -0
  27. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/alembic.ini +0 -0
  28. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/__init__.py +0 -0
  29. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/app.py +0 -0
  30. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/authentication.py +0 -0
  31. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/commands.py +0 -0
  32. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/internal/__init__.py +0 -0
  33. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/internal/inspect.py +0 -0
  34. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/internal/router.py +0 -0
  35. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/logging.py +0 -0
  36. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/models.py +0 -0
  37. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/session.py +0 -0
  38. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/settings.py +0 -0
  39. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/utils.py +0 -0
  40. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/v1/__init__.py +0 -0
  41. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/v1/agents.py +0 -0
  42. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/v1/interaction.py +0 -0
  43. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/v1/mcp_interaction.py +0 -0
  44. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/v1/prompt.py +0 -0
  45. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/v1/router.py +0 -0
  46. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/v1/schema.py +0 -0
  47. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/v1/session.py +0 -0
  48. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/v1/utils.py +0 -0
  49. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/api/v1/workflows.py +0 -0
  50. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/arag.py +0 -0
  51. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/broker/__init__.py +0 -0
  52. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/broker/local.py +0 -0
  53. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/broker/redis.py +0 -0
  54. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/configure.py +0 -0
  55. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/context/__init__.py +0 -0
  56. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/context/agent.py +0 -0
  57. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/context/config.py +0 -0
  58. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/database.py +0 -0
  59. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/db/__init__.py +0 -0
  60. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/db/agents.py +0 -0
  61. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/db/encryption.py +0 -0
  62. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/db/exceptions.py +0 -0
  63. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/db/settings.py +0 -0
  64. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/db/workflow_cleanup.py +0 -0
  65. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/definition.py +0 -0
  66. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/driver.py +0 -0
  67. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/dummy.py +0 -0
  68. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/engine.py +0 -0
  69. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/exceptions.py +0 -0
  70. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/feature_flag.py +0 -0
  71. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/fixtures.py +0 -0
  72. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/llm.py +0 -0
  73. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/manager.py +0 -0
  74. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/memory/__init__.py +0 -0
  75. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/minimal_fixtures.py +0 -0
  76. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/nua.py +0 -0
  77. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/openapi.py +0 -0
  78. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/prompts.py +0 -0
  79. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/pubsub.py +0 -0
  80. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/py.typed +0 -0
  81. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/redis_utils.py +0 -0
  82. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/retrieval/__init__.py +0 -0
  83. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/retrieval/agent.py +0 -0
  84. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/retrieval/config.py +0 -0
  85. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/server/__init__.py +0 -0
  86. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/server/cache.py +0 -0
  87. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/server/run.py +0 -0
  88. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/server/sandbox.py +0 -0
  89. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/server/settings.py +0 -0
  90. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/server/web.py +0 -0
  91. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/standalone/__init__.py +0 -0
  92. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/standalone/agent.py +0 -0
  93. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/standalone/app.py +0 -0
  94. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/standalone/config.py +0 -0
  95. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/standalone/const.py +0 -0
  96. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/standalone/run.py +0 -0
  97. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/standalone/settings.py +0 -0
  98. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/standalone/ui_router.py +0 -0
  99. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/static/assets/index-DLejS5jC.js +0 -0
  100. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/static/assets/index-MoxkZ1_3.css +0 -0
  101. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/static/favicon.svg +0 -0
  102. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/static/index.html +0 -0
  103. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/trace.py +0 -0
  104. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/utils/__init__.py +0 -0
  105. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/utils/http.py +0 -0
  106. {hyperforge-1.0.0.post56 → hyperforge-1.0.0.post60}/src/hyperforge/workflows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperforge
3
- Version: 1.0.0.post56
3
+ Version: 1.0.0.post60
4
4
  Summary: Agentic Framework for Orchestrated Runtime, Governance, and Execution
5
5
  Author: AI Data Team
6
6
  Author-email: AI Data Team <learning@nuclia.com>
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "hyperforge"
7
- version = "1.0.0.post56"
7
+ version = "1.0.0.post60"
8
8
  authors = [{ name = "AI Data Team", email = "learning@nuclia.com" }]
9
9
  description = "Agentic Framework for Orchestrated Runtime, Governance, and Execution"
10
10
  readme = "README.md"
@@ -229,6 +229,19 @@ def convert_arag_answer_to_content(
229
229
  )
230
230
  )
231
231
 
232
+ if context.json_objects:
233
+ for idx, json_obj in enumerate(context.json_objects):
234
+ contents.append(
235
+ EmbeddedResource(
236
+ type="resource",
237
+ resource=TextResourceContents(
238
+ uri=f"rao-response://context/{context.id}/json-objects/{idx}", # type: ignore[arg-type]
239
+ text=json.dumps(pydantic_core.to_jsonable_python(json_obj)),
240
+ mimeType="application/json",
241
+ ),
242
+ )
243
+ )
244
+
232
245
  if context.image_urls:
233
246
  contents.append(
234
247
  EmbeddedResource(
@@ -272,6 +285,7 @@ def convert_arag_answer_to_content(
272
285
  type="text",
273
286
  text="\n".join(parts),
274
287
  annotations=Annotations(audience=["assistant"]),
288
+ _meta=step.metadata,
275
289
  )
276
290
  )
277
291
 
@@ -0,0 +1,163 @@
1
+ import logging
2
+ from urllib.parse import urlencode
3
+
4
+ from cryptography.fernet import InvalidToken
5
+ from fastapi import Query
6
+ from starlette.requests import Request
7
+ from starlette.responses import HTMLResponse
8
+
9
+ from hyperforge.api.settings import Settings
10
+ from hyperforge.api.v1.router import router
11
+ from hyperforge.api.v1.utils import tracer
12
+ from hyperforge_mcp.http import _fingerprint, decrypt_mcp_oauth_state
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ RENDER = "<html><body><h1>OAuth Completed</h1><p>You can close this window and return to the application.</p></body></html>"
17
+
18
+
19
+ def _build_oauth_subject(
20
+ settings: Settings,
21
+ account_id: str,
22
+ agent_id: str,
23
+ workflow_id: str,
24
+ session: str,
25
+ question_id: str,
26
+ oauth_uuid: str,
27
+ ) -> str:
28
+ return settings.oauth_subject.format(
29
+ account=account_id,
30
+ agent_id=agent_id,
31
+ session=session,
32
+ question=question_id,
33
+ oauth_uuid=oauth_uuid,
34
+ workflow_id=workflow_id,
35
+ )
36
+
37
+
38
+ @router.get(
39
+ "/api/auth/agent/{agent_id}/workflow/{workflow_id}/session/{session}/oauth/{oauth_uuid}/callback",
40
+ status_code=200,
41
+ description="Get Agent Schema",
42
+ tags=["Retrieval Agent"],
43
+ include_in_schema=False,
44
+ )
45
+ async def oauth_callback(
46
+ request: Request,
47
+ agent_id: str,
48
+ session: str,
49
+ workflow_id: str,
50
+ oauth_uuid: str,
51
+ question_id: str = Query(..., include_in_schema=False),
52
+ state: str = Query(..., include_in_schema=False),
53
+ account_id: str = Query(..., include_in_schema=False),
54
+ ):
55
+ """
56
+ Callback from oauth flow on RAO that requires to send creds to websocket
57
+ """
58
+ settings: Settings = request.app.settings
59
+ subject = _build_oauth_subject(
60
+ settings,
61
+ account_id,
62
+ agent_id,
63
+ workflow_id,
64
+ session,
65
+ question_id,
66
+ oauth_uuid,
67
+ )
68
+ # Request a question
69
+ with tracer().start_as_current_span("Request activation"):
70
+ logger.info(
71
+ "OAuth callback received for agent=%s, session=%s, oauth_uuid=%s, question_id=%s",
72
+ agent_id,
73
+ session,
74
+ oauth_uuid,
75
+ question_id,
76
+ )
77
+ await request.app.broker.send_reply(subject, state)
78
+ logger.info(
79
+ "OAuth callback published to stream %s",
80
+ subject,
81
+ )
82
+
83
+ return HTMLResponse(content=RENDER)
84
+
85
+
86
+ @router.get(
87
+ "/api/auth/mcp/callback",
88
+ status_code=200,
89
+ description="Generic MCP OAuth callback (fixed redirect URI, state-routed)",
90
+ tags=["Retrieval Agent"],
91
+ include_in_schema=False,
92
+ )
93
+ async def mcp_oauth_callback_generic(
94
+ request: Request,
95
+ code: str | None = Query(None, include_in_schema=False),
96
+ state: str | None = Query(None, include_in_schema=False),
97
+ error: str | None = Query(None, include_in_schema=False),
98
+ error_description: str | None = Query(None, include_in_schema=False),
99
+ ):
100
+ settings: Settings = request.app.settings
101
+
102
+ if state is None:
103
+ logger.warning("MCP generic OAuth callback received without state parameter")
104
+ return HTMLResponse(content="Missing OAuth state parameter", status_code=400)
105
+
106
+ try:
107
+ routing = decrypt_mcp_oauth_state(state)
108
+ except InvalidToken:
109
+ logger.warning(
110
+ "MCP generic OAuth callback: invalid or expired state (Fernet decryption failed)"
111
+ )
112
+ return HTMLResponse(content="Invalid or expired OAuth state", status_code=400)
113
+
114
+ sdk_state = routing.sdk_state or None
115
+ if sdk_state is None:
116
+ logger.warning(
117
+ "MCP generic OAuth callback: decrypted state missing sdk_state field"
118
+ )
119
+ return HTMLResponse(content="Malformed OAuth state", status_code=400)
120
+
121
+ subject = _build_oauth_subject(
122
+ settings,
123
+ routing.account_id,
124
+ routing.agent_id,
125
+ routing.workflow_id,
126
+ routing.session_id,
127
+ routing.question_id,
128
+ routing.oauth_uuid,
129
+ )
130
+
131
+ payload_data: dict[str, str] = {}
132
+ if code is not None:
133
+ payload_data["code"] = code
134
+ if sdk_state is not None:
135
+ payload_data["state"] = sdk_state
136
+ if error is not None:
137
+ payload_data["error"] = error
138
+ if error_description is not None:
139
+ payload_data["error_description"] = error_description
140
+
141
+ payload = urlencode(payload_data)
142
+
143
+ with tracer().start_as_current_span("MCP generic OAuth callback"):
144
+ logger.info(
145
+ "MCP generic OAuth callback: agent=%s workflow=%s session=%s question_id=%s oauth_uuid=%s sdk_state_fp=%s has_code=%s has_error=%s",
146
+ routing.agent_id,
147
+ routing.workflow_id,
148
+ routing.session_id,
149
+ routing.question_id,
150
+ routing.oauth_uuid,
151
+ _fingerprint(sdk_state),
152
+ code is not None,
153
+ error is not None,
154
+ )
155
+ await request.app.broker.send_reply(subject, payload)
156
+ logger.info("mcp_oauth send_reply: published to stream %s", subject)
157
+
158
+ if error is not None:
159
+ desc = f": {error_description}" if error_description else ""
160
+ return HTMLResponse(
161
+ content=f"OAuth authorization failed ({error}{desc})", status_code=400
162
+ )
163
+ return HTMLResponse(content=RENDER)
@@ -60,6 +60,7 @@ class Provider(Enum):
60
60
  AZURE_CERTIFICATE_CREDENTIALS = "azure_certificate_credentials"
61
61
  AWS_S3_ACCESS_KEYS = "aws_s3_access_keys"
62
62
  SHAREFILE_OAUTH = "sharefile_oauth"
63
+ MCP_OAUTH = "mcp_oauth"
63
64
 
64
65
 
65
66
  class OAuthAuthenticateURL(BaseModel):
@@ -85,6 +85,7 @@ class BaseSessionMemory:
85
85
 
86
86
  agent_id: str = ""
87
87
  workflow_id: str = ""
88
+ account_id: str = ""
88
89
  kbid: Optional[str] = None
89
90
 
90
91
  # User information dictionary
@@ -638,6 +639,10 @@ class QuestionMemory:
638
639
  """Returns the workflow ID for the current question. The workflow ID is a unique identifier that is shared across all questions and interactions that belong to the same workflow. This can be used to group related interactions together, and to keep track of the conversation history in a coherent way."""
639
640
  return self.session.workflow_id
640
641
 
642
+ def get_account_id(self) -> str:
643
+ """Returns the account ID for the current question."""
644
+ return self.session.account_id
645
+
641
646
  def context_user_info(self) -> str:
642
647
  """Returns a string with user information that can be used in the context of the agent. This can include information such as user preferences, user history, or any other relevant information about the user that can help the agent to generate a more personalized and accurate response."""
643
648
  return self.session.context_user_info()
@@ -122,6 +122,7 @@ class Step(BaseModel):
122
122
  input_nuclia_tokens: Optional[float]
123
123
  output_nuclia_tokens: Optional[float]
124
124
  error: Optional[str] = None
125
+ metadata: Optional[Dict[str, Any]] = None
125
126
 
126
127
  def __str__(self):
127
128
  return f"({self.timeit:.2f}s) {self.module}: {self.title} \n {self.value} \n {self.reason} \n NT:({self.input_nuclia_tokens}:{self.output_nuclia_tokens})"
@@ -248,6 +249,25 @@ CONTEXT_TEMPLATE = """
248
249
  CONTEXT_PROMPT_TEMPLATE = PROMPT_ENVIRONMENT.from_string(CONTEXT_TEMPLATE)
249
250
 
250
251
 
252
+ class JSONObject(BaseModel):
253
+ json_schema: Optional[Dict[str, Any]] = Field(
254
+ default=None,
255
+ description="JSON schema that defines the structure of the JSON object.",
256
+ )
257
+ json_object: Dict[str, Any] = Field(
258
+ default_factory=dict,
259
+ description="The actual JSON object that conforms to the provided JSON schema.",
260
+ )
261
+ metadata: Optional[Dict[str, Any]] = Field(
262
+ default_factory=dict,
263
+ description="Optional metadata associated with the JSON object.",
264
+ )
265
+ id: Optional[str] = Field(
266
+ default_factory=lambda: uuid.uuid4().hex,
267
+ description="Unique identifier for this JSON object instance.",
268
+ )
269
+
270
+
251
271
  class Context(BaseModel):
252
272
  id: str = Field(
253
273
  default_factory=lambda: uuid.uuid4().hex,
@@ -260,6 +280,7 @@ class Context(BaseModel):
260
280
  images: Dict[str, Image] = Field(default_factory=dict)
261
281
  prompts: List[Prompt] = Field(default_factory=list)
262
282
  structured: List[str] = Field(default_factory=list)
283
+ json_objects: List[JSONObject] = Field(default_factory=list)
263
284
  source: str
264
285
  agent: str
265
286
  # XXX: This is not actually a summary, but an answer attempt for now!
@@ -181,8 +181,12 @@ class SessionManager:
181
181
  config=config.memory,
182
182
  agent=message.agent_id,
183
183
  workflow_id=message.workflow_id,
184
+ account_id=message.account,
184
185
  )
185
- self.memory[message.session] = memory
186
+ # Ephemeral sessions are short-lived and should not be persisted in
187
+ # the shared LRU cache across activations.
188
+ if message.session != "ephemeral":
189
+ self.memory[message.session] = memory
186
190
  else:
187
191
  memory = self.memory[message.session]
188
192
 
@@ -16,6 +16,7 @@ async def get_memory(
16
16
  config: MemoryConfig,
17
17
  agent: str,
18
18
  workflow_id: str,
19
+ account_id: str = "",
19
20
  ) -> BaseSessionMemory:
20
21
  memory: BaseSessionMemory
21
22
 
@@ -53,5 +54,6 @@ async def get_memory(
53
54
  )
54
55
 
55
56
  memory.init(session=session)
57
+ memory.account_id = account_id
56
58
 
57
59
  return memory
@@ -2,17 +2,23 @@ from pydantic import model_validator
2
2
  from pydantic_settings import BaseSettings
3
3
 
4
4
  _REDIRECT_PATH = "/api/auth/agent/{agent_id}/workflow/{workflow_id}/session/{session_id}/oauth/{oauth_uuid}/callback"
5
+ _MCP_CALLBACK_PATH = "/api/auth/mcp/callback"
5
6
 
6
7
 
7
8
  class OAuthSettings(BaseSettings):
8
9
  nuclia_public_url: str = "https://{zone}.nuclia.com"
9
10
  nuclia_zone: str = "arag"
10
11
  rao_redirect_url: str = ""
12
+ mcp_callback_url: str = ""
11
13
 
12
14
  @model_validator(mode="after")
13
15
  def _resolve_urls(self) -> "OAuthSettings":
14
16
  self.nuclia_public_url = self.nuclia_public_url.format(zone=self.nuclia_zone)
15
17
  if not self.rao_redirect_url:
16
18
  self.rao_redirect_url = self.nuclia_public_url.rstrip("/") + _REDIRECT_PATH
19
+ if not self.mcp_callback_url:
20
+ self.mcp_callback_url = (
21
+ self.nuclia_public_url.rstrip("/") + _MCP_CALLBACK_PATH
22
+ )
17
23
 
18
24
  return self
@@ -1,60 +0,0 @@
1
- import logging
2
-
3
- from fastapi import Query
4
- from starlette.requests import Request
5
- from starlette.responses import HTMLResponse
6
-
7
- from hyperforge.api.settings import Settings
8
- from hyperforge.api.v1.router import router
9
- from hyperforge.api.v1.utils import tracer
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
- RENDER = "<html><body><h1>OAuth Completed</h1><p>You can close this window and return to the application.</p></body></html>"
14
-
15
-
16
- @router.get(
17
- "/api/auth/agent/{agent_id}/workflow/{workflow_id}/session/{session}/oauth/{oauth_uuid}/callback",
18
- status_code=200,
19
- description="Get Agent Schema",
20
- tags=["Retrieval Agent"],
21
- include_in_schema=False,
22
- )
23
- async def oauth_callback(
24
- request: Request,
25
- agent_id: str,
26
- session: str,
27
- workflow_id: str,
28
- oauth_uuid: str,
29
- question_id: str = Query(..., include_in_schema=False),
30
- state: str = Query(..., include_in_schema=False),
31
- account_id: str = Query(..., include_in_schema=False),
32
- ):
33
- """
34
- Callback from oauth flow on RAO that requires to send creds to websocket
35
- """
36
- settings: Settings = request.app.settings
37
- subject = settings.oauth_subject.format(
38
- account=account_id,
39
- agent_id=agent_id,
40
- session=session,
41
- question=question_id,
42
- oauth_uuid=oauth_uuid,
43
- workflow_id=workflow_id,
44
- )
45
- # Request a question
46
- with tracer().start_as_current_span("Request activation"):
47
- logger.info(
48
- "OAuth callback received for agent=%s, session=%s, oauth_uuid=%s, question_id=%s",
49
- agent_id,
50
- session,
51
- oauth_uuid,
52
- question_id,
53
- )
54
- await request.app.broker.send_reply(subject, state)
55
- logger.info(
56
- "OAuth callback published to stream %s",
57
- subject,
58
- )
59
-
60
- return HTMLResponse(content=RENDER)