feat: add validation to fastapi routes for agent IDs (#5454)
* change my PR to match Caren's * add path parameter validation for agent id first * remove old import * remove old agent_id_pattern pattern * add example and fix max/min calculation to include hyphen * fix regex string interpolation * example deprecated in favour of examples * openapi autogen * change template test to expect 422 * fix 422 swallow * expect 422 or 400 * rewrite error codes * fix hallucinated uuid * tweaked error message test * print docker logs on failure
This commit is contained in:
@@ -3755,8 +3755,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "max_steps",
|
||||
@@ -3885,8 +3891,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -3926,8 +3938,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
@@ -3975,8 +3993,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "include_relationships",
|
||||
@@ -4035,8 +4059,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4074,8 +4104,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
@@ -4194,15 +4230,6 @@
|
||||
"description": "Attach a tool to an agent.",
|
||||
"operationId": "attach_tool",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tool_id",
|
||||
"in": "path",
|
||||
@@ -4211,6 +4238,21 @@
|
||||
"type": "string",
|
||||
"title": "Tool Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4244,15 +4286,6 @@
|
||||
"description": "Detach a tool from an agent.",
|
||||
"operationId": "detach_tool",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tool_id",
|
||||
"in": "path",
|
||||
@@ -4261,6 +4294,21 @@
|
||||
"type": "string",
|
||||
"title": "Tool Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4294,15 +4342,6 @@
|
||||
"description": "Attach a tool to an agent.",
|
||||
"operationId": "modify_approval",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "tool_name",
|
||||
"in": "path",
|
||||
@@ -4312,6 +4351,21 @@
|
||||
"title": "Tool Name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "requires_approval",
|
||||
"in": "query",
|
||||
@@ -4353,15 +4407,6 @@
|
||||
"description": "Attach a source to an agent.",
|
||||
"operationId": "attach_source_to_agent",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "source_id",
|
||||
"in": "path",
|
||||
@@ -4370,6 +4415,21 @@
|
||||
"type": "string",
|
||||
"title": "Source Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4403,15 +4463,6 @@
|
||||
"description": "Attach a folder to an agent.",
|
||||
"operationId": "attach_folder_to_agent",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "folder_id",
|
||||
"in": "path",
|
||||
@@ -4420,6 +4471,21 @@
|
||||
"type": "string",
|
||||
"title": "Folder Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4453,15 +4519,6 @@
|
||||
"description": "Detach a source from an agent.",
|
||||
"operationId": "detach_source_from_agent",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "source_id",
|
||||
"in": "path",
|
||||
@@ -4470,6 +4527,21 @@
|
||||
"type": "string",
|
||||
"title": "Source Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4503,15 +4575,6 @@
|
||||
"description": "Detach a folder from an agent.",
|
||||
"operationId": "detach_folder_from_agent",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "folder_id",
|
||||
"in": "path",
|
||||
@@ -4520,6 +4583,21 @@
|
||||
"type": "string",
|
||||
"title": "Folder Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4559,8 +4637,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4598,15 +4682,6 @@
|
||||
"description": "Opens a specific file for a given agent.\n\nThis endpoint marks a specific file as open in the agent's file state.\nThe file will be included in the agent's working memory view.\nReturns a list of file names that were closed due to LRU eviction.",
|
||||
"operationId": "open_file",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "file_id",
|
||||
"in": "path",
|
||||
@@ -4615,6 +4690,21 @@
|
||||
"type": "string",
|
||||
"title": "File Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4652,15 +4742,6 @@
|
||||
"description": "Closes a specific file for a given agent.\n\nThis endpoint marks a specific file as closed in the agent's file state.\nThe file will be removed from the agent's working memory view.",
|
||||
"operationId": "close_file",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "file_id",
|
||||
"in": "path",
|
||||
@@ -4669,6 +4750,21 @@
|
||||
"type": "string",
|
||||
"title": "File Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -4706,8 +4802,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
@@ -4832,8 +4934,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
@@ -4958,8 +5066,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
@@ -5118,8 +5232,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -5153,15 +5273,6 @@
|
||||
"description": "Retrieve a core memory block from an agent.",
|
||||
"operationId": "retrieve_core_memory_block",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "block_label",
|
||||
"in": "path",
|
||||
@@ -5170,6 +5281,21 @@
|
||||
"type": "string",
|
||||
"title": "Block Label"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -5201,15 +5327,6 @@
|
||||
"description": "Updates a core memory block of an agent.",
|
||||
"operationId": "modify_core_memory_block",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "block_label",
|
||||
"in": "path",
|
||||
@@ -5218,6 +5335,21 @@
|
||||
"type": "string",
|
||||
"title": "Block Label"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
@@ -5267,8 +5399,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
@@ -5387,15 +5525,6 @@
|
||||
"description": "Attach a core memory block to an agent.",
|
||||
"operationId": "attach_core_memory_block",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "block_id",
|
||||
"in": "path",
|
||||
@@ -5404,6 +5533,21 @@
|
||||
"type": "string",
|
||||
"title": "Block Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -5437,15 +5581,6 @@
|
||||
"description": "Detach a core memory block from an agent.",
|
||||
"operationId": "detach_core_memory_block",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "block_id",
|
||||
"in": "path",
|
||||
@@ -5454,6 +5589,21 @@
|
||||
"type": "string",
|
||||
"title": "Block Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -5493,8 +5643,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "after",
|
||||
@@ -5627,8 +5783,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
@@ -5682,8 +5844,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "query",
|
||||
@@ -5818,15 +5986,6 @@
|
||||
"description": "Delete a memory from an agent's archival memory store.",
|
||||
"operationId": "delete_passage",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "memory_id",
|
||||
"in": "path",
|
||||
@@ -5835,6 +5994,21 @@
|
||||
"type": "string",
|
||||
"title": "Memory Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -5872,8 +6046,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
@@ -6068,8 +6248,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
@@ -6113,15 +6299,6 @@
|
||||
"description": "Update the details of a message associated with an agent.",
|
||||
"operationId": "modify_message",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "message_id",
|
||||
"in": "path",
|
||||
@@ -6130,6 +6307,21 @@
|
||||
"type": "string",
|
||||
"title": "Message Id"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
@@ -6236,8 +6428,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
@@ -6288,8 +6486,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
@@ -6385,8 +6589,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
@@ -6436,8 +6646,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "add_default_initial_messages",
|
||||
@@ -6489,8 +6705,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "manager_type",
|
||||
@@ -6633,8 +6855,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
@@ -6694,8 +6922,14 @@
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 42,
|
||||
"maxLength": 42,
|
||||
"pattern": "^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'",
|
||||
"examples": ["agent-123e4567-e89b-42d3-8456-426614174000"],
|
||||
"title": "Agent Id"
|
||||
}
|
||||
},
|
||||
"description": "The ID of the agent in the format 'agent-<uuid4>'"
|
||||
},
|
||||
{
|
||||
"name": "max_message_length",
|
||||
|
||||
@@ -33,8 +33,6 @@ LETTA_TOOL_MODULE_NAMES = [
|
||||
DEFAULT_ORG_ID = "org-00000000-0000-4000-8000-000000000000"
|
||||
DEFAULT_ORG_NAME = "default_org"
|
||||
|
||||
AGENT_ID_PATTERN = re.compile(r"^agent-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", re.IGNORECASE)
|
||||
|
||||
# String in the error message for when the context window is too large
|
||||
# Example full message:
|
||||
# This model's maximum context length is 8192 tokens. However, your messages resulted in 8198 tokens (7450 in the messages, 748 in the functions). Please reduce the length of the messages or functions.
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import uuid
|
||||
|
||||
|
||||
def is_valid_agent_id(agent_id: str) -> bool:
|
||||
"""Check if string matches the pattern 'agent-{uuid}'"""
|
||||
|
||||
if not agent_id or not agent_id.startswith("agent-"):
|
||||
return False
|
||||
|
||||
uuid_section = agent_id[6:]
|
||||
|
||||
try:
|
||||
uuid.UUID(uuid_section)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
@@ -11,6 +11,7 @@ from typing import Optional
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
from marshmallow import ValidationError
|
||||
from sqlalchemy.exc import IntegrityError, OperationalError
|
||||
@@ -46,6 +47,7 @@ from letta.helpers.pinecone_utils import get_pinecone_indices, should_use_pineco
|
||||
from letta.jobs.scheduler import start_scheduler_with_leader_election
|
||||
from letta.log import get_logger
|
||||
from letta.orm.errors import DatabaseTimeoutError, ForeignKeyConstraintViolationError, NoResultFound, UniqueConstraintViolationError
|
||||
from letta.otel.tracing import get_trace_id
|
||||
from letta.schemas.letta_message import create_letta_message_union_schema, create_letta_ping_schema
|
||||
from letta.schemas.letta_message_content import (
|
||||
create_letta_assistant_message_content_union_schema,
|
||||
@@ -66,6 +68,7 @@ from letta.server.rest_api.static_files import mount_static_files
|
||||
from letta.server.rest_api.utils import SENTRY_ENABLED
|
||||
from letta.server.server import SyncServer
|
||||
from letta.settings import settings, telemetry_settings
|
||||
from letta.validators import PATH_VALIDATORS, PRIMITIVE_ID_PATTERNS
|
||||
|
||||
if SENTRY_ENABLED:
|
||||
import sentry_sdk
|
||||
@@ -230,6 +233,65 @@ def create_application() -> "FastAPI":
|
||||
},
|
||||
)
|
||||
|
||||
# Reasoning for this handler is the default path validation logic returns a pretty gnarly error message
|
||||
# because of the uuid4 pattern. This handler rewrites the error message to be more user-friendly and less intimidating.
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def custom_request_validation_handler(request: Request, exc: RequestValidationError):
|
||||
"""Generalize path `_id` validation messages and include example IDs.
|
||||
|
||||
- Rewrites string pattern/length mismatches to "primitive-{uuid4}"
|
||||
- Preserves stringified `detail` and includes `trace_id`
|
||||
- Adds top-level `examples` from `PATH_VALIDATORS` for offending params
|
||||
"""
|
||||
errors = exc.errors()
|
||||
examples_set: set[str] = set()
|
||||
content = {"trace_id": get_trace_id() or ""}
|
||||
for err in errors:
|
||||
fastapi_error_loc = err.get("loc", [])
|
||||
# only rewrite path param validation errors (should expand in future)
|
||||
if len(fastapi_error_loc) != 2 or fastapi_error_loc[0] != "path":
|
||||
continue
|
||||
|
||||
# re-write the error message
|
||||
parameter_name = fastapi_error_loc[1]
|
||||
err_type = err.get("type")
|
||||
if (
|
||||
err_type in {"string_pattern_mismatch", "string_too_short", "string_too_long"}
|
||||
and isinstance(parameter_name, str)
|
||||
and parameter_name.endswith("_id")
|
||||
):
|
||||
primitive = parameter_name[:-3]
|
||||
validator = PATH_VALIDATORS.get(primitive)
|
||||
if validator:
|
||||
# simplify default error message
|
||||
err["msg"] = f"String should match pattern '{primitive}-{{uuid4}}'"
|
||||
|
||||
# rewrite as string_pattern_mismatch even if the input length is too short or too long (more intuitive for user)
|
||||
if err_type in {"string_too_short", "string_too_long"}:
|
||||
# FYI: the pattern is the same as the pattern inthe validator object but for some reason the validator object
|
||||
# doesn't let you access it directly (unless you call into pydantic layer)
|
||||
err["ctx"] = {"pattern": PRIMITIVE_ID_PATTERNS[primitive].pattern}
|
||||
err["type"] = "string_pattern_mismatch"
|
||||
|
||||
# collect examples for top-level examples field (prevents duplicates and allows for multiple examples for multiple primitives)
|
||||
# e.g. if there are 2 malformed agent ids, the examples field will contain 2 examples for the agent primitive
|
||||
# e.g. if there is a malformed agent id and malformed folder id, the examples field will contain both examples, for both the agent and folder primitives
|
||||
try:
|
||||
exs = getattr(validator, "examples", None)
|
||||
if exs:
|
||||
for ex in exs:
|
||||
examples_set.add(ex)
|
||||
else:
|
||||
examples_set.add(f"{primitive}-123e4567-e89b-42d3-8456-426614174000")
|
||||
except Exception:
|
||||
examples_set.add(f"{primitive}-123e4567-e89b-42d3-8456-426614174000")
|
||||
|
||||
# Preserve current API contract: stringified list of errors
|
||||
content["detail"] = repr(errors)
|
||||
if examples_set:
|
||||
content["examples"] = sorted(examples_set)
|
||||
return JSONResponse(status_code=422, content=content)
|
||||
|
||||
async def error_handler_with_code(request: Request, exc: Exception, code: int, detail: str | None = None):
|
||||
logger.error(f"{type(exc).__name__}", exc_info=exc)
|
||||
|
||||
@@ -448,6 +510,9 @@ def create_application() -> "FastAPI":
|
||||
logger.warning(f"Failed to setup SQLAlchemy instrumentation: {e}")
|
||||
# Don't fail startup if instrumentation fails
|
||||
|
||||
# Ensure our validation handler overrides tracing's handler when tracing is enabled
|
||||
app.add_exception_handler(RequestValidationError, custom_request_validation_handler)
|
||||
|
||||
for route in v1_routes:
|
||||
app.include_router(route, prefix=API_PREFIX)
|
||||
# this gives undocumented routes for "latest" and bare api calls.
|
||||
|
||||
@@ -4,7 +4,7 @@ import traceback
|
||||
from datetime import datetime, timezone
|
||||
from typing import Annotated, Any, Dict, List, Literal, Optional, Union
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, File, Form, Header, HTTPException, Query, Request, UploadFile, status
|
||||
from fastapi import APIRouter, Body, Depends, File, Form, Header, HTTPException, Path, Query, Request, UploadFile, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from marshmallow import ValidationError
|
||||
from orjson import orjson
|
||||
@@ -14,7 +14,7 @@ from starlette.responses import Response, StreamingResponse
|
||||
|
||||
from letta.agents.agent_loop import AgentLoop
|
||||
from letta.agents.letta_agent_v2 import LettaAgentV2
|
||||
from letta.constants import AGENT_ID_PATTERN, DEFAULT_MAX_STEPS, DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG, REDIS_RUN_ID_PREFIX
|
||||
from letta.constants import DEFAULT_MAX_STEPS, DEFAULT_MESSAGE_TOOL, DEFAULT_MESSAGE_TOOL_KWARG, REDIS_RUN_ID_PREFIX
|
||||
from letta.data_sources.redis_client import get_redis_client
|
||||
from letta.errors import (
|
||||
AgentExportIdMappingError,
|
||||
@@ -59,6 +59,7 @@ from letta.services.lettuce import LettuceClient
|
||||
from letta.services.run_manager import RunManager
|
||||
from letta.settings import settings
|
||||
from letta.utils import safe_create_shielded_task, safe_create_task, truncate_file_visible_content
|
||||
from letta.validators import PATH_VALIDATORS
|
||||
|
||||
# These can be forward refs, but because Fastapi needs them at runtime the must be imported normally
|
||||
|
||||
@@ -169,7 +170,7 @@ class IndentedORJSONResponse(Response):
|
||||
|
||||
@router.get("/{agent_id}/export", response_class=IndentedORJSONResponse, operation_id="export_agent")
|
||||
async def export_agent(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
max_steps: int = 100,
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -352,7 +353,7 @@ async def import_agent(
|
||||
|
||||
@router.get("/{agent_id}/context", response_model=ContextWindowOverview, operation_id="retrieve_agent_context_window")
|
||||
async def retrieve_agent_context_window(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -392,7 +393,7 @@ async def create_agent(
|
||||
|
||||
@router.patch("/{agent_id}", response_model=AgentState, operation_id="modify_agent")
|
||||
async def modify_agent(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
update_agent: UpdateAgent = Body(...),
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -404,7 +405,7 @@ async def modify_agent(
|
||||
|
||||
@router.get("/{agent_id}/tools", response_model=list[Tool], operation_id="list_agent_tools")
|
||||
async def list_agent_tools(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
before: Optional[str] = Query(
|
||||
@@ -433,8 +434,8 @@ async def list_agent_tools(
|
||||
|
||||
@router.patch("/{agent_id}/tools/attach/{tool_id}", response_model=AgentState, operation_id="attach_tool")
|
||||
async def attach_tool(
|
||||
agent_id: str,
|
||||
tool_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -449,8 +450,8 @@ async def attach_tool(
|
||||
|
||||
@router.patch("/{agent_id}/tools/detach/{tool_id}", response_model=AgentState, operation_id="detach_tool")
|
||||
async def detach_tool(
|
||||
agent_id: str,
|
||||
tool_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -465,9 +466,9 @@ async def detach_tool(
|
||||
|
||||
@router.patch("/{agent_id}/tools/approval/{tool_name}", response_model=AgentState, operation_id="modify_approval")
|
||||
async def modify_approval(
|
||||
agent_id: str,
|
||||
tool_name: str,
|
||||
requires_approval: bool,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -484,8 +485,8 @@ async def modify_approval(
|
||||
|
||||
@router.patch("/{agent_id}/sources/attach/{source_id}", response_model=AgentState, operation_id="attach_source_to_agent")
|
||||
async def attach_source(
|
||||
agent_id: str,
|
||||
source_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -511,8 +512,8 @@ async def attach_source(
|
||||
|
||||
@router.patch("/{agent_id}/folders/attach/{folder_id}", response_model=AgentState, operation_id="attach_folder_to_agent")
|
||||
async def attach_folder_to_agent(
|
||||
agent_id: str,
|
||||
folder_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -538,8 +539,8 @@ async def attach_folder_to_agent(
|
||||
|
||||
@router.patch("/{agent_id}/sources/detach/{source_id}", response_model=AgentState, operation_id="detach_source_from_agent")
|
||||
async def detach_source(
|
||||
agent_id: str,
|
||||
source_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -568,8 +569,8 @@ async def detach_source(
|
||||
|
||||
@router.patch("/{agent_id}/folders/detach/{folder_id}", response_model=AgentState, operation_id="detach_folder_from_agent")
|
||||
async def detach_folder_from_agent(
|
||||
agent_id: str,
|
||||
folder_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -598,7 +599,7 @@ async def detach_folder_from_agent(
|
||||
|
||||
@router.patch("/{agent_id}/files/close-all", response_model=List[str], operation_id="close_all_open_files")
|
||||
async def close_all_open_files(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -615,8 +616,8 @@ async def close_all_open_files(
|
||||
|
||||
@router.patch("/{agent_id}/files/{file_id}/open", response_model=List[str], operation_id="open_file")
|
||||
async def open_file(
|
||||
agent_id: str,
|
||||
file_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -664,8 +665,8 @@ async def open_file(
|
||||
|
||||
@router.patch("/{agent_id}/files/{file_id}/close", response_model=None, operation_id="close_file")
|
||||
async def close_file(
|
||||
agent_id: str,
|
||||
file_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -689,7 +690,7 @@ async def close_file(
|
||||
|
||||
@router.get("/{agent_id}", response_model=AgentState, operation_id="retrieve_agent")
|
||||
async def retrieve_agent(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
include_relationships: list[str] | None = Query(
|
||||
None,
|
||||
description=(
|
||||
@@ -704,9 +705,6 @@ async def retrieve_agent(
|
||||
"""
|
||||
Get the state of the agent.
|
||||
"""
|
||||
# Check if agent_id matches uuid4 format
|
||||
if not AGENT_ID_PATTERN.match(agent_id):
|
||||
raise HTTPException(status_code=400, detail=f"agent_id {agent_id} is not in the valid format 'agent-<uuid4>'")
|
||||
|
||||
actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
|
||||
|
||||
@@ -715,7 +713,7 @@ async def retrieve_agent(
|
||||
|
||||
@router.delete("/{agent_id}", response_model=None, operation_id="delete_agent")
|
||||
async def delete_agent(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -729,7 +727,7 @@ async def delete_agent(
|
||||
|
||||
@router.get("/{agent_id}/sources", response_model=list[Source], operation_id="list_agent_sources")
|
||||
async def list_agent_sources(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
before: Optional[str] = Query(
|
||||
@@ -760,7 +758,7 @@ async def list_agent_sources(
|
||||
|
||||
@router.get("/{agent_id}/folders", response_model=list[Source], operation_id="list_agent_folders")
|
||||
async def list_agent_folders(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
before: Optional[str] = Query(
|
||||
@@ -791,7 +789,7 @@ async def list_agent_folders(
|
||||
|
||||
@router.get("/{agent_id}/files", response_model=PaginatedAgentFiles, operation_id="list_agent_files")
|
||||
async def list_agent_files(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
before: Optional[str] = Query(
|
||||
None, description="File ID cursor for pagination. Returns files that come before this file ID in the specified sort order"
|
||||
),
|
||||
@@ -856,7 +854,7 @@ async def list_agent_files(
|
||||
# TODO: remove? can also get with agent blocks
|
||||
@router.get("/{agent_id}/core-memory", response_model=Memory, operation_id="retrieve_agent_memory")
|
||||
async def retrieve_agent_memory(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -871,8 +869,8 @@ async def retrieve_agent_memory(
|
||||
|
||||
@router.get("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="retrieve_core_memory_block")
|
||||
async def retrieve_block(
|
||||
agent_id: str,
|
||||
block_label: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -886,7 +884,7 @@ async def retrieve_block(
|
||||
|
||||
@router.get("/{agent_id}/core-memory/blocks", response_model=list[Block], operation_id="list_core_memory_blocks")
|
||||
async def list_blocks(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
before: Optional[str] = Query(
|
||||
@@ -918,8 +916,8 @@ async def list_blocks(
|
||||
|
||||
@router.patch("/{agent_id}/core-memory/blocks/{block_label}", response_model=Block, operation_id="modify_core_memory_block")
|
||||
async def modify_block(
|
||||
agent_id: str,
|
||||
block_label: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
block_update: BlockUpdate = Body(...),
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -941,8 +939,8 @@ async def modify_block(
|
||||
|
||||
@router.patch("/{agent_id}/core-memory/blocks/attach/{block_id}", response_model=AgentState, operation_id="attach_core_memory_block")
|
||||
async def attach_block(
|
||||
agent_id: str,
|
||||
block_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -955,8 +953,8 @@ async def attach_block(
|
||||
|
||||
@router.patch("/{agent_id}/core-memory/blocks/detach/{block_id}", response_model=AgentState, operation_id="detach_core_memory_block")
|
||||
async def detach_block(
|
||||
agent_id: str,
|
||||
block_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
):
|
||||
@@ -969,7 +967,7 @@ async def detach_block(
|
||||
|
||||
@router.get("/{agent_id}/archival-memory", response_model=list[Passage], operation_id="list_passages")
|
||||
async def list_passages(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
after: str | None = Query(None, description="Unique ID of the memory to start the query range at."),
|
||||
before: str | None = Query(None, description="Unique ID of the memory to end the query range at."),
|
||||
@@ -998,7 +996,7 @@ async def list_passages(
|
||||
|
||||
@router.post("/{agent_id}/archival-memory", response_model=list[Passage], operation_id="create_passage")
|
||||
async def create_passage(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
request: CreateArchivalMemory = Body(...),
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -1015,7 +1013,7 @@ async def create_passage(
|
||||
|
||||
@router.get("/{agent_id}/archival-memory/search", response_model=ArchivalMemorySearchResponse, operation_id="search_archival_memory")
|
||||
async def search_archival_memory(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
query: str = Query(..., description="String to search for using semantic similarity"),
|
||||
tags: Optional[List[str]] = Query(None, description="Optional list of tags to filter search results"),
|
||||
tag_match_mode: Literal["any", "all"] = Query(
|
||||
@@ -1062,8 +1060,8 @@ async def search_archival_memory(
|
||||
# @router.delete("/{agent_id}/archival")
|
||||
@router.delete("/{agent_id}/archival-memory/{memory_id}", response_model=None, operation_id="delete_passage")
|
||||
async def delete_passage(
|
||||
agent_id: str,
|
||||
memory_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
# memory_id: str = Query(..., description="Unique ID of the memory to be deleted."),
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -1084,7 +1082,7 @@ AgentMessagesResponse = Annotated[
|
||||
|
||||
@router.get("/{agent_id}/messages", response_model=AgentMessagesResponse, operation_id="list_messages")
|
||||
async def list_messages(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
before: Optional[str] = Query(
|
||||
None, description="Message ID cursor for pagination. Returns messages that come before this message ID in the specified sort order"
|
||||
@@ -1129,8 +1127,8 @@ async def list_messages(
|
||||
|
||||
@router.patch("/{agent_id}/messages/{message_id}", response_model=LettaMessageUnion, operation_id="modify_message")
|
||||
async def modify_message(
|
||||
agent_id: str,
|
||||
message_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
request: LettaMessageUpdateUnion = Body(...),
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -1152,8 +1150,8 @@ async def modify_message(
|
||||
operation_id="send_message",
|
||||
)
|
||||
async def send_message(
|
||||
agent_id: str,
|
||||
request_obj: Request, # FastAPI Request
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: SyncServer = Depends(get_letta_server),
|
||||
request: LettaRequest = Body(...),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -1279,8 +1277,8 @@ async def send_message(
|
||||
},
|
||||
)
|
||||
async def send_message_streaming(
|
||||
agent_id: str,
|
||||
request_obj: Request, # FastAPI Request
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: SyncServer = Depends(get_letta_server),
|
||||
request: LettaStreamingRequest = Body(...),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -1313,7 +1311,7 @@ class CancelAgentRunRequest(BaseModel):
|
||||
|
||||
@router.post("/{agent_id}/messages/cancel", operation_id="cancel_agent_run")
|
||||
async def cancel_agent_run(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
request: CancelAgentRunRequest = Body(None),
|
||||
server: SyncServer = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -1394,11 +1392,11 @@ async def _process_message_background(
|
||||
run_id: str,
|
||||
server: SyncServer,
|
||||
actor: User,
|
||||
agent_id: str,
|
||||
messages: list[MessageCreate],
|
||||
use_assistant_message: bool,
|
||||
assistant_message_tool_name: str,
|
||||
assistant_message_tool_kwarg: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
max_steps: int = DEFAULT_MAX_STEPS,
|
||||
include_return_message_types: list[MessageType] | None = None,
|
||||
) -> None:
|
||||
@@ -1489,7 +1487,7 @@ async def _process_message_background(
|
||||
operation_id="create_agent_message_async",
|
||||
)
|
||||
async def send_message_async(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
server: SyncServer = Depends(get_letta_server),
|
||||
request: LettaAsyncRequest = Body(...),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -1593,7 +1591,7 @@ async def send_message_async(
|
||||
|
||||
@router.patch("/{agent_id}/reset-messages", response_model=AgentState, operation_id="reset_messages")
|
||||
async def reset_messages(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
add_default_initial_messages: bool = Query(default=False, description="If true, adds the default initial messages after resetting."),
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -1607,7 +1605,7 @@ async def reset_messages(
|
||||
|
||||
@router.get("/{agent_id}/groups", response_model=list[Group], operation_id="list_agent_groups")
|
||||
async def list_agent_groups(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
manager_type: str | None = Query(None, description="Manager type to filter groups by"),
|
||||
server: "SyncServer" = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -1643,7 +1641,7 @@ async def list_agent_groups(
|
||||
operation_id="preview_raw_payload",
|
||||
)
|
||||
async def preview_raw_payload(
|
||||
agent_id: str,
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
request: Union[LettaRequest, LettaStreamingRequest] = Body(...),
|
||||
server: SyncServer = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
@@ -1688,8 +1686,8 @@ async def preview_raw_payload(
|
||||
|
||||
@router.post("/{agent_id}/summarize", status_code=204, operation_id="summarize_agent_conversation")
|
||||
async def summarize_agent_conversation(
|
||||
agent_id: str,
|
||||
request_obj: Request, # FastAPI Request
|
||||
agent_id: str = PATH_VALIDATORS["agent"],
|
||||
max_message_length: int = Query(..., description="Maximum number of messages to retain after summarization."),
|
||||
server: SyncServer = Depends(get_letta_server),
|
||||
headers: HeaderParams = Depends(get_headers),
|
||||
|
||||
@@ -25,9 +25,9 @@ from letta.constants import (
|
||||
INCLUDE_MODEL_KEYWORDS_BASE_TOOL_RULES,
|
||||
RETRIEVAL_QUERY_DEFAULT_PAGE_SIZE,
|
||||
)
|
||||
from letta.errors import LettaAgentNotFoundError
|
||||
from letta.helpers import ToolRulesSolver
|
||||
from letta.helpers.datetime_helpers import get_utc_time
|
||||
from letta.helpers.validators import is_valid_agent_id
|
||||
from letta.llm_api.llm_client import LLMClient
|
||||
from letta.log import get_logger
|
||||
from letta.orm import (
|
||||
@@ -110,6 +110,7 @@ from letta.services.source_manager import SourceManager
|
||||
from letta.services.tool_manager import ToolManager
|
||||
from letta.settings import DatabaseChoice, model_settings, settings
|
||||
from letta.utils import calculate_file_defaults_based_on_context_window, enforce_types, united_diff
|
||||
from letta.validators import is_valid_id
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -982,8 +983,10 @@ class AgentManager:
|
||||
include_relationships: Optional[List[str]] = None,
|
||||
) -> PydanticAgentState:
|
||||
"""Fetch an agent by its ID."""
|
||||
if not is_valid_agent_id(agent_id):
|
||||
raise NoResultFound("Agent id should match agent-{uuid}")
|
||||
|
||||
# Check if agent_id matches uuid4 format
|
||||
if not is_valid_id("agent", agent_id):
|
||||
raise LettaAgentNotFoundError(f"agent_id {agent_id} is not in the valid format 'agent-<uuid4>'")
|
||||
|
||||
async with db_registry.async_session() as session:
|
||||
try:
|
||||
|
||||
27
letta/validators.py
Normal file
27
letta/validators.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import re
|
||||
|
||||
from fastapi import Path
|
||||
|
||||
# TODO: extract this list from routers/v1/__init__.py and ROUTERS
|
||||
primitives = ["agent", "message", "run", "job", "group", "block", "file", "folder", "source", "tool", "mcp_server"]
|
||||
|
||||
PRIMITIVE_ID_PATTERNS = {
|
||||
# f-string interpolation gets confused because of the regex's required curly braces {}
|
||||
primitive: re.compile("^" + primitive + "-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
|
||||
for primitive in primitives
|
||||
}
|
||||
|
||||
PATH_VALIDATORS = {}
|
||||
for primitive in primitives:
|
||||
PATH_VALIDATORS[primitive] = Path(
|
||||
description=f"The ID of the {primitive} in the format '{primitive}-<uuid4>'",
|
||||
pattern=PRIMITIVE_ID_PATTERNS[primitive].pattern,
|
||||
examples=[f"{primitive}-123e4567-e89b-42d3-8456-426614174000"],
|
||||
# len(agent) + len("-") + len(uuid4)
|
||||
min_length=len(primitive) + 1 + 36,
|
||||
max_length=len(primitive) + 1 + 36,
|
||||
)
|
||||
|
||||
|
||||
def is_valid_id(primitive: str, id: str) -> bool:
|
||||
return PRIMITIVE_ID_PATTERNS[primitive].match(id) is not None
|
||||
Reference in New Issue
Block a user