Skip to content

Commit 95db71d

Browse files
authored
feat: add content block presentation metadata (#320)
* feat: add content block presentation metadata * fix: address Slack content block review feedback * fix: clean up skipped-file summary display * fix: enforce content block metadata limits
1 parent 4daf8f2 commit 95db71d

29 files changed

Lines changed: 1323 additions & 676 deletions

‎api/openapi/openapi.yaml‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7068,6 +7068,19 @@ components:
70687068
- ModelOutputStopReasonRefusal
70697069
- ModelOutputStopReasonContentFilter
70707070
- ModelOutputStopReasonError
7071+
ContentBlockMetadata:
7072+
type: object
7073+
description: >-
7074+
String key-value metadata. Maximum 16 pairs, keys up to 64 characters,
7075+
and values up to 512 characters. Keys beginning with `omnara_` are
7076+
reserved for Omnara and may affect product behavior; use them only when
7077+
intentionally invoking Omnara-defined behavior.
7078+
x-go-type: json.RawMessage
7079+
x-go-type-skip-optional-pointer: true
7080+
maxProperties: 16
7081+
additionalProperties:
7082+
type: string
7083+
maxLength: 512
70717084
TextContentBlock:
70727085
type: object
70737086
additionalProperties: false
@@ -7078,6 +7091,8 @@ components:
70787091
enum: [text]
70797092
text:
70807093
type: string
7094+
metadata:
7095+
$ref: '#/components/schemas/ContentBlockMetadata'
70817096
InlineMediaContentBlock:
70827097
type: object
70837098
additionalProperties: false
@@ -7108,6 +7123,8 @@ components:
71087123
type: string
71097124
format: byte
71107125
minLength: 1
7126+
metadata:
7127+
$ref: '#/components/schemas/ContentBlockMetadata'
71117128
MediaRefContentBlock:
71127129
type: object
71137130
additionalProperties: false
@@ -7118,6 +7135,8 @@ components:
71187135
enum: [media_ref]
71197136
artifact_id:
71207137
$ref: '#/components/schemas/ArtifactID'
7138+
metadata:
7139+
$ref: '#/components/schemas/ContentBlockMetadata'
71217140
ReasoningContentBlock:
71227141
type: object
71237142
additionalProperties: false
@@ -7128,6 +7147,8 @@ components:
71287147
enum: [reasoning]
71297148
text:
71307149
type: string
7150+
metadata:
7151+
$ref: '#/components/schemas/ContentBlockMetadata'
71317152
ErrorContentBlock:
71327153
type: object
71337154
additionalProperties: false
@@ -7138,6 +7159,8 @@ components:
71387159
enum: [error]
71397160
text:
71407161
type: string
7162+
metadata:
7163+
$ref: '#/components/schemas/ContentBlockMetadata'
71417164
StructuredDataContentBlock:
71427165
type: object
71437166
additionalProperties: false
@@ -7148,6 +7171,8 @@ components:
71487171
enum: [structured_data]
71497172
value:
71507173
$ref: '#/components/schemas/JSONBlob'
7174+
metadata:
7175+
$ref: '#/components/schemas/ContentBlockMetadata'
71517176
ModelToolCallContentBlock:
71527177
type: object
71537178
additionalProperties: false
@@ -7164,6 +7189,8 @@ components:
71647189
type: string
71657190
input:
71667191
$ref: '#/components/schemas/ToolInput'
7192+
metadata:
7193+
$ref: '#/components/schemas/ContentBlockMetadata'
71677194
CreateAgentInputContentBlock:
71687195
oneOf:
71697196
- $ref: '#/components/schemas/TextContentBlock'

‎docs/api-reference/openapi.yaml‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7069,6 +7069,19 @@ components:
70697069
- ModelOutputStopReasonRefusal
70707070
- ModelOutputStopReasonContentFilter
70717071
- ModelOutputStopReasonError
7072+
ContentBlockMetadata:
7073+
type: object
7074+
description: >-
7075+
String key-value metadata. Maximum 16 pairs, keys up to 64 characters,
7076+
and values up to 512 characters. Keys beginning with `omnara_` are
7077+
reserved for Omnara and may affect product behavior; use them only when
7078+
intentionally invoking Omnara-defined behavior.
7079+
x-go-type: json.RawMessage
7080+
x-go-type-skip-optional-pointer: true
7081+
maxProperties: 16
7082+
additionalProperties:
7083+
type: string
7084+
maxLength: 512
70727085
TextContentBlock:
70737086
type: object
70747087
additionalProperties: false
@@ -7079,6 +7092,8 @@ components:
70797092
enum: [text]
70807093
text:
70817094
type: string
7095+
metadata:
7096+
$ref: '#/components/schemas/ContentBlockMetadata'
70827097
InlineMediaContentBlock:
70837098
type: object
70847099
additionalProperties: false
@@ -7109,6 +7124,8 @@ components:
71097124
type: string
71107125
format: byte
71117126
minLength: 1
7127+
metadata:
7128+
$ref: '#/components/schemas/ContentBlockMetadata'
71127129
MediaRefContentBlock:
71137130
type: object
71147131
additionalProperties: false
@@ -7119,6 +7136,8 @@ components:
71197136
enum: [media_ref]
71207137
artifact_id:
71217138
$ref: '#/components/schemas/ArtifactID'
7139+
metadata:
7140+
$ref: '#/components/schemas/ContentBlockMetadata'
71227141
ReasoningContentBlock:
71237142
type: object
71247143
additionalProperties: false
@@ -7129,6 +7148,8 @@ components:
71297148
enum: [reasoning]
71307149
text:
71317150
type: string
7151+
metadata:
7152+
$ref: '#/components/schemas/ContentBlockMetadata'
71327153
ErrorContentBlock:
71337154
type: object
71347155
additionalProperties: false
@@ -7139,6 +7160,8 @@ components:
71397160
enum: [error]
71407161
text:
71417162
type: string
7163+
metadata:
7164+
$ref: '#/components/schemas/ContentBlockMetadata'
71427165
StructuredDataContentBlock:
71437166
type: object
71447167
additionalProperties: false
@@ -7149,6 +7172,8 @@ components:
71497172
enum: [structured_data]
71507173
value:
71517174
$ref: '#/components/schemas/JSONBlob'
7175+
metadata:
7176+
$ref: '#/components/schemas/ContentBlockMetadata'
71527177
ModelToolCallContentBlock:
71537178
type: object
71547179
additionalProperties: false
@@ -7165,6 +7190,8 @@ components:
71657190
type: string
71667191
input:
71677192
$ref: '#/components/schemas/ToolInput'
7193+
metadata:
7194+
$ref: '#/components/schemas/ContentBlockMetadata'
71687195
CreateAgentInputContentBlock:
71697196
oneOf:
71707197
- $ref: '#/components/schemas/TextContentBlock'

‎frontend/apps/web/src/components/agents/AgentChatMessage.tsx‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,37 @@
1-
import type { OmnaraUIMessage } from '@omnara/react'
1+
import { type OmnaraUIMessage, useOmnaraClient } from '@omnara/react'
2+
import { getActorOptions } from '@omnara/sdk/tanstack'
3+
import { useQuery } from '@tanstack/react-query'
24
import { Brain, Check, ChevronRight, CircleDashed, Terminal } from 'lucide-react'
35
import { Streamdown } from 'streamdown'
46

57
import { AgentAttachment } from '@/components/agents/AgentAttachment'
68
import { Bubble, BubbleContent } from '@/components/ui/bubble'
79
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
8-
import { Message, MessageContent } from '@/components/ui/message'
10+
import { Message, MessageContent, MessageHeader } from '@/components/ui/message'
11+
12+
function ActorLabel({
13+
actorID,
14+
orgID,
15+
projectID,
16+
}: {
17+
actorID: string
18+
orgID: string
19+
projectID: string
20+
}) {
21+
const client = useOmnaraClient()
22+
const { data: actor } = useQuery(getActorOptions({ path: { orgID, projectID, actorID }, client }))
23+
if (actor == null) return null
24+
25+
const displayName = actor.display_name?.trim()
26+
const provider =
27+
actor.provider === 'slack' ? 'Slack' : actor.provider === 'external' ? 'External' : 'Omnara'
28+
return (
29+
<MessageHeader>
30+
{displayName == null || displayName === '' ? actor.provider_user_id : displayName}
31+
{` · ${provider}`}
32+
</MessageHeader>
33+
)
34+
}
935

1036
function JsonValue({ value }: { value: unknown }) {
1137
return (
@@ -82,9 +108,13 @@ function ToolPart({
82108
export function AgentChatMessage({
83109
message,
84110
currentActorId,
111+
orgID,
112+
projectID,
85113
}: {
86114
message: OmnaraUIMessage
87115
currentActorId?: string
116+
orgID: string
117+
projectID: string
88118
}) {
89119
const metadata = message.metadata
90120
const mine =
@@ -94,6 +124,9 @@ export function AgentChatMessage({
94124
return (
95125
<Message align={mine ? 'end' : 'start'}>
96126
<MessageContent>
127+
{message.role === 'user' && !mine && metadata?.actorId != null ? (
128+
<ActorLabel actorID={metadata.actorId} orgID={orgID} projectID={projectID} />
129+
) : null}
97130
{message.parts.map((part, index) => {
98131
if (part.type === 'text') {
99132
if (message.role === 'assistant') {

‎frontend/apps/web/src/components/agents/AgentConversation.tsx‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ import { Spinner } from '@/components/ui/spinner'
1717
export function AgentConversation({
1818
chat,
1919
currentActorId,
20+
orgID,
21+
projectID,
2022
}: {
2123
chat: UseAgentChatResult
2224
currentActorId?: string
25+
orgID: string
26+
projectID: string
2327
}) {
2428
return (
2529
<MessageScrollerProvider>
@@ -69,7 +73,12 @@ export function AgentConversation({
6973
messageId={message.id}
7074
scrollAnchor={index === chat.messages.length - 1}
7175
>
72-
<AgentChatMessage message={message} currentActorId={currentActorId} />
76+
<AgentChatMessage
77+
message={message}
78+
currentActorId={currentActorId}
79+
orgID={orgID}
80+
projectID={projectID}
81+
/>
7382
</MessageScrollerItem>
7483
))}
7584
</>

‎frontend/apps/web/src/routes/AgentView.tsx‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,12 @@ export function AgentView() {
6060
</header>
6161

6262
<main className="min-h-0 flex-1">
63-
<AgentConversation chat={chat} currentActorId={currentActorId} />
63+
<AgentConversation
64+
chat={chat}
65+
currentActorId={currentActorId}
66+
orgID={activeOrg.id}
67+
projectID={projectId}
68+
/>
6469
</main>
6570

6671
<div className="mx-auto grid w-full max-w-3xl shrink-0 gap-3">

‎frontend/packages/react/src/domains/agent-chat-messages.ts‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,21 @@ function mediaPart(block: MediaRefContentBlock): OmnaraUIMessage['parts'][number
7070
}
7171
}
7272

73+
function isHiddenContentBlock(block: { metadata?: Record<string, unknown> }): boolean {
74+
return block.metadata?.omnara_hidden === 'true'
75+
}
76+
7377
function agentInputParts(event: AgentInputEvent): OmnaraUIMessage['parts'] {
7478
const parts: OmnaraUIMessage['parts'] = []
7579
for (const block of event.content_blocks) {
80+
if (isHiddenContentBlock(block)) continue
7681
if (block.type === 'text') {
77-
parts.push({ type: 'text', text: block.text, state: 'done' })
82+
const displayText = block.metadata?.omnara_display_text
83+
parts.push({
84+
type: 'text',
85+
text: typeof displayText === 'string' ? displayText : block.text,
86+
state: 'done',
87+
})
7888
}
7989
if (block.type === 'media_ref') {
8090
parts.push(mediaPart(block))
@@ -86,6 +96,7 @@ function agentInputParts(event: AgentInputEvent): OmnaraUIMessage['parts'] {
8696
function modelOutputParts(event: ModelOutputEvent): OmnaraUIMessage['parts'] {
8797
const parts: OmnaraUIMessage['parts'] = []
8898
for (const block of event.content_blocks) {
99+
if (isHiddenContentBlock(block)) continue
89100
if (block.type === 'text') {
90101
parts.push({ type: 'text', text: block.text, state: 'done' })
91102
}
@@ -189,7 +200,7 @@ export function agentEventsToMessages(
189200
input: part.input,
190201
output: {
191202
outcome: event.outcome,
192-
contentBlocks: event.content_blocks,
203+
contentBlocks: event.content_blocks.filter((block) => !isHiddenContentBlock(block)),
193204
},
194205
}
195206
message.metadata = eventMetadata(event)

0 commit comments

Comments
 (0)