diff --git a/.sqlx/query-2907637008db9d3febc5f8f2f79f7f073b35973aad326acfd54970ef0ab2a731.json b/.sqlx/query-2907637008db9d3febc5f8f2f79f7f073b35973aad326acfd54970ef0ab2a731.json new file mode 100644 index 0000000..251eff4 --- /dev/null +++ b/.sqlx/query-2907637008db9d3febc5f8f2f79f7f073b35973aad326acfd54970ef0ab2a731.json @@ -0,0 +1,65 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n operation_id,\n version,\n descriptor_kind,\n storage_ref,\n source_name,\n package_index_json,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\"\n from descriptors\n where operation_id = $1 and version = $2\n order by created_at asc", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "operation_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "version", + "type_info": "Int4" + }, + { + "ordinal": 3, + "name": "descriptor_kind", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "storage_ref", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "source_name", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "package_index_json", + "type_info": "Jsonb" + }, + { + "ordinal": 7, + "name": "created_at!", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Int4" + ] + }, + "nullable": [ + false, + true, + true, + false, + false, + true, + true, + null + ] + }, + "hash": "2907637008db9d3febc5f8f2f79f7f073b35973aad326acfd54970ef0ab2a731" +} diff --git a/.sqlx/query-33071dad43cffdb3bc1a55d07a5aee93f9af115155e319e49acc6f02d006fcc0.json b/.sqlx/query-33071dad43cffdb3bc1a55d07a5aee93f9af115155e319e49acc6f02d006fcc0.json new file mode 100644 index 0000000..18e64c6 --- /dev/null +++ b/.sqlx/query-33071dad43cffdb3bc1a55d07a5aee93f9af115155e319e49acc6f02d006fcc0.json @@ -0,0 +1,160 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n o.id,\n o.workspace_id,\n o.name,\n o.display_name,\n o.category,\n o.protocol,\n to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_created_at!\",\n to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_updated_at!\",\n to_char(o.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_published_at,\n ov.version,\n ov.status,\n ov.target_json,\n ov.input_schema_json,\n ov.output_schema_json,\n ov.input_mapping_json,\n ov.output_mapping_json,\n ov.execution_config_json,\n ov.tool_description_json,\n ov.samples_json,\n ov.generated_draft_json,\n ov.config_export_json,\n ov.change_note,\n to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n ov.created_by\n from published_operations po\n join operation_versions ov\n on ov.operation_id = po.operation_id and ov.version = po.version\n join operations o on o.id = po.operation_id\n where po.operation_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "display_name", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "category", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "protocol", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "operation_created_at!", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "operation_updated_at!", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "operation_published_at", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "version", + "type_info": "Int4" + }, + { + "ordinal": 10, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "target_json", + "type_info": "Jsonb" + }, + { + "ordinal": 12, + "name": "input_schema_json", + "type_info": "Jsonb" + }, + { + "ordinal": 13, + "name": "output_schema_json", + "type_info": "Jsonb" + }, + { + "ordinal": 14, + "name": "input_mapping_json", + "type_info": "Jsonb" + }, + { + "ordinal": 15, + "name": "output_mapping_json", + "type_info": "Jsonb" + }, + { + "ordinal": 16, + "name": "execution_config_json", + "type_info": "Jsonb" + }, + { + "ordinal": 17, + "name": "tool_description_json", + "type_info": "Jsonb" + }, + { + "ordinal": 18, + "name": "samples_json", + "type_info": "Jsonb" + }, + { + "ordinal": 19, + "name": "generated_draft_json", + "type_info": "Jsonb" + }, + { + "ordinal": 20, + "name": "config_export_json", + "type_info": "Jsonb" + }, + { + "ordinal": 21, + "name": "change_note", + "type_info": "Text" + }, + { + "ordinal": 22, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 23, + "name": "created_by", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + null, + null, + null, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + null, + true + ] + }, + "hash": "33071dad43cffdb3bc1a55d07a5aee93f9af115155e319e49acc6f02d006fcc0" +} diff --git a/.sqlx/query-3348d60983e16bac76e81ec2158c1c6b1333ec49067d23bdd3c2fd33caa93a3c.json b/.sqlx/query-3348d60983e16bac76e81ec2158c1c6b1333ec49067d23bdd3c2fd33caa93a3c.json new file mode 100644 index 0000000..6de4c5f --- /dev/null +++ b/.sqlx/query-3348d60983e16bac76e81ec2158c1c6b1333ec49067d23bdd3c2fd33caa93a3c.json @@ -0,0 +1,158 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n o.id,\n o.workspace_id,\n o.name,\n o.display_name,\n o.category,\n o.protocol,\n to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_created_at!\",\n to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_updated_at!\",\n to_char(o.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_published_at,\n ov.version,\n ov.status,\n ov.target_json,\n ov.input_schema_json,\n ov.output_schema_json,\n ov.input_mapping_json,\n ov.output_mapping_json,\n ov.execution_config_json,\n ov.tool_description_json,\n ov.samples_json,\n ov.generated_draft_json,\n ov.config_export_json,\n ov.change_note,\n to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n ov.created_by\n from published_operations po\n join operation_versions ov\n on ov.operation_id = po.operation_id and ov.version = po.version\n join operations o on o.id = po.operation_id\n order by o.name asc", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "display_name", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "category", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "protocol", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "operation_created_at!", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "operation_updated_at!", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "operation_published_at", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "version", + "type_info": "Int4" + }, + { + "ordinal": 10, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "target_json", + "type_info": "Jsonb" + }, + { + "ordinal": 12, + "name": "input_schema_json", + "type_info": "Jsonb" + }, + { + "ordinal": 13, + "name": "output_schema_json", + "type_info": "Jsonb" + }, + { + "ordinal": 14, + "name": "input_mapping_json", + "type_info": "Jsonb" + }, + { + "ordinal": 15, + "name": "output_mapping_json", + "type_info": "Jsonb" + }, + { + "ordinal": 16, + "name": "execution_config_json", + "type_info": "Jsonb" + }, + { + "ordinal": 17, + "name": "tool_description_json", + "type_info": "Jsonb" + }, + { + "ordinal": 18, + "name": "samples_json", + "type_info": "Jsonb" + }, + { + "ordinal": 19, + "name": "generated_draft_json", + "type_info": "Jsonb" + }, + { + "ordinal": 20, + "name": "config_export_json", + "type_info": "Jsonb" + }, + { + "ordinal": 21, + "name": "change_note", + "type_info": "Text" + }, + { + "ordinal": 22, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 23, + "name": "created_by", + "type_info": "Text" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + null, + null, + null, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + null, + true + ] + }, + "hash": "3348d60983e16bac76e81ec2158c1c6b1333ec49067d23bdd3c2fd33caa93a3c" +} diff --git a/.sqlx/query-3422060fba1de6d85f89c46b41cc1cf462f49f0e2fe93e9230d15f97710b74ee.json b/.sqlx/query-3422060fba1de6d85f89c46b41cc1cf462f49f0e2fe93e9230d15f97710b74ee.json new file mode 100644 index 0000000..e9828b6 --- /dev/null +++ b/.sqlx/query-3422060fba1de6d85f89c46b41cc1cf462f49f0e2fe93e9230d15f97710b74ee.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "select 1 as \"present!\"\n from workspaces w\n join agents a on a.workspace_id = w.id\n join published_agents pa on pa.agent_id = a.id\n where w.slug = $1 and a.slug = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "present!", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "3422060fba1de6d85f89c46b41cc1cf462f49f0e2fe93e9230d15f97710b74ee" +} diff --git a/.sqlx/query-da58664742ddda01bd0bb8fccf69c69167b1c4f4868ca5310fbd8f5fdc5cd546.json b/.sqlx/query-da58664742ddda01bd0bb8fccf69c69167b1c4f4868ca5310fbd8f5fdc5cd546.json new file mode 100644 index 0000000..d1994a4 --- /dev/null +++ b/.sqlx/query-da58664742ddda01bd0bb8fccf69c69167b1c4f4868ca5310fbd8f5fdc5cd546.json @@ -0,0 +1,65 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n operation_id,\n version,\n sample_kind,\n storage_ref,\n content_type,\n file_name,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\"\n from operation_samples\n where operation_id = $1 and version = $2\n order by created_at asc", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "operation_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "version", + "type_info": "Int4" + }, + { + "ordinal": 3, + "name": "sample_kind", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "storage_ref", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "content_type", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "file_name", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "created_at!", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + true, + null + ] + }, + "hash": "da58664742ddda01bd0bb8fccf69c69167b1c4f4868ca5310fbd8f5fdc5cd546" +} diff --git a/.sqlx/query-f7b3017dafdc3b8291e4336b2f1c8290985f30802a9c159f75b9387bf44fb611.json b/.sqlx/query-f7b3017dafdc3b8291e4336b2f1c8290985f30802a9c159f75b9387bf44fb611.json new file mode 100644 index 0000000..7fe80b2 --- /dev/null +++ b/.sqlx/query-f7b3017dafdc3b8291e4336b2f1c8290985f30802a9c159f75b9387bf44fb611.json @@ -0,0 +1,197 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n w.id as workspace_id,\n w.slug as workspace_slug,\n a.id as agent_id,\n a.slug as agent_slug,\n b.tool_name,\n b.tool_title,\n coalesce(b.tool_description_override, ov.tool_description_json->>'description') as \"tool_description!\",\n o.id,\n o.name,\n o.display_name,\n o.category,\n o.protocol,\n to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_created_at!\",\n to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_updated_at!\",\n to_char(o.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_published_at,\n ov.version,\n ov.status,\n ov.target_json,\n ov.input_schema_json,\n ov.output_schema_json,\n ov.input_mapping_json,\n ov.output_mapping_json,\n ov.execution_config_json,\n ov.tool_description_json,\n ov.samples_json,\n ov.generated_draft_json,\n ov.config_export_json,\n ov.change_note,\n to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n ov.created_by\n from workspaces w\n join agents a on a.workspace_id = w.id\n join published_agents pa on pa.agent_id = a.id\n join agent_operation_bindings b on b.agent_id = a.id and b.agent_version = pa.version\n join operation_versions ov on ov.operation_id = b.operation_id and ov.version = b.operation_version\n join operations o on o.id = ov.operation_id and o.workspace_id = w.id\n where w.slug = $1 and a.slug = $2 and b.enabled = true\n order by b.tool_name asc", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_slug", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "agent_id", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "agent_slug", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "tool_name", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "tool_title", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "tool_description!", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 8, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "display_name", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "category", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "protocol", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "operation_created_at!", + "type_info": "Text" + }, + { + "ordinal": 13, + "name": "operation_updated_at!", + "type_info": "Text" + }, + { + "ordinal": 14, + "name": "operation_published_at", + "type_info": "Text" + }, + { + "ordinal": 15, + "name": "version", + "type_info": "Int4" + }, + { + "ordinal": 16, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 17, + "name": "target_json", + "type_info": "Jsonb" + }, + { + "ordinal": 18, + "name": "input_schema_json", + "type_info": "Jsonb" + }, + { + "ordinal": 19, + "name": "output_schema_json", + "type_info": "Jsonb" + }, + { + "ordinal": 20, + "name": "input_mapping_json", + "type_info": "Jsonb" + }, + { + "ordinal": 21, + "name": "output_mapping_json", + "type_info": "Jsonb" + }, + { + "ordinal": 22, + "name": "execution_config_json", + "type_info": "Jsonb" + }, + { + "ordinal": 23, + "name": "tool_description_json", + "type_info": "Jsonb" + }, + { + "ordinal": 24, + "name": "samples_json", + "type_info": "Jsonb" + }, + { + "ordinal": 25, + "name": "generated_draft_json", + "type_info": "Jsonb" + }, + { + "ordinal": 26, + "name": "config_export_json", + "type_info": "Jsonb" + }, + { + "ordinal": 27, + "name": "change_note", + "type_info": "Text" + }, + { + "ordinal": 28, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 29, + "name": "created_by", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + null, + false, + false, + false, + false, + false, + null, + null, + null, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true, + true, + true, + true, + null, + true + ] + }, + "hash": "f7b3017dafdc3b8291e4336b2f1c8290985f30802a9c159f75b9387bf44fb611" +} diff --git a/crates/crank-registry/src/postgres/agent.rs b/crates/crank-registry/src/postgres/agent.rs index 15c78af..dd270db 100644 --- a/crates/crank-registry/src/postgres/agent.rs +++ b/crates/crank-registry/src/postgres/agent.rs @@ -438,7 +438,7 @@ impl PostgresRegistry { workspace_slug: &str, agent_slug: &str, ) -> Result, RegistryError> { - let rows = sqlx::query( + let rows = sqlx::query!( "select w.id as workspace_id, w.slug as workspace_slug, @@ -446,14 +446,14 @@ impl PostgresRegistry { a.slug as agent_slug, b.tool_name, b.tool_title, - coalesce(b.tool_description_override, ov.tool_description_json->>'description') as tool_description, + coalesce(b.tool_description_override, ov.tool_description_json->>'description') as \"tool_description!\", o.id, o.name, o.display_name, o.category, o.protocol, - to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_created_at, - to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_updated_at, + to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_created_at!\", + to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_updated_at!\", to_char(o.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_published_at, ov.version, ov.status, @@ -468,7 +468,7 @@ impl PostgresRegistry { ov.generated_draft_json, ov.config_export_json, ov.change_note, - to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, + to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", ov.created_by from workspaces w join agents a on a.workspace_id = w.id @@ -478,22 +478,22 @@ impl PostgresRegistry { join operations o on o.id = ov.operation_id and o.workspace_id = w.id where w.slug = $1 and a.slug = $2 and b.enabled = true order by b.tool_name asc", + workspace_slug, + agent_slug, ) - .bind(workspace_slug) - .bind(agent_slug) .fetch_all(&self.pool) .await?; if rows.is_empty() { - let exists = sqlx::query( - "select 1 + let exists = sqlx::query!( + "select 1 as \"present!\" from workspaces w join agents a on a.workspace_id = w.id join published_agents pa on pa.agent_id = a.id where w.slug = $1 and a.slug = $2", + workspace_slug, + agent_slug, ) - .bind(workspace_slug) - .bind(agent_slug) .fetch_optional(&self.pool) .await?; @@ -505,6 +505,46 @@ impl PostgresRegistry { } } - rows.iter().map(map_published_agent_tool).collect() + rows.into_iter() + .map(|row| { + let workspace_id = row.workspace_id.clone(); + build_published_agent_tool( + row.workspace_id, + row.workspace_slug, + row.agent_id, + row.agent_slug, + row.tool_name, + row.tool_title, + row.tool_description, + build_operation_version_record( + row.id, + workspace_id, + row.name, + row.display_name, + row.category, + row.protocol, + row.operation_created_at, + row.operation_updated_at, + row.operation_published_at, + row.version, + row.status, + row.target_json, + row.input_schema_json, + row.output_schema_json, + row.input_mapping_json, + row.output_mapping_json, + row.execution_config_json, + row.tool_description_json, + row.samples_json, + row.generated_draft_json, + row.config_export_json, + row.change_note, + row.created_at, + row.created_by, + )? + .snapshot, + ) + }) + .collect() } } diff --git a/crates/crank-registry/src/postgres/mod.rs b/crates/crank-registry/src/postgres/mod.rs index de80330..ef92a23 100644 --- a/crates/crank-registry/src/postgres/mod.rs +++ b/crates/crank-registry/src/postgres/mod.rs @@ -665,38 +665,6 @@ fn map_operation_agent_ref(row: &PgRow) -> Result Result { - build_operation_version_record( - row.try_get("id")?, - row.try_get("workspace_id")?, - row.try_get("name")?, - row.try_get("display_name")?, - row.try_get("category")?, - row.try_get("protocol")?, - row.try_get("operation_created_at")?, - row.try_get("operation_updated_at")?, - row.try_get("operation_published_at")?, - row.try_get("version")?, - row.try_get("status")?, - row.try_get::, _>("target_json")?.0, - row.try_get::, _>("input_schema_json")?.0, - row.try_get::, _>("output_schema_json")?.0, - row.try_get::, _>("input_mapping_json")?.0, - row.try_get::, _>("output_mapping_json")?.0, - row.try_get::, _>("execution_config_json")?.0, - row.try_get::, _>("tool_description_json")?.0, - row.try_get::>, _>("samples_json")? - .map(|value| value.0), - row.try_get::>, _>("generated_draft_json")? - .map(|value| value.0), - row.try_get::>, _>("config_export_json")? - .map(|value| value.0), - row.try_get("change_note")?, - row.try_get("created_at")?, - row.try_get("created_by")?, - ) -} - fn map_auth_profile(row: &PgRow) -> Result { Ok(AuthProfile { id: crank_core::AuthProfileId::new(row.try_get::("id")?), @@ -886,57 +854,74 @@ fn build_operation_version_record( }) } -fn map_published_agent_tool(row: &PgRow) -> Result { - let record = map_operation_version_record(row)?; - +#[allow(clippy::too_many_arguments)] +fn build_published_agent_tool( + workspace_id: String, + workspace_slug: String, + agent_id: String, + agent_slug: String, + tool_name: String, + tool_title: String, + tool_description: String, + operation: RegistryOperation, +) -> Result { Ok(PublishedAgentTool { - workspace_id: WorkspaceId::new(row.try_get::("workspace_id")?), - workspace_slug: row.try_get("workspace_slug")?, - agent_id: AgentId::new(row.try_get::("agent_id")?), - agent_slug: row.try_get("agent_slug")?, - tool_name: row.try_get("tool_name")?, - tool_title: row.try_get("tool_title")?, - tool_description: row.try_get("tool_description")?, - operation: record.snapshot, + workspace_id: WorkspaceId::new(workspace_id), + workspace_slug, + agent_id: AgentId::new(agent_id), + agent_slug, + tool_name, + tool_title, + tool_description, + operation, }) } -fn map_sample_metadata(row: &PgRow) -> Result { +#[allow(clippy::too_many_arguments)] +fn build_sample_metadata( + id: String, + operation_id: String, + version: i32, + sample_kind: String, + storage_ref: String, + content_type: String, + file_name: Option, + created_at: String, +) -> Result { Ok(OperationSampleMetadata { - id: crank_core::SampleId::new(row.try_get::("id")?), - operation_id: OperationId::new(row.try_get::("operation_id")?), - version: from_db_version(row.try_get("version")?, "version")?, - sample_kind: deserialize_enum_text( - &row.try_get::("sample_kind")?, - "sample_kind", - )?, - storage_ref: row.try_get("storage_ref")?, - content_type: row.try_get("content_type")?, - file_name: row.try_get("file_name")?, - created_at: row.try_get("created_at")?, + id: crank_core::SampleId::new(id), + operation_id: OperationId::new(operation_id), + version: from_db_version(version, "version")?, + sample_kind: deserialize_enum_text(&sample_kind, "sample_kind")?, + storage_ref, + content_type, + file_name, + created_at, }) } -fn map_descriptor_metadata(row: &PgRow) -> Result { +#[allow(clippy::too_many_arguments)] +fn build_descriptor_metadata( + id: String, + operation_id: Option, + version: Option, + descriptor_kind: String, + storage_ref: String, + source_name: Option, + package_index: Option, + created_at: String, +) -> Result { Ok(DescriptorMetadata { - id: crank_core::DescriptorId::new(row.try_get::("id")?), - operation_id: row - .try_get::, _>("operation_id")? - .map(OperationId::new), - version: row - .try_get::, _>("version")? + id: crank_core::DescriptorId::new(id), + operation_id: operation_id.map(OperationId::new), + version: version .map(|value| from_db_version(value, "version")) .transpose()?, - descriptor_kind: deserialize_enum_text( - &row.try_get::("descriptor_kind")?, - "descriptor_kind", - )?, - storage_ref: row.try_get("storage_ref")?, - source_name: row.try_get("source_name")?, - package_index: row - .try_get::>, _>("package_index_json")? - .map(|value| value.0), - created_at: row.try_get("created_at")?, + descriptor_kind: deserialize_enum_text(&descriptor_kind, "descriptor_kind")?, + storage_ref, + source_name, + package_index, + created_at, }) } @@ -1126,8 +1111,8 @@ mod tests { AsyncJobFilter, CreateAgentRequest, CreateAsyncJobRequest, CreatePlatformApiKeyRequest, CreateStreamSessionRequest, CreateVersionRequest, CreateWorkspaceRequest, CreateYamlImportJobRequest, DescriptorKind, DescriptorMetadata, - OperationSampleMetadata, PlatformApiKeyRecord, PublishRequest, RegistryOperation, - SampleKind, SaveAuthProfileRequest, SaveDescriptorMetadataRequest, + OperationSampleMetadata, PlatformApiKeyRecord, PublishAgentRequest, PublishRequest, + RegistryOperation, SampleKind, SaveAuthProfileRequest, SaveDescriptorMetadataRequest, SaveSampleMetadataRequest, StreamSessionFilter, UpdateAsyncJobStatusRequest, UpdateStreamSessionStateRequest, WorkspaceRecord, YamlImportJobCompletion, YamlImportJobId, YamlImportJobStatus, @@ -1186,6 +1171,7 @@ mod tests { .await .unwrap() .unwrap(); + let published_list = registry.list_published_operations().await.unwrap(); assert_eq!(summary.current_draft_version, 2); assert_eq!(summary.latest_published_version, Some(2)); @@ -1197,6 +1183,7 @@ mod tests { ); assert_eq!(published.version, 2); assert!(published.is_published()); + assert_eq!(published_list, vec![published.clone()]); database.cleanup().await; } @@ -1641,6 +1628,86 @@ mod tests { database.cleanup().await; } + #[tokio::test] + async fn manages_published_agent_tool_reads() { + let database = TestDatabase::new().await; + let registry = database.registry().await; + let operation = test_operation("op_agent_pub_01", 1, OperationStatus::Draft); + let operation_v2 = test_operation("op_agent_pub_01", 2, OperationStatus::Draft); + let agent = test_agent("agent_pub_01", AgentStatus::Draft); + let version = test_agent_version(&agent.id, 1, AgentStatus::Draft); + let bindings = vec![AgentOperationBinding { + operation_id: operation.id.clone(), + operation_version: operation_v2.version, + tool_name: "create_lead".to_owned(), + tool_title: "Create lead".to_owned(), + tool_description_override: Some("Creates CRM lead".to_owned()), + enabled: true, + }]; + + registry + .create_operation(&test_workspace_id(), &operation, None) + .await + .unwrap(); + registry + .create_version(CreateVersionRequest { + workspace_id: &test_workspace_id(), + snapshot: &operation_v2, + change_note: Some("publishable"), + created_by: Some("alice"), + }) + .await + .unwrap(); + registry + .publish_operation(PublishRequest { + workspace_id: &test_workspace_id(), + operation_id: &operation.id, + version: operation_v2.version, + published_at: "2026-03-25T12:10:00Z", + published_by: Some("alice"), + }) + .await + .unwrap(); + registry + .create_agent(CreateAgentRequest { + agent: &agent, + version: &version, + bindings: &bindings, + }) + .await + .unwrap(); + registry + .publish_agent(PublishAgentRequest { + workspace_id: &test_workspace_id(), + agent_id: &agent.id, + version: version.version, + published_at: "2026-03-25T12:11:00Z", + published_by: Some("alice"), + }) + .await + .unwrap(); + + let tools = registry + .get_published_agent_tools_by_slug("default", &agent.slug) + .await + .unwrap(); + + assert_eq!(tools.len(), 1); + assert_eq!(tools[0].workspace_id, test_workspace_id()); + assert_eq!(tools[0].workspace_slug, "default"); + assert_eq!(tools[0].agent_id, agent.id); + assert_eq!(tools[0].agent_slug, agent.slug); + assert_eq!(tools[0].tool_name, bindings[0].tool_name); + assert_eq!(tools[0].tool_title, bindings[0].tool_title); + assert_eq!(tools[0].tool_description, "Creates CRM lead"); + assert_eq!(tools[0].operation.id, operation_v2.id); + assert_eq!(tools[0].operation.version, operation_v2.version); + assert_eq!(tools[0].operation.protocol, operation_v2.protocol); + assert!(tools[0].operation.is_published()); + + database.cleanup().await; + } + #[tokio::test] async fn manages_stream_sessions_with_transitions_and_cleanup() { let database = TestDatabase::new().await; diff --git a/crates/crank-registry/src/postgres/operation.rs b/crates/crank-registry/src/postgres/operation.rs index 0d2433b..b06ab0e 100644 --- a/crates/crank-registry/src/postgres/operation.rs +++ b/crates/crank-registry/src/postgres/operation.rs @@ -674,7 +674,7 @@ impl PostgresRegistry { &self, operation_id: &OperationId, ) -> Result, RegistryError> { - let row = sqlx::query( + let row = sqlx::query!( "select o.id, o.workspace_id, @@ -682,8 +682,8 @@ impl PostgresRegistry { o.display_name, o.category, o.protocol, - to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_created_at, - to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_updated_at, + to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_created_at!\", + to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_updated_at!\", to_char(o.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_published_at, ov.version, ov.status, @@ -698,25 +698,52 @@ impl PostgresRegistry { ov.generated_draft_json, ov.config_export_json, ov.change_note, - to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, + to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", ov.created_by from published_operations po join operation_versions ov on ov.operation_id = po.operation_id and ov.version = po.version join operations o on o.id = po.operation_id where po.operation_id = $1", + operation_id.as_str(), ) - .bind(operation_id.as_str()) .fetch_optional(&self.pool) .await?; - row.as_ref() - .map(|value| map_operation_version_record(value).map(|record| record.snapshot)) - .transpose() + row.map(|row| { + build_operation_version_record( + row.id, + row.workspace_id, + row.name, + row.display_name, + row.category, + row.protocol, + row.operation_created_at, + row.operation_updated_at, + row.operation_published_at, + row.version, + row.status, + row.target_json, + row.input_schema_json, + row.output_schema_json, + row.input_mapping_json, + row.output_mapping_json, + row.execution_config_json, + row.tool_description_json, + row.samples_json, + row.generated_draft_json, + row.config_export_json, + row.change_note, + row.created_at, + row.created_by, + ) + .map(|record| record.snapshot) + }) + .transpose() } pub async fn list_published_operations(&self) -> Result, RegistryError> { - let rows = sqlx::query( + let rows = sqlx::query!( "select o.id, o.workspace_id, @@ -724,8 +751,8 @@ impl PostgresRegistry { o.display_name, o.category, o.protocol, - to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_created_at, - to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_updated_at, + to_char(o.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_created_at!\", + to_char(o.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"operation_updated_at!\", to_char(o.published_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as operation_published_at, ov.version, ov.status, @@ -740,7 +767,7 @@ impl PostgresRegistry { ov.generated_draft_json, ov.config_export_json, ov.change_note, - to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, + to_char(ov.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", ov.created_by from published_operations po join operation_versions ov @@ -751,8 +778,36 @@ impl PostgresRegistry { .fetch_all(&self.pool) .await?; - rows.iter() - .map(|row| map_operation_version_record(row).map(|record| record.snapshot)) + rows.into_iter() + .map(|row| { + build_operation_version_record( + row.id, + row.workspace_id, + row.name, + row.display_name, + row.category, + row.protocol, + row.operation_created_at, + row.operation_updated_at, + row.operation_published_at, + row.version, + row.status, + row.target_json, + row.input_schema_json, + row.output_schema_json, + row.input_mapping_json, + row.output_mapping_json, + row.execution_config_json, + row.tool_description_json, + row.samples_json, + row.generated_draft_json, + row.config_export_json, + row.change_note, + row.created_at, + row.created_by, + ) + .map(|record| record.snapshot) + }) .collect() } @@ -802,7 +857,7 @@ impl PostgresRegistry { operation_id: &OperationId, version: u32, ) -> Result, RegistryError> { - let rows = sqlx::query( + let rows = sqlx::query!( "select id, operation_id, @@ -811,17 +866,30 @@ impl PostgresRegistry { storage_ref, content_type, file_name, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\" from operation_samples where operation_id = $1 and version = $2 order by created_at asc", + operation_id.as_str(), + to_db_version(version), ) - .bind(operation_id.as_str()) - .bind(to_db_version(version)) .fetch_all(&self.pool) .await?; - rows.iter().map(map_sample_metadata).collect() + rows.into_iter() + .map(|row| { + build_sample_metadata( + row.id, + row.operation_id, + row.version, + row.sample_kind, + row.storage_ref, + row.content_type, + row.file_name, + row.created_at, + ) + }) + .collect() } pub async fn save_descriptor_metadata( @@ -876,7 +944,7 @@ impl PostgresRegistry { operation_id: &OperationId, version: u32, ) -> Result, RegistryError> { - let rows = sqlx::query( + let rows = sqlx::query!( "select id, operation_id, @@ -885,16 +953,29 @@ impl PostgresRegistry { storage_ref, source_name, package_index_json, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\" from descriptors where operation_id = $1 and version = $2 order by created_at asc", + operation_id.as_str(), + to_db_version(version), ) - .bind(operation_id.as_str()) - .bind(to_db_version(version)) .fetch_all(&self.pool) .await?; - rows.iter().map(map_descriptor_metadata).collect() + rows.into_iter() + .map(|row| { + build_descriptor_metadata( + row.id, + row.operation_id, + row.version, + row.descriptor_kind, + row.storage_ref, + row.source_name, + row.package_index_json, + row.created_at, + ) + }) + .collect() } }