From b91504b85c69393cab896b8e7643de008421d884 Mon Sep 17 00:00:00 2001 From: yanny-sec Date: Wed, 12 Aug 2026 12:23:48 +0100 Subject: [PATCH 1/5] chore(proto): regenerate bindings from unchanged schema and pin mypy-protobuf --- backend/internal/proto/event.pb.go | 3 + .../claude-code/adrian_cc/proto/event_pb2.py | 123 +-- .../claude-code/adrian_cc/proto/event_pb2.pyi | 895 ++++++++++++++---- sdk/python/adrian/proto/event_pb2.py | 60 +- sdk/python/adrian/proto/event_pb2.pyi | 66 +- sdk/python/pyproject.toml | 1 + sdk/python/uv.lock | 24 + 7 files changed, 857 insertions(+), 315 deletions(-) diff --git a/backend/internal/proto/event.pb.go b/backend/internal/proto/event.pb.go index fa30119..ed7581a 100644 --- a/backend/internal/proto/event.pb.go +++ b/backend/internal/proto/event.pb.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2026 SecureAgentics + // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 diff --git a/integrations/claude-code/adrian_cc/proto/event_pb2.py b/integrations/claude-code/adrian_cc/proto/event_pb2.py index d03816d..dadbbbe 100644 --- a/integrations/claude-code/adrian_cc/proto/event_pb2.py +++ b/integrations/claude-code/adrian_cc/proto/event_pb2.py @@ -1,12 +1,22 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE # source: event.proto -# Protobuf Python Version: 4.25.3 +# Protobuf Python Version: 6.33.5 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version from google.protobuf import symbol_database as _symbol_database from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'event.proto' +) # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -15,76 +25,77 @@ from .buf.validate import validate_pb2 as buf_dot_validate_dot_validate__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x65vent.proto\x12\x12\x61\x64rian.core_api.v1\x1a\x1b\x62uf/validate/validate.proto\";\n\x0b\x43hatMessage\x12\x12\n\x04role\x18\x01 \x01(\tR\x04role\x12\x18\n\x07\x63ontent\x18\x02 \x01(\tR\x07\x63ontent\"K\n\x08ToolCall\x12\x1b\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x04name\x12\x12\n\x04\x61rgs\x18\x02 \x01(\tR\x04\x61rgs\x12\x0e\n\x02id\x18\x03 \x01(\tR\x02id\"\x9c\x01\n\nTokenUsage\x12,\n\rprompt_tokens\x18\x01 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00R\x0cpromptTokens\x12\x34\n\x11\x63ompletion_tokens\x18\x02 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00R\x10\x63ompletionTokens\x12*\n\x0ctotal_tokens\x18\x03 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00R\x0btotalTokens\"y\n\x0c\x41gentContext\x12\x19\n\x08\x61gent_id\x18\x01 \x01(\tR\x07\x61gentId\x12#\n\rsystem_prompt\x18\x02 \x01(\tR\x0csystemPrompt\x12)\n\x10user_instruction\x18\x03 \x01(\tR\x0fuserInstruction\"\xeb\x01\n\x0bLlmPairData\x12\x14\n\x05model\x18\x01 \x01(\tR\x05model\x12;\n\x08messages\x18\x02 \x03(\x0b\x32\x1f.adrian.core_api.v1.ChatMessageR\x08messages\x12\x16\n\x06output\x18\x03 \x01(\tR\x06output\x12;\n\ntool_calls\x18\x04 \x03(\x0b\x32\x1c.adrian.core_api.v1.ToolCallR\ttoolCalls\x12\x34\n\x05usage\x18\x05 \x01(\x0b\x32\x1e.adrian.core_api.v1.TokenUsageR\x05usage\"\x84\x01\n\x0cToolPairData\x12$\n\ttool_name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x08toolName\x12 \n\x0ctool_call_id\x18\x02 \x01(\tR\ntoolCallId\x12\x14\n\x05input\x18\x03 \x01(\tR\x05input\x12\x16\n\x06output\x18\x04 \x01(\tR\x06output\"\xe3\x04\n\x0bPairedEvent\x12\"\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x07\x65ventId\x12#\n\rinvocation_id\x18\x02 \x01(\tR\x0cinvocationId\x12&\n\nsession_id\x18\x03 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\tsessionId\x12\x15\n\x06run_id\x18\x04 \x01(\tR\x05runId\x12\"\n\rparent_run_id\x18\x05 \x01(\tR\x0bparentRunId\x12\x1c\n\ttimestamp\x18\x06 \x01(\tR\ttimestamp\x12\x41\n\tpair_type\x18\x07 \x01(\x0e\x32\x1c.adrian.core_api.v1.PairTypeB\x06\xbaH\x03\xc8\x01\x01R\x08pairType\x12\x36\n\x05\x61gent\x18\x08 \x01(\x0b\x32 .adrian.core_api.v1.AgentContextR\x05\x61gent\x12\x38\n\x06parent\x18\t \x01(\x0b\x32 .adrian.core_api.v1.AgentContextR\x06parent\x12\x33\n\x03llm\x18\n \x01(\x0b\x32\x1f.adrian.core_api.v1.LlmPairDataH\x00R\x03llm\x12\x36\n\x04tool\x18\x0b \x01(\x0b\x32 .adrian.core_api.v1.ToolPairDataH\x00R\x04tool\x12#\n\rmetadata_json\x18\x14 \x01(\x0cR\x0cmetadataJson\x12#\n\rconnection_id\x18\x0c \x01(\tR\x0c\x63onnectionId\x12\x16\n\x06source\x18\x15 \x01(\tR\x06sourceB\x06\n\x04\x64\x61ta\"K\n\x10PairedEventBatch\x12\x37\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x1f.adrian.core_api.v1.PairedEventR\x06\x65vents\"b\n\tMcpServer\x12\x1b\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x04name\x12\x1c\n\ttransport\x18\x02 \x01(\tR\ttransport\x12\x1a\n\x08\x65ndpoint\x18\x03 \x01(\tR\x08\x65ndpoint\"G\n\x0cMcpInventory\x12\x37\n\x07servers\x18\x01 \x03(\x0b\x32\x1d.adrian.core_api.v1.McpServerR\x07servers\"<\n\x08LLMStack\x12\x1a\n\x08provider\x18\x01 \x01(\tR\x08provider\x12\x14\n\x05model\x18\x02 \x01(\tR\x05model\"\xe7\x01\n\x0cSessionLogin\x12&\n\nsession_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\tsessionId\x12\x39\n\tllm_stack\x18\x02 \x01(\x0b\x32\x1c.adrian.core_api.v1.LLMStackR\x08llmStack\x12%\n\x0eschema_version\x18\x04 \x01(\rR\rschemaVersion\x12\x16\n\x06source\x18\x05 \x01(\tR\x06source\x12#\n\rconnection_id\x18\x06 \x01(\tR\x0c\x63onnectionIdJ\x04\x08\x03\x10\x04R\nblock_mode\"\xf1\x01\n\x0b\x43lientFrame\x12\x38\n\x05login\x18\x01 \x01(\x0b\x32 .adrian.core_api.v1.SessionLoginH\x00R\x05login\x12I\n\x0cpaired_batch\x18\x03 \x01(\x0b\x32$.adrian.core_api.v1.PairedEventBatchH\x00R\x0bpairedBatch\x12G\n\rmcp_inventory\x18\x04 \x01(\x0b\x32 .adrian.core_api.v1.McpInventoryH\x00R\x0cmcpInventoryB\x07\n\x05\x66rameJ\x04\x08\x02\x10\x03R\x05\x62\x61tch\"\xb2\x01\n\x0ePolicySnapshot\x12,\n\x04mode\x18\x01 \x01(\x0e\x32\x18.adrian.core_api.v1.ModeR\x04mode\x12\x1b\n\tpolicy_m0\x18\x02 \x01(\x08R\x08policyM0\x12\x1b\n\tpolicy_m2\x18\x03 \x01(\x08R\x08policyM2\x12\x1b\n\tpolicy_m3\x18\x04 \x01(\x08R\x08policyM3\x12\x1b\n\tpolicy_m4\x18\x05 \x01(\x08R\x08policyM4\"=\n\x0cHitlResponse\x12-\n\x12\x63ontinue_execution\x18\x01 \x01(\x08R\x11\x63ontinueExecution\"^\n\x08LoginAck\x12:\n\x06policy\x18\x01 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshotR\x06policy\x12\x16\n\x06source\x18\x02 \x01(\tR\x06source\"\x8c\x01\n\x0bServerFrame\x12;\n\tlogin_ack\x18\x01 \x01(\x0b\x32\x1c.adrian.core_api.v1.LoginAckH\x00R\x08loginAck\x12\x37\n\x07verdict\x18\x02 \x01(\x0b\x32\x1b.adrian.core_api.v1.VerdictH\x00R\x07verdictB\x07\n\x05\x66rame\"\x88\x02\n\x07Verdict\x12\"\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\x07\x65ventId\x12&\n\nsession_id\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01R\tsessionId\x12\x19\n\x08mad_code\x18\x04 \x01(\tR\x07madCode\x12:\n\x06policy\x18\x06 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshotR\x06policy\x12\x34\n\x04hitl\x18\x07 \x01(\x0b\x32 .adrian.core_api.v1.HitlResponseR\x04hitlJ\x04\x08\x03\x10\x04J\x04\x08\x05\x10\x06R\x0e\x63lassificationR\x08\x65scalate*L\n\x08PairType\x12\x19\n\x15PAIR_TYPE_UNSPECIFIED\x10\x00\x12\x11\n\rPAIR_TYPE_LLM\x10\x01\x12\x12\n\x0ePAIR_TYPE_TOOL\x10\x02*K\n\x04Mode\x12\x14\n\x10MODE_UNSPECIFIED\x10\x00\x12\x0e\n\nMODE_ALERT\x10\x01\x12\r\n\tMODE_HITL\x10\x02\x12\x0e\n\nMODE_BLOCK\x10\x03\x42\x31Z/github.com/secure-agentics/adrian/server/pkg/pbb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x65vent.proto\x12\x12\x61\x64rian.core_api.v1\x1a\x1b\x62uf/validate/validate.proto\",\n\x0b\x43hatMessage\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\";\n\x08ToolCall\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0c\n\x04\x61rgs\x18\x02 \x01(\t\x12\n\n\x02id\x18\x03 \x01(\t\"o\n\nTokenUsage\x12\x1e\n\rprompt_tokens\x18\x01 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\"\n\x11\x63ompletion_tokens\x18\x02 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\x1d\n\x0ctotal_tokens\x18\x03 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\"Q\n\x0c\x41gentContext\x12\x10\n\x08\x61gent_id\x18\x01 \x01(\t\x12\x15\n\rsystem_prompt\x18\x02 \x01(\t\x12\x18\n\x10user_instruction\x18\x03 \x01(\t\"\xc0\x01\n\x0bLlmPairData\x12\r\n\x05model\x18\x01 \x01(\t\x12\x31\n\x08messages\x18\x02 \x03(\x0b\x32\x1f.adrian.core_api.v1.ChatMessage\x12\x0e\n\x06output\x18\x03 \x01(\t\x12\x30\n\ntool_calls\x18\x04 \x03(\x0b\x32\x1c.adrian.core_api.v1.ToolCall\x12-\n\x05usage\x18\x05 \x01(\x0b\x32\x1e.adrian.core_api.v1.TokenUsage\"_\n\x0cToolPairData\x12\x1a\n\ttool_name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x14\n\x0ctool_call_id\x18\x02 \x01(\t\x12\r\n\x05input\x18\x03 \x01(\t\x12\x0e\n\x06output\x18\x04 \x01(\t\"\xda\x03\n\x0bPairedEvent\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x15\n\rinvocation_id\x18\x02 \x01(\t\x12\x1b\n\nsession_id\x18\x03 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0e\n\x06run_id\x18\x04 \x01(\t\x12\x15\n\rparent_run_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x37\n\tpair_type\x18\x07 \x01(\x0e\x32\x1c.adrian.core_api.v1.PairTypeB\x06\xbaH\x03\xc8\x01\x01\x12/\n\x05\x61gent\x18\x08 \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12\x30\n\x06parent\x18\t \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12.\n\x03llm\x18\n \x01(\x0b\x32\x1f.adrian.core_api.v1.LlmPairDataH\x00\x12\x30\n\x04tool\x18\x0b \x01(\x0b\x32 .adrian.core_api.v1.ToolPairDataH\x00\x12\x15\n\rconnection_id\x18\x0c \x01(\t\x12\x15\n\rmetadata_json\x18\x14 \x01(\x0c\x12\x0e\n\x06source\x18\x15 \x01(\tB\x06\n\x04\x64\x61ta\"C\n\x10PairedEventBatch\x12/\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x1f.adrian.core_api.v1.PairedEvent\"G\n\tMcpServer\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x11\n\ttransport\x18\x02 \x01(\t\x12\x10\n\x08\x65ndpoint\x18\x03 \x01(\t\">\n\x0cMcpInventory\x12.\n\x07servers\x18\x01 \x03(\x0b\x32\x1d.adrian.core_api.v1.McpServer\"+\n\x08LLMStack\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\r\n\x05model\x18\x02 \x01(\t\"\xad\x01\n\x0cSessionLogin\x12\x1b\n\nsession_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12/\n\tllm_stack\x18\x02 \x01(\x0b\x32\x1c.adrian.core_api.v1.LLMStack\x12\x16\n\x0eschema_version\x18\x04 \x01(\r\x12\x0e\n\x06source\x18\x05 \x01(\t\x12\x15\n\rconnection_id\x18\x06 \x01(\tJ\x04\x08\x03\x10\x04R\nblock_mode\"\xcf\x01\n\x0b\x43lientFrame\x12\x31\n\x05login\x18\x01 \x01(\x0b\x32 .adrian.core_api.v1.SessionLoginH\x00\x12<\n\x0cpaired_batch\x18\x03 \x01(\x0b\x32$.adrian.core_api.v1.PairedEventBatchH\x00\x12\x39\n\rmcp_inventory\x18\x04 \x01(\x0b\x32 .adrian.core_api.v1.McpInventoryH\x00\x42\x07\n\x05\x66rameJ\x04\x08\x02\x10\x03R\x05\x62\x61tch\"\xad\x01\n\x0ePolicySnapshot\x12&\n\x04mode\x18\x01 \x01(\x0e\x32\x18.adrian.core_api.v1.Mode\x12\x11\n\tpolicy_m0\x18\x02 \x01(\x08\x12\x11\n\tpolicy_m2\x18\x03 \x01(\x08\x12\x11\n\tpolicy_m3\x18\x04 \x01(\x08\x12\x11\n\tpolicy_m4\x18\x05 \x01(\x08\x12\'\n\x1f\x66\x61il_closed_on_classifier_error\x18\x06 \x01(\x08\"*\n\x0cHitlResponse\x12\x1a\n\x12\x63ontinue_execution\x18\x01 \x01(\x08\">\n\x08LoginAck\x12\x32\n\x06policy\x18\x01 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\"y\n\x0bServerFrame\x12\x31\n\tlogin_ack\x18\x01 \x01(\x0b\x32\x1c.adrian.core_api.v1.LoginAckH\x00\x12.\n\x07verdict\x18\x02 \x01(\x0b\x32\x1b.adrian.core_api.v1.VerdictH\x00\x42\x07\n\x05\x66rame\"\x90\x02\n\x07Verdict\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x1b\n\nsession_id\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x10\n\x08mad_code\x18\x04 \x01(\t\x12\x32\n\x06policy\x18\x06 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\x12.\n\x04hitl\x18\x07 \x01(\x0b\x32 .adrian.core_api.v1.HitlResponse\x12\x31\n\x06status\x18\x08 \x01(\x0e\x32!.adrian.core_api.v1.VerdictStatusJ\x04\x08\x03\x10\x04J\x04\x08\x05\x10\x06R\x0e\x63lassificationR\x08\x65scalate*L\n\x08PairType\x12\x19\n\x15PAIR_TYPE_UNSPECIFIED\x10\x00\x12\x11\n\rPAIR_TYPE_LLM\x10\x01\x12\x12\n\x0ePAIR_TYPE_TOOL\x10\x02*K\n\x04Mode\x12\x14\n\x10MODE_UNSPECIFIED\x10\x00\x12\x0e\n\nMODE_ALERT\x10\x01\x12\r\n\tMODE_HITL\x10\x02\x12\x0e\n\nMODE_BLOCK\x10\x03*`\n\rVerdictStatus\x12\x1e\n\x1aVERDICT_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11VERDICT_STATUS_OK\x10\x01\x12\x18\n\x14VERDICT_STATUS_ERROR\x10\x02\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'event_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: - _globals['DESCRIPTOR']._options = None - _globals['DESCRIPTOR']._serialized_options = b'Z/github.com/secure-agentics/adrian/server/pkg/pb' - _globals['_TOOLCALL'].fields_by_name['name']._options = None +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_TOOLCALL'].fields_by_name['name']._loaded_options = None _globals['_TOOLCALL'].fields_by_name['name']._serialized_options = b'\272H\004r\002\020\001' - _globals['_TOKENUSAGE'].fields_by_name['prompt_tokens']._options = None + _globals['_TOKENUSAGE'].fields_by_name['prompt_tokens']._loaded_options = None _globals['_TOKENUSAGE'].fields_by_name['prompt_tokens']._serialized_options = b'\272H\004\032\002(\000' - _globals['_TOKENUSAGE'].fields_by_name['completion_tokens']._options = None + _globals['_TOKENUSAGE'].fields_by_name['completion_tokens']._loaded_options = None _globals['_TOKENUSAGE'].fields_by_name['completion_tokens']._serialized_options = b'\272H\004\032\002(\000' - _globals['_TOKENUSAGE'].fields_by_name['total_tokens']._options = None + _globals['_TOKENUSAGE'].fields_by_name['total_tokens']._loaded_options = None _globals['_TOKENUSAGE'].fields_by_name['total_tokens']._serialized_options = b'\272H\004\032\002(\000' - _globals['_TOOLPAIRDATA'].fields_by_name['tool_name']._options = None + _globals['_TOOLPAIRDATA'].fields_by_name['tool_name']._loaded_options = None _globals['_TOOLPAIRDATA'].fields_by_name['tool_name']._serialized_options = b'\272H\004r\002\020\001' - _globals['_PAIREDEVENT'].fields_by_name['event_id']._options = None + _globals['_PAIREDEVENT'].fields_by_name['event_id']._loaded_options = None _globals['_PAIREDEVENT'].fields_by_name['event_id']._serialized_options = b'\272H\004r\002\020\001' - _globals['_PAIREDEVENT'].fields_by_name['session_id']._options = None + _globals['_PAIREDEVENT'].fields_by_name['session_id']._loaded_options = None _globals['_PAIREDEVENT'].fields_by_name['session_id']._serialized_options = b'\272H\004r\002\020\001' - _globals['_PAIREDEVENT'].fields_by_name['pair_type']._options = None + _globals['_PAIREDEVENT'].fields_by_name['pair_type']._loaded_options = None _globals['_PAIREDEVENT'].fields_by_name['pair_type']._serialized_options = b'\272H\003\310\001\001' - _globals['_MCPSERVER'].fields_by_name['name']._options = None + _globals['_MCPSERVER'].fields_by_name['name']._loaded_options = None _globals['_MCPSERVER'].fields_by_name['name']._serialized_options = b'\272H\004r\002\020\001' - _globals['_SESSIONLOGIN'].fields_by_name['session_id']._options = None + _globals['_SESSIONLOGIN'].fields_by_name['session_id']._loaded_options = None _globals['_SESSIONLOGIN'].fields_by_name['session_id']._serialized_options = b'\272H\004r\002\020\001' - _globals['_VERDICT'].fields_by_name['event_id']._options = None + _globals['_VERDICT'].fields_by_name['event_id']._loaded_options = None _globals['_VERDICT'].fields_by_name['event_id']._serialized_options = b'\272H\004r\002\020\001' - _globals['_VERDICT'].fields_by_name['session_id']._options = None + _globals['_VERDICT'].fields_by_name['session_id']._loaded_options = None _globals['_VERDICT'].fields_by_name['session_id']._serialized_options = b'\272H\004r\002\020\001' - _globals['_PAIRTYPE']._serialized_start=3011 - _globals['_PAIRTYPE']._serialized_end=3087 - _globals['_MODE']._serialized_start=3089 - _globals['_MODE']._serialized_end=3164 + _globals['_PAIRTYPE']._serialized_start=2455 + _globals['_PAIRTYPE']._serialized_end=2531 + _globals['_MODE']._serialized_start=2533 + _globals['_MODE']._serialized_end=2608 + _globals['_VERDICTSTATUS']._serialized_start=2610 + _globals['_VERDICTSTATUS']._serialized_end=2706 _globals['_CHATMESSAGE']._serialized_start=64 - _globals['_CHATMESSAGE']._serialized_end=123 - _globals['_TOOLCALL']._serialized_start=125 - _globals['_TOOLCALL']._serialized_end=200 - _globals['_TOKENUSAGE']._serialized_start=203 - _globals['_TOKENUSAGE']._serialized_end=359 - _globals['_AGENTCONTEXT']._serialized_start=361 - _globals['_AGENTCONTEXT']._serialized_end=482 - _globals['_LLMPAIRDATA']._serialized_start=485 - _globals['_LLMPAIRDATA']._serialized_end=720 - _globals['_TOOLPAIRDATA']._serialized_start=723 - _globals['_TOOLPAIRDATA']._serialized_end=855 - _globals['_PAIREDEVENT']._serialized_start=858 - _globals['_PAIREDEVENT']._serialized_end=1469 - _globals['_PAIREDEVENTBATCH']._serialized_start=1471 - _globals['_PAIREDEVENTBATCH']._serialized_end=1546 - _globals['_MCPSERVER']._serialized_start=1548 - _globals['_MCPSERVER']._serialized_end=1646 - _globals['_MCPINVENTORY']._serialized_start=1648 - _globals['_MCPINVENTORY']._serialized_end=1719 - _globals['_LLMSTACK']._serialized_start=1721 - _globals['_LLMSTACK']._serialized_end=1781 - _globals['_SESSIONLOGIN']._serialized_start=1784 - _globals['_SESSIONLOGIN']._serialized_end=2015 - _globals['_CLIENTFRAME']._serialized_start=2018 - _globals['_CLIENTFRAME']._serialized_end=2259 - _globals['_POLICYSNAPSHOT']._serialized_start=2262 - _globals['_POLICYSNAPSHOT']._serialized_end=2440 - _globals['_HITLRESPONSE']._serialized_start=2442 - _globals['_HITLRESPONSE']._serialized_end=2503 - _globals['_LOGINACK']._serialized_start=2505 - _globals['_LOGINACK']._serialized_end=2599 - _globals['_SERVERFRAME']._serialized_start=2602 - _globals['_SERVERFRAME']._serialized_end=2742 - _globals['_VERDICT']._serialized_start=2745 - _globals['_VERDICT']._serialized_end=3009 + _globals['_CHATMESSAGE']._serialized_end=108 + _globals['_TOOLCALL']._serialized_start=110 + _globals['_TOOLCALL']._serialized_end=169 + _globals['_TOKENUSAGE']._serialized_start=171 + _globals['_TOKENUSAGE']._serialized_end=282 + _globals['_AGENTCONTEXT']._serialized_start=284 + _globals['_AGENTCONTEXT']._serialized_end=365 + _globals['_LLMPAIRDATA']._serialized_start=368 + _globals['_LLMPAIRDATA']._serialized_end=560 + _globals['_TOOLPAIRDATA']._serialized_start=562 + _globals['_TOOLPAIRDATA']._serialized_end=657 + _globals['_PAIREDEVENT']._serialized_start=660 + _globals['_PAIREDEVENT']._serialized_end=1134 + _globals['_PAIREDEVENTBATCH']._serialized_start=1136 + _globals['_PAIREDEVENTBATCH']._serialized_end=1203 + _globals['_MCPSERVER']._serialized_start=1205 + _globals['_MCPSERVER']._serialized_end=1276 + _globals['_MCPINVENTORY']._serialized_start=1278 + _globals['_MCPINVENTORY']._serialized_end=1340 + _globals['_LLMSTACK']._serialized_start=1342 + _globals['_LLMSTACK']._serialized_end=1385 + _globals['_SESSIONLOGIN']._serialized_start=1388 + _globals['_SESSIONLOGIN']._serialized_end=1561 + _globals['_CLIENTFRAME']._serialized_start=1564 + _globals['_CLIENTFRAME']._serialized_end=1771 + _globals['_POLICYSNAPSHOT']._serialized_start=1774 + _globals['_POLICYSNAPSHOT']._serialized_end=1947 + _globals['_HITLRESPONSE']._serialized_start=1949 + _globals['_HITLRESPONSE']._serialized_end=1991 + _globals['_LOGINACK']._serialized_start=1993 + _globals['_LOGINACK']._serialized_end=2055 + _globals['_SERVERFRAME']._serialized_start=2057 + _globals['_SERVERFRAME']._serialized_end=2178 + _globals['_VERDICT']._serialized_start=2181 + _globals['_VERDICT']._serialized_end=2453 # @@protoc_insertion_point(module_scope) diff --git a/integrations/claude-code/adrian_cc/proto/event_pb2.pyi b/integrations/claude-code/adrian_cc/proto/event_pb2.pyi index e295646..a99a39c 100644 --- a/integrations/claude-code/adrian_cc/proto/event_pb2.pyi +++ b/integrations/claude-code/adrian_cc/proto/event_pb2.pyi @@ -1,228 +1,749 @@ -from .buf.validate import validate_pb2 as _validate_pb2 -from google.protobuf.internal import containers as _containers -from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +SPDX-License-Identifier: Apache-2.0 +Copyright (c) 2026 SecureAgentics +""" + +from collections import abc as _abc from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message -from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +import builtins as _builtins +import sys +import typing as _typing + +if sys.version_info >= (3, 10): + from typing import TypeAlias as _TypeAlias +else: + from typing_extensions import TypeAlias as _TypeAlias DESCRIPTOR: _descriptor.FileDescriptor -class PairType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - PAIR_TYPE_UNSPECIFIED: _ClassVar[PairType] - PAIR_TYPE_LLM: _ClassVar[PairType] - PAIR_TYPE_TOOL: _ClassVar[PairType] - -class Mode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): - __slots__ = () - MODE_UNSPECIFIED: _ClassVar[Mode] - MODE_ALERT: _ClassVar[Mode] - MODE_HITL: _ClassVar[Mode] - MODE_BLOCK: _ClassVar[Mode] -PAIR_TYPE_UNSPECIFIED: PairType -PAIR_TYPE_LLM: PairType -PAIR_TYPE_TOOL: PairType -MODE_UNSPECIFIED: Mode -MODE_ALERT: Mode -MODE_HITL: Mode -MODE_BLOCK: Mode +class _PairType: + ValueType = _typing.NewType("ValueType", _builtins.int) + V: _TypeAlias = ValueType # noqa: Y015 + +class _PairTypeEnumTypeWrapper(_enum_type_wrapper._EnumTypeWrapper[_PairType.ValueType], _builtins.type): + DESCRIPTOR: _descriptor.EnumDescriptor + PAIR_TYPE_UNSPECIFIED: _PairType.ValueType # 0 + PAIR_TYPE_LLM: _PairType.ValueType # 1 + """LLM pair: chat_model_start + llm_end folded together.""" + PAIR_TYPE_TOOL: _PairType.ValueType # 2 + """Tool pair: tool_start + tool_end folded together.""" + +class PairType(_PairType, metaclass=_PairTypeEnumTypeWrapper): + """PairType identifies what an event pair represents.""" + +PAIR_TYPE_UNSPECIFIED: PairType.ValueType # 0 +PAIR_TYPE_LLM: PairType.ValueType # 1 +"""LLM pair: chat_model_start + llm_end folded together.""" +PAIR_TYPE_TOOL: PairType.ValueType # 2 +"""Tool pair: tool_start + tool_end folded together.""" +Global___PairType: _TypeAlias = PairType # noqa: Y015 + +class _Mode: + ValueType = _typing.NewType("ValueType", _builtins.int) + V: _TypeAlias = ValueType # noqa: Y015 + +class _ModeEnumTypeWrapper(_enum_type_wrapper._EnumTypeWrapper[_Mode.ValueType], _builtins.type): + DESCRIPTOR: _descriptor.EnumDescriptor + MODE_UNSPECIFIED: _Mode.ValueType # 0 + MODE_ALERT: _Mode.ValueType # 1 + """Verdicts are dashboard-only, the server never pushes them to the SDK.""" + MODE_HITL: _Mode.ValueType # 2 + """Server holds in-policy-scope verdicts pending dashboard approve/reject. + Out-of-scope verdicts are forwarded immediately so the SDK doesn't stall. + """ + MODE_BLOCK: _Mode.ValueType # 3 + """Server forwards every verdict; the SDK enforces per the policy snapshot.""" +class Mode(_Mode, metaclass=_ModeEnumTypeWrapper): + """Mode of execution governs how the server handles verdicts for a session. + Set via policies.mode and delivered to the SDK via PolicySnapshot + on every Verdict. + """ + +MODE_UNSPECIFIED: Mode.ValueType # 0 +MODE_ALERT: Mode.ValueType # 1 +"""Verdicts are dashboard-only, the server never pushes them to the SDK.""" +MODE_HITL: Mode.ValueType # 2 +"""Server holds in-policy-scope verdicts pending dashboard approve/reject. +Out-of-scope verdicts are forwarded immediately so the SDK doesn't stall. +""" +MODE_BLOCK: Mode.ValueType # 3 +"""Server forwards every verdict; the SDK enforces per the policy snapshot.""" +Global___Mode: _TypeAlias = Mode # noqa: Y015 + +class _VerdictStatus: + ValueType = _typing.NewType("ValueType", _builtins.int) + V: _TypeAlias = ValueType # noqa: Y015 + +class _VerdictStatusEnumTypeWrapper(_enum_type_wrapper._EnumTypeWrapper[_VerdictStatus.ValueType], _builtins.type): + DESCRIPTOR: _descriptor.EnumDescriptor + VERDICT_STATUS_UNSPECIFIED: _VerdictStatus.ValueType # 0 + VERDICT_STATUS_OK: _VerdictStatus.ValueType # 1 + VERDICT_STATUS_ERROR: _VerdictStatus.ValueType # 2 + +class VerdictStatus(_VerdictStatus, metaclass=_VerdictStatusEnumTypeWrapper): + """VerdictStatus says whether a Verdict came from a completed classifier + decision or represents a classifier failure. ERROR verdicts carry no + classifier-produced MAD code; policy decides whether they fail open + or fail closed. + """ + +VERDICT_STATUS_UNSPECIFIED: VerdictStatus.ValueType # 0 +VERDICT_STATUS_OK: VerdictStatus.ValueType # 1 +VERDICT_STATUS_ERROR: VerdictStatus.ValueType # 2 +Global___VerdictStatus: _TypeAlias = VerdictStatus # noqa: Y015 + +@_typing.final class ChatMessage(_message.Message): - __slots__ = ("role", "content") - ROLE_FIELD_NUMBER: _ClassVar[int] - CONTENT_FIELD_NUMBER: _ClassVar[int] - role: str - content: str - def __init__(self, role: _Optional[str] = ..., content: _Optional[str] = ...) -> None: ... + """ChatMessage represents a conversation message with a string role.""" + DESCRIPTOR: _descriptor.Descriptor + + ROLE_FIELD_NUMBER: _builtins.int + CONTENT_FIELD_NUMBER: _builtins.int + role: _builtins.str + """Role of this message (e.g. "system", "human", "ai", "tool").""" + content: _builtins.str + """Text content of the message.""" + def __init__( + self, + *, + role: _builtins.str = ..., + content: _builtins.str = ..., + ) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["content", b"content", "role", b"role"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + +Global___ChatMessage: _TypeAlias = ChatMessage # noqa: Y015 + +@_typing.final class ToolCall(_message.Message): - __slots__ = ("name", "args", "id") - NAME_FIELD_NUMBER: _ClassVar[int] - ARGS_FIELD_NUMBER: _ClassVar[int] - ID_FIELD_NUMBER: _ClassVar[int] - name: str - args: str - id: str - def __init__(self, name: _Optional[str] = ..., args: _Optional[str] = ..., id: _Optional[str] = ...) -> None: ... + """ToolCall represents a tool invocation the model decided to make.""" + + DESCRIPTOR: _descriptor.Descriptor + + NAME_FIELD_NUMBER: _builtins.int + ARGS_FIELD_NUMBER: _builtins.int + ID_FIELD_NUMBER: _builtins.int + name: _builtins.str + """Tool name.""" + args: _builtins.str + """JSON-encoded arguments object.""" + id: _builtins.str + """Provider-assigned tool call ID (may be empty for non-LLM-originated tools).""" + def __init__( + self, + *, + name: _builtins.str = ..., + args: _builtins.str = ..., + id: _builtins.str = ..., + ) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["args", b"args", "id", b"id", "name", b"name"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... +Global___ToolCall: _TypeAlias = ToolCall # noqa: Y015 + +@_typing.final class TokenUsage(_message.Message): - __slots__ = ("prompt_tokens", "completion_tokens", "total_tokens") - PROMPT_TOKENS_FIELD_NUMBER: _ClassVar[int] - COMPLETION_TOKENS_FIELD_NUMBER: _ClassVar[int] - TOTAL_TOKENS_FIELD_NUMBER: _ClassVar[int] - prompt_tokens: int - completion_tokens: int - total_tokens: int - def __init__(self, prompt_tokens: _Optional[int] = ..., completion_tokens: _Optional[int] = ..., total_tokens: _Optional[int] = ...) -> None: ... + """TokenUsage holds token consumption counters.""" + + DESCRIPTOR: _descriptor.Descriptor + + PROMPT_TOKENS_FIELD_NUMBER: _builtins.int + COMPLETION_TOKENS_FIELD_NUMBER: _builtins.int + TOTAL_TOKENS_FIELD_NUMBER: _builtins.int + prompt_tokens: _builtins.int + """Number of tokens in the prompt.""" + completion_tokens: _builtins.int + """Number of tokens in the completion.""" + total_tokens: _builtins.int + """Total tokens consumed.""" + def __init__( + self, + *, + prompt_tokens: _builtins.int = ..., + completion_tokens: _builtins.int = ..., + total_tokens: _builtins.int = ..., + ) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["completion_tokens", b"completion_tokens", "prompt_tokens", b"prompt_tokens", "total_tokens", b"total_tokens"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... +Global___TokenUsage: _TypeAlias = TokenUsage # noqa: Y015 + +@_typing.final class AgentContext(_message.Message): - __slots__ = ("agent_id", "system_prompt", "user_instruction") - AGENT_ID_FIELD_NUMBER: _ClassVar[int] - SYSTEM_PROMPT_FIELD_NUMBER: _ClassVar[int] - USER_INSTRUCTION_FIELD_NUMBER: _ClassVar[int] - agent_id: str - system_prompt: str - user_instruction: str - def __init__(self, agent_id: _Optional[str] = ..., system_prompt: _Optional[str] = ..., user_instruction: _Optional[str] = ...) -> None: ... + """AgentContext carries the identity and prompts for an agent at a given + point in the call stack. An empty agent_id signals "no agent" and is + used as the parent sentinel for top-level events. + """ + + DESCRIPTOR: _descriptor.Descriptor + + AGENT_ID_FIELD_NUMBER: _builtins.int + SYSTEM_PROMPT_FIELD_NUMBER: _builtins.int + USER_INSTRUCTION_FIELD_NUMBER: _builtins.int + agent_id: _builtins.str + """Opaque agent identifier derived from the framework's checkpoint + namespace (e.g. "tools|reason"). + """ + system_prompt: _builtins.str + """System prompt the agent is running under.""" + user_instruction: _builtins.str + """User instruction driving the current invocation.""" + def __init__( + self, + *, + agent_id: _builtins.str = ..., + system_prompt: _builtins.str = ..., + user_instruction: _builtins.str = ..., + ) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["agent_id", b"agent_id", "system_prompt", b"system_prompt", "user_instruction", b"user_instruction"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... +Global___AgentContext: _TypeAlias = AgentContext # noqa: Y015 + +@_typing.final class LlmPairData(_message.Message): - __slots__ = ("model", "messages", "output", "tool_calls", "usage") - MODEL_FIELD_NUMBER: _ClassVar[int] - MESSAGES_FIELD_NUMBER: _ClassVar[int] - OUTPUT_FIELD_NUMBER: _ClassVar[int] - TOOL_CALLS_FIELD_NUMBER: _ClassVar[int] - USAGE_FIELD_NUMBER: _ClassVar[int] - model: str - messages: _containers.RepeatedCompositeFieldContainer[ChatMessage] - output: str - tool_calls: _containers.RepeatedCompositeFieldContainer[ToolCall] - usage: TokenUsage - def __init__(self, model: _Optional[str] = ..., messages: _Optional[_Iterable[_Union[ChatMessage, _Mapping]]] = ..., output: _Optional[str] = ..., tool_calls: _Optional[_Iterable[_Union[ToolCall, _Mapping]]] = ..., usage: _Optional[_Union[TokenUsage, _Mapping]] = ...) -> None: ... + """LlmPairData is the payload for PAIR_TYPE_LLM events: one chat_model_start + folded with its matching llm_end. + """ + + DESCRIPTOR: _descriptor.Descriptor + + MODEL_FIELD_NUMBER: _builtins.int + MESSAGES_FIELD_NUMBER: _builtins.int + OUTPUT_FIELD_NUMBER: _builtins.int + TOOL_CALLS_FIELD_NUMBER: _builtins.int + USAGE_FIELD_NUMBER: _builtins.int + model: _builtins.str + """Model class name (e.g. "ChatAnthropic", "ChatOpenAI").""" + output: _builtins.str + """The model's text response.""" + @_builtins.property + def messages(self) -> _containers.RepeatedCompositeFieldContainer[Global___ChatMessage]: + """Ordered conversation messages sent to the model.""" + + @_builtins.property + def tool_calls(self) -> _containers.RepeatedCompositeFieldContainer[Global___ToolCall]: + """Tool calls the model decided to make.""" + + @_builtins.property + def usage(self) -> Global___TokenUsage: + """Token usage counters.""" + def __init__( + self, + *, + model: _builtins.str = ..., + messages: _abc.Iterable[Global___ChatMessage] | None = ..., + output: _builtins.str = ..., + tool_calls: _abc.Iterable[Global___ToolCall] | None = ..., + usage: Global___TokenUsage | None = ..., + ) -> None: ... + _HasFieldArgType: _TypeAlias = _typing.Literal["usage", b"usage"] # noqa: Y015 + def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["messages", b"messages", "model", b"model", "output", b"output", "tool_calls", b"tool_calls", "usage", b"usage"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + +Global___LlmPairData: _TypeAlias = LlmPairData # noqa: Y015 + +@_typing.final class ToolPairData(_message.Message): - __slots__ = ("tool_name", "tool_call_id", "input", "output") - TOOL_NAME_FIELD_NUMBER: _ClassVar[int] - TOOL_CALL_ID_FIELD_NUMBER: _ClassVar[int] - INPUT_FIELD_NUMBER: _ClassVar[int] - OUTPUT_FIELD_NUMBER: _ClassVar[int] - tool_name: str - tool_call_id: str - input: str - output: str - def __init__(self, tool_name: _Optional[str] = ..., tool_call_id: _Optional[str] = ..., input: _Optional[str] = ..., output: _Optional[str] = ...) -> None: ... + """ToolPairData is the payload for PAIR_TYPE_TOOL events: one tool_start + folded with its matching tool_end. + """ + + DESCRIPTOR: _descriptor.Descriptor + TOOL_NAME_FIELD_NUMBER: _builtins.int + TOOL_CALL_ID_FIELD_NUMBER: _builtins.int + INPUT_FIELD_NUMBER: _builtins.int + OUTPUT_FIELD_NUMBER: _builtins.int + tool_name: _builtins.str + """Name of the tool that was invoked.""" + tool_call_id: _builtins.str + """ID linking this invocation to the model's tool call. May be empty + for tools invoked outside an LLM flow. + """ + input: _builtins.str + """JSON-encoded input passed to the tool.""" + output: _builtins.str + """Tool execution output.""" + def __init__( + self, + *, + tool_name: _builtins.str = ..., + tool_call_id: _builtins.str = ..., + input: _builtins.str = ..., + output: _builtins.str = ..., + ) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["input", b"input", "output", b"output", "tool_call_id", b"tool_call_id", "tool_name", b"tool_name"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + +Global___ToolPairData: _TypeAlias = ToolPairData # noqa: Y015 + +@_typing.final class PairedEvent(_message.Message): - __slots__ = ("event_id", "invocation_id", "session_id", "run_id", "parent_run_id", "timestamp", "pair_type", "agent", "parent", "llm", "tool", "metadata_json", "connection_id", "source") - EVENT_ID_FIELD_NUMBER: _ClassVar[int] - INVOCATION_ID_FIELD_NUMBER: _ClassVar[int] - SESSION_ID_FIELD_NUMBER: _ClassVar[int] - RUN_ID_FIELD_NUMBER: _ClassVar[int] - PARENT_RUN_ID_FIELD_NUMBER: _ClassVar[int] - TIMESTAMP_FIELD_NUMBER: _ClassVar[int] - PAIR_TYPE_FIELD_NUMBER: _ClassVar[int] - AGENT_FIELD_NUMBER: _ClassVar[int] - PARENT_FIELD_NUMBER: _ClassVar[int] - LLM_FIELD_NUMBER: _ClassVar[int] - TOOL_FIELD_NUMBER: _ClassVar[int] - METADATA_JSON_FIELD_NUMBER: _ClassVar[int] - CONNECTION_ID_FIELD_NUMBER: _ClassVar[int] - SOURCE_FIELD_NUMBER: _ClassVar[int] - event_id: str - invocation_id: str - session_id: str - run_id: str - parent_run_id: str - timestamp: str - pair_type: PairType - agent: AgentContext - parent: AgentContext - llm: LlmPairData - tool: ToolPairData - metadata_json: bytes - connection_id: str - source: str - def __init__(self, event_id: _Optional[str] = ..., invocation_id: _Optional[str] = ..., session_id: _Optional[str] = ..., run_id: _Optional[str] = ..., parent_run_id: _Optional[str] = ..., timestamp: _Optional[str] = ..., pair_type: _Optional[_Union[PairType, str]] = ..., agent: _Optional[_Union[AgentContext, _Mapping]] = ..., parent: _Optional[_Union[AgentContext, _Mapping]] = ..., llm: _Optional[_Union[LlmPairData, _Mapping]] = ..., tool: _Optional[_Union[ToolPairData, _Mapping]] = ..., metadata_json: _Optional[bytes] = ..., connection_id: _Optional[str] = ..., source: _Optional[str] = ...) -> None: ... + """PairedEvent is a completed event pair with agent context attached.""" + + DESCRIPTOR: _descriptor.Descriptor + + EVENT_ID_FIELD_NUMBER: _builtins.int + INVOCATION_ID_FIELD_NUMBER: _builtins.int + SESSION_ID_FIELD_NUMBER: _builtins.int + RUN_ID_FIELD_NUMBER: _builtins.int + PARENT_RUN_ID_FIELD_NUMBER: _builtins.int + TIMESTAMP_FIELD_NUMBER: _builtins.int + PAIR_TYPE_FIELD_NUMBER: _builtins.int + AGENT_FIELD_NUMBER: _builtins.int + PARENT_FIELD_NUMBER: _builtins.int + LLM_FIELD_NUMBER: _builtins.int + TOOL_FIELD_NUMBER: _builtins.int + CONNECTION_ID_FIELD_NUMBER: _builtins.int + METADATA_JSON_FIELD_NUMBER: _builtins.int + SOURCE_FIELD_NUMBER: _builtins.int + event_id: _builtins.str + """Unique event identifier.""" + invocation_id: _builtins.str + """Groups all events for one user-prompted invocation of the top-level graph.""" + session_id: _builtins.str + """Session identifier.""" + run_id: _builtins.str + """Framework run ID for this pair (LLM run_id for LLM pairs, tool run_id for tool pairs).""" + parent_run_id: _builtins.str + """Parent run ID. For tool pairs this points at the producing LLM's run_id + and is the key used for block-mode verdict correlation. + """ + timestamp: _builtins.str + """ISO 8601 timestamp of the pair's completion (end event).""" + pair_type: Global___PairType.ValueType + """Whether this is an LLM pair or a tool pair.""" + connection_id: _builtins.str + """Per-connection routing id (mirrors SessionLogin.connection_id). When set, + the server routes this connection's verdict on connection_id instead of + session_id, so concurrent connections sharing one session_id (parallel + Claude Code tool-call hooks) do not evict each other. + """ + metadata_json: _builtins.bytes + """Escape hatch for framework metadata not modelled as first-class fields + (e.g. LangGraph checkpoint_ns, arbitrary tags). + """ + source: _builtins.str + @_builtins.property + def agent(self) -> Global___AgentContext: + """The agent that produced this pair.""" + + @_builtins.property + def parent(self) -> Global___AgentContext: + """The agent that delegated to this one. agent_id == "" means top-level.""" + + @_builtins.property + def llm(self) -> Global___LlmPairData: + """Payload for PAIR_TYPE_LLM.""" + + @_builtins.property + def tool(self) -> Global___ToolPairData: + """Payload for PAIR_TYPE_TOOL.""" + + def __init__( + self, + *, + event_id: _builtins.str = ..., + invocation_id: _builtins.str = ..., + session_id: _builtins.str = ..., + run_id: _builtins.str = ..., + parent_run_id: _builtins.str = ..., + timestamp: _builtins.str = ..., + pair_type: Global___PairType.ValueType = ..., + agent: Global___AgentContext | None = ..., + parent: Global___AgentContext | None = ..., + llm: Global___LlmPairData | None = ..., + tool: Global___ToolPairData | None = ..., + connection_id: _builtins.str = ..., + metadata_json: _builtins.bytes = ..., + source: _builtins.str = ..., + ) -> None: ... + _HasFieldArgType: _TypeAlias = _typing.Literal["agent", b"agent", "data", b"data", "llm", b"llm", "parent", b"parent", "tool", b"tool"] # noqa: Y015 + def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["agent", b"agent", "connection_id", b"connection_id", "data", b"data", "event_id", b"event_id", "invocation_id", b"invocation_id", "llm", b"llm", "metadata_json", b"metadata_json", "pair_type", b"pair_type", "parent", b"parent", "parent_run_id", b"parent_run_id", "run_id", b"run_id", "session_id", b"session_id", "source", b"source", "timestamp", b"timestamp", "tool", b"tool"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + _WhichOneofReturnType_data: _TypeAlias = _typing.Literal["llm", "tool"] # noqa: Y015 + _WhichOneofArgType_data: _TypeAlias = _typing.Literal["data", b"data"] # noqa: Y015 + def WhichOneof(self, oneof_group: _WhichOneofArgType_data) -> _WhichOneofReturnType_data | None: ... +Global___PairedEvent: _TypeAlias = PairedEvent # noqa: Y015 + +@_typing.final class PairedEventBatch(_message.Message): - __slots__ = ("events",) - EVENTS_FIELD_NUMBER: _ClassVar[int] - events: _containers.RepeatedCompositeFieldContainer[PairedEvent] - def __init__(self, events: _Optional[_Iterable[_Union[PairedEvent, _Mapping]]] = ...) -> None: ... + """PairedEventBatch wraps multiple paired events for batched WebSocket frames.""" + + DESCRIPTOR: _descriptor.Descriptor + + EVENTS_FIELD_NUMBER: _builtins.int + @_builtins.property + def events(self) -> _containers.RepeatedCompositeFieldContainer[Global___PairedEvent]: ... + def __init__( + self, + *, + events: _abc.Iterable[Global___PairedEvent] | None = ..., + ) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["events", b"events"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... +Global___PairedEventBatch: _TypeAlias = PairedEventBatch # noqa: Y015 + +@_typing.final class McpServer(_message.Message): - __slots__ = ("name", "transport", "endpoint") - NAME_FIELD_NUMBER: _ClassVar[int] - TRANSPORT_FIELD_NUMBER: _ClassVar[int] - ENDPOINT_FIELD_NUMBER: _ClassVar[int] - name: str - transport: str - endpoint: str - def __init__(self, name: _Optional[str] = ..., transport: _Optional[str] = ..., endpoint: _Optional[str] = ...) -> None: ... + """McpServer is one MCP server the SDK observed at startup. Mirrors the + SDK-side McpServer dataclass. + """ + + DESCRIPTOR: _descriptor.Descriptor + + NAME_FIELD_NUMBER: _builtins.int + TRANSPORT_FIELD_NUMBER: _builtins.int + ENDPOINT_FIELD_NUMBER: _builtins.int + name: _builtins.str + """Server identifier. Adapter-layer captures use the connection key + from MultiServerMCPClient.connections; raw-transport captures + synthesise ":". + """ + transport: _builtins.str + """One of "stdio", "sse", "streamable_http", "websocket", or "unknown".""" + endpoint: _builtins.str + """URL for SSE/HTTP/WebSocket transports, or the joined command line + for stdio. Empty string when neither is available. + """ + def __init__( + self, + *, + name: _builtins.str = ..., + transport: _builtins.str = ..., + endpoint: _builtins.str = ..., + ) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["endpoint", b"endpoint", "name", b"name", "transport", b"transport"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... +Global___McpServer: _TypeAlias = McpServer # noqa: Y015 + +@_typing.final class McpInventory(_message.Message): - __slots__ = ("servers",) - SERVERS_FIELD_NUMBER: _ClassVar[int] - servers: _containers.RepeatedCompositeFieldContainer[McpServer] - def __init__(self, servers: _Optional[_Iterable[_Union[McpServer, _Mapping]]] = ...) -> None: ... + """McpInventory is the SDK's one-shot dump of its connected MCP servers. + Sent once per WS session right after the SDK login. The server replaces + the session's entire MCP-server list on receipt, no diff/merge. + """ + + DESCRIPTOR: _descriptor.Descriptor + + SERVERS_FIELD_NUMBER: _builtins.int + @_builtins.property + def servers(self) -> _containers.RepeatedCompositeFieldContainer[Global___McpServer]: ... + def __init__( + self, + *, + servers: _abc.Iterable[Global___McpServer] | None = ..., + ) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["servers", b"servers"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... +Global___McpInventory: _TypeAlias = McpInventory # noqa: Y015 + +@_typing.final class LLMStack(_message.Message): - __slots__ = ("provider", "model") - PROVIDER_FIELD_NUMBER: _ClassVar[int] - MODEL_FIELD_NUMBER: _ClassVar[int] - provider: str - model: str - def __init__(self, provider: _Optional[str] = ..., model: _Optional[str] = ...) -> None: ... + """LLMStack identifies the LLM provider and model for a session.""" + + DESCRIPTOR: _descriptor.Descriptor + PROVIDER_FIELD_NUMBER: _builtins.int + MODEL_FIELD_NUMBER: _builtins.int + provider: _builtins.str + """Provider name (e.g. "anthropic", "openai").""" + model: _builtins.str + """Model identifier (e.g. "claude-sonnet-4-20250514").""" + def __init__( + self, + *, + provider: _builtins.str = ..., + model: _builtins.str = ..., + ) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["model", b"model", "provider", b"provider"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + +Global___LLMStack: _TypeAlias = LLMStack # noqa: Y015 + +@_typing.final class SessionLogin(_message.Message): - __slots__ = ("session_id", "llm_stack", "schema_version", "source", "connection_id") - SESSION_ID_FIELD_NUMBER: _ClassVar[int] - LLM_STACK_FIELD_NUMBER: _ClassVar[int] - SCHEMA_VERSION_FIELD_NUMBER: _ClassVar[int] - SOURCE_FIELD_NUMBER: _ClassVar[int] - CONNECTION_ID_FIELD_NUMBER: _ClassVar[int] - session_id: str - llm_stack: LLMStack - schema_version: int - source: str - connection_id: str - def __init__(self, session_id: _Optional[str] = ..., llm_stack: _Optional[_Union[LLMStack, _Mapping]] = ..., schema_version: _Optional[int] = ..., source: _Optional[str] = ..., connection_id: _Optional[str] = ...) -> None: ... + """SessionLogin is sent as the first frame after WebSocket upgrade.""" + + DESCRIPTOR: _descriptor.Descriptor + + SESSION_ID_FIELD_NUMBER: _builtins.int + LLM_STACK_FIELD_NUMBER: _builtins.int + SCHEMA_VERSION_FIELD_NUMBER: _builtins.int + SOURCE_FIELD_NUMBER: _builtins.int + CONNECTION_ID_FIELD_NUMBER: _builtins.int + session_id: _builtins.str + """Session identifier.""" + schema_version: _builtins.int + """Wire schema version the client is speaking. Server rejects unknown values.""" + source: _builtins.str + """SDK / integration that produced this session (e.g. "claude-code"); the + server branches HITL behavior on it. Empty for legacy SDKs. + """ + connection_id: _builtins.str + """Optional per-connection routing id, unique per live WebSocket connection. + When set, the server routes this connection's verdicts on connection_id + instead of session_id (and keys the single-owner guard on it), so multiple + concurrent connections sharing one session_id (parallel Claude Code + tool-call hooks) no longer evict each other. + """ + @_builtins.property + def llm_stack(self) -> Global___LLMStack: + """LLM stack information for this session.""" + + def __init__( + self, + *, + session_id: _builtins.str = ..., + llm_stack: Global___LLMStack | None = ..., + schema_version: _builtins.int = ..., + source: _builtins.str = ..., + connection_id: _builtins.str = ..., + ) -> None: ... + _HasFieldArgType: _TypeAlias = _typing.Literal["llm_stack", b"llm_stack"] # noqa: Y015 + def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["connection_id", b"connection_id", "llm_stack", b"llm_stack", "schema_version", b"schema_version", "session_id", b"session_id", "source", b"source"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + +Global___SessionLogin: _TypeAlias = SessionLogin # noqa: Y015 +@_typing.final class ClientFrame(_message.Message): - __slots__ = ("login", "paired_batch", "mcp_inventory") - LOGIN_FIELD_NUMBER: _ClassVar[int] - PAIRED_BATCH_FIELD_NUMBER: _ClassVar[int] - MCP_INVENTORY_FIELD_NUMBER: _ClassVar[int] - login: SessionLogin - paired_batch: PairedEventBatch - mcp_inventory: McpInventory - def __init__(self, login: _Optional[_Union[SessionLogin, _Mapping]] = ..., paired_batch: _Optional[_Union[PairedEventBatch, _Mapping]] = ..., mcp_inventory: _Optional[_Union[McpInventory, _Mapping]] = ...) -> None: ... + """ClientFrame is the top-level envelope for all client WebSocket messages.""" + + DESCRIPTOR: _descriptor.Descriptor + + LOGIN_FIELD_NUMBER: _builtins.int + PAIRED_BATCH_FIELD_NUMBER: _builtins.int + MCP_INVENTORY_FIELD_NUMBER: _builtins.int + @_builtins.property + def login(self) -> Global___SessionLogin: + """Session login (must be first frame).""" + + @_builtins.property + def paired_batch(self) -> Global___PairedEventBatch: + """Paired event batch (requires prior login).""" + + @_builtins.property + def mcp_inventory(self) -> Global___McpInventory: + """One-shot MCP-server inventory dump from the SDK after login.""" + + def __init__( + self, + *, + login: Global___SessionLogin | None = ..., + paired_batch: Global___PairedEventBatch | None = ..., + mcp_inventory: Global___McpInventory | None = ..., + ) -> None: ... + _HasFieldArgType: _TypeAlias = _typing.Literal["frame", b"frame", "login", b"login", "mcp_inventory", b"mcp_inventory", "paired_batch", b"paired_batch"] # noqa: Y015 + def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["frame", b"frame", "login", b"login", "mcp_inventory", b"mcp_inventory", "paired_batch", b"paired_batch"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + _WhichOneofReturnType_frame: _TypeAlias = _typing.Literal["login", "paired_batch", "mcp_inventory"] # noqa: Y015 + _WhichOneofArgType_frame: _TypeAlias = _typing.Literal["frame", b"frame"] # noqa: Y015 + def WhichOneof(self, oneof_group: _WhichOneofArgType_frame) -> _WhichOneofReturnType_frame | None: ... +Global___ClientFrame: _TypeAlias = ClientFrame # noqa: Y015 + +@_typing.final class PolicySnapshot(_message.Message): - __slots__ = ("mode", "policy_m0", "policy_m2", "policy_m3", "policy_m4") - MODE_FIELD_NUMBER: _ClassVar[int] - POLICY_M0_FIELD_NUMBER: _ClassVar[int] - POLICY_M2_FIELD_NUMBER: _ClassVar[int] - POLICY_M3_FIELD_NUMBER: _ClassVar[int] - POLICY_M4_FIELD_NUMBER: _ClassVar[int] - mode: Mode - policy_m0: bool - policy_m2: bool - policy_m3: bool - policy_m4: bool - def __init__(self, mode: _Optional[_Union[Mode, str]] = ..., policy_m0: bool = ..., policy_m2: bool = ..., policy_m3: bool = ..., policy_m4: bool = ...) -> None: ... + """PolicySnapshot is the org's effective execution-mode policy at the moment + a verdict was decided. Attached by the server to every Verdict it sends + so the SDK can apply user-configured behaviour (halt vs continue, + review-pending vs proceed) without an extra round-trip to fetch policy. + + Per-MAD-code booleans say whether the active mode's behaviour fires on + that code. False means "treat this code as silent regardless of mode". + fail_closed_on_classifier_error controls ERROR verdicts and BLOCK-mode + SDK verdict timeouts. The default false value preserves fail-open + availability when talking to older backends. + """ + + DESCRIPTOR: _descriptor.Descriptor + MODE_FIELD_NUMBER: _builtins.int + POLICY_M0_FIELD_NUMBER: _builtins.int + POLICY_M2_FIELD_NUMBER: _builtins.int + POLICY_M3_FIELD_NUMBER: _builtins.int + POLICY_M4_FIELD_NUMBER: _builtins.int + FAIL_CLOSED_ON_CLASSIFIER_ERROR_FIELD_NUMBER: _builtins.int + mode: Global___Mode.ValueType + policy_m0: _builtins.bool + policy_m2: _builtins.bool + policy_m3: _builtins.bool + policy_m4: _builtins.bool + fail_closed_on_classifier_error: _builtins.bool + def __init__( + self, + *, + mode: Global___Mode.ValueType = ..., + policy_m0: _builtins.bool = ..., + policy_m2: _builtins.bool = ..., + policy_m3: _builtins.bool = ..., + policy_m4: _builtins.bool = ..., + fail_closed_on_classifier_error: _builtins.bool = ..., + ) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["fail_closed_on_classifier_error", b"fail_closed_on_classifier_error", "mode", b"mode", "policy_m0", b"policy_m0", "policy_m2", b"policy_m2", "policy_m3", b"policy_m3", "policy_m4", b"policy_m4"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + +Global___PolicySnapshot: _TypeAlias = PolicySnapshot # noqa: Y015 + +@_typing.final class HitlResponse(_message.Message): - __slots__ = ("continue_execution",) - CONTINUE_EXECUTION_FIELD_NUMBER: _ClassVar[int] - continue_execution: bool - def __init__(self, continue_execution: bool = ...) -> None: ... + """HitlResponse rides on a Verdict that has been resolved through the + human-in-the-loop review queue. Absent on regular (non-HITL or + out-of-scope) verdicts. + """ + + DESCRIPTOR: _descriptor.Descriptor + CONTINUE_EXECUTION_FIELD_NUMBER: _builtins.int + continue_execution: _builtins.bool + """True if the reviewer approved (agent should continue), false if rejected.""" + def __init__( + self, + *, + continue_execution: _builtins.bool = ..., + ) -> None: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["continue_execution", b"continue_execution"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + +Global___HitlResponse: _TypeAlias = HitlResponse # noqa: Y015 + +@_typing.final class LoginAck(_message.Message): - __slots__ = ("policy", "source") - POLICY_FIELD_NUMBER: _ClassVar[int] - SOURCE_FIELD_NUMBER: _ClassVar[int] - policy: PolicySnapshot - source: str - def __init__(self, policy: _Optional[_Union[PolicySnapshot, _Mapping]] = ..., source: _Optional[str] = ...) -> None: ... + """LoginAck is the server's first frame after a successful SessionLogin. + Carries the org's effective policy so the SDK can apply correct mode + + timeout behaviour from the very first event, before any verdict round-trip. + + Policy snapshot is captured at WS login time. Mid-session policy changes + do NOT propagate to existing sessions, reconnect for new policy to take + effect. + """ + + DESCRIPTOR: _descriptor.Descriptor + + POLICY_FIELD_NUMBER: _builtins.int + @_builtins.property + def policy(self) -> Global___PolicySnapshot: ... + def __init__( + self, + *, + policy: Global___PolicySnapshot | None = ..., + ) -> None: ... + _HasFieldArgType: _TypeAlias = _typing.Literal["policy", b"policy"] # noqa: Y015 + def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["policy", b"policy"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... +Global___LoginAck: _TypeAlias = LoginAck # noqa: Y015 + +@_typing.final class ServerFrame(_message.Message): - __slots__ = ("login_ack", "verdict") - LOGIN_ACK_FIELD_NUMBER: _ClassVar[int] - VERDICT_FIELD_NUMBER: _ClassVar[int] - login_ack: LoginAck - verdict: Verdict - def __init__(self, login_ack: _Optional[_Union[LoginAck, _Mapping]] = ..., verdict: _Optional[_Union[Verdict, _Mapping]] = ...) -> None: ... + """ServerFrame is the top-level envelope for all server→SDK WebSocket + messages. Multiplexes login-ack, verdicts, and any future server-pushed + frame types over the same WebSocket without ambiguity. + """ + + DESCRIPTOR: _descriptor.Descriptor + + LOGIN_ACK_FIELD_NUMBER: _builtins.int + VERDICT_FIELD_NUMBER: _builtins.int + @_builtins.property + def login_ack(self) -> Global___LoginAck: + """Sent exactly once, immediately after a successful SessionLogin.""" + + @_builtins.property + def verdict(self) -> Global___Verdict: + """Sent for every classified event the server forwards to the SDK + (subject to mode/policy gates). + """ + + def __init__( + self, + *, + login_ack: Global___LoginAck | None = ..., + verdict: Global___Verdict | None = ..., + ) -> None: ... + _HasFieldArgType: _TypeAlias = _typing.Literal["frame", b"frame", "login_ack", b"login_ack", "verdict", b"verdict"] # noqa: Y015 + def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["frame", b"frame", "login_ack", b"login_ack", "verdict", b"verdict"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + _WhichOneofReturnType_frame: _TypeAlias = _typing.Literal["login_ack", "verdict"] # noqa: Y015 + _WhichOneofArgType_frame: _TypeAlias = _typing.Literal["frame", b"frame"] # noqa: Y015 + def WhichOneof(self, oneof_group: _WhichOneofArgType_frame) -> _WhichOneofReturnType_frame | None: ... + +Global___ServerFrame: _TypeAlias = ServerFrame # noqa: Y015 +@_typing.final class Verdict(_message.Message): - __slots__ = ("event_id", "session_id", "mad_code", "policy", "hitl") - EVENT_ID_FIELD_NUMBER: _ClassVar[int] - SESSION_ID_FIELD_NUMBER: _ClassVar[int] - MAD_CODE_FIELD_NUMBER: _ClassVar[int] - POLICY_FIELD_NUMBER: _ClassVar[int] - HITL_FIELD_NUMBER: _ClassVar[int] - event_id: str - session_id: str - mad_code: str - policy: PolicySnapshot - hitl: HitlResponse - def __init__(self, event_id: _Optional[str] = ..., session_id: _Optional[str] = ..., mad_code: _Optional[str] = ..., policy: _Optional[_Union[PolicySnapshot, _Mapping]] = ..., hitl: _Optional[_Union[HitlResponse, _Mapping]] = ...) -> None: ... + """Verdict is the classification result for a PairedEvent.""" + + DESCRIPTOR: _descriptor.Descriptor + + EVENT_ID_FIELD_NUMBER: _builtins.int + SESSION_ID_FIELD_NUMBER: _builtins.int + MAD_CODE_FIELD_NUMBER: _builtins.int + POLICY_FIELD_NUMBER: _builtins.int + HITL_FIELD_NUMBER: _builtins.int + STATUS_FIELD_NUMBER: _builtins.int + event_id: _builtins.str + """The event_id of the PairedEvent being classified.""" + session_id: _builtins.str + """Session identifier for routing.""" + mad_code: _builtins.str + """MAD code the classifier returned (e.g. "M0", "M2_C", "M4_a"). + Empty string means no MAD code was produced, such as for a + VerdictStatus.ERROR classifier failure. Benign classifier success + is represented by status OK with mad_code "M0". + """ + status: Global___VerdictStatus.ValueType + """Status of the classifier result. OK means mad_code carries a normal + classifier decision. ERROR means classification did not complete and + mad_code is empty; fail-open/fail-closed behaviour comes from policy. + """ + @_builtins.property + def policy(self) -> Global___PolicySnapshot: + """Org's effective execution-mode policy at the time of this verdict. + Always populated by the server; SDK reads this to decide whether to + halt, pause, or continue. + """ + + @_builtins.property + def hitl(self) -> Global___HitlResponse: + """Present only when this verdict represents a human-in-the-loop review + resolution (approve or reject from the dashboard). Absent on auto- + classified verdicts and on out-of-scope verdicts forwarded immediately. + """ + + def __init__( + self, + *, + event_id: _builtins.str = ..., + session_id: _builtins.str = ..., + mad_code: _builtins.str = ..., + policy: Global___PolicySnapshot | None = ..., + hitl: Global___HitlResponse | None = ..., + status: Global___VerdictStatus.ValueType = ..., + ) -> None: ... + _HasFieldArgType: _TypeAlias = _typing.Literal["hitl", b"hitl", "policy", b"policy"] # noqa: Y015 + def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["event_id", b"event_id", "hitl", b"hitl", "mad_code", b"mad_code", "policy", b"policy", "session_id", b"session_id", "status", b"status"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + +Global___Verdict: _TypeAlias = Verdict # noqa: Y015 diff --git a/sdk/python/adrian/proto/event_pb2.py b/sdk/python/adrian/proto/event_pb2.py index e8f2f50..dadbbbe 100644 --- a/sdk/python/adrian/proto/event_pb2.py +++ b/sdk/python/adrian/proto/event_pb2.py @@ -25,7 +25,7 @@ from .buf.validate import validate_pb2 as buf_dot_validate_dot_validate__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x65vent.proto\x12\x12\x61\x64rian.core_api.v1\x1a\x1b\x62uf/validate/validate.proto\",\n\x0b\x43hatMessage\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\";\n\x08ToolCall\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0c\n\x04\x61rgs\x18\x02 \x01(\t\x12\n\n\x02id\x18\x03 \x01(\t\"o\n\nTokenUsage\x12\x1e\n\rprompt_tokens\x18\x01 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\"\n\x11\x63ompletion_tokens\x18\x02 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\x1d\n\x0ctotal_tokens\x18\x03 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\"Q\n\x0c\x41gentContext\x12\x10\n\x08\x61gent_id\x18\x01 \x01(\t\x12\x15\n\rsystem_prompt\x18\x02 \x01(\t\x12\x18\n\x10user_instruction\x18\x03 \x01(\t\"\xc0\x01\n\x0bLlmPairData\x12\r\n\x05model\x18\x01 \x01(\t\x12\x31\n\x08messages\x18\x02 \x03(\x0b\x32\x1f.adrian.core_api.v1.ChatMessage\x12\x0e\n\x06output\x18\x03 \x01(\t\x12\x30\n\ntool_calls\x18\x04 \x03(\x0b\x32\x1c.adrian.core_api.v1.ToolCall\x12-\n\x05usage\x18\x05 \x01(\x0b\x32\x1e.adrian.core_api.v1.TokenUsage\"_\n\x0cToolPairData\x12\x1a\n\ttool_name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x14\n\x0ctool_call_id\x18\x02 \x01(\t\x12\r\n\x05input\x18\x03 \x01(\t\x12\x0e\n\x06output\x18\x04 \x01(\t\"\xc3\x03\n\x0bPairedEvent\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x15\n\rinvocation_id\x18\x02 \x01(\t\x12\x1b\n\nsession_id\x18\x03 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0e\n\x06run_id\x18\x04 \x01(\t\x12\x15\n\rparent_run_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x37\n\tpair_type\x18\x07 \x01(\x0e\x32\x1c.adrian.core_api.v1.PairTypeB\x06\xbaH\x03\xc8\x01\x01\x12/\n\x05\x61gent\x18\x08 \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12\x30\n\x06parent\x18\t \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12.\n\x03llm\x18\n \x01(\x0b\x32\x1f.adrian.core_api.v1.LlmPairDataH\x00\x12\x30\n\x04tool\x18\x0b \x01(\x0b\x32 .adrian.core_api.v1.ToolPairDataH\x00\x12\x15\n\rmetadata_json\x18\x14 \x01(\x0c\x12\x0e\n\x06source\x18\x15 \x01(\tB\x06\n\x04\x64\x61ta\"C\n\x10PairedEventBatch\x12/\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x1f.adrian.core_api.v1.PairedEvent\"G\n\tMcpServer\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x11\n\ttransport\x18\x02 \x01(\t\x12\x10\n\x08\x65ndpoint\x18\x03 \x01(\t\">\n\x0cMcpInventory\x12.\n\x07servers\x18\x01 \x03(\x0b\x32\x1d.adrian.core_api.v1.McpServer\"+\n\x08LLMStack\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\r\n\x05model\x18\x02 \x01(\t\"\x96\x01\n\x0cSessionLogin\x12\x1b\n\nsession_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12/\n\tllm_stack\x18\x02 \x01(\x0b\x32\x1c.adrian.core_api.v1.LLMStack\x12\x16\n\x0eschema_version\x18\x04 \x01(\r\x12\x0e\n\x06source\x18\x05 \x01(\tJ\x04\x08\x03\x10\x04R\nblock_mode\"\xcf\x01\n\x0b\x43lientFrame\x12\x31\n\x05login\x18\x01 \x01(\x0b\x32 .adrian.core_api.v1.SessionLoginH\x00\x12<\n\x0cpaired_batch\x18\x03 \x01(\x0b\x32$.adrian.core_api.v1.PairedEventBatchH\x00\x12\x39\n\rmcp_inventory\x18\x04 \x01(\x0b\x32 .adrian.core_api.v1.McpInventoryH\x00\x42\x07\n\x05\x66rameJ\x04\x08\x02\x10\x03R\x05\x62\x61tch\"\xad\x01\n\x0ePolicySnapshot\x12&\n\x04mode\x18\x01 \x01(\x0e\x32\x18.adrian.core_api.v1.Mode\x12\x11\n\tpolicy_m0\x18\x02 \x01(\x08\x12\x11\n\tpolicy_m2\x18\x03 \x01(\x08\x12\x11\n\tpolicy_m3\x18\x04 \x01(\x08\x12\x11\n\tpolicy_m4\x18\x05 \x01(\x08\x12\'\n\x1f\x66\x61il_closed_on_classifier_error\x18\x06 \x01(\x08\"*\n\x0cHitlResponse\x12\x1a\n\x12\x63ontinue_execution\x18\x01 \x01(\x08\">\n\x08LoginAck\x12\x32\n\x06policy\x18\x01 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\"y\n\x0bServerFrame\x12\x31\n\tlogin_ack\x18\x01 \x01(\x0b\x32\x1c.adrian.core_api.v1.LoginAckH\x00\x12.\n\x07verdict\x18\x02 \x01(\x0b\x32\x1b.adrian.core_api.v1.VerdictH\x00\x42\x07\n\x05\x66rame\"\x90\x02\n\x07Verdict\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x1b\n\nsession_id\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x10\n\x08mad_code\x18\x04 \x01(\t\x12\x32\n\x06policy\x18\x06 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\x12.\n\x04hitl\x18\x07 \x01(\x0b\x32 .adrian.core_api.v1.HitlResponse\x12\x31\n\x06status\x18\x08 \x01(\x0e\x32!.adrian.core_api.v1.VerdictStatusJ\x04\x08\x03\x10\x04J\x04\x08\x05\x10\x06R\x0e\x63lassificationR\x08\x65scalate*L\n\x08PairType\x12\x19\n\x15PAIR_TYPE_UNSPECIFIED\x10\x00\x12\x11\n\rPAIR_TYPE_LLM\x10\x01\x12\x12\n\x0ePAIR_TYPE_TOOL\x10\x02*K\n\x04Mode\x12\x14\n\x10MODE_UNSPECIFIED\x10\x00\x12\x0e\n\nMODE_ALERT\x10\x01\x12\r\n\tMODE_HITL\x10\x02\x12\x0e\n\nMODE_BLOCK\x10\x03*`\n\rVerdictStatus\x12\x1e\n\x1aVERDICT_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11VERDICT_STATUS_OK\x10\x01\x12\x18\n\x14VERDICT_STATUS_ERROR\x10\x02\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x65vent.proto\x12\x12\x61\x64rian.core_api.v1\x1a\x1b\x62uf/validate/validate.proto\",\n\x0b\x43hatMessage\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\";\n\x08ToolCall\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0c\n\x04\x61rgs\x18\x02 \x01(\t\x12\n\n\x02id\x18\x03 \x01(\t\"o\n\nTokenUsage\x12\x1e\n\rprompt_tokens\x18\x01 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\"\n\x11\x63ompletion_tokens\x18\x02 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\x1d\n\x0ctotal_tokens\x18\x03 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\"Q\n\x0c\x41gentContext\x12\x10\n\x08\x61gent_id\x18\x01 \x01(\t\x12\x15\n\rsystem_prompt\x18\x02 \x01(\t\x12\x18\n\x10user_instruction\x18\x03 \x01(\t\"\xc0\x01\n\x0bLlmPairData\x12\r\n\x05model\x18\x01 \x01(\t\x12\x31\n\x08messages\x18\x02 \x03(\x0b\x32\x1f.adrian.core_api.v1.ChatMessage\x12\x0e\n\x06output\x18\x03 \x01(\t\x12\x30\n\ntool_calls\x18\x04 \x03(\x0b\x32\x1c.adrian.core_api.v1.ToolCall\x12-\n\x05usage\x18\x05 \x01(\x0b\x32\x1e.adrian.core_api.v1.TokenUsage\"_\n\x0cToolPairData\x12\x1a\n\ttool_name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x14\n\x0ctool_call_id\x18\x02 \x01(\t\x12\r\n\x05input\x18\x03 \x01(\t\x12\x0e\n\x06output\x18\x04 \x01(\t\"\xda\x03\n\x0bPairedEvent\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x15\n\rinvocation_id\x18\x02 \x01(\t\x12\x1b\n\nsession_id\x18\x03 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0e\n\x06run_id\x18\x04 \x01(\t\x12\x15\n\rparent_run_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x37\n\tpair_type\x18\x07 \x01(\x0e\x32\x1c.adrian.core_api.v1.PairTypeB\x06\xbaH\x03\xc8\x01\x01\x12/\n\x05\x61gent\x18\x08 \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12\x30\n\x06parent\x18\t \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12.\n\x03llm\x18\n \x01(\x0b\x32\x1f.adrian.core_api.v1.LlmPairDataH\x00\x12\x30\n\x04tool\x18\x0b \x01(\x0b\x32 .adrian.core_api.v1.ToolPairDataH\x00\x12\x15\n\rconnection_id\x18\x0c \x01(\t\x12\x15\n\rmetadata_json\x18\x14 \x01(\x0c\x12\x0e\n\x06source\x18\x15 \x01(\tB\x06\n\x04\x64\x61ta\"C\n\x10PairedEventBatch\x12/\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x1f.adrian.core_api.v1.PairedEvent\"G\n\tMcpServer\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x11\n\ttransport\x18\x02 \x01(\t\x12\x10\n\x08\x65ndpoint\x18\x03 \x01(\t\">\n\x0cMcpInventory\x12.\n\x07servers\x18\x01 \x03(\x0b\x32\x1d.adrian.core_api.v1.McpServer\"+\n\x08LLMStack\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\r\n\x05model\x18\x02 \x01(\t\"\xad\x01\n\x0cSessionLogin\x12\x1b\n\nsession_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12/\n\tllm_stack\x18\x02 \x01(\x0b\x32\x1c.adrian.core_api.v1.LLMStack\x12\x16\n\x0eschema_version\x18\x04 \x01(\r\x12\x0e\n\x06source\x18\x05 \x01(\t\x12\x15\n\rconnection_id\x18\x06 \x01(\tJ\x04\x08\x03\x10\x04R\nblock_mode\"\xcf\x01\n\x0b\x43lientFrame\x12\x31\n\x05login\x18\x01 \x01(\x0b\x32 .adrian.core_api.v1.SessionLoginH\x00\x12<\n\x0cpaired_batch\x18\x03 \x01(\x0b\x32$.adrian.core_api.v1.PairedEventBatchH\x00\x12\x39\n\rmcp_inventory\x18\x04 \x01(\x0b\x32 .adrian.core_api.v1.McpInventoryH\x00\x42\x07\n\x05\x66rameJ\x04\x08\x02\x10\x03R\x05\x62\x61tch\"\xad\x01\n\x0ePolicySnapshot\x12&\n\x04mode\x18\x01 \x01(\x0e\x32\x18.adrian.core_api.v1.Mode\x12\x11\n\tpolicy_m0\x18\x02 \x01(\x08\x12\x11\n\tpolicy_m2\x18\x03 \x01(\x08\x12\x11\n\tpolicy_m3\x18\x04 \x01(\x08\x12\x11\n\tpolicy_m4\x18\x05 \x01(\x08\x12\'\n\x1f\x66\x61il_closed_on_classifier_error\x18\x06 \x01(\x08\"*\n\x0cHitlResponse\x12\x1a\n\x12\x63ontinue_execution\x18\x01 \x01(\x08\">\n\x08LoginAck\x12\x32\n\x06policy\x18\x01 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\"y\n\x0bServerFrame\x12\x31\n\tlogin_ack\x18\x01 \x01(\x0b\x32\x1c.adrian.core_api.v1.LoginAckH\x00\x12.\n\x07verdict\x18\x02 \x01(\x0b\x32\x1b.adrian.core_api.v1.VerdictH\x00\x42\x07\n\x05\x66rame\"\x90\x02\n\x07Verdict\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x1b\n\nsession_id\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x10\n\x08mad_code\x18\x04 \x01(\t\x12\x32\n\x06policy\x18\x06 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\x12.\n\x04hitl\x18\x07 \x01(\x0b\x32 .adrian.core_api.v1.HitlResponse\x12\x31\n\x06status\x18\x08 \x01(\x0e\x32!.adrian.core_api.v1.VerdictStatusJ\x04\x08\x03\x10\x04J\x04\x08\x05\x10\x06R\x0e\x63lassificationR\x08\x65scalate*L\n\x08PairType\x12\x19\n\x15PAIR_TYPE_UNSPECIFIED\x10\x00\x12\x11\n\rPAIR_TYPE_LLM\x10\x01\x12\x12\n\x0ePAIR_TYPE_TOOL\x10\x02*K\n\x04Mode\x12\x14\n\x10MODE_UNSPECIFIED\x10\x00\x12\x0e\n\nMODE_ALERT\x10\x01\x12\r\n\tMODE_HITL\x10\x02\x12\x0e\n\nMODE_BLOCK\x10\x03*`\n\rVerdictStatus\x12\x1e\n\x1aVERDICT_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11VERDICT_STATUS_OK\x10\x01\x12\x18\n\x14VERDICT_STATUS_ERROR\x10\x02\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -56,12 +56,12 @@ _globals['_VERDICT'].fields_by_name['event_id']._serialized_options = b'\272H\004r\002\020\001' _globals['_VERDICT'].fields_by_name['session_id']._loaded_options = None _globals['_VERDICT'].fields_by_name['session_id']._serialized_options = b'\272H\004r\002\020\001' - _globals['_PAIRTYPE']._serialized_start=2409 - _globals['_PAIRTYPE']._serialized_end=2485 - _globals['_MODE']._serialized_start=2487 - _globals['_MODE']._serialized_end=2562 - _globals['_VERDICTSTATUS']._serialized_start=2564 - _globals['_VERDICTSTATUS']._serialized_end=2660 + _globals['_PAIRTYPE']._serialized_start=2455 + _globals['_PAIRTYPE']._serialized_end=2531 + _globals['_MODE']._serialized_start=2533 + _globals['_MODE']._serialized_end=2608 + _globals['_VERDICTSTATUS']._serialized_start=2610 + _globals['_VERDICTSTATUS']._serialized_end=2706 _globals['_CHATMESSAGE']._serialized_start=64 _globals['_CHATMESSAGE']._serialized_end=108 _globals['_TOOLCALL']._serialized_start=110 @@ -75,27 +75,27 @@ _globals['_TOOLPAIRDATA']._serialized_start=562 _globals['_TOOLPAIRDATA']._serialized_end=657 _globals['_PAIREDEVENT']._serialized_start=660 - _globals['_PAIREDEVENT']._serialized_end=1111 - _globals['_PAIREDEVENTBATCH']._serialized_start=1113 - _globals['_PAIREDEVENTBATCH']._serialized_end=1180 - _globals['_MCPSERVER']._serialized_start=1182 - _globals['_MCPSERVER']._serialized_end=1253 - _globals['_MCPINVENTORY']._serialized_start=1255 - _globals['_MCPINVENTORY']._serialized_end=1317 - _globals['_LLMSTACK']._serialized_start=1319 - _globals['_LLMSTACK']._serialized_end=1362 - _globals['_SESSIONLOGIN']._serialized_start=1365 - _globals['_SESSIONLOGIN']._serialized_end=1515 - _globals['_CLIENTFRAME']._serialized_start=1518 - _globals['_CLIENTFRAME']._serialized_end=1725 - _globals['_POLICYSNAPSHOT']._serialized_start=1728 - _globals['_POLICYSNAPSHOT']._serialized_end=1901 - _globals['_HITLRESPONSE']._serialized_start=1903 - _globals['_HITLRESPONSE']._serialized_end=1945 - _globals['_LOGINACK']._serialized_start=1947 - _globals['_LOGINACK']._serialized_end=2009 - _globals['_SERVERFRAME']._serialized_start=2011 - _globals['_SERVERFRAME']._serialized_end=2132 - _globals['_VERDICT']._serialized_start=2135 - _globals['_VERDICT']._serialized_end=2407 + _globals['_PAIREDEVENT']._serialized_end=1134 + _globals['_PAIREDEVENTBATCH']._serialized_start=1136 + _globals['_PAIREDEVENTBATCH']._serialized_end=1203 + _globals['_MCPSERVER']._serialized_start=1205 + _globals['_MCPSERVER']._serialized_end=1276 + _globals['_MCPINVENTORY']._serialized_start=1278 + _globals['_MCPINVENTORY']._serialized_end=1340 + _globals['_LLMSTACK']._serialized_start=1342 + _globals['_LLMSTACK']._serialized_end=1385 + _globals['_SESSIONLOGIN']._serialized_start=1388 + _globals['_SESSIONLOGIN']._serialized_end=1561 + _globals['_CLIENTFRAME']._serialized_start=1564 + _globals['_CLIENTFRAME']._serialized_end=1771 + _globals['_POLICYSNAPSHOT']._serialized_start=1774 + _globals['_POLICYSNAPSHOT']._serialized_end=1947 + _globals['_HITLRESPONSE']._serialized_start=1949 + _globals['_HITLRESPONSE']._serialized_end=1991 + _globals['_LOGINACK']._serialized_start=1993 + _globals['_LOGINACK']._serialized_end=2055 + _globals['_SERVERFRAME']._serialized_start=2057 + _globals['_SERVERFRAME']._serialized_end=2178 + _globals['_VERDICT']._serialized_start=2181 + _globals['_VERDICT']._serialized_end=2453 # @@protoc_insertion_point(module_scope) diff --git a/sdk/python/adrian/proto/event_pb2.pyi b/sdk/python/adrian/proto/event_pb2.pyi index 1094342..a99a39c 100644 --- a/sdk/python/adrian/proto/event_pb2.pyi +++ b/sdk/python/adrian/proto/event_pb2.pyi @@ -1,6 +1,8 @@ """ @generated by mypy-protobuf. Do not edit manually! isort:skip_file +SPDX-License-Identifier: Apache-2.0 +Copyright (c) 2026 SecureAgentics """ from collections import abc as _abc @@ -12,10 +14,10 @@ import builtins as _builtins import sys import typing as _typing -if sys.version_info >= (3, 11): - from typing import TypeAlias as _TypeAlias, Never as _Never +if sys.version_info >= (3, 10): + from typing import TypeAlias as _TypeAlias else: - from typing_extensions import TypeAlias as _TypeAlias, Never as _Never + from typing_extensions import TypeAlias as _TypeAlias DESCRIPTOR: _descriptor.FileDescriptor @@ -114,11 +116,8 @@ class ChatMessage(_message.Message): role: _builtins.str = ..., content: _builtins.str = ..., ) -> None: ... - _HasFieldArgType: _TypeAlias = _Never # noqa: Y015 - def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["content", b"content", "role", b"role"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___ChatMessage: _TypeAlias = ChatMessage # noqa: Y015 @@ -144,11 +143,8 @@ class ToolCall(_message.Message): args: _builtins.str = ..., id: _builtins.str = ..., ) -> None: ... - _HasFieldArgType: _TypeAlias = _Never # noqa: Y015 - def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["args", b"args", "id", b"id", "name", b"name"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___ToolCall: _TypeAlias = ToolCall # noqa: Y015 @@ -174,11 +170,8 @@ class TokenUsage(_message.Message): completion_tokens: _builtins.int = ..., total_tokens: _builtins.int = ..., ) -> None: ... - _HasFieldArgType: _TypeAlias = _Never # noqa: Y015 - def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["completion_tokens", b"completion_tokens", "prompt_tokens", b"prompt_tokens", "total_tokens", b"total_tokens"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___TokenUsage: _TypeAlias = TokenUsage # noqa: Y015 @@ -209,11 +202,8 @@ class AgentContext(_message.Message): system_prompt: _builtins.str = ..., user_instruction: _builtins.str = ..., ) -> None: ... - _HasFieldArgType: _TypeAlias = _Never # noqa: Y015 - def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["agent_id", b"agent_id", "system_prompt", b"system_prompt", "user_instruction", b"user_instruction"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___AgentContext: _TypeAlias = AgentContext # noqa: Y015 @@ -259,7 +249,6 @@ class LlmPairData(_message.Message): def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["messages", b"messages", "model", b"model", "output", b"output", "tool_calls", b"tool_calls", "usage", b"usage"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___LlmPairData: _TypeAlias = LlmPairData # noqa: Y015 @@ -293,11 +282,8 @@ class ToolPairData(_message.Message): input: _builtins.str = ..., output: _builtins.str = ..., ) -> None: ... - _HasFieldArgType: _TypeAlias = _Never # noqa: Y015 - def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["input", b"input", "output", b"output", "tool_call_id", b"tool_call_id", "tool_name", b"tool_name"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___ToolPairData: _TypeAlias = ToolPairData # noqa: Y015 @@ -318,6 +304,7 @@ class PairedEvent(_message.Message): PARENT_FIELD_NUMBER: _builtins.int LLM_FIELD_NUMBER: _builtins.int TOOL_FIELD_NUMBER: _builtins.int + CONNECTION_ID_FIELD_NUMBER: _builtins.int METADATA_JSON_FIELD_NUMBER: _builtins.int SOURCE_FIELD_NUMBER: _builtins.int event_id: _builtins.str @@ -336,6 +323,12 @@ class PairedEvent(_message.Message): """ISO 8601 timestamp of the pair's completion (end event).""" pair_type: Global___PairType.ValueType """Whether this is an LLM pair or a tool pair.""" + connection_id: _builtins.str + """Per-connection routing id (mirrors SessionLogin.connection_id). When set, + the server routes this connection's verdict on connection_id instead of + session_id, so concurrent connections sharing one session_id (parallel + Claude Code tool-call hooks) do not evict each other. + """ metadata_json: _builtins.bytes """Escape hatch for framework metadata not modelled as first-class fields (e.g. LangGraph checkpoint_ns, arbitrary tags). @@ -371,12 +364,13 @@ class PairedEvent(_message.Message): parent: Global___AgentContext | None = ..., llm: Global___LlmPairData | None = ..., tool: Global___ToolPairData | None = ..., + connection_id: _builtins.str = ..., metadata_json: _builtins.bytes = ..., source: _builtins.str = ..., ) -> None: ... _HasFieldArgType: _TypeAlias = _typing.Literal["agent", b"agent", "data", b"data", "llm", b"llm", "parent", b"parent", "tool", b"tool"] # noqa: Y015 def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... - _ClearFieldArgType: _TypeAlias = _typing.Literal["agent", b"agent", "data", b"data", "event_id", b"event_id", "invocation_id", b"invocation_id", "llm", b"llm", "metadata_json", b"metadata_json", "pair_type", b"pair_type", "parent", b"parent", "parent_run_id", b"parent_run_id", "run_id", b"run_id", "session_id", b"session_id", "source", b"source", "timestamp", b"timestamp", "tool", b"tool"] # noqa: Y015 + _ClearFieldArgType: _TypeAlias = _typing.Literal["agent", b"agent", "connection_id", b"connection_id", "data", b"data", "event_id", b"event_id", "invocation_id", b"invocation_id", "llm", b"llm", "metadata_json", b"metadata_json", "pair_type", b"pair_type", "parent", b"parent", "parent_run_id", b"parent_run_id", "run_id", b"run_id", "session_id", b"session_id", "source", b"source", "timestamp", b"timestamp", "tool", b"tool"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... _WhichOneofReturnType_data: _TypeAlias = _typing.Literal["llm", "tool"] # noqa: Y015 _WhichOneofArgType_data: _TypeAlias = _typing.Literal["data", b"data"] # noqa: Y015 @@ -398,11 +392,8 @@ class PairedEventBatch(_message.Message): *, events: _abc.Iterable[Global___PairedEvent] | None = ..., ) -> None: ... - _HasFieldArgType: _TypeAlias = _Never # noqa: Y015 - def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["events", b"events"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___PairedEventBatch: _TypeAlias = PairedEventBatch # noqa: Y015 @@ -435,11 +426,8 @@ class McpServer(_message.Message): transport: _builtins.str = ..., endpoint: _builtins.str = ..., ) -> None: ... - _HasFieldArgType: _TypeAlias = _Never # noqa: Y015 - def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["endpoint", b"endpoint", "name", b"name", "transport", b"transport"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___McpServer: _TypeAlias = McpServer # noqa: Y015 @@ -460,11 +448,8 @@ class McpInventory(_message.Message): *, servers: _abc.Iterable[Global___McpServer] | None = ..., ) -> None: ... - _HasFieldArgType: _TypeAlias = _Never # noqa: Y015 - def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["servers", b"servers"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___McpInventory: _TypeAlias = McpInventory # noqa: Y015 @@ -486,11 +471,8 @@ class LLMStack(_message.Message): provider: _builtins.str = ..., model: _builtins.str = ..., ) -> None: ... - _HasFieldArgType: _TypeAlias = _Never # noqa: Y015 - def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["model", b"model", "provider", b"provider"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___LLMStack: _TypeAlias = LLMStack # noqa: Y015 @@ -504,6 +486,7 @@ class SessionLogin(_message.Message): LLM_STACK_FIELD_NUMBER: _builtins.int SCHEMA_VERSION_FIELD_NUMBER: _builtins.int SOURCE_FIELD_NUMBER: _builtins.int + CONNECTION_ID_FIELD_NUMBER: _builtins.int session_id: _builtins.str """Session identifier.""" schema_version: _builtins.int @@ -512,6 +495,13 @@ class SessionLogin(_message.Message): """SDK / integration that produced this session (e.g. "claude-code"); the server branches HITL behavior on it. Empty for legacy SDKs. """ + connection_id: _builtins.str + """Optional per-connection routing id, unique per live WebSocket connection. + When set, the server routes this connection's verdicts on connection_id + instead of session_id (and keys the single-owner guard on it), so multiple + concurrent connections sharing one session_id (parallel Claude Code + tool-call hooks) no longer evict each other. + """ @_builtins.property def llm_stack(self) -> Global___LLMStack: """LLM stack information for this session.""" @@ -523,12 +513,12 @@ class SessionLogin(_message.Message): llm_stack: Global___LLMStack | None = ..., schema_version: _builtins.int = ..., source: _builtins.str = ..., + connection_id: _builtins.str = ..., ) -> None: ... _HasFieldArgType: _TypeAlias = _typing.Literal["llm_stack", b"llm_stack"] # noqa: Y015 def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... - _ClearFieldArgType: _TypeAlias = _typing.Literal["llm_stack", b"llm_stack", "schema_version", b"schema_version", "session_id", b"session_id", "source", b"source"] # noqa: Y015 + _ClearFieldArgType: _TypeAlias = _typing.Literal["connection_id", b"connection_id", "llm_stack", b"llm_stack", "schema_version", b"schema_version", "session_id", b"session_id", "source", b"source"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___SessionLogin: _TypeAlias = SessionLogin # noqa: Y015 @@ -608,11 +598,8 @@ class PolicySnapshot(_message.Message): policy_m4: _builtins.bool = ..., fail_closed_on_classifier_error: _builtins.bool = ..., ) -> None: ... - _HasFieldArgType: _TypeAlias = _Never # noqa: Y015 - def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["fail_closed_on_classifier_error", b"fail_closed_on_classifier_error", "mode", b"mode", "policy_m0", b"policy_m0", "policy_m2", b"policy_m2", "policy_m3", b"policy_m3", "policy_m4", b"policy_m4"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___PolicySnapshot: _TypeAlias = PolicySnapshot # noqa: Y015 @@ -633,11 +620,8 @@ class HitlResponse(_message.Message): *, continue_execution: _builtins.bool = ..., ) -> None: ... - _HasFieldArgType: _TypeAlias = _Never # noqa: Y015 - def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["continue_execution", b"continue_execution"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___HitlResponse: _TypeAlias = HitlResponse # noqa: Y015 @@ -666,7 +650,6 @@ class LoginAck(_message.Message): def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["policy", b"policy"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___LoginAck: _TypeAlias = LoginAck # noqa: Y015 @@ -762,6 +745,5 @@ class Verdict(_message.Message): def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... _ClearFieldArgType: _TypeAlias = _typing.Literal["event_id", b"event_id", "hitl", b"hitl", "mad_code", b"mad_code", "policy", b"policy", "session_id", b"session_id", "status", b"status"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... - def WhichOneof(self, oneof_group: _Never) -> None: ... Global___Verdict: _TypeAlias = Verdict # noqa: Y015 diff --git a/sdk/python/pyproject.toml b/sdk/python/pyproject.toml index 632d4aa..2081290 100644 --- a/sdk/python/pyproject.toml +++ b/sdk/python/pyproject.toml @@ -51,6 +51,7 @@ dev = [ "ruff==0.15.12", "basedpyright==1.39.3", "pre-commit>=4.6.0", + "mypy-protobuf==5.0.0", "langgraph==1.1.2", "langgraph-prebuilt==1.0.8", "langchain-mcp-adapters>=0.2.2", diff --git a/sdk/python/uv.lock b/sdk/python/uv.lock index 35c6157..1a6da39 100644 --- a/sdk/python/uv.lock +++ b/sdk/python/uv.lock @@ -22,6 +22,7 @@ dev = [ { name = "langchain-mcp-adapters" }, { name = "langgraph" }, { name = "langgraph-prebuilt" }, + { name = "mypy-protobuf" }, { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -38,6 +39,7 @@ requires-dist = [ { name = "langchain-mcp-adapters", marker = "extra == 'dev'", specifier = ">=0.2.2" }, { name = "langgraph", marker = "extra == 'dev'", specifier = "==1.1.2" }, { name = "langgraph-prebuilt", marker = "extra == 'dev'", specifier = "==1.0.8" }, + { name = "mypy-protobuf", marker = "extra == 'dev'", specifier = "==5.0.0" }, { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.6.0" }, { name = "protobuf", specifier = ">=5.29.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" }, @@ -775,6 +777,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/46/f6b4ad632c67ef35209a66127e4bddc95759649dd595f71f13fba11bdf9a/mcp-1.27.0-py3-none-any.whl", hash = "sha256:5ce1fa81614958e267b21fb2aa34e0aea8e2c6ede60d52aba45fd47246b4d741", size = 215967, upload-time = "2026-04-02T14:48:07.24Z" }, ] +[[package]] +name = "mypy-protobuf" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, + { name = "types-protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/48/658827446368bca30a94e545598065587ece9cd09b678d7d2895c37a59d2/mypy_protobuf-5.0.0.tar.gz", hash = "sha256:6fdd1cfdbb4419c713291d800a332d4bba6510dbd1341ed95e0bcc82fcadb6b5", size = 37309, upload-time = "2026-01-13T17:10:12.616Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/b1/ab1e7a49930a8c1d1f7a570bbd4ec7d552ef035acc7aa4b97906e17a34a9/mypy_protobuf-5.0.0-py3-none-any.whl", hash = "sha256:3a7dd753ef3e3b8783a824eb51f07983f62812f9ec066e4fbb1b22d6c5dc36d0", size = 26008, upload-time = "2026-01-13T17:10:11.053Z" }, +] + [[package]] name = "nodeenv" version = "1.10.0" @@ -1413,6 +1428,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, ] +[[package]] +name = "types-protobuf" +version = "7.34.1.20260518" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/59/e2b13b499d15e6720150c4b1a8d91e31fcacf716b432397475b3151ff7e4/types_protobuf-7.34.1.20260518.tar.gz", hash = "sha256:28cfaded25889cb83ebfb63cfb0a43628f0b6f3785767bec17287dc6468795f2", size = 68936, upload-time = "2026-05-18T06:01:47.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/1f/ec5caf72c2e3b688ca3927e0979a04ddad19e1afc4bf1c199bd743e0f419/types_protobuf-7.34.1.20260518-py3-none-any.whl", hash = "sha256:a0a5337413347166439c0e07cbc26c6164d091401c6f01b1dfd8cdb966c4dd8f", size = 85992, upload-time = "2026-05-18T06:01:45.696Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" From 50da3ca0534a9a1ed576adc008e7511b39a8d4df Mon Sep 17 00:00:00 2001 From: yanny-sec Date: Wed, 12 Aug 2026 12:24:32 +0100 Subject: [PATCH 2/5] feat(proto): add LlmPairData.reasoning for model chain of thought --- backend/internal/proto/event.pb.go | 16 ++++- backend/proto/event.proto | 2 + .../claude-code/adrian_cc/proto/event_pb2.py | 68 +++++++++---------- .../claude-code/adrian_cc/proto/event_pb2.pyi | 6 +- proto/event.proto | 2 + sdk/python/adrian/proto/event_pb2.py | 68 +++++++++---------- sdk/python/adrian/proto/event_pb2.pyi | 6 +- 7 files changed, 95 insertions(+), 73 deletions(-) diff --git a/backend/internal/proto/event.pb.go b/backend/internal/proto/event.pb.go index ed7581a..a6b7252 100644 --- a/backend/internal/proto/event.pb.go +++ b/backend/internal/proto/event.pb.go @@ -451,7 +451,9 @@ type LlmPairData struct { // Tool calls the model decided to make. ToolCalls []*ToolCall `protobuf:"bytes,4,rep,name=tool_calls,json=toolCalls,proto3" json:"tool_calls,omitempty"` // Token usage counters. - Usage *TokenUsage `protobuf:"bytes,5,opt,name=usage,proto3" json:"usage,omitempty"` + Usage *TokenUsage `protobuf:"bytes,5,opt,name=usage,proto3" json:"usage,omitempty"` + // The model's reasoning / chain of thought. + Reasoning string `protobuf:"bytes,6,opt,name=reasoning,proto3" json:"reasoning,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -521,6 +523,13 @@ func (x *LlmPairData) GetUsage() *TokenUsage { return nil } +func (x *LlmPairData) GetReasoning() string { + if x != nil { + return x.Reasoning + } + return "" +} + // ToolPairData is the payload for PAIR_TYPE_TOOL events: one tool_start // folded with its matching tool_end. type ToolPairData struct { @@ -1600,14 +1609,15 @@ const file_event_proto_rawDesc = "" + "\fAgentContext\x12\x19\n" + "\bagent_id\x18\x01 \x01(\tR\aagentId\x12#\n" + "\rsystem_prompt\x18\x02 \x01(\tR\fsystemPrompt\x12)\n" + - "\x10user_instruction\x18\x03 \x01(\tR\x0fuserInstruction\"\xeb\x01\n" + + "\x10user_instruction\x18\x03 \x01(\tR\x0fuserInstruction\"\x89\x02\n" + "\vLlmPairData\x12\x14\n" + "\x05model\x18\x01 \x01(\tR\x05model\x12;\n" + "\bmessages\x18\x02 \x03(\v2\x1f.adrian.core_api.v1.ChatMessageR\bmessages\x12\x16\n" + "\x06output\x18\x03 \x01(\tR\x06output\x12;\n" + "\n" + "tool_calls\x18\x04 \x03(\v2\x1c.adrian.core_api.v1.ToolCallR\ttoolCalls\x124\n" + - "\x05usage\x18\x05 \x01(\v2\x1e.adrian.core_api.v1.TokenUsageR\x05usage\"{\n" + + "\x05usage\x18\x05 \x01(\v2\x1e.adrian.core_api.v1.TokenUsageR\x05usage\x12\x1c\n" + + "\treasoning\x18\x06 \x01(\tR\treasoning\"{\n" + "\fToolPairData\x12\x1b\n" + "\ttool_name\x18\x01 \x01(\tR\btoolName\x12 \n" + "\ftool_call_id\x18\x02 \x01(\tR\n" + diff --git a/backend/proto/event.proto b/backend/proto/event.proto index daf7e59..34bf842 100644 --- a/backend/proto/event.proto +++ b/backend/proto/event.proto @@ -70,6 +70,8 @@ message LlmPairData { repeated ToolCall tool_calls = 4; // Token usage counters. TokenUsage usage = 5; + // The model's reasoning / chain of thought. + string reasoning = 6; } // ToolPairData is the payload for PAIR_TYPE_TOOL events: one tool_start diff --git a/integrations/claude-code/adrian_cc/proto/event_pb2.py b/integrations/claude-code/adrian_cc/proto/event_pb2.py index dadbbbe..8792ed6 100644 --- a/integrations/claude-code/adrian_cc/proto/event_pb2.py +++ b/integrations/claude-code/adrian_cc/proto/event_pb2.py @@ -25,7 +25,7 @@ from .buf.validate import validate_pb2 as buf_dot_validate_dot_validate__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x65vent.proto\x12\x12\x61\x64rian.core_api.v1\x1a\x1b\x62uf/validate/validate.proto\",\n\x0b\x43hatMessage\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\";\n\x08ToolCall\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0c\n\x04\x61rgs\x18\x02 \x01(\t\x12\n\n\x02id\x18\x03 \x01(\t\"o\n\nTokenUsage\x12\x1e\n\rprompt_tokens\x18\x01 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\"\n\x11\x63ompletion_tokens\x18\x02 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\x1d\n\x0ctotal_tokens\x18\x03 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\"Q\n\x0c\x41gentContext\x12\x10\n\x08\x61gent_id\x18\x01 \x01(\t\x12\x15\n\rsystem_prompt\x18\x02 \x01(\t\x12\x18\n\x10user_instruction\x18\x03 \x01(\t\"\xc0\x01\n\x0bLlmPairData\x12\r\n\x05model\x18\x01 \x01(\t\x12\x31\n\x08messages\x18\x02 \x03(\x0b\x32\x1f.adrian.core_api.v1.ChatMessage\x12\x0e\n\x06output\x18\x03 \x01(\t\x12\x30\n\ntool_calls\x18\x04 \x03(\x0b\x32\x1c.adrian.core_api.v1.ToolCall\x12-\n\x05usage\x18\x05 \x01(\x0b\x32\x1e.adrian.core_api.v1.TokenUsage\"_\n\x0cToolPairData\x12\x1a\n\ttool_name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x14\n\x0ctool_call_id\x18\x02 \x01(\t\x12\r\n\x05input\x18\x03 \x01(\t\x12\x0e\n\x06output\x18\x04 \x01(\t\"\xda\x03\n\x0bPairedEvent\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x15\n\rinvocation_id\x18\x02 \x01(\t\x12\x1b\n\nsession_id\x18\x03 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0e\n\x06run_id\x18\x04 \x01(\t\x12\x15\n\rparent_run_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x37\n\tpair_type\x18\x07 \x01(\x0e\x32\x1c.adrian.core_api.v1.PairTypeB\x06\xbaH\x03\xc8\x01\x01\x12/\n\x05\x61gent\x18\x08 \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12\x30\n\x06parent\x18\t \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12.\n\x03llm\x18\n \x01(\x0b\x32\x1f.adrian.core_api.v1.LlmPairDataH\x00\x12\x30\n\x04tool\x18\x0b \x01(\x0b\x32 .adrian.core_api.v1.ToolPairDataH\x00\x12\x15\n\rconnection_id\x18\x0c \x01(\t\x12\x15\n\rmetadata_json\x18\x14 \x01(\x0c\x12\x0e\n\x06source\x18\x15 \x01(\tB\x06\n\x04\x64\x61ta\"C\n\x10PairedEventBatch\x12/\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x1f.adrian.core_api.v1.PairedEvent\"G\n\tMcpServer\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x11\n\ttransport\x18\x02 \x01(\t\x12\x10\n\x08\x65ndpoint\x18\x03 \x01(\t\">\n\x0cMcpInventory\x12.\n\x07servers\x18\x01 \x03(\x0b\x32\x1d.adrian.core_api.v1.McpServer\"+\n\x08LLMStack\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\r\n\x05model\x18\x02 \x01(\t\"\xad\x01\n\x0cSessionLogin\x12\x1b\n\nsession_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12/\n\tllm_stack\x18\x02 \x01(\x0b\x32\x1c.adrian.core_api.v1.LLMStack\x12\x16\n\x0eschema_version\x18\x04 \x01(\r\x12\x0e\n\x06source\x18\x05 \x01(\t\x12\x15\n\rconnection_id\x18\x06 \x01(\tJ\x04\x08\x03\x10\x04R\nblock_mode\"\xcf\x01\n\x0b\x43lientFrame\x12\x31\n\x05login\x18\x01 \x01(\x0b\x32 .adrian.core_api.v1.SessionLoginH\x00\x12<\n\x0cpaired_batch\x18\x03 \x01(\x0b\x32$.adrian.core_api.v1.PairedEventBatchH\x00\x12\x39\n\rmcp_inventory\x18\x04 \x01(\x0b\x32 .adrian.core_api.v1.McpInventoryH\x00\x42\x07\n\x05\x66rameJ\x04\x08\x02\x10\x03R\x05\x62\x61tch\"\xad\x01\n\x0ePolicySnapshot\x12&\n\x04mode\x18\x01 \x01(\x0e\x32\x18.adrian.core_api.v1.Mode\x12\x11\n\tpolicy_m0\x18\x02 \x01(\x08\x12\x11\n\tpolicy_m2\x18\x03 \x01(\x08\x12\x11\n\tpolicy_m3\x18\x04 \x01(\x08\x12\x11\n\tpolicy_m4\x18\x05 \x01(\x08\x12\'\n\x1f\x66\x61il_closed_on_classifier_error\x18\x06 \x01(\x08\"*\n\x0cHitlResponse\x12\x1a\n\x12\x63ontinue_execution\x18\x01 \x01(\x08\">\n\x08LoginAck\x12\x32\n\x06policy\x18\x01 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\"y\n\x0bServerFrame\x12\x31\n\tlogin_ack\x18\x01 \x01(\x0b\x32\x1c.adrian.core_api.v1.LoginAckH\x00\x12.\n\x07verdict\x18\x02 \x01(\x0b\x32\x1b.adrian.core_api.v1.VerdictH\x00\x42\x07\n\x05\x66rame\"\x90\x02\n\x07Verdict\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x1b\n\nsession_id\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x10\n\x08mad_code\x18\x04 \x01(\t\x12\x32\n\x06policy\x18\x06 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\x12.\n\x04hitl\x18\x07 \x01(\x0b\x32 .adrian.core_api.v1.HitlResponse\x12\x31\n\x06status\x18\x08 \x01(\x0e\x32!.adrian.core_api.v1.VerdictStatusJ\x04\x08\x03\x10\x04J\x04\x08\x05\x10\x06R\x0e\x63lassificationR\x08\x65scalate*L\n\x08PairType\x12\x19\n\x15PAIR_TYPE_UNSPECIFIED\x10\x00\x12\x11\n\rPAIR_TYPE_LLM\x10\x01\x12\x12\n\x0ePAIR_TYPE_TOOL\x10\x02*K\n\x04Mode\x12\x14\n\x10MODE_UNSPECIFIED\x10\x00\x12\x0e\n\nMODE_ALERT\x10\x01\x12\r\n\tMODE_HITL\x10\x02\x12\x0e\n\nMODE_BLOCK\x10\x03*`\n\rVerdictStatus\x12\x1e\n\x1aVERDICT_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11VERDICT_STATUS_OK\x10\x01\x12\x18\n\x14VERDICT_STATUS_ERROR\x10\x02\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x65vent.proto\x12\x12\x61\x64rian.core_api.v1\x1a\x1b\x62uf/validate/validate.proto\",\n\x0b\x43hatMessage\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\";\n\x08ToolCall\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0c\n\x04\x61rgs\x18\x02 \x01(\t\x12\n\n\x02id\x18\x03 \x01(\t\"o\n\nTokenUsage\x12\x1e\n\rprompt_tokens\x18\x01 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\"\n\x11\x63ompletion_tokens\x18\x02 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\x1d\n\x0ctotal_tokens\x18\x03 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\"Q\n\x0c\x41gentContext\x12\x10\n\x08\x61gent_id\x18\x01 \x01(\t\x12\x15\n\rsystem_prompt\x18\x02 \x01(\t\x12\x18\n\x10user_instruction\x18\x03 \x01(\t\"\xd3\x01\n\x0bLlmPairData\x12\r\n\x05model\x18\x01 \x01(\t\x12\x31\n\x08messages\x18\x02 \x03(\x0b\x32\x1f.adrian.core_api.v1.ChatMessage\x12\x0e\n\x06output\x18\x03 \x01(\t\x12\x30\n\ntool_calls\x18\x04 \x03(\x0b\x32\x1c.adrian.core_api.v1.ToolCall\x12-\n\x05usage\x18\x05 \x01(\x0b\x32\x1e.adrian.core_api.v1.TokenUsage\x12\x11\n\treasoning\x18\x06 \x01(\t\"_\n\x0cToolPairData\x12\x1a\n\ttool_name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x14\n\x0ctool_call_id\x18\x02 \x01(\t\x12\r\n\x05input\x18\x03 \x01(\t\x12\x0e\n\x06output\x18\x04 \x01(\t\"\xda\x03\n\x0bPairedEvent\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x15\n\rinvocation_id\x18\x02 \x01(\t\x12\x1b\n\nsession_id\x18\x03 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0e\n\x06run_id\x18\x04 \x01(\t\x12\x15\n\rparent_run_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x37\n\tpair_type\x18\x07 \x01(\x0e\x32\x1c.adrian.core_api.v1.PairTypeB\x06\xbaH\x03\xc8\x01\x01\x12/\n\x05\x61gent\x18\x08 \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12\x30\n\x06parent\x18\t \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12.\n\x03llm\x18\n \x01(\x0b\x32\x1f.adrian.core_api.v1.LlmPairDataH\x00\x12\x30\n\x04tool\x18\x0b \x01(\x0b\x32 .adrian.core_api.v1.ToolPairDataH\x00\x12\x15\n\rconnection_id\x18\x0c \x01(\t\x12\x15\n\rmetadata_json\x18\x14 \x01(\x0c\x12\x0e\n\x06source\x18\x15 \x01(\tB\x06\n\x04\x64\x61ta\"C\n\x10PairedEventBatch\x12/\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x1f.adrian.core_api.v1.PairedEvent\"G\n\tMcpServer\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x11\n\ttransport\x18\x02 \x01(\t\x12\x10\n\x08\x65ndpoint\x18\x03 \x01(\t\">\n\x0cMcpInventory\x12.\n\x07servers\x18\x01 \x03(\x0b\x32\x1d.adrian.core_api.v1.McpServer\"+\n\x08LLMStack\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\r\n\x05model\x18\x02 \x01(\t\"\xad\x01\n\x0cSessionLogin\x12\x1b\n\nsession_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12/\n\tllm_stack\x18\x02 \x01(\x0b\x32\x1c.adrian.core_api.v1.LLMStack\x12\x16\n\x0eschema_version\x18\x04 \x01(\r\x12\x0e\n\x06source\x18\x05 \x01(\t\x12\x15\n\rconnection_id\x18\x06 \x01(\tJ\x04\x08\x03\x10\x04R\nblock_mode\"\xcf\x01\n\x0b\x43lientFrame\x12\x31\n\x05login\x18\x01 \x01(\x0b\x32 .adrian.core_api.v1.SessionLoginH\x00\x12<\n\x0cpaired_batch\x18\x03 \x01(\x0b\x32$.adrian.core_api.v1.PairedEventBatchH\x00\x12\x39\n\rmcp_inventory\x18\x04 \x01(\x0b\x32 .adrian.core_api.v1.McpInventoryH\x00\x42\x07\n\x05\x66rameJ\x04\x08\x02\x10\x03R\x05\x62\x61tch\"\xad\x01\n\x0ePolicySnapshot\x12&\n\x04mode\x18\x01 \x01(\x0e\x32\x18.adrian.core_api.v1.Mode\x12\x11\n\tpolicy_m0\x18\x02 \x01(\x08\x12\x11\n\tpolicy_m2\x18\x03 \x01(\x08\x12\x11\n\tpolicy_m3\x18\x04 \x01(\x08\x12\x11\n\tpolicy_m4\x18\x05 \x01(\x08\x12\'\n\x1f\x66\x61il_closed_on_classifier_error\x18\x06 \x01(\x08\"*\n\x0cHitlResponse\x12\x1a\n\x12\x63ontinue_execution\x18\x01 \x01(\x08\">\n\x08LoginAck\x12\x32\n\x06policy\x18\x01 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\"y\n\x0bServerFrame\x12\x31\n\tlogin_ack\x18\x01 \x01(\x0b\x32\x1c.adrian.core_api.v1.LoginAckH\x00\x12.\n\x07verdict\x18\x02 \x01(\x0b\x32\x1b.adrian.core_api.v1.VerdictH\x00\x42\x07\n\x05\x66rame\"\x90\x02\n\x07Verdict\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x1b\n\nsession_id\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x10\n\x08mad_code\x18\x04 \x01(\t\x12\x32\n\x06policy\x18\x06 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\x12.\n\x04hitl\x18\x07 \x01(\x0b\x32 .adrian.core_api.v1.HitlResponse\x12\x31\n\x06status\x18\x08 \x01(\x0e\x32!.adrian.core_api.v1.VerdictStatusJ\x04\x08\x03\x10\x04J\x04\x08\x05\x10\x06R\x0e\x63lassificationR\x08\x65scalate*L\n\x08PairType\x12\x19\n\x15PAIR_TYPE_UNSPECIFIED\x10\x00\x12\x11\n\rPAIR_TYPE_LLM\x10\x01\x12\x12\n\x0ePAIR_TYPE_TOOL\x10\x02*K\n\x04Mode\x12\x14\n\x10MODE_UNSPECIFIED\x10\x00\x12\x0e\n\nMODE_ALERT\x10\x01\x12\r\n\tMODE_HITL\x10\x02\x12\x0e\n\nMODE_BLOCK\x10\x03*`\n\rVerdictStatus\x12\x1e\n\x1aVERDICT_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11VERDICT_STATUS_OK\x10\x01\x12\x18\n\x14VERDICT_STATUS_ERROR\x10\x02\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -56,12 +56,12 @@ _globals['_VERDICT'].fields_by_name['event_id']._serialized_options = b'\272H\004r\002\020\001' _globals['_VERDICT'].fields_by_name['session_id']._loaded_options = None _globals['_VERDICT'].fields_by_name['session_id']._serialized_options = b'\272H\004r\002\020\001' - _globals['_PAIRTYPE']._serialized_start=2455 - _globals['_PAIRTYPE']._serialized_end=2531 - _globals['_MODE']._serialized_start=2533 - _globals['_MODE']._serialized_end=2608 - _globals['_VERDICTSTATUS']._serialized_start=2610 - _globals['_VERDICTSTATUS']._serialized_end=2706 + _globals['_PAIRTYPE']._serialized_start=2474 + _globals['_PAIRTYPE']._serialized_end=2550 + _globals['_MODE']._serialized_start=2552 + _globals['_MODE']._serialized_end=2627 + _globals['_VERDICTSTATUS']._serialized_start=2629 + _globals['_VERDICTSTATUS']._serialized_end=2725 _globals['_CHATMESSAGE']._serialized_start=64 _globals['_CHATMESSAGE']._serialized_end=108 _globals['_TOOLCALL']._serialized_start=110 @@ -71,31 +71,31 @@ _globals['_AGENTCONTEXT']._serialized_start=284 _globals['_AGENTCONTEXT']._serialized_end=365 _globals['_LLMPAIRDATA']._serialized_start=368 - _globals['_LLMPAIRDATA']._serialized_end=560 - _globals['_TOOLPAIRDATA']._serialized_start=562 - _globals['_TOOLPAIRDATA']._serialized_end=657 - _globals['_PAIREDEVENT']._serialized_start=660 - _globals['_PAIREDEVENT']._serialized_end=1134 - _globals['_PAIREDEVENTBATCH']._serialized_start=1136 - _globals['_PAIREDEVENTBATCH']._serialized_end=1203 - _globals['_MCPSERVER']._serialized_start=1205 - _globals['_MCPSERVER']._serialized_end=1276 - _globals['_MCPINVENTORY']._serialized_start=1278 - _globals['_MCPINVENTORY']._serialized_end=1340 - _globals['_LLMSTACK']._serialized_start=1342 - _globals['_LLMSTACK']._serialized_end=1385 - _globals['_SESSIONLOGIN']._serialized_start=1388 - _globals['_SESSIONLOGIN']._serialized_end=1561 - _globals['_CLIENTFRAME']._serialized_start=1564 - _globals['_CLIENTFRAME']._serialized_end=1771 - _globals['_POLICYSNAPSHOT']._serialized_start=1774 - _globals['_POLICYSNAPSHOT']._serialized_end=1947 - _globals['_HITLRESPONSE']._serialized_start=1949 - _globals['_HITLRESPONSE']._serialized_end=1991 - _globals['_LOGINACK']._serialized_start=1993 - _globals['_LOGINACK']._serialized_end=2055 - _globals['_SERVERFRAME']._serialized_start=2057 - _globals['_SERVERFRAME']._serialized_end=2178 - _globals['_VERDICT']._serialized_start=2181 - _globals['_VERDICT']._serialized_end=2453 + _globals['_LLMPAIRDATA']._serialized_end=579 + _globals['_TOOLPAIRDATA']._serialized_start=581 + _globals['_TOOLPAIRDATA']._serialized_end=676 + _globals['_PAIREDEVENT']._serialized_start=679 + _globals['_PAIREDEVENT']._serialized_end=1153 + _globals['_PAIREDEVENTBATCH']._serialized_start=1155 + _globals['_PAIREDEVENTBATCH']._serialized_end=1222 + _globals['_MCPSERVER']._serialized_start=1224 + _globals['_MCPSERVER']._serialized_end=1295 + _globals['_MCPINVENTORY']._serialized_start=1297 + _globals['_MCPINVENTORY']._serialized_end=1359 + _globals['_LLMSTACK']._serialized_start=1361 + _globals['_LLMSTACK']._serialized_end=1404 + _globals['_SESSIONLOGIN']._serialized_start=1407 + _globals['_SESSIONLOGIN']._serialized_end=1580 + _globals['_CLIENTFRAME']._serialized_start=1583 + _globals['_CLIENTFRAME']._serialized_end=1790 + _globals['_POLICYSNAPSHOT']._serialized_start=1793 + _globals['_POLICYSNAPSHOT']._serialized_end=1966 + _globals['_HITLRESPONSE']._serialized_start=1968 + _globals['_HITLRESPONSE']._serialized_end=2010 + _globals['_LOGINACK']._serialized_start=2012 + _globals['_LOGINACK']._serialized_end=2074 + _globals['_SERVERFRAME']._serialized_start=2076 + _globals['_SERVERFRAME']._serialized_end=2197 + _globals['_VERDICT']._serialized_start=2200 + _globals['_VERDICT']._serialized_end=2472 # @@protoc_insertion_point(module_scope) diff --git a/integrations/claude-code/adrian_cc/proto/event_pb2.pyi b/integrations/claude-code/adrian_cc/proto/event_pb2.pyi index a99a39c..5ae9a22 100644 --- a/integrations/claude-code/adrian_cc/proto/event_pb2.pyi +++ b/integrations/claude-code/adrian_cc/proto/event_pb2.pyi @@ -220,10 +220,13 @@ class LlmPairData(_message.Message): OUTPUT_FIELD_NUMBER: _builtins.int TOOL_CALLS_FIELD_NUMBER: _builtins.int USAGE_FIELD_NUMBER: _builtins.int + REASONING_FIELD_NUMBER: _builtins.int model: _builtins.str """Model class name (e.g. "ChatAnthropic", "ChatOpenAI").""" output: _builtins.str """The model's text response.""" + reasoning: _builtins.str + """The model's reasoning / chain of thought.""" @_builtins.property def messages(self) -> _containers.RepeatedCompositeFieldContainer[Global___ChatMessage]: """Ordered conversation messages sent to the model.""" @@ -244,10 +247,11 @@ class LlmPairData(_message.Message): output: _builtins.str = ..., tool_calls: _abc.Iterable[Global___ToolCall] | None = ..., usage: Global___TokenUsage | None = ..., + reasoning: _builtins.str = ..., ) -> None: ... _HasFieldArgType: _TypeAlias = _typing.Literal["usage", b"usage"] # noqa: Y015 def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... - _ClearFieldArgType: _TypeAlias = _typing.Literal["messages", b"messages", "model", b"model", "output", b"output", "tool_calls", b"tool_calls", "usage", b"usage"] # noqa: Y015 + _ClearFieldArgType: _TypeAlias = _typing.Literal["messages", b"messages", "model", b"model", "output", b"output", "reasoning", b"reasoning", "tool_calls", b"tool_calls", "usage", b"usage"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... Global___LlmPairData: _TypeAlias = LlmPairData # noqa: Y015 diff --git a/proto/event.proto b/proto/event.proto index 1281cbe..a4d40e0 100644 --- a/proto/event.proto +++ b/proto/event.proto @@ -70,6 +70,8 @@ message LlmPairData { repeated ToolCall tool_calls = 4; // Token usage counters. TokenUsage usage = 5; + // The model's reasoning / chain of thought. + string reasoning = 6; } // ToolPairData is the payload for PAIR_TYPE_TOOL events: one tool_start diff --git a/sdk/python/adrian/proto/event_pb2.py b/sdk/python/adrian/proto/event_pb2.py index dadbbbe..8792ed6 100644 --- a/sdk/python/adrian/proto/event_pb2.py +++ b/sdk/python/adrian/proto/event_pb2.py @@ -25,7 +25,7 @@ from .buf.validate import validate_pb2 as buf_dot_validate_dot_validate__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x65vent.proto\x12\x12\x61\x64rian.core_api.v1\x1a\x1b\x62uf/validate/validate.proto\",\n\x0b\x43hatMessage\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\";\n\x08ToolCall\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0c\n\x04\x61rgs\x18\x02 \x01(\t\x12\n\n\x02id\x18\x03 \x01(\t\"o\n\nTokenUsage\x12\x1e\n\rprompt_tokens\x18\x01 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\"\n\x11\x63ompletion_tokens\x18\x02 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\x1d\n\x0ctotal_tokens\x18\x03 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\"Q\n\x0c\x41gentContext\x12\x10\n\x08\x61gent_id\x18\x01 \x01(\t\x12\x15\n\rsystem_prompt\x18\x02 \x01(\t\x12\x18\n\x10user_instruction\x18\x03 \x01(\t\"\xc0\x01\n\x0bLlmPairData\x12\r\n\x05model\x18\x01 \x01(\t\x12\x31\n\x08messages\x18\x02 \x03(\x0b\x32\x1f.adrian.core_api.v1.ChatMessage\x12\x0e\n\x06output\x18\x03 \x01(\t\x12\x30\n\ntool_calls\x18\x04 \x03(\x0b\x32\x1c.adrian.core_api.v1.ToolCall\x12-\n\x05usage\x18\x05 \x01(\x0b\x32\x1e.adrian.core_api.v1.TokenUsage\"_\n\x0cToolPairData\x12\x1a\n\ttool_name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x14\n\x0ctool_call_id\x18\x02 \x01(\t\x12\r\n\x05input\x18\x03 \x01(\t\x12\x0e\n\x06output\x18\x04 \x01(\t\"\xda\x03\n\x0bPairedEvent\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x15\n\rinvocation_id\x18\x02 \x01(\t\x12\x1b\n\nsession_id\x18\x03 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0e\n\x06run_id\x18\x04 \x01(\t\x12\x15\n\rparent_run_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x37\n\tpair_type\x18\x07 \x01(\x0e\x32\x1c.adrian.core_api.v1.PairTypeB\x06\xbaH\x03\xc8\x01\x01\x12/\n\x05\x61gent\x18\x08 \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12\x30\n\x06parent\x18\t \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12.\n\x03llm\x18\n \x01(\x0b\x32\x1f.adrian.core_api.v1.LlmPairDataH\x00\x12\x30\n\x04tool\x18\x0b \x01(\x0b\x32 .adrian.core_api.v1.ToolPairDataH\x00\x12\x15\n\rconnection_id\x18\x0c \x01(\t\x12\x15\n\rmetadata_json\x18\x14 \x01(\x0c\x12\x0e\n\x06source\x18\x15 \x01(\tB\x06\n\x04\x64\x61ta\"C\n\x10PairedEventBatch\x12/\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x1f.adrian.core_api.v1.PairedEvent\"G\n\tMcpServer\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x11\n\ttransport\x18\x02 \x01(\t\x12\x10\n\x08\x65ndpoint\x18\x03 \x01(\t\">\n\x0cMcpInventory\x12.\n\x07servers\x18\x01 \x03(\x0b\x32\x1d.adrian.core_api.v1.McpServer\"+\n\x08LLMStack\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\r\n\x05model\x18\x02 \x01(\t\"\xad\x01\n\x0cSessionLogin\x12\x1b\n\nsession_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12/\n\tllm_stack\x18\x02 \x01(\x0b\x32\x1c.adrian.core_api.v1.LLMStack\x12\x16\n\x0eschema_version\x18\x04 \x01(\r\x12\x0e\n\x06source\x18\x05 \x01(\t\x12\x15\n\rconnection_id\x18\x06 \x01(\tJ\x04\x08\x03\x10\x04R\nblock_mode\"\xcf\x01\n\x0b\x43lientFrame\x12\x31\n\x05login\x18\x01 \x01(\x0b\x32 .adrian.core_api.v1.SessionLoginH\x00\x12<\n\x0cpaired_batch\x18\x03 \x01(\x0b\x32$.adrian.core_api.v1.PairedEventBatchH\x00\x12\x39\n\rmcp_inventory\x18\x04 \x01(\x0b\x32 .adrian.core_api.v1.McpInventoryH\x00\x42\x07\n\x05\x66rameJ\x04\x08\x02\x10\x03R\x05\x62\x61tch\"\xad\x01\n\x0ePolicySnapshot\x12&\n\x04mode\x18\x01 \x01(\x0e\x32\x18.adrian.core_api.v1.Mode\x12\x11\n\tpolicy_m0\x18\x02 \x01(\x08\x12\x11\n\tpolicy_m2\x18\x03 \x01(\x08\x12\x11\n\tpolicy_m3\x18\x04 \x01(\x08\x12\x11\n\tpolicy_m4\x18\x05 \x01(\x08\x12\'\n\x1f\x66\x61il_closed_on_classifier_error\x18\x06 \x01(\x08\"*\n\x0cHitlResponse\x12\x1a\n\x12\x63ontinue_execution\x18\x01 \x01(\x08\">\n\x08LoginAck\x12\x32\n\x06policy\x18\x01 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\"y\n\x0bServerFrame\x12\x31\n\tlogin_ack\x18\x01 \x01(\x0b\x32\x1c.adrian.core_api.v1.LoginAckH\x00\x12.\n\x07verdict\x18\x02 \x01(\x0b\x32\x1b.adrian.core_api.v1.VerdictH\x00\x42\x07\n\x05\x66rame\"\x90\x02\n\x07Verdict\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x1b\n\nsession_id\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x10\n\x08mad_code\x18\x04 \x01(\t\x12\x32\n\x06policy\x18\x06 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\x12.\n\x04hitl\x18\x07 \x01(\x0b\x32 .adrian.core_api.v1.HitlResponse\x12\x31\n\x06status\x18\x08 \x01(\x0e\x32!.adrian.core_api.v1.VerdictStatusJ\x04\x08\x03\x10\x04J\x04\x08\x05\x10\x06R\x0e\x63lassificationR\x08\x65scalate*L\n\x08PairType\x12\x19\n\x15PAIR_TYPE_UNSPECIFIED\x10\x00\x12\x11\n\rPAIR_TYPE_LLM\x10\x01\x12\x12\n\x0ePAIR_TYPE_TOOL\x10\x02*K\n\x04Mode\x12\x14\n\x10MODE_UNSPECIFIED\x10\x00\x12\x0e\n\nMODE_ALERT\x10\x01\x12\r\n\tMODE_HITL\x10\x02\x12\x0e\n\nMODE_BLOCK\x10\x03*`\n\rVerdictStatus\x12\x1e\n\x1aVERDICT_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11VERDICT_STATUS_OK\x10\x01\x12\x18\n\x14VERDICT_STATUS_ERROR\x10\x02\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x65vent.proto\x12\x12\x61\x64rian.core_api.v1\x1a\x1b\x62uf/validate/validate.proto\",\n\x0b\x43hatMessage\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\";\n\x08ToolCall\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0c\n\x04\x61rgs\x18\x02 \x01(\t\x12\n\n\x02id\x18\x03 \x01(\t\"o\n\nTokenUsage\x12\x1e\n\rprompt_tokens\x18\x01 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\"\n\x11\x63ompletion_tokens\x18\x02 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\x12\x1d\n\x0ctotal_tokens\x18\x03 \x01(\x05\x42\x07\xbaH\x04\x1a\x02(\x00\"Q\n\x0c\x41gentContext\x12\x10\n\x08\x61gent_id\x18\x01 \x01(\t\x12\x15\n\rsystem_prompt\x18\x02 \x01(\t\x12\x18\n\x10user_instruction\x18\x03 \x01(\t\"\xd3\x01\n\x0bLlmPairData\x12\r\n\x05model\x18\x01 \x01(\t\x12\x31\n\x08messages\x18\x02 \x03(\x0b\x32\x1f.adrian.core_api.v1.ChatMessage\x12\x0e\n\x06output\x18\x03 \x01(\t\x12\x30\n\ntool_calls\x18\x04 \x03(\x0b\x32\x1c.adrian.core_api.v1.ToolCall\x12-\n\x05usage\x18\x05 \x01(\x0b\x32\x1e.adrian.core_api.v1.TokenUsage\x12\x11\n\treasoning\x18\x06 \x01(\t\"_\n\x0cToolPairData\x12\x1a\n\ttool_name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x14\n\x0ctool_call_id\x18\x02 \x01(\t\x12\r\n\x05input\x18\x03 \x01(\t\x12\x0e\n\x06output\x18\x04 \x01(\t\"\xda\x03\n\x0bPairedEvent\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x15\n\rinvocation_id\x18\x02 \x01(\t\x12\x1b\n\nsession_id\x18\x03 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x0e\n\x06run_id\x18\x04 \x01(\t\x12\x15\n\rparent_run_id\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x37\n\tpair_type\x18\x07 \x01(\x0e\x32\x1c.adrian.core_api.v1.PairTypeB\x06\xbaH\x03\xc8\x01\x01\x12/\n\x05\x61gent\x18\x08 \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12\x30\n\x06parent\x18\t \x01(\x0b\x32 .adrian.core_api.v1.AgentContext\x12.\n\x03llm\x18\n \x01(\x0b\x32\x1f.adrian.core_api.v1.LlmPairDataH\x00\x12\x30\n\x04tool\x18\x0b \x01(\x0b\x32 .adrian.core_api.v1.ToolPairDataH\x00\x12\x15\n\rconnection_id\x18\x0c \x01(\t\x12\x15\n\rmetadata_json\x18\x14 \x01(\x0c\x12\x0e\n\x06source\x18\x15 \x01(\tB\x06\n\x04\x64\x61ta\"C\n\x10PairedEventBatch\x12/\n\x06\x65vents\x18\x01 \x03(\x0b\x32\x1f.adrian.core_api.v1.PairedEvent\"G\n\tMcpServer\x12\x15\n\x04name\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x11\n\ttransport\x18\x02 \x01(\t\x12\x10\n\x08\x65ndpoint\x18\x03 \x01(\t\">\n\x0cMcpInventory\x12.\n\x07servers\x18\x01 \x03(\x0b\x32\x1d.adrian.core_api.v1.McpServer\"+\n\x08LLMStack\x12\x10\n\x08provider\x18\x01 \x01(\t\x12\r\n\x05model\x18\x02 \x01(\t\"\xad\x01\n\x0cSessionLogin\x12\x1b\n\nsession_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12/\n\tllm_stack\x18\x02 \x01(\x0b\x32\x1c.adrian.core_api.v1.LLMStack\x12\x16\n\x0eschema_version\x18\x04 \x01(\r\x12\x0e\n\x06source\x18\x05 \x01(\t\x12\x15\n\rconnection_id\x18\x06 \x01(\tJ\x04\x08\x03\x10\x04R\nblock_mode\"\xcf\x01\n\x0b\x43lientFrame\x12\x31\n\x05login\x18\x01 \x01(\x0b\x32 .adrian.core_api.v1.SessionLoginH\x00\x12<\n\x0cpaired_batch\x18\x03 \x01(\x0b\x32$.adrian.core_api.v1.PairedEventBatchH\x00\x12\x39\n\rmcp_inventory\x18\x04 \x01(\x0b\x32 .adrian.core_api.v1.McpInventoryH\x00\x42\x07\n\x05\x66rameJ\x04\x08\x02\x10\x03R\x05\x62\x61tch\"\xad\x01\n\x0ePolicySnapshot\x12&\n\x04mode\x18\x01 \x01(\x0e\x32\x18.adrian.core_api.v1.Mode\x12\x11\n\tpolicy_m0\x18\x02 \x01(\x08\x12\x11\n\tpolicy_m2\x18\x03 \x01(\x08\x12\x11\n\tpolicy_m3\x18\x04 \x01(\x08\x12\x11\n\tpolicy_m4\x18\x05 \x01(\x08\x12\'\n\x1f\x66\x61il_closed_on_classifier_error\x18\x06 \x01(\x08\"*\n\x0cHitlResponse\x12\x1a\n\x12\x63ontinue_execution\x18\x01 \x01(\x08\">\n\x08LoginAck\x12\x32\n\x06policy\x18\x01 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\"y\n\x0bServerFrame\x12\x31\n\tlogin_ack\x18\x01 \x01(\x0b\x32\x1c.adrian.core_api.v1.LoginAckH\x00\x12.\n\x07verdict\x18\x02 \x01(\x0b\x32\x1b.adrian.core_api.v1.VerdictH\x00\x42\x07\n\x05\x66rame\"\x90\x02\n\x07Verdict\x12\x19\n\x08\x65vent_id\x18\x01 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x1b\n\nsession_id\x18\x02 \x01(\tB\x07\xbaH\x04r\x02\x10\x01\x12\x10\n\x08mad_code\x18\x04 \x01(\t\x12\x32\n\x06policy\x18\x06 \x01(\x0b\x32\".adrian.core_api.v1.PolicySnapshot\x12.\n\x04hitl\x18\x07 \x01(\x0b\x32 .adrian.core_api.v1.HitlResponse\x12\x31\n\x06status\x18\x08 \x01(\x0e\x32!.adrian.core_api.v1.VerdictStatusJ\x04\x08\x03\x10\x04J\x04\x08\x05\x10\x06R\x0e\x63lassificationR\x08\x65scalate*L\n\x08PairType\x12\x19\n\x15PAIR_TYPE_UNSPECIFIED\x10\x00\x12\x11\n\rPAIR_TYPE_LLM\x10\x01\x12\x12\n\x0ePAIR_TYPE_TOOL\x10\x02*K\n\x04Mode\x12\x14\n\x10MODE_UNSPECIFIED\x10\x00\x12\x0e\n\nMODE_ALERT\x10\x01\x12\r\n\tMODE_HITL\x10\x02\x12\x0e\n\nMODE_BLOCK\x10\x03*`\n\rVerdictStatus\x12\x1e\n\x1aVERDICT_STATUS_UNSPECIFIED\x10\x00\x12\x15\n\x11VERDICT_STATUS_OK\x10\x01\x12\x18\n\x14VERDICT_STATUS_ERROR\x10\x02\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -56,12 +56,12 @@ _globals['_VERDICT'].fields_by_name['event_id']._serialized_options = b'\272H\004r\002\020\001' _globals['_VERDICT'].fields_by_name['session_id']._loaded_options = None _globals['_VERDICT'].fields_by_name['session_id']._serialized_options = b'\272H\004r\002\020\001' - _globals['_PAIRTYPE']._serialized_start=2455 - _globals['_PAIRTYPE']._serialized_end=2531 - _globals['_MODE']._serialized_start=2533 - _globals['_MODE']._serialized_end=2608 - _globals['_VERDICTSTATUS']._serialized_start=2610 - _globals['_VERDICTSTATUS']._serialized_end=2706 + _globals['_PAIRTYPE']._serialized_start=2474 + _globals['_PAIRTYPE']._serialized_end=2550 + _globals['_MODE']._serialized_start=2552 + _globals['_MODE']._serialized_end=2627 + _globals['_VERDICTSTATUS']._serialized_start=2629 + _globals['_VERDICTSTATUS']._serialized_end=2725 _globals['_CHATMESSAGE']._serialized_start=64 _globals['_CHATMESSAGE']._serialized_end=108 _globals['_TOOLCALL']._serialized_start=110 @@ -71,31 +71,31 @@ _globals['_AGENTCONTEXT']._serialized_start=284 _globals['_AGENTCONTEXT']._serialized_end=365 _globals['_LLMPAIRDATA']._serialized_start=368 - _globals['_LLMPAIRDATA']._serialized_end=560 - _globals['_TOOLPAIRDATA']._serialized_start=562 - _globals['_TOOLPAIRDATA']._serialized_end=657 - _globals['_PAIREDEVENT']._serialized_start=660 - _globals['_PAIREDEVENT']._serialized_end=1134 - _globals['_PAIREDEVENTBATCH']._serialized_start=1136 - _globals['_PAIREDEVENTBATCH']._serialized_end=1203 - _globals['_MCPSERVER']._serialized_start=1205 - _globals['_MCPSERVER']._serialized_end=1276 - _globals['_MCPINVENTORY']._serialized_start=1278 - _globals['_MCPINVENTORY']._serialized_end=1340 - _globals['_LLMSTACK']._serialized_start=1342 - _globals['_LLMSTACK']._serialized_end=1385 - _globals['_SESSIONLOGIN']._serialized_start=1388 - _globals['_SESSIONLOGIN']._serialized_end=1561 - _globals['_CLIENTFRAME']._serialized_start=1564 - _globals['_CLIENTFRAME']._serialized_end=1771 - _globals['_POLICYSNAPSHOT']._serialized_start=1774 - _globals['_POLICYSNAPSHOT']._serialized_end=1947 - _globals['_HITLRESPONSE']._serialized_start=1949 - _globals['_HITLRESPONSE']._serialized_end=1991 - _globals['_LOGINACK']._serialized_start=1993 - _globals['_LOGINACK']._serialized_end=2055 - _globals['_SERVERFRAME']._serialized_start=2057 - _globals['_SERVERFRAME']._serialized_end=2178 - _globals['_VERDICT']._serialized_start=2181 - _globals['_VERDICT']._serialized_end=2453 + _globals['_LLMPAIRDATA']._serialized_end=579 + _globals['_TOOLPAIRDATA']._serialized_start=581 + _globals['_TOOLPAIRDATA']._serialized_end=676 + _globals['_PAIREDEVENT']._serialized_start=679 + _globals['_PAIREDEVENT']._serialized_end=1153 + _globals['_PAIREDEVENTBATCH']._serialized_start=1155 + _globals['_PAIREDEVENTBATCH']._serialized_end=1222 + _globals['_MCPSERVER']._serialized_start=1224 + _globals['_MCPSERVER']._serialized_end=1295 + _globals['_MCPINVENTORY']._serialized_start=1297 + _globals['_MCPINVENTORY']._serialized_end=1359 + _globals['_LLMSTACK']._serialized_start=1361 + _globals['_LLMSTACK']._serialized_end=1404 + _globals['_SESSIONLOGIN']._serialized_start=1407 + _globals['_SESSIONLOGIN']._serialized_end=1580 + _globals['_CLIENTFRAME']._serialized_start=1583 + _globals['_CLIENTFRAME']._serialized_end=1790 + _globals['_POLICYSNAPSHOT']._serialized_start=1793 + _globals['_POLICYSNAPSHOT']._serialized_end=1966 + _globals['_HITLRESPONSE']._serialized_start=1968 + _globals['_HITLRESPONSE']._serialized_end=2010 + _globals['_LOGINACK']._serialized_start=2012 + _globals['_LOGINACK']._serialized_end=2074 + _globals['_SERVERFRAME']._serialized_start=2076 + _globals['_SERVERFRAME']._serialized_end=2197 + _globals['_VERDICT']._serialized_start=2200 + _globals['_VERDICT']._serialized_end=2472 # @@protoc_insertion_point(module_scope) diff --git a/sdk/python/adrian/proto/event_pb2.pyi b/sdk/python/adrian/proto/event_pb2.pyi index a99a39c..5ae9a22 100644 --- a/sdk/python/adrian/proto/event_pb2.pyi +++ b/sdk/python/adrian/proto/event_pb2.pyi @@ -220,10 +220,13 @@ class LlmPairData(_message.Message): OUTPUT_FIELD_NUMBER: _builtins.int TOOL_CALLS_FIELD_NUMBER: _builtins.int USAGE_FIELD_NUMBER: _builtins.int + REASONING_FIELD_NUMBER: _builtins.int model: _builtins.str """Model class name (e.g. "ChatAnthropic", "ChatOpenAI").""" output: _builtins.str """The model's text response.""" + reasoning: _builtins.str + """The model's reasoning / chain of thought.""" @_builtins.property def messages(self) -> _containers.RepeatedCompositeFieldContainer[Global___ChatMessage]: """Ordered conversation messages sent to the model.""" @@ -244,10 +247,11 @@ class LlmPairData(_message.Message): output: _builtins.str = ..., tool_calls: _abc.Iterable[Global___ToolCall] | None = ..., usage: Global___TokenUsage | None = ..., + reasoning: _builtins.str = ..., ) -> None: ... _HasFieldArgType: _TypeAlias = _typing.Literal["usage", b"usage"] # noqa: Y015 def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... - _ClearFieldArgType: _TypeAlias = _typing.Literal["messages", b"messages", "model", b"model", "output", b"output", "tool_calls", b"tool_calls", "usage", b"usage"] # noqa: Y015 + _ClearFieldArgType: _TypeAlias = _typing.Literal["messages", b"messages", "model", b"model", "output", b"output", "reasoning", b"reasoning", "tool_calls", b"tool_calls", "usage", b"usage"] # noqa: Y015 def ClearField(self, field_name: _ClearFieldArgType) -> None: ... Global___LlmPairData: _TypeAlias = LlmPairData # noqa: Y015 From d32266a7db15e90f7baa4ece9f10903d0edc539a Mon Sep 17 00:00:00 2001 From: yanny-sec Date: Wed, 12 Aug 2026 13:33:07 +0100 Subject: [PATCH 3/5] fix(adrian_cc): drop LoginAck.source reads for a field the schema never defined --- integrations/claude-code/adrian_cc/agent.py | 11 +++-------- .../claude-code/tests/test_state_and_subagent.py | 4 ++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/integrations/claude-code/adrian_cc/agent.py b/integrations/claude-code/adrian_cc/agent.py index b87a3e0..a3c857c 100644 --- a/integrations/claude-code/adrian_cc/agent.py +++ b/integrations/claude-code/adrian_cc/agent.py @@ -656,7 +656,6 @@ async def _ws_send_event( "policy_m3": policy.policy_m3, "policy_m4": policy.policy_m4, } - result["source_ack"] = sf.login_ack.source # --- Send event --- batch = pb.PairedEventBatch(events=[event]) @@ -754,8 +753,8 @@ async def _verify_connection() -> dict[str, Any]: """Connect and SessionLogin only, with no event and no verdict. Confirms the URL is reachable and the API key authenticates. Returns - {ok, source_ack, mode_name} or {ok: False, error}. Never touches the key - value beyond the auth header. + {ok, mode_name} or {ok: False, error}. Never touches the key value + beyond the auth header. """ headers: dict[str, str] = {} if ADRIAN_API_KEY: @@ -782,7 +781,6 @@ async def _verify_connection() -> dict[str, Any]: if sf.WhichOneof("frame") == "login_ack": return { "ok": True, - "source_ack": sf.login_ack.source, "mode_name": _mode_name(sf.login_ack.policy.mode), } return {"ok": False, "error": "server did not send a LoginAck"} @@ -802,10 +800,7 @@ def _handle_verify() -> None: sys.exit(1) res = asyncio.run(_verify_connection()) if res.get("ok"): - print( - f"OK: connected to {ADRIAN_WS_URL} " - f"(source_ack={res.get('source_ack')!r}, backend mode={res.get('mode_name')})" - ) + print(f"OK: connected to {ADRIAN_WS_URL} (backend mode={res.get('mode_name')})") sys.exit(0) print( f"FAIL: {res.get('error')} (check ADRIAN_WS_URL and ADRIAN_API_KEY in ~/.adrian/.env)" diff --git a/integrations/claude-code/tests/test_state_and_subagent.py b/integrations/claude-code/tests/test_state_and_subagent.py index b768218..b189fcc 100644 --- a/integrations/claude-code/tests/test_state_and_subagent.py +++ b/integrations/claude-code/tests/test_state_and_subagent.py @@ -443,7 +443,7 @@ def test_verify_reports_ok_from_login_ack( self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] ) -> None: async def _ok(*_a: object, **_k: object) -> dict[str, Any]: - return {"ok": True, "source_ack": "claude-code", "mode_name": "ALERT"} + return {"ok": True, "mode_name": "ALERT"} monkeypatch.setattr(agent, "ADRIAN_API_KEY", "adr_live_x") monkeypatch.setattr(agent, "_verify_connection", _ok) @@ -451,7 +451,7 @@ async def _ok(*_a: object, **_k: object) -> dict[str, Any]: agent._handle_verify() assert e.value.code == 0 out = capsys.readouterr().out - assert out.startswith("OK:") and "claude-code" in out + assert out.startswith("OK:") and "ALERT" in out assert "adr_live_x" not in out # key value never printed def test_verify_reports_fail( From 6403d2e2f5183a0bd6582db3affc097d8e3553b6 Mon Sep 17 00:00:00 2001 From: yanny-sec Date: Wed, 12 Aug 2026 13:33:13 +0100 Subject: [PATCH 4/5] feat(sdk,backend): capture model reasoning and feed it to the classifier --- backend/internal/engine/client_test.go | 48 ++++++ backend/internal/engine/trace.go | 13 +- backend/internal/ws/pairing.go | 1 + backend/internal/ws/pairing_test.go | 47 ++++++ sdk/python/adrian/anthropic_handler.py | 53 ++++++ sdk/python/adrian/format/types.py | 2 + sdk/python/adrian/handler.py | 52 ++++++ sdk/python/adrian/pairing.py | 1 + sdk/python/adrian/pii/_redactor.py | 2 + sdk/python/adrian/types.py | 2 + sdk/python/adrian/ws.py | 1 + sdk/python/tests/test_anthropic_handler.py | 156 +++++++++++++++++- sdk/python/tests/test_handler.py | 79 +++++++++ sdk/python/tests/test_pii_redactor.py | 20 +++ sdk/python/tests/test_ws.py | 18 ++ .../packages/core/src/proto/schema.ts | 2 +- 16 files changed, 493 insertions(+), 4 deletions(-) create mode 100644 backend/internal/ws/pairing_test.go diff --git a/backend/internal/engine/client_test.go b/backend/internal/engine/client_test.go index 24799a6..a22f32c 100644 --- a/backend/internal/engine/client_test.go +++ b/backend/internal/engine/client_test.go @@ -189,6 +189,54 @@ func TestExtractTraceLLMWithParent(t *testing.T) { } } +// TestExtractTraceLLMReasoning covers a client that sends reasoning: +// it becomes the chain of thought and the output moves to its own +// labelled line. Both are wrapped, reasoning is model-generated and no +// more trustworthy than the response. +func TestExtractTraceLLMReasoning(t *testing.T) { + ev := &pb.PairedEvent{ + PairType: pb.PairType_PAIR_TYPE_LLM, + Agent: &pb.AgentContext{AgentId: "billing"}, + Data: &pb.PairedEvent_Llm{ + Llm: &pb.LlmPairData{ + Model: "claude-opus-5", + Output: "Refund issued.", + Reasoning: "Order is 5 days old so the policy allows it.", + }, + }, + } + const guid = "test-guid" + got := extractTrace(ev, guid) + wantWrapped := []string{ + `Chain of Thought: Order is 5 days old so the policy allows it.`, + `Response: Refund issued.`, + } + for _, w := range wantWrapped { + if !strings.Contains(got, w) { + t.Errorf("extractTrace missing line %q in:\n%s", w, got) + } + } +} + +// TestExtractTraceLLMReasoningAbsentUnchanged pins the pre-reasoning +// rendering for clients that never set the field: output stays the +// chain of thought and no Response line appears. +func TestExtractTraceLLMReasoningAbsentUnchanged(t *testing.T) { + ev := &pb.PairedEvent{ + PairType: pb.PairType_PAIR_TYPE_LLM, + Agent: &pb.AgentContext{AgentId: "billing"}, + Data: &pb.PairedEvent_Llm{ + Llm: &pb.LlmPairData{Model: "gpt-4o-mini", Output: "Refund issued."}, + }, + } + got := extractTrace(ev, "test-guid") + want := "\nClassify this agent trace:\n\n" + + `Chain of Thought: Refund issued.` + if got != want { + t.Errorf("rendering changed for a client without reasoning:\ngot: %q\nwant: %q", got, want) + } +} + // ----------------------------------------------------------------- // HTTPClient.Classify // ----------------------------------------------------------------- diff --git a/backend/internal/engine/trace.go b/backend/internal/engine/trace.go index c55b6f1..75ef716 100644 --- a/backend/internal/engine/trace.go +++ b/backend/internal/engine/trace.go @@ -68,11 +68,20 @@ func writeLLMSection(b *strings.Builder, llm *pb.LlmPairData, guid string) { if llm == nil { return } - writeLabelled(b, "Chain of Thought: ", llm.Output, guid) + // Clients that send reasoning put it here; older ones send only output, + // which has always been rendered under this label. + cot := llm.Reasoning + if cot == "" { + cot = llm.Output + } + writeLabelled(b, "Chain of Thought: ", cot, guid) + if llm.Reasoning != "" { + writeLabelled(b, "Response: ", llm.Output, guid) + } if len(llm.ToolCalls) == 0 { return } - if llm.Output != "" { + if cot != "" { b.WriteString("\n") } b.WriteString("Tool Calls:\n") diff --git a/backend/internal/ws/pairing.go b/backend/internal/ws/pairing.go index e064919..5b6d6a6 100644 --- a/backend/internal/ws/pairing.go +++ b/backend/internal/ws/pairing.go @@ -36,6 +36,7 @@ func pairedEventToJSON(ev *pb.PairedEvent) (string, error) { view["llm"] = map[string]any{ "model": llm.Model, "output": llm.Output, + "reasoning": llm.Reasoning, "messages": chatMessagesToJSON(llm.Messages), "tool_calls": toolCallsToJSON(llm.ToolCalls), "usage": tokenUsageToJSON(llm.Usage), diff --git a/backend/internal/ws/pairing_test.go b/backend/internal/ws/pairing_test.go new file mode 100644 index 0000000..e010ca8 --- /dev/null +++ b/backend/internal/ws/pairing_test.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2026 SecureAgentics + +package ws + +import ( + "encoding/json" + "testing" + + pb "github.com/secureagentics/Adrian/backend/internal/proto" +) + +// TestPairedEventToJSONReasoning checks the reasoning field survives the +// proto to JSON conversion, the dashboard and the engine read the +// persisted payload rather than the wire message. +func TestPairedEventToJSONReasoning(t *testing.T) { + ev := &pb.PairedEvent{ + EventId: "ev-1", + PairType: pb.PairType_PAIR_TYPE_LLM, + Data: &pb.PairedEvent_Llm{ + Llm: &pb.LlmPairData{ + Model: "claude-opus-5", + Output: "Refund issued.", + Reasoning: "Order is within the refund window.", + }, + }, + } + payload, err := pairedEventToJSON(ev) + if err != nil { + t.Fatalf("pairedEventToJSON: %v", err) + } + var got struct { + LLM struct { + Output string `json:"output"` + Reasoning string `json:"reasoning"` + } `json:"llm"` + } + if err := json.Unmarshal([]byte(payload), &got); err != nil { + t.Fatalf("unmarshal payload: %v", err) + } + if got.LLM.Reasoning != "Order is within the refund window." { + t.Errorf("reasoning = %q, want it persisted", got.LLM.Reasoning) + } + if got.LLM.Output != "Refund issued." { + t.Errorf("output = %q, want it left alone", got.LLM.Output) + } +} diff --git a/sdk/python/adrian/anthropic_handler.py b/sdk/python/adrian/anthropic_handler.py index f2f2194..58167fc 100644 --- a/sdk/python/adrian/anthropic_handler.py +++ b/sdk/python/adrian/anthropic_handler.py @@ -284,6 +284,53 @@ def _extract_response_text(content: list[Any]) -> str: return "\n".join(p for p in parts if p) +def _request_summarised_thinking(kwargs: dict[str, Any]) -> None: + """Ask for summarised thinking when the caller enabled it but left it hidden. + + ``thinking.display`` defaults to ``"omitted"`` on current models, so + thinking blocks arrive with empty text and there is nothing to capture. + ``display`` controls visibility only: thinking still runs and bills the + same either way, so opting in costs nothing. Callers who set ``display`` + themselves are left alone, as are callers who never enabled thinking. + + Args: + kwargs: ``messages.create`` keyword arguments, mutated in place. + """ + thinking = kwargs.get("thinking") + + if not isinstance(thinking, dict): + return + + if thinking.get("type") not in ("adaptive", "enabled"): + return + + if "display" in thinking: + return + + kwargs["thinking"] = {**thinking, "display": "summarized"} + + +def _extract_reasoning(content: list[Any]) -> str: + """Extract the model's reasoning from an Anthropic response content list. + + Args: + content: ``Message.content`` from the Anthropic response. + + Returns: + Concatenated text from all ``ThinkingBlock`` entries. Empty when the + model produced none, or when ``thinking.display`` left them redacted. + """ + parts: list[str] = [] + + for block in content: + if hasattr(block, "type") and block.type == "thinking": + parts.append(getattr(block, "thinking", "")) + elif isinstance(block, dict) and block.get("type") == "thinking": + parts.append(str(block.get("thinking", ""))) + + return "\n\n".join(p for p in parts if p) + + def _derive_agent_id(messages: list[ChatMessage]) -> str: """Derive a stable agent identity from the system prompt. @@ -349,6 +396,7 @@ def build_anthropic_llm_pair( content: list[Any] = getattr(response, "content", []) output_text = _extract_response_text(content) + reasoning = _extract_reasoning(content) tool_calls = _extract_anthropic_tool_calls(content) usage = _extract_anthropic_usage(response) @@ -374,6 +422,7 @@ def build_anthropic_llm_pair( output=output_text, tool_calls=tool_calls, usage=usage, + reasoning=reasoning, ), ) @@ -1054,6 +1103,7 @@ def _patched_sync_create( *args: Any, **kwargs: Any, # noqa: ANN401 ) -> Any: # noqa: ANN401 + _request_summarised_thinking(kwargs) response = _original_sync(self, *args, **kwargs) # ChatAnthropic routes through here, but the LangChain @@ -1077,6 +1127,7 @@ def _patched_sync_stream( # Sampled now, not at consumption time: the caller may read the # final message after leaving the anthropic_invocation() block. captured = get_invocation_id() + _request_summarised_thinking(kwargs) inner = _original_sync_stream(self, *args, **kwargs) # Owned by the LangChain callbacks: hand back the SDK's own @@ -1108,6 +1159,7 @@ async def _patched_async_create( *args: Any, **kwargs: Any, # noqa: ANN401 ) -> Any: # noqa: ANN401 + _request_summarised_thinking(kwargs) response = await _original_async(self, *args, **kwargs) # See the sync wrapper: the LangChain callbacks own this @@ -1131,6 +1183,7 @@ def _patched_async_stream( # Not async: stream() returns an async context manager without # being awaited itself. captured = get_invocation_id() + _request_summarised_thinking(kwargs) inner = _original_async_stream(self, *args, **kwargs) # See the sync stream wrapper. diff --git a/sdk/python/adrian/format/types.py b/sdk/python/adrian/format/types.py index 7541b84..1f07504 100644 --- a/sdk/python/adrian/format/types.py +++ b/sdk/python/adrian/format/types.py @@ -72,6 +72,7 @@ class LlmPairData: output: Generated text output from llm_end. tool_calls: Tool calls requested by the model. usage: Token usage statistics, or ``None``. + reasoning: The model's reasoning, where the provider exposes it. """ model: str @@ -79,6 +80,7 @@ class LlmPairData: output: str = "" tool_calls: list[ToolCallRecord] = field(default_factory=list) usage: TokenUsage | None = None + reasoning: str = "" @dataclass(slots=True) diff --git a/sdk/python/adrian/handler.py b/sdk/python/adrian/handler.py index 166b7d2..948b8f2 100644 --- a/sdk/python/adrian/handler.py +++ b/sdk/python/adrian/handler.py @@ -368,6 +368,7 @@ async def on_llm_end( **kwargs: Additional LangChain callback kwargs. """ output = "" + reasoning = "" typed_tool_calls: list[ToolCallRecord] = [] if response.generations: @@ -376,6 +377,7 @@ async def on_llm_end( if isinstance(gen, ChatGeneration): msg = gen.message + reasoning = _extract_reasoning(msg) raw_calls: list[dict[str, str | dict[str, object]]] = ( getattr(msg, "tool_calls", None) or [] ) @@ -404,6 +406,7 @@ async def on_llm_end( "output": output, "tool_calls": typed_tool_calls, "usage": usage, + "reasoning": reasoning, } session_id = self._resolve_session_id() @@ -539,6 +542,55 @@ async def on_tool_end( # ------------------------------------------------------------------ +def _extract_reasoning(message: BaseMessage) -> str: + """Pull the model's reasoning out of an AIMessage's content blocks. + + ``ChatGeneration.text`` concatenates text blocks only, so reasoning is + dropped unless read off the blocks directly. Two provider shapes are + recognised: OpenAI's ``reasoning`` block carrying a ``summary`` list, + and Anthropic's ``thinking`` block carrying a ``thinking`` string. + + Args: + message: The AIMessage from the generation. + + Returns: + Reasoning text, or ``""`` when the model exposed none. + """ + content: Any = getattr(message, "content", None) + + if not isinstance(content, list): + return "" + + parts: list[str] = [] + + for raw in cast(list[object], content): + if not isinstance(raw, dict): + continue + + block = cast(dict[str, Any], raw) + btype = block.get("type") + + if btype == "reasoning": + summary: Any = block.get("summary") + + if isinstance(summary, list): + for entry in cast(list[object], summary): + if not isinstance(entry, dict): + continue + + text: Any = cast(dict[str, Any], entry).get("text") + + if text: + parts.append(str(text)) + elif btype == "thinking": + thinking: Any = block.get("thinking") + + if isinstance(thinking, str) and thinking: + parts.append(thinking) + + return "\n\n".join(parts) + + def _build_llm_start_data( model: str, messages: list[ChatMessage], diff --git a/sdk/python/adrian/pairing.py b/sdk/python/adrian/pairing.py index be63728..2a76acb 100644 --- a/sdk/python/adrian/pairing.py +++ b/sdk/python/adrian/pairing.py @@ -221,6 +221,7 @@ def _assemble_llm_pair( output=llm_end.get("output", ""), tool_calls=tool_calls, usage=llm_end.get("usage"), + reasoning=llm_end.get("reasoning", ""), ), parent_run_id=start.parent_run_id, metadata=start.metadata, diff --git a/sdk/python/adrian/pii/_redactor.py b/sdk/python/adrian/pii/_redactor.py index 9f99e91..9765b63 100644 --- a/sdk/python/adrian/pii/_redactor.py +++ b/sdk/python/adrian/pii/_redactor.py @@ -101,6 +101,7 @@ def _iter_event_text(self, event: PairedEvent) -> list[str]: for msg in event.data.messages: texts.append(msg["content"]) texts.append(event.data.output) + texts.append(event.data.reasoning) for tc in event.data.tool_calls: _collect_strings(tc.get("args", {}), texts) else: @@ -142,6 +143,7 @@ def _redact_llm_data(self, data: LlmPairData) -> None: msg["content"] = self._redact_str(msg["content"]) data.output = self._redact_str(data.output) + data.reasoning = self._redact_str(data.reasoning) for tc in data.tool_calls: args = tc["args"] diff --git a/sdk/python/adrian/types.py b/sdk/python/adrian/types.py index b934a17..37d7b52 100644 --- a/sdk/python/adrian/types.py +++ b/sdk/python/adrian/types.py @@ -116,11 +116,13 @@ class LlmEndData(TypedDict): output: Generated text output. tool_calls: Tool calls extracted from the response. usage: Token usage statistics, or ``None`` if unavailable. + reasoning: The model's reasoning, where the provider exposes it. """ output: str tool_calls: list[ToolCallRecord] usage: TokenUsage | None + reasoning: str class ToolStartData(TypedDict): diff --git a/sdk/python/adrian/ws.py b/sdk/python/adrian/ws.py index 2462807..4254ccd 100644 --- a/sdk/python/adrian/ws.py +++ b/sdk/python/adrian/ws.py @@ -154,6 +154,7 @@ def _paired_event_to_proto(event: PairedEvent) -> pb.PairedEvent: pb_msg.content = msg["content"] proto.llm.output = event.data.output + proto.llm.reasoning = event.data.reasoning for tc in event.data.tool_calls: pb_tc = proto.llm.tool_calls.add() diff --git a/sdk/python/tests/test_anthropic_handler.py b/sdk/python/tests/test_anthropic_handler.py index ff2848f..7e8fa56 100644 --- a/sdk/python/tests/test_anthropic_handler.py +++ b/sdk/python/tests/test_anthropic_handler.py @@ -23,12 +23,14 @@ _emit_pair, _extract_anthropic_tool_calls, _extract_anthropic_usage, + _extract_reasoning, _extract_response_text, _flatten_anthropic_messages, _flatten_content, _gate_response, _GatedAsyncMessageStreamManager, _GatedMessageStreamManager, + _request_summarised_thinking, _resolve_invocation_id, _rewrite_blocked_response, _safe_snapshot, @@ -92,6 +94,23 @@ def _make_text_response( return response +def _make_thinking_response( + *, + model: str = "claude-opus-5", + thinking: str = "Let me work through this.", + text: str = "Hello!", +) -> MagicMock: + """Mock response whose content leads with a thinking block.""" + thinking_block = MagicMock() + thinking_block.type = "thinking" + thinking_block.thinking = thinking + + response = _make_text_response(model=model, text=text) + response.content = [thinking_block, *response.content] + + return response + + def _wired_hooks(config: AdrianConfig) -> tuple[HookRegistry, _Collector]: """Return a HookRegistry + Collector pair and wire them into the handler.""" collector = _Collector() @@ -553,6 +572,121 @@ def test_parent_is_none(self) -> None: ) assert pair.parent is None + def test_thinking_becomes_reasoning(self) -> None: + pair = build_anthropic_llm_pair( + flat_messages=[ChatMessage(role="user", content="hi")], + response=_make_thinking_response( + thinking="Weighing the options.", text="Done." + ), + model="m", + session_id="s", + invocation_id="i", + run_id="r", + ) + assert isinstance(pair.data, LlmPairData) + assert pair.data.reasoning == "Weighing the options." + # Reasoning is additive: the response text is untouched. + assert pair.data.output == "Done." + + def test_no_thinking_leaves_reasoning_empty(self) -> None: + pair = build_anthropic_llm_pair( + flat_messages=[ChatMessage(role="user", content="hi")], + response=_make_text_response(text="Done."), + model="m", + session_id="s", + invocation_id="i", + run_id="r", + ) + assert isinstance(pair.data, LlmPairData) + assert pair.data.reasoning == "" + + +# ------------------------------------------------------------------ +# _extract_reasoning +# ------------------------------------------------------------------ + + +class TestExtractReasoning: + def test_object_thinking_block(self) -> None: + block = MagicMock() + block.type = "thinking" + block.thinking = "step one" + assert _extract_reasoning([block]) == "step one" + + def test_dict_thinking_block(self) -> None: + blocks = [{"type": "thinking", "thinking": "step one"}] + assert _extract_reasoning(blocks) == "step one" + + def test_multiple_blocks_joined(self) -> None: + blocks = [ + {"type": "thinking", "thinking": "step one"}, + {"type": "text", "text": "ignored"}, + {"type": "thinking", "thinking": "step two"}, + ] + assert _extract_reasoning(blocks) == "step one\n\nstep two" + + def test_redacted_thinking_is_not_reasoning(self) -> None: + # redacted_thinking carries an encrypted blob, not readable text. + blocks = [{"type": "redacted_thinking", "data": "AAAA"}] + assert _extract_reasoning(blocks) == "" + + def test_omitted_thinking_yields_nothing(self) -> None: + # display="omitted" still emits a block, with empty text. + blocks = [{"type": "thinking", "thinking": "", "signature": "sig"}] + assert _extract_reasoning(blocks) == "" + + def test_text_only_content(self) -> None: + assert _extract_reasoning([{"type": "text", "text": "hi"}]) == "" + + +# ------------------------------------------------------------------ +# _request_summarised_thinking +# ------------------------------------------------------------------ + + +class TestRequestSummarisedThinking: + def test_adaptive_gets_summarized_display(self) -> None: + kwargs: dict[str, Any] = {"thinking": {"type": "adaptive"}} + _request_summarised_thinking(kwargs) + assert kwargs["thinking"] == {"type": "adaptive", "display": "summarized"} + + def test_enabled_gets_summarized_display(self) -> None: + kwargs: dict[str, Any] = { + "thinking": {"type": "enabled", "budget_tokens": 2048} + } + _request_summarised_thinking(kwargs) + assert kwargs["thinking"]["display"] == "summarized" + assert kwargs["thinking"]["budget_tokens"] == 2048 + + def test_explicit_display_is_respected(self) -> None: + kwargs: dict[str, Any] = { + "thinking": {"type": "adaptive", "display": "omitted"} + } + _request_summarised_thinking(kwargs) + assert kwargs["thinking"]["display"] == "omitted" + + def test_thinking_disabled_untouched(self) -> None: + kwargs: dict[str, Any] = {"thinking": {"type": "disabled"}} + _request_summarised_thinking(kwargs) + assert kwargs["thinking"] == {"type": "disabled"} + + def test_no_thinking_key_untouched(self) -> None: + kwargs: dict[str, Any] = {"model": "claude-opus-5"} + _request_summarised_thinking(kwargs) + assert "thinking" not in kwargs + + def test_non_dict_thinking_untouched(self) -> None: + kwargs: dict[str, Any] = {"thinking": "adaptive"} + _request_summarised_thinking(kwargs) + assert kwargs["thinking"] == "adaptive" + + def test_caller_dict_is_not_mutated_in_place(self) -> None: + # The caller may reuse their thinking config across calls. + original = {"type": "adaptive"} + kwargs: dict[str, Any] = {"thinking": original} + _request_summarised_thinking(kwargs) + assert original == {"type": "adaptive"} + # ------------------------------------------------------------------ # _emit_pair @@ -1884,7 +2018,9 @@ class TestChatModelScopeSuppression: """ChatAnthropic drives the same SDK methods Adrian patches. Without suppression one LangChain call produces two LLM events: one - from the callbacks and one from the Anthropic patch. + from the callbacks and one from the Anthropic patch. The thinking + injection must survive suppression, since it is what gives the + LangChain event its reasoning in the first place. """ @pytest.fixture(autouse=True) # pyright: ignore[reportUntypedFunctionDecorator] @@ -1947,6 +2083,24 @@ def test_event_emitted_outside_scope(self) -> None: assert len(collector.events) == 1 + def test_thinking_injection_still_applies_inside_scope(self) -> None: + from anthropic.resources.messages import Messages + + seen: list[dict[str, Any]] = [] + + def _capture(_self: Any, **kw: Any) -> Any: # noqa: ANN401 + seen.append(kw) + return _make_text_response() + + Messages.create = _capture # type: ignore[method-assign, assignment] + hooks, _ = _wired_hooks(AdrianConfig(session_id="s")) + patch_anthropic(lambda: hooks, lambda: AdrianConfig(session_id="s")) + + with chat_model_scope(): + Messages.create(MagicMock(), thinking={"type": "adaptive"}, **_KWARGS) + + assert seen[0]["thinking"] == {"type": "adaptive", "display": "summarized"} + def test_stream_returns_sdk_manager_inside_scope(self) -> None: from anthropic.resources.messages import Messages diff --git a/sdk/python/tests/test_handler.py b/sdk/python/tests/test_handler.py index eed45f6..d20504f 100644 --- a/sdk/python/tests/test_handler.py +++ b/sdk/python/tests/test_handler.py @@ -212,6 +212,85 @@ async def test_tool_calls_extracted( assert data.tool_calls[0]["name"] == "get_fleet_status" +class TestReasoningInLlmPair: + """Reasoning blocks are captured alongside, not instead of, output. + + The shapes here are the two the providers actually emit: OpenAI's + ``reasoning`` block with a summary list, and Anthropic's ``thinking`` + block, both reaching us through LangChain as content blocks. + """ + + async def _emit( + self, + handler: AdrianCallbackHandler, + message: AIMessage, + ) -> None: + run_id = uuid4() + await handler.on_chat_model_start( + serialized={"kwargs": {"model_name": "gpt-5"}}, + messages=[[HumanMessage(content="17 * 23?")]], + run_id=run_id, + ) + await handler.on_llm_end( + response=LLMResult(generations=[[ChatGeneration(message=message)]]), + run_id=run_id, + ) + + async def test_openai_reasoning_summary_captured( + self, + handler_and_events: tuple[AdrianCallbackHandler, list[PairedEvent]], + ) -> None: + handler, events = handler_and_events + msg = AIMessage( + content=[ + { + "type": "reasoning", + "summary": [ + {"type": "summary_text", "text": "First step."}, + {"type": "summary_text", "text": "Second step."}, + ], + }, + {"type": "text", "text": "391"}, + ] + ) + await self._emit(handler, msg) + + data = events[0].data + assert isinstance(data, LlmPairData) + assert data.reasoning == "First step.\n\nSecond step." + assert data.output == "391" + + async def test_anthropic_thinking_captured( + self, + handler_and_events: tuple[AdrianCallbackHandler, list[PairedEvent]], + ) -> None: + handler, events = handler_and_events + msg = AIMessage( + content=[ + {"type": "thinking", "thinking": "17 * 20 + 17 * 3."}, + {"type": "text", "text": "391"}, + ] + ) + await self._emit(handler, msg) + + data = events[0].data + assert isinstance(data, LlmPairData) + assert data.reasoning == "17 * 20 + 17 * 3." + assert data.output == "391" + + async def test_plain_string_content_has_no_reasoning( + self, + handler_and_events: tuple[AdrianCallbackHandler, list[PairedEvent]], + ) -> None: + handler, events = handler_and_events + await self._emit(handler, AIMessage(content="391")) + + data = events[0].data + assert isinstance(data, LlmPairData) + assert data.reasoning == "" + assert data.output == "391" + + class TestExtractModelName: def test_none_returns_unknown(self) -> None: assert extract_model_name(None) == "unknown" diff --git a/sdk/python/tests/test_pii_redactor.py b/sdk/python/tests/test_pii_redactor.py index fcf7b29..9c99394 100644 --- a/sdk/python/tests/test_pii_redactor.py +++ b/sdk/python/tests/test_pii_redactor.py @@ -24,6 +24,7 @@ def _llm_event( system_prompt: str = "", user_instruction: str = "", parent: ParentContext | None = None, + reasoning: str = "", ) -> PairedEvent: return PairedEvent( event_id="evt-1", @@ -43,6 +44,7 @@ def _llm_event( messages=messages or [], # type: ignore[arg-type] output=output, tool_calls=tool_calls or [], # type: ignore[arg-type] + reasoning=reasoning, ), ) @@ -99,6 +101,24 @@ def test_redacts_tool_call_args(self) -> None: args = redacted.data.tool_calls[0]["args"] assert "user@test.com" not in str(args) + def test_redacts_reasoning(self) -> None: + # Reasoning quotes the prompt back verbatim, so it leaks the same + # PII the other fields do. + event = _llm_event(reasoning="The user gave 555-123-4567 as a contact") + redactor = PiiRedactor() + redacted = redactor.redact_event(event) + assert isinstance(redacted.data, LlmPairData) + assert "555-123-4567" not in redacted.data.reasoning + assert "[PHONE_REDACTED]" in redacted.data.reasoning + + def test_reasoning_pii_triggers_a_copy(self) -> None: + # A hit anywhere must not mutate the caller's event in place. + event = _llm_event(reasoning="email user@test.com") + redacted = PiiRedactor().redact_event(event) + assert redacted is not event + assert isinstance(event.data, LlmPairData) + assert event.data.reasoning == "email user@test.com" + def test_redacts_system_prompt(self) -> None: event = _llm_event(system_prompt="Contact admin@corp.com for help") redactor = PiiRedactor() diff --git a/sdk/python/tests/test_ws.py b/sdk/python/tests/test_ws.py index cbb6da3..2f1f805 100644 --- a/sdk/python/tests/test_ws.py +++ b/sdk/python/tests/test_ws.py @@ -102,6 +102,24 @@ def test_llm_pair_with_tool_calls(self) -> None: assert proto.llm.tool_calls[0].id == "tc-1" assert proto.llm.tool_calls[0].name == "foo" + def test_llm_reasoning_serialised(self) -> None: + pe = _llm_pair() + assert isinstance(pe.data, LlmPairData) + pe.data.reasoning = "thinking it through" + proto = _paired_event_to_proto(pe) + assert proto.llm.reasoning == "thinking it through" + assert proto.llm.output == "sure" + + def test_llm_reasoning_absent_costs_nothing(self) -> None: + # An unset proto3 scalar is not serialised, so older clients that + # never populate reasoning put no extra bytes on the wire. + pe = _llm_pair() + proto = _paired_event_to_proto(pe) + assert proto.llm.reasoning == "" + without = len(proto.SerializeToString()) + proto.llm.reasoning = "x" + assert len(proto.SerializeToString()) > without + def test_parent_context(self) -> None: pe = _tool_pair() pe.parent = ParentContext( diff --git a/sdk/typescript/packages/core/src/proto/schema.ts b/sdk/typescript/packages/core/src/proto/schema.ts index 818a722..9fb48b0 100644 --- a/sdk/typescript/packages/core/src/proto/schema.ts +++ b/sdk/typescript/packages/core/src/proto/schema.ts @@ -61,7 +61,7 @@ message ChatMessage { string role = 1; string content = 2; } message ToolCall { string name = 1; string args = 2; string id = 3; } message TokenUsage { int32 prompt_tokens = 1; int32 completion_tokens = 2; int32 total_tokens = 3; } message AgentContext { string agent_id = 1; string system_prompt = 2; string user_instruction = 3; } -message LlmPairData { string model = 1; repeated ChatMessage messages = 2; string output = 3; repeated ToolCall tool_calls = 4; TokenUsage usage = 5; } +message LlmPairData { string model = 1; repeated ChatMessage messages = 2; string output = 3; repeated ToolCall tool_calls = 4; TokenUsage usage = 5; string reasoning = 6; } message ToolPairData { string tool_name = 1; string tool_call_id = 2; string input = 3; string output = 4; } message PairedEvent { string event_id = 1; string invocation_id = 2; string session_id = 3; string run_id = 4; string parent_run_id = 5; string timestamp = 6; PairType pair_type = 7; AgentContext agent = 8; AgentContext parent = 9; oneof data { LlmPairData llm = 10; ToolPairData tool = 11; } bytes metadata_json = 20; } message PairedEventBatch { repeated PairedEvent events = 1; } From 9f04ffa944c8645371440ff126d013f678ee1497 Mon Sep 17 00:00:00 2001 From: yanny-sec Date: Mon, 17 Aug 2026 12:28:41 +0100 Subject: [PATCH 5/5] fix(sdk): capture reasoning from the v0 and v1 langchain output shapes --- sdk/python/adrian/handler.py | 99 +++++++++++++++++++++++--------- sdk/python/tests/test_handler.py | 97 +++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+), 27 deletions(-) diff --git a/sdk/python/adrian/handler.py b/sdk/python/adrian/handler.py index 948b8f2..173aa89 100644 --- a/sdk/python/adrian/handler.py +++ b/sdk/python/adrian/handler.py @@ -542,13 +542,57 @@ async def on_tool_end( # ------------------------------------------------------------------ +def _reasoning_block_text(block: dict[str, Any]) -> str: + """Read the human-readable text out of one reasoning block. + + Providers and LangChain output versions disagree on where the text + sits, so three layouts are accepted: + + * a ``summary`` list of ``{"type": "summary_text", "text": ...}`` + entries (OpenAI Responses, LangChain's default and + ``responses/v1``), + * a bare ``reasoning`` string (LangChain ``output_version="v1"``, + which emits one block per reasoning pass), + * Anthropic's ``thinking`` string. + + ``encrypted_content`` is skipped: it is an opaque blob for + multi-turn reuse, not readable text. + """ + parts: list[str] = [] + summary: Any = block.get("summary") + + if isinstance(summary, list): + for entry in cast(list[object], summary): + if not isinstance(entry, dict): + continue + + text: Any = cast(dict[str, Any], entry).get("text") + + if text: + parts.append(str(text)) + + if parts: + return "\n\n".join(parts) + + # No summary: fall through to the single-string layouts. Checked + # second so a populated summary never gets duplicated by a block + # that carries both. + for key in ("reasoning", "thinking"): + value: Any = block.get(key) + + if isinstance(value, str) and value: + return value + + return "" + + def _extract_reasoning(message: BaseMessage) -> str: - """Pull the model's reasoning out of an AIMessage's content blocks. + """Pull the model's reasoning off an AIMessage. - ``ChatGeneration.text`` concatenates text blocks only, so reasoning is - dropped unless read off the blocks directly. Two provider shapes are - recognised: OpenAI's ``reasoning`` block carrying a ``summary`` list, - and Anthropic's ``thinking`` block carrying a ``thinking`` string. + ``ChatGeneration.text`` concatenates text blocks only, so reasoning + is dropped unless read off the message directly. Content blocks are + the usual home; ``output_version="v0"`` instead parks the whole + block in ``additional_kwargs``, so that is checked as a fallback. Args: message: The AIMessage from the generation. @@ -557,38 +601,39 @@ def _extract_reasoning(message: BaseMessage) -> str: Reasoning text, or ``""`` when the model exposed none. """ content: Any = getattr(message, "content", None) + parts: list[str] = [] - if not isinstance(content, list): - return "" + if isinstance(content, list): + for raw in cast(list[object], content): + if not isinstance(raw, dict): + continue - parts: list[str] = [] + block = cast(dict[str, Any], raw) + + # redacted_thinking carries an encrypted blob, never text. + if block.get("type") not in ("reasoning", "thinking"): + continue - for raw in cast(list[object], content): - if not isinstance(raw, dict): - continue + text = _reasoning_block_text(block) - block = cast(dict[str, Any], raw) - btype = block.get("type") + if text: + parts.append(text) - if btype == "reasoning": - summary: Any = block.get("summary") + if parts: + return "\n\n".join(parts) - if isinstance(summary, list): - for entry in cast(list[object], summary): - if not isinstance(entry, dict): - continue + extra: Any = getattr(message, "additional_kwargs", None) - text: Any = cast(dict[str, Any], entry).get("text") + if isinstance(extra, dict): + blob: Any = cast(dict[str, Any], extra).get("reasoning") - if text: - parts.append(str(text)) - elif btype == "thinking": - thinking: Any = block.get("thinking") + if isinstance(blob, dict): + return _reasoning_block_text(cast(dict[str, Any], blob)) - if isinstance(thinking, str) and thinking: - parts.append(thinking) + if isinstance(blob, str): + return blob - return "\n\n".join(parts) + return "" def _build_llm_start_data( diff --git a/sdk/python/tests/test_handler.py b/sdk/python/tests/test_handler.py index d20504f..2b6f8a2 100644 --- a/sdk/python/tests/test_handler.py +++ b/sdk/python/tests/test_handler.py @@ -290,6 +290,103 @@ async def test_plain_string_content_has_no_reasoning( assert data.reasoning == "" assert data.output == "391" + async def test_output_version_v1_reasoning_string_blocks( + self, + handler_and_events: tuple[AdrianCallbackHandler, list[PairedEvent]], + ) -> None: + # output_version="v1" drops the summary list and emits one block + # per reasoning pass, each carrying a bare `reasoning` string. + handler, events = handler_and_events + msg = AIMessage( + content=[ + {"type": "reasoning", "id": "rs_1", "reasoning": "First pass."}, + {"type": "reasoning", "id": "rs_1", "reasoning": "Second pass."}, + {"type": "text", "text": "391"}, + ] + ) + await self._emit(handler, msg) + + data = events[0].data + assert isinstance(data, LlmPairData) + assert data.reasoning == "First pass.\n\nSecond pass." + assert data.output == "391" + + async def test_output_version_v0_reasoning_in_additional_kwargs( + self, + handler_and_events: tuple[AdrianCallbackHandler, list[PairedEvent]], + ) -> None: + # output_version="v0" keeps content text-only and parks the whole + # reasoning block in additional_kwargs. + handler, events = handler_and_events + msg = AIMessage( + content=[{"type": "text", "text": "391"}], + additional_kwargs={ + "reasoning": { + "id": "rs_1", + "type": "reasoning", + "summary": [ + {"type": "summary_text", "text": "First step."}, + {"type": "summary_text", "text": "Second step."}, + ], + "encrypted_content": "gAAAA...", + } + }, + ) + await self._emit(handler, msg) + + data = events[0].data + assert isinstance(data, LlmPairData) + assert data.reasoning == "First step.\n\nSecond step." + assert data.output == "391" + + async def test_content_blocks_win_over_additional_kwargs( + self, + handler_and_events: tuple[AdrianCallbackHandler, list[PairedEvent]], + ) -> None: + # The fallback must not append to, or duplicate, a block that + # content already supplied. + handler, events = handler_and_events + msg = AIMessage( + content=[ + { + "type": "reasoning", + "summary": [{"type": "summary_text", "text": "From content."}], + }, + {"type": "text", "text": "391"}, + ], + additional_kwargs={"reasoning": {"reasoning": "From kwargs."}}, + ) + await self._emit(handler, msg) + + data = events[0].data + assert isinstance(data, LlmPairData) + assert data.reasoning == "From content." + + async def test_encrypted_only_reasoning_yields_nothing( + self, + handler_and_events: tuple[AdrianCallbackHandler, list[PairedEvent]], + ) -> None: + # o4-mini on an unverified org: block present, summary empty, the + # real chain of thought only as an opaque blob. + handler, events = handler_and_events + msg = AIMessage( + content=[ + { + "type": "reasoning", + "summary": [], + "content": [], + "encrypted_content": "gAAAAABqgu5W...", + }, + {"type": "text", "text": "391"}, + ] + ) + await self._emit(handler, msg) + + data = events[0].data + assert isinstance(data, LlmPairData) + assert data.reasoning == "" + assert data.output == "391" + class TestExtractModelName: def test_none_returns_unknown(self) -> None: