| 52 | |
| 53 | |
| 54 | class ChatDocMetaData(DocMetaData): |
| 55 | parent_id: str = "" # msg (ChatDocument) to which this is a response |
| 56 | child_id: str = "" # ChatDocument that has response to this message |
| 57 | agent_id: str = "" # ChatAgent that generated this message |
| 58 | msg_idx: int = -1 # index of this message in the agent `message_history` |
| 59 | sender: Entity # sender of the message |
| 60 | # True if this msg was relayed from another agent's LLM/handler output in a |
| 61 | # multi-agent handoff (a Task then relabels sender -> USER). Lets handle-only |
| 62 | # tools be dispatched for legitimate handoffs while still blocking raw |
| 63 | # USER-injected tool JSON. See ChatAgent._filter_user_origin_tools and |
| 64 | # GHSA-gjgq-w2m6-wr5q. |
| 65 | tools_from_agent: bool = False |
| 66 | # True if this msg's content/tools derive from external untrusted (USER) |
| 67 | # input via a MECHANICAL path: a deepcopy of a tainted msg, or tools |
| 68 | # repackaged from a USER msg's content (e.g. DonePassTool/AgentDoneTool). |
| 69 | # Unlike `tools_from_agent` (a trust signal), this is a DISTRUST signal that |
| 70 | # vetoes handle-only tool dispatch even across a USER relabel, closing the |
| 71 | # content-laundering hole. Propagated (deepcopy carries it), never cleared. |
| 72 | # See ChatAgent._filter_user_origin_tools and issue #1035 (taint propagation). |
| 73 | tainted: bool = False |
| 74 | # tool_id corresponding to single tool result in ChatDocument.content |
| 75 | oai_tool_id: str | None = None |
| 76 | tool_ids: List[str] = [] # stack of tool_ids; used by OpenAIAssistant |
| 77 | block: None | Entity = None |
| 78 | sender_name: str = "" |
| 79 | recipient: str = "" |
| 80 | usage: Optional[LLMTokenUsage] = None |
| 81 | cached: bool = False |
| 82 | displayed: bool = False |
| 83 | has_citation: bool = False |
| 84 | status: Optional[StatusCode] = None |
| 85 | |
| 86 | @model_validator(mode="after") |
| 87 | def _mark_tools_from_agent(self) -> "ChatDocMetaData": |
| 88 | # Mark messages produced directly by an LLM: the tools in an LLM's |
| 89 | # output are the LLM's own decision (the trusted trigger for handle-only |
| 90 | # tools -- see GHSA-gjgq-w2m6-wr5q), so the mark lets a Task relay them |
| 91 | # to another agent even after relabeling the sender to USER. The mark is |
| 92 | # only ever SET, never cleared, so it survives relabeling and deepcopies |
| 93 | # (e.g. ForwardTool/PassTool deepcopy the LLM-born message, carrying the |
| 94 | # mark across the handoff). We deliberately do NOT mark generic AGENT |
| 95 | # messages: a pass-through/echoing agent could surface untrusted USER |
| 96 | # text (with embedded tool JSON) as AGENT content, and marking that |
| 97 | # would re-open the user-origin bypass. Raw USER input is never marked, |
| 98 | # so it stays filtered. See ChatAgent._filter_user_origin_tools. |
| 99 | if self.sender == Entity.LLM: |
| 100 | self.tools_from_agent = True |
| 101 | return self |
| 102 | |
| 103 | @property |
| 104 | def parent(self) -> Optional["ChatDocument"]: |
| 105 | return ChatDocument.from_id(self.parent_id) |
| 106 | |
| 107 | @property |
| 108 | def child(self) -> Optional["ChatDocument"]: |
| 109 | return ChatDocument.from_id(self.child_id) |
| 110 | |
| 111 |
no outgoing calls
searching dependent graphs…