-
Notifications
You must be signed in to change notification settings - Fork 0
fix: use state.values.recentMessages for cloud compatibility #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,111 +1,59 @@ | ||
| import { describe, test, expect } from "bun:test"; | ||
| import { buildConversationContext } from "../../src/utils/context"; | ||
| import { | ||
| createMockRuntime, | ||
| createMockMessage, | ||
| createMockState, | ||
| } from "../helpers/mockRuntime"; | ||
| import { describe, test, expect } from 'bun:test'; | ||
| import { buildConversationContext } from '../../src/utils/context'; | ||
| import { createMockMessage, createMockState } from '../helpers/mockRuntime'; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused Import: |
||
|
|
||
| describe("buildConversationContext", () => { | ||
| test("returns message text when no recent messages", () => { | ||
| const runtime = createMockRuntime(); | ||
| describe('buildConversationContext', () => { | ||
| test('returns message text when no recent messages in values', () => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test Coverage Gap: The old tests validated important behaviors that are now gone:
Consider adding a test that verifies behavior when |
||
| const message = createMockMessage({ | ||
| content: { text: "Activate my workflow" }, | ||
| content: { text: 'Activate my workflow' }, | ||
| }); | ||
| const state = createMockState(); | ||
|
|
||
| const result = buildConversationContext(runtime, message, state); | ||
| expect(result).toBe("Activate my workflow"); | ||
| const result = buildConversationContext(message, state); | ||
| expect(result).toBe('Activate my workflow'); | ||
| }); | ||
|
|
||
| test("returns empty string when no text and no recent messages", () => { | ||
| const runtime = createMockRuntime(); | ||
| const message = createMockMessage({ content: { text: "" } }); | ||
| test('returns empty string when no text and no recent messages', () => { | ||
| const message = createMockMessage({ content: { text: '' } }); | ||
| const state = createMockState(); | ||
|
|
||
| const result = buildConversationContext(runtime, message, state); | ||
| expect(result).toBe(""); | ||
| const result = buildConversationContext(message, state); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test Clarity: This test expects an empty string when both text and recentMessages are empty. This is correct, but consider adding a test case for when |
||
| expect(result).toBe(''); | ||
| }); | ||
|
|
||
| test("handles undefined state", () => { | ||
| const runtime = createMockRuntime(); | ||
| const message = createMockMessage({ content: { text: "Hello" } }); | ||
| test('handles undefined state', () => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing Edge Case Test: Consider adding a test for when test('handles undefined message content', () => {
const message = createMockMessage({ content: undefined });
const state = createMockState();
const result = buildConversationContext(message, undefined);
expect(result).toBe('');
});This would ensure the type safety issue mentioned in |
||
| const message = createMockMessage({ content: { text: 'Hello' } }); | ||
|
|
||
| const result = buildConversationContext(runtime, message, undefined); | ||
| expect(result).toBe("Hello"); | ||
| const result = buildConversationContext(message, undefined); | ||
| expect(result).toBe('Hello'); | ||
| }); | ||
|
|
||
| test("includes recent messages in context", () => { | ||
| const runtime = createMockRuntime({ agentId: "agent-001" }); | ||
| const message = createMockMessage({ content: { text: "Activate it" } }); | ||
| test('appends current request to recentMessages', () => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test Coverage: The tests now validate the new string-based format, which is good. However, consider adding tests for:
These edge cases are important for robustness in production. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test Coverage Gap: Consider adding a test for when test('handles empty string recentMessages', () => {
const message = createMockMessage({ content: { text: 'Test' } });
const state = createMockState({
values: { recentMessages: '' },
});
const result = buildConversationContext(message, state);
expect(result).toBe('Test');
});This would clarify the expected behavior for the falsy check on line 8 of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test Coverage - Missing Case: Consider adding a test for when Example: test('handles non-string recentMessages gracefully', () => {
const message = createMockMessage({ content: { text: 'Hello' } });
const state = createMockState({
values: { recentMessages: { invalid: 'object' } },
});
const result = buildConversationContext(message, state);
expect(result).toBe('Hello'); // Should fall back to just the message
}); |
||
| const message = createMockMessage({ content: { text: 'Activate it' } }); | ||
| const state = createMockState({ | ||
| data: { | ||
| recentMessages: [ | ||
| { | ||
| entityId: "user-001", | ||
| content: { text: "Show me my workflows" }, | ||
| }, | ||
| { | ||
| entityId: "agent-001", | ||
| content: { text: "Here are your workflows: Stripe, Gmail" }, | ||
| }, | ||
| ], | ||
| values: { | ||
| recentMessages: | ||
| 'User: Show me my workflows\nAssistant: Here are your workflows: Stripe, Gmail', | ||
| }, | ||
| }); | ||
|
|
||
| const result = buildConversationContext(runtime, message, state); | ||
| expect(result).toContain("User: Show me my workflows"); | ||
| expect(result).toContain("Assistant: Here are your workflows"); | ||
| expect(result).toContain("Current request: Activate it"); | ||
| const result = buildConversationContext(message, state); | ||
| expect(result).toContain('User: Show me my workflows'); | ||
| expect(result).toContain('Assistant: Here are your workflows'); | ||
| expect(result).toContain('Current request: Activate it'); | ||
| }); | ||
|
|
||
| test("limits to last 5 messages", () => { | ||
| const runtime = createMockRuntime({ agentId: "agent-001" }); | ||
| const message = createMockMessage({ content: { text: "Current" } }); | ||
|
|
||
| const messages = Array.from({ length: 10 }, (_, i) => ({ | ||
| entityId: "user-001", | ||
| content: { text: `Message ${i}` }, | ||
| })); | ||
| test('preserves recentMessages formatting from provider', () => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing Test Coverage: While this test verifies that preformatted messages are preserved, there's no test coverage for:
Consider adding tests like: test('handles non-string recentMessages gracefully', () => {
const message = createMockMessage({ content: { text: 'Test' } });
const state = createMockState({
values: { recentMessages: 123 }, // number instead of string
});
const result = buildConversationContext(message, state);
expect(result).toBe('Test'); // Should fall back to message text
});There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good test coverage: This test verifies that the function preserves whatever formatting the provider supplies, which is exactly the right behavior. This demonstrates the simplified approach works correctly with different provider implementations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good Test: This test validates that pre-formatted messages from providers are preserved as-is, which is important for the cloud compatibility fix. Nice addition! 👍 However, the test name "preserves recentMessages formatting from provider" could be more specific about what formatting is being preserved (timestamps, custom prefixes, etc.). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Excellent Test: This test is critical for ensuring the fix works correctly. It verifies that:
This is a key requirement for cross-provider compatibility. Great addition! |
||
| const message = createMockMessage({ content: { text: 'Do something' } }); | ||
| const preformattedMessages = `[2024-01-01 10:00] Alice: Hello | ||
| [2024-01-01 10:01] Bot: Hi there! | ||
| [2024-01-01 10:02] Alice: Help me`; | ||
|
|
||
| const state = createMockState({ | ||
| data: { recentMessages: messages }, | ||
| }); | ||
|
|
||
| const result = buildConversationContext(runtime, message, state); | ||
| // Should only contain the last 5 messages (5-9) | ||
| expect(result).not.toContain("Message 4"); | ||
| expect(result).toContain("Message 5"); | ||
| expect(result).toContain("Message 9"); | ||
| }); | ||
|
|
||
| test("labels agent messages as Assistant", () => { | ||
| const runtime = createMockRuntime({ agentId: "agent-001" }); | ||
| const message = createMockMessage({ content: { text: "Next" } }); | ||
| const state = createMockState({ | ||
| data: { | ||
| recentMessages: [ | ||
| { entityId: "agent-001", content: { text: "Bot response" } }, | ||
| ], | ||
| }, | ||
| }); | ||
|
|
||
| const result = buildConversationContext(runtime, message, state); | ||
| expect(result).toContain("Assistant: Bot response"); | ||
| }); | ||
|
|
||
| test("labels non-agent messages as User", () => { | ||
| const runtime = createMockRuntime({ agentId: "agent-001" }); | ||
| const message = createMockMessage({ content: { text: "Next" } }); | ||
| const state = createMockState({ | ||
| data: { | ||
| recentMessages: [ | ||
| { entityId: "someone-else", content: { text: "User msg" } }, | ||
| ], | ||
| }, | ||
| values: { recentMessages: preformattedMessages }, | ||
| }); | ||
|
|
||
| const result = buildConversationContext(runtime, message, state); | ||
| expect(result).toContain("User: User msg"); | ||
| const result = buildConversationContext(message, state); | ||
| expect(result).toBe(`${preformattedMessages}\n\nCurrent request: Do something`); | ||
| }); | ||
| }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing test coverage: Consider adding tests for edge cases:
test('handles non-string recentMessages gracefully', () => {
const message = createMockMessage({ content: { text: 'Test' } });
const state = createMockState({
values: { recentMessages: ['msg1', 'msg2'] }, // Wrong type
});
const result = buildConversationContext(message, state);
expect(result).toBe('Test'); // Should fallback safely
});
test('handles undefined message content', () => {
const message = createMockMessage({ content: undefined });
const state = createMockState();
expect(() => buildConversationContext(message, state)).not.toThrow();
});There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing Test Coverage: The tests don't cover important edge cases:
Consider adding tests like: test('handles non-string recentMessages gracefully', () => {
const message = createMockMessage({ content: { text: 'Request' } });
const state = createMockState({
values: { recentMessages: ['msg1', 'msg2'] }, // Array instead of string
});
const result = buildConversationContext(message, state);
expect(result).toBe('Request'); // Should fallback safely
});
test('handles empty current message with recentMessages', () => {
const message = createMockMessage({ content: { text: '' } });
const state = createMockState({
values: { recentMessages: 'User: Previous message' },
});
const result = buildConversationContext(message, state);
// Verify expected behavior for this edge case
}); |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| { | ||
| "name": "@elizaos/plugin-n8n-workflow", | ||
| "version": "1.0.10", | ||
| "version": "1.0.11", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Version bump is appropriate: The change from 1.0.10 to 1.0.11 is a patch version bump, which is correct since this is a bug fix. However, note that removing the If this utility is only used internally within the plugin (not exported in the public API), then patch version is fine. Otherwise, consider if this should be a minor version bump (1.1.0). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Version Bump: ✅ Appropriate patch version bump (1.0.10 → 1.0.11) for a bug fix. This follows semantic versioning correctly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Version Bump: Patch version bump (1.0.10 → 1.0.11) is appropriate for a bug fix. Good adherence to semantic versioning. |
||
| "description": "ElizaOS plugin for generating and managing n8n workflows from natural language", | ||
| "type": "module", | ||
| "main": "dist/index.js", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -269,7 +269,7 @@ export const createWorkflowAction: Action = { | |
| const userText = (content.text ?? '').trim(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consistent refactoring: Good - all 5 action files (createWorkflow, activateWorkflow, deactivateWorkflow, deleteWorkflow, getExecutions) have been updated consistently to remove the |
||
| const userId = message.entityId; | ||
| const cacheKey = `workflow_draft:${userId}`; | ||
| const generationContext = buildConversationContext(runtime, message, state); | ||
| const generationContext = buildConversationContext(message, state); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consistent Update: ✅ Correctly updated to use the new 2-parameter signature. This is the critical path where conversation context is used for workflow generation, so the fix here ensures the LLM gets proper context on both cloud and bootstrap providers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Correct Usage: The Verification: All 6 actions ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good Practice: This is the most critical usage of The PR description correctly identifies this as fixing workflows being generated from confirmation messages on cloud - this change directly addresses that issue. |
||
|
|
||
| try { | ||
| let existingDraft = await runtime.getCache<WorkflowDraft>(cacheKey); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,22 +1,15 @@ | ||||||||||||||
| import { type IAgentRuntime, type Memory, type State, type UUID } from '@elizaos/core'; | ||||||||||||||
standujar marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused Import: import { type Memory, type State } from '@elizaos/core'; |
||||||||||||||
|
|
||||||||||||||
| export function buildConversationContext( | ||||||||||||||
| runtime: IAgentRuntime, | ||||||||||||||
| message: Memory, | ||||||||||||||
| state: State | undefined | ||||||||||||||
| ): string { | ||||||||||||||
| const recentMessages = (state?.data?.recentMessages as Memory[]) || []; | ||||||||||||||
| export function buildConversationContext(message: Memory, state: State | undefined): string { | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Breaking Change Alert: Removing the Code Quality: The change from processing individual messages to receiving pre-formatted strings is good for simplification, but consider:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good refactoring: Removing the unused |
||||||||||||||
| const raw = state?.values?.recentMessages; | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Edge Case - Type Safety: While Current behavior: Falls back to empty string, which is safe but silent. Consider logging a warning if the value exists but has an unexpected type: const raw = state?.values?.recentMessages;
if (raw !== undefined && typeof raw !== 'string') {
logger.warn('state.values.recentMessages has unexpected type:', typeof raw);
}
const recentMessages = typeof raw === 'string' ? raw : '';This would help catch provider contract violations during development. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TypeScript Best Practice: The type check here is good, but consider using a type guard for better type safety throughout the function: const raw = state?.values?.recentMessages;
const recentMessages = typeof raw === 'string' ? raw : '';This is already implemented correctly. Good defensive coding! ✅ However, if |
||||||||||||||
| const recentMessages = typeof raw === 'string' ? raw : ''; | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Type Safety Issue: This type check is too narrow. If Consider explicit validation:
Suggested change
This would make unexpected types fail fast during development rather than silently converting to empty string. |
||||||||||||||
| const currentText = message.content?.text ?? ''; | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Type Safety Issue:
Suggested change
This ensures type safety even if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Type Safety Concern: The use of However, looking at the usage in the codebase, all callers cast to |
||||||||||||||
|
|
||||||||||||||
| if (recentMessages.length === 0) { | ||||||||||||||
| return message.content.text || ''; | ||||||||||||||
| if (!recentMessages) { | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential Logic Bug: This condition treats an empty string Consider making this check more explicit:
Suggested change
Or alternatively, if empty string should be treated the same as missing:
Suggested change
This makes the intention clearer and prevents potential bugs with different providers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logic Question: Should an empty string be treated the same as Currently, Is this intended behavior? If so, it's fine. If not, consider: if (!recentMessages || recentMessages.length === 0) {or be explicit about what you're checking. |
||||||||||||||
| return currentText; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Security Consideration: The pre-formatted
Recommendation: Add length validation: if (recentMessages.length > 10000) { // reasonable limit
return `${recentMessages.slice(-10000)}\n\nCurrent request: ${message.content.text || ''}`;
} |
||||||||||||||
| const context = recentMessages | ||||||||||||||
| .slice(-5) | ||||||||||||||
| .map((m) => `${m.entityId === runtime.agentId ? 'Assistant' : 'User'}: ${m.content.text}`) | ||||||||||||||
| .join('\n'); | ||||||||||||||
|
|
||||||||||||||
| return `Recent conversation:\n${context}\n\nCurrent request: ${message.content.text || ''}`; | ||||||||||||||
| return `${recentMessages}\n\nCurrent request: ${currentText}`; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| export async function getUserTagName(runtime: IAgentRuntime, userId: string): Promise<string> { | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor - Code Style: Good job removing the unused
createMockRuntimeimport. The test cleanup is thorough and maintains good coverage.