Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/desktop/src/renderer/src/hooks/agent-event-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ export function applyAgentEvent(
setMessages(prev =>
prev.map(m => {
if (m.id !== event.reply_id) return m;
const cloned: Msg = { ...m, content: m.content.map(b => ({ ...b })) };
const cloned: Msg = {
...m,
content: m.content.map(b => ({ ...b })),
usage: m.usage ? { ...m.usage } : null,
};
appendEvent(cloned, event);
return cloned;
})
Expand Down
3 changes: 0 additions & 3 deletions packages/agentscope/src/message/append-event.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,6 @@ describe('appendEvent', () => {
// Apply all events and check ground truths
expect(events.length).toBe(groundTruths.length);
for (let i = 0; i < events.length; i++) {
jest.setSystemTime(new Date(events[i].created_at));
appendEvent(msg, events[i]);
expect(msgDump(msg)).toEqual(groundTruths[i]);
}
Expand Down Expand Up @@ -946,7 +945,6 @@ describe('appendEvent', () => {

// String hint with source — created_at and finished_at both come
// from the one-shot event's timestamp.
jest.setSystemTime(new Date(ts(1)));
appendEvent(msg, {
id: 'e1',
created_at: ts(1),
Expand Down Expand Up @@ -977,7 +975,6 @@ describe('appendEvent', () => {
created_at: ts(2),
},
];
jest.setSystemTime(new Date(ts(2)));
appendEvent(msg, {
id: 'e2',
created_at: ts(2),
Expand Down
28 changes: 27 additions & 1 deletion packages/agentscope/src/message/message.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ErrorType } from '../type';
import { createMsg, getContentBlocks, getTextContent, hasContentBlocks } from './message';
import { parseMsg } from './schema';
import { parseContentBlock, parseMsg } from './schema';

const TS = '2024-01-01T00:00:00.000Z';

Expand Down Expand Up @@ -136,4 +136,30 @@ describe('Message', () => {
error: { type: ErrorType.UNKNOWN, message: 'failed' },
});
});

test('validate permission rules in serialized tool calls', () => {
const toolCall = {
type: 'tool_call',
id: 'call-1',
name: 'Bash',
input: '{}',
suggested_rules: [
{
tool_name: 'Bash',
rule_content: null,
behavior: 'ask',
source: 'userSettings',
},
],
};

expect(parseContentBlock(toolCall)).toMatchObject(toolCall);
expect(() =>
parseContentBlock({
...toolCall,
suggested_rules: [{ ...toolCall.suggested_rules[0], behavior: 'invalid' }],
})
).toThrow();
expect(() => parseContentBlock({ ...toolCall, suggested_rules: [42] })).toThrow();
});
});
28 changes: 21 additions & 7 deletions packages/agentscope/src/message/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,9 @@ export function appendEvent(msg: Msg, event: AgentEvent): Msg {
break;

case EventType.TEXT_BLOCK_START:
msg.content.push(TextBlock({ id: event.block_id, text: '' }));
msg.content.push(
TextBlock({ id: event.block_id, text: '', created_at: event.created_at })
);
break;

case EventType.TEXT_BLOCK_DELTA: {
Expand All @@ -420,7 +422,9 @@ export function appendEvent(msg: Msg, event: AgentEvent): Msg {
}

case EventType.THINKING_BLOCK_START:
msg.content.push(ThinkingBlock({ id: event.block_id, thinking: '' }));
msg.content.push(
ThinkingBlock({ id: event.block_id, thinking: '', created_at: event.created_at })
);
break;

case EventType.THINKING_BLOCK_DELTA: {
Expand Down Expand Up @@ -450,8 +454,9 @@ export function appendEvent(msg: Msg, event: AgentEvent): Msg {
id: event.block_id,
hint: event.hint,
source: event.source ?? null,
created_at: event.created_at,
finished_at: event.created_at,
});
hintBlock.finished_at = hintBlock.created_at;
msg.content.push(hintBlock);
break;
}
Expand All @@ -461,6 +466,7 @@ export function appendEvent(msg: Msg, event: AgentEvent): Msg {
DataBlock({
id: event.block_id,
source: Base64Source({ data: '', media_type: event.media_type }),
created_at: event.created_at,
})
);
break;
Expand Down Expand Up @@ -501,6 +507,7 @@ export function appendEvent(msg: Msg, event: AgentEvent): Msg {
id: event.tool_call_id,
name: event.tool_call_name,
input: '',
created_at: event.created_at,
})
);
break;
Expand Down Expand Up @@ -531,6 +538,7 @@ export function appendEvent(msg: Msg, event: AgentEvent): Msg {
id: event.tool_call_id,
name: event.tool_call_name,
output: [],
created_at: event.created_at,
})
);
break;
Expand All @@ -542,11 +550,11 @@ export function appendEvent(msg: Msg, event: AgentEvent): Msg {
} else {
const trb = block as ToolResultBlock;
if (typeof trb.output === 'string') {
trb.output = [TextBlock({ text: trb.output })];
trb.output = [TextBlock({ text: trb.output, created_at: event.created_at })];
}
const last = trb.output[trb.output.length - 1];
if (!last || last.type !== 'text') {
trb.output.push(TextBlock({ text: event.delta }));
trb.output.push(TextBlock({ text: event.delta, created_at: event.created_at }));
} else {
(last as TextBlock).text += event.delta;
}
Expand All @@ -561,13 +569,19 @@ export function appendEvent(msg: Msg, event: AgentEvent): Msg {
} else {
const trb = block as ToolResultBlock;
if (typeof trb.output === 'string') {
trb.output = [TextBlock({ text: trb.output })];
trb.output = [TextBlock({ text: trb.output, created_at: event.created_at })];
}
const source: Base64Source | URLSource =
event.data != null
? Base64Source({ data: event.data, media_type: event.media_type })
: URLSource({ url: event.url!, media_type: event.media_type });
trb.output.push(DataBlock({ id: event.block_id ?? _generateId(), source }));
trb.output.push(
DataBlock({
id: event.block_id ?? _generateId(),
source,
created_at: event.created_at,
})
);
}
break;
}
Expand Down
12 changes: 10 additions & 2 deletions packages/agentscope/src/message/schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from 'zod';

import { PermissionBehavior } from '../permission';
import { ErrorType, ReplyFinishedReason } from '../type';
import {
Base64Source,
Expand All @@ -23,6 +24,13 @@ const entityFields = {

const jsonRecordSchema = z.record(z.string(), z.json());

const permissionRuleSchema = z.object({
tool_name: z.string(),
rule_content: z.string().nullable(),
behavior: z.nativeEnum(PermissionBehavior),
source: z.string(),
});

/** Runtime schema for Python-compatible text blocks. */
export const TextBlockSchema = z
.object({
Expand Down Expand Up @@ -95,11 +103,11 @@ export const ToolCallBlockSchema = z
name: z.string(),
input: z.string(),
state: z.enum(['pending', 'asking', 'allowed', 'submitted', 'finished']).optional(),
suggested_rules: z.array(z.unknown()).optional(),
suggested_rules: z.array(permissionRuleSchema).optional(),
created_at: z.string().optional(),
finished_at: z.string().nullable().optional(),
})
.transform(value => ToolCallBlock(value as Parameters<typeof ToolCallBlock>[0]));
.transform(value => ToolCallBlock(value));

/** Runtime schema for tool-result blocks. */
export const ToolResultBlockSchema = z
Expand Down
Loading