feat: Release PR for v2.4.0 (#229)
This commit is contained in:
parent
68acf38134
commit
d094ef8e10
|
|
@ -0,0 +1,61 @@
|
|||
# See https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/
|
||||
|
||||
name: Fly Deploy (Production Environment)
|
||||
permissions:
|
||||
contents: read
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version to deploy (without v prefix)"
|
||||
required: true
|
||||
type: string
|
||||
default: 'manual'
|
||||
|
||||
jobs:
|
||||
deploy-honcho-prod-image:
|
||||
name: Deploy Honcho Image (Production Environment)
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: deploy-prod-group
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: superfly/flyctl-actions/setup-flyctl@1.5
|
||||
- run: |
|
||||
# Determine the image label based on trigger type
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
IMAGE_LABEL="deployment-${{ github.event.inputs.version }}"
|
||||
else
|
||||
IMAGE_LABEL="deployment-${{ github.ref_name }}"
|
||||
fi
|
||||
flyctl deploy -a honcho-prod-image --remote-only --build-only --push --no-cache --image-label "$IMAGE_LABEL"
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_PROD_API_TOKEN }}
|
||||
|
||||
prompt-service:
|
||||
name: Push to Service (Production Environment)
|
||||
needs: deploy-honcho-prod-image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send POST request
|
||||
env:
|
||||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
# Determine version and image label based on trigger type
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
TAG="${{ github.event.inputs.version }}"
|
||||
IMAGE_LABEL="honcho-prod-image:deployment-${{ github.event.inputs.version }}"
|
||||
else
|
||||
TAG=${GITHUB_REF_NAME#v}
|
||||
IMAGE_LABEL="honcho-prod-image:deployment-${GITHUB_REF_NAME}"
|
||||
fi
|
||||
|
||||
curl --fail -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer ${{ secrets.PROD_ENV_WEBHOOK_SECRET }}" \
|
||||
-d "{\"version\":\"$TAG\",\"image_label\":\"$IMAGE_LABEL\"}" \
|
||||
"${{ secrets.PROD_ENV_URL }}/webhooks/v1/add_honcho_version"
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# See https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/
|
||||
|
||||
name: Fly Deploy (Test Environment)
|
||||
permissions:
|
||||
contents: read
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version to deploy (without v prefix)"
|
||||
required: true
|
||||
type: string
|
||||
default: 'manual'
|
||||
|
||||
jobs:
|
||||
deploy-honcho-image:
|
||||
name: Deploy Honcho Image (Test Environment)
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: deploy-test-group
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: superfly/flyctl-actions/setup-flyctl@1.5
|
||||
- run: |
|
||||
# Determine the image label based on trigger type
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
IMAGE_LABEL="deployment-${{ github.event.inputs.version }}"
|
||||
else
|
||||
IMAGE_LABEL="deployment-${{ github.ref_name }}"
|
||||
fi
|
||||
flyctl deploy -a honcho-image --remote-only --build-only --push --no-cache --image-label "$IMAGE_LABEL"
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
|
||||
prompt-service:
|
||||
name: Push to Service (Test Environment)
|
||||
runs-on: ubuntu-latest
|
||||
needs: deploy-honcho-image
|
||||
steps:
|
||||
- name: Send POST request
|
||||
env:
|
||||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
# Determine version and image label based on trigger type
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
TAG="${{ github.event.inputs.version }}"
|
||||
IMAGE_LABEL="honcho-image:deployment-${{ github.event.inputs.version }}"
|
||||
else
|
||||
TAG=${GITHUB_REF_NAME#v}
|
||||
IMAGE_LABEL="honcho-image:deployment-${GITHUB_REF_NAME}"
|
||||
fi
|
||||
|
||||
curl --fail -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer ${{ secrets.TEST_ENV_WEBHOOK_SECRET }}" \
|
||||
-d "{\"version\":\"$TAG\",\"image_label\":\"$IMAGE_LABEL\"}" \
|
||||
"${{ secrets.TEST_ENV_URL }}/webhooks/v1/add_honcho_version"
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
|
|
@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [2.4.0] - 2025-10-08
|
||||
|
||||
### Added
|
||||
|
||||
- Unified `Representation` class
|
||||
- vllm client support
|
||||
- Periodic queue cleanup logic
|
||||
- WIP Dreaming Feature
|
||||
- LongMemEval to Test Bench
|
||||
- Prometheus Client for better Metrics
|
||||
- Performance metrics instrumentation
|
||||
- Error reporting to deriver
|
||||
|
||||
### Changed
|
||||
|
||||
- Working Representations are Queried on the fly rather than cached in metadata
|
||||
- EmbeddingStore to RepresentationFactory
|
||||
- Summary Response Model to use public_id of message for cutoff
|
||||
- Semantic across codebase to reference resources based on `observer` and `observed`
|
||||
- Prompts for Deriver & Dialectic to reference peer_id and add examples
|
||||
- `Get Context` route returns peer card and representation in addition to messages and summaries
|
||||
|
||||
## [2.3.3] — 2025-10-01
|
||||
|
||||
### Changed
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
---
|
||||
|
||||

|
||||

|
||||
[](https://pypi.org/project/honcho-ai/)
|
||||
[](https://npmjs.org/package/@honcho-ai/sdk)
|
||||
[](https://discord.gg/plasticlabs)
|
||||
|
|
|
|||
|
|
@ -8,23 +8,23 @@ This guide helps you understand which versions of Honcho's API are compatible wi
|
|||
|
||||
## Version Compatibility
|
||||
|
||||
### Honcho API v2.3.3 (Current)
|
||||
### Honcho API v2.4.0 (Current)
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="TypeScript SDK" icon="js">
|
||||
**Compatible Version:** v1.4.1
|
||||
**Compatible Version:** v1.5.0
|
||||
|
||||
Install with:
|
||||
```bash
|
||||
npm install @honcho-ai/sdk@1.4.1
|
||||
npm install @honcho-ai/sdk@1.5.0
|
||||
```
|
||||
</Card>
|
||||
<Card title="Python SDK" icon="python">
|
||||
**Compatible Version:** v1.4.1
|
||||
**Compatible Version:** v1.5.0
|
||||
|
||||
Install with:
|
||||
```bash
|
||||
pip install honcho-ai==1.4.1
|
||||
pip install honcho-ai==1.5.0
|
||||
```
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
|
@ -35,7 +35,8 @@ This guide helps you understand which versions of Honcho's API are compatible wi
|
|||
|
||||
| Honcho API Version | TypeScript SDK | Python SDK |
|
||||
|-------------------|---------------|------------|
|
||||
| v2.3.3 (Current) | v1.4.1 | v1.4.1 |
|
||||
| v2.4.0 (Current) | v1.5.0 | v1.5.0 |
|
||||
| v2.3.3 | v1.4.1 | v1.4.1 |
|
||||
| v2.3.2 | v1.4.0 | v1.4.0 |
|
||||
| v2.3.1 | v1.4.0 | v1.4.0 |
|
||||
| v2.3.0 | v1.4.0 | v1.4.0 |
|
||||
|
|
|
|||
|
|
@ -27,7 +27,29 @@ Welcome to the Honcho changelog! This section documents all notable changes to t
|
|||
### Honcho API and SDK Changelogs
|
||||
<Tabs>
|
||||
<Tab title="Honcho API">
|
||||
<Update label="v2.3.3 (Current)">
|
||||
<Update label="v2.4.0 (Current)">
|
||||
### Added
|
||||
|
||||
- Unified `Representation` class
|
||||
- vllm client support
|
||||
- Periodic queue cleanup logic
|
||||
- WIP Dreaming Feature
|
||||
- LongMemEval to Test Bench
|
||||
- Prometheus Client for better Metrics
|
||||
- Performance metrics instrumentation
|
||||
- Error reporting to deriver
|
||||
|
||||
### Changed
|
||||
|
||||
- Working Representations are Queried on the fly rather than cached in metadata
|
||||
- EmbeddingStore to RepresentationFactory
|
||||
- Summary Response Model to use public_id of message for cutoff
|
||||
- Semantic across codebase to reference resources based on `observer` and `observed`
|
||||
- Prompts for Deriver & Dialectic to reference peer_id and add examples
|
||||
- `Get Context` route returns peer card and representation in addition to messages and summaries
|
||||
</Update>
|
||||
|
||||
<Update label="v2.3.3">
|
||||
### Changed
|
||||
|
||||
- Deriver Rollup Queue processes interleaved messages for more context
|
||||
|
|
@ -305,6 +327,11 @@ Welcome to the Honcho changelog! This section documents all notable changes to t
|
|||
|
||||
<Tab title="Python SDK">
|
||||
[Python SDK](https://pypi.org/project/honcho-ai/)
|
||||
<Update label="v1.5.0 (Current)">
|
||||
### Changed
|
||||
|
||||
- message_id of `Summary` model is a string nanoid
|
||||
</Update>
|
||||
<Update label="v1.4.1">
|
||||
### Added
|
||||
|
||||
|
|
@ -369,6 +396,11 @@ Welcome to the Honcho changelog! This section documents all notable changes to t
|
|||
|
||||
<Tab title="TypeScript SDK">
|
||||
[TypeScript SDK](https://www.npmjs.com/package/@honcho-ai/sdk)
|
||||
<Update label="v1.5.0 (Current)">
|
||||
### Changed
|
||||
|
||||
- message_id of `Summary` model is a string nanoid
|
||||
</Update>
|
||||
<Update label="v1.4.1">
|
||||
### Added
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
"navigation": {
|
||||
"versions": [
|
||||
{
|
||||
"version": "v2.3.3",
|
||||
"version": "v2.4.0",
|
||||
"api": {
|
||||
"openapi": ["openapi.documented.yml"]
|
||||
"openapi": ["openapi.documented.json"]
|
||||
},
|
||||
"tabs": [
|
||||
{
|
||||
|
|
@ -147,10 +147,7 @@
|
|||
"v2/api-reference/endpoint/messages/create-messages-with-file"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "keys",
|
||||
"pages": ["v2/api-reference/endpoint/keys/create-key"]
|
||||
},
|
||||
|
||||
{
|
||||
"group": "webhooks",
|
||||
"pages": [
|
||||
|
|
@ -159,6 +156,13 @@
|
|||
"v2/api-reference/endpoint/webhooks/delete-webhook-endpoint",
|
||||
"v2/api-reference/endpoint/webhooks/test-emit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "miscellaneous",
|
||||
"pages": [
|
||||
"v2/api-reference/endpoint/keys/create-key",
|
||||
"v2/api-reference/endpoint/metrics"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
openapi: get /metrics
|
||||
---
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "honcho"
|
||||
version = "2.3.3"
|
||||
version = "2.4.0"
|
||||
description = "Honcho Server"
|
||||
authors = [
|
||||
{name = "Plastic Labs", email = "hello@plasticlabs.ai"},
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [1.5.0] - 2025-10-08
|
||||
|
||||
### Changed
|
||||
|
||||
- message_id of `Summary` model is a string nanoid
|
||||
|
||||
## [1.4.1] — 2025-10-09
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ from .session import Session
|
|||
from .session_context import SessionContext, SessionSummaries, Summary
|
||||
from .types import DialecticStreamResponse
|
||||
|
||||
__version__ = "1.4.0"
|
||||
__version__ = "1.5.0"
|
||||
__author__ = "Plastic Labs"
|
||||
__email__ = "hello@plasticlabs.ai"
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [1.5.0] - 2025-10-08
|
||||
|
||||
### Changed
|
||||
|
||||
- message_id of `Summary` model is a string nanoid
|
||||
|
||||
## [1.4.1] — 2025-10-09
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import { SessionContext, Summary } from '../src/session_context';
|
||||
import { Peer } from '../src/peer';
|
||||
import { SessionContext, Summary } from '../src/session_context'
|
||||
import { Peer } from '../src/peer'
|
||||
|
||||
/**
|
||||
* Helper function to create a proper Message object for testing
|
||||
*/
|
||||
function createTestMessage(id: string, content: string, peer_id: string, additionalProps: any = {}): any {
|
||||
function createTestMessage(
|
||||
id: string,
|
||||
content: string,
|
||||
peer_id: string,
|
||||
additionalProps: any = {}
|
||||
): any {
|
||||
return {
|
||||
id,
|
||||
content,
|
||||
|
|
@ -13,8 +18,8 @@ function createTestMessage(id: string, content: string, peer_id: string, additio
|
|||
session_id: 'test-session',
|
||||
token_count: 0,
|
||||
workspace_id: 'test-workspace',
|
||||
...additionalProps
|
||||
};
|
||||
...additionalProps,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -23,16 +28,16 @@ function createTestMessage(id: string, content: string, peer_id: string, additio
|
|||
function createTestSummary(content: string): Summary {
|
||||
return new Summary({
|
||||
content,
|
||||
message_id: "1",
|
||||
message_id: 'test-message-id',
|
||||
summary_type: 'short',
|
||||
created_at: new Date().toISOString(),
|
||||
token_count: content.length
|
||||
});
|
||||
token_count: content.length,
|
||||
})
|
||||
}
|
||||
|
||||
describe('SessionContext', () => {
|
||||
let sessionContext: SessionContext;
|
||||
let mockMessages: any[];
|
||||
let sessionContext: SessionContext
|
||||
let mockMessages: any[]
|
||||
|
||||
beforeEach(() => {
|
||||
mockMessages = [
|
||||
|
|
@ -40,185 +45,223 @@ describe('SessionContext', () => {
|
|||
createTestMessage('msg2', 'Hi there', 'user'),
|
||||
createTestMessage('msg3', 'How are you?', 'user'),
|
||||
createTestMessage('msg4', 'I am doing well, thank you!', 'assistant'),
|
||||
];
|
||||
]
|
||||
|
||||
sessionContext = new SessionContext('test-session', mockMessages, null);
|
||||
});
|
||||
sessionContext = new SessionContext('test-session', mockMessages, null)
|
||||
})
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with all properties', () => {
|
||||
expect(sessionContext.sessionId).toBe('test-session');
|
||||
expect(sessionContext.messages).toEqual(mockMessages);
|
||||
expect(sessionContext.summary).toBe(null);
|
||||
});
|
||||
expect(sessionContext.sessionId).toBe('test-session')
|
||||
expect(sessionContext.messages).toEqual(mockMessages)
|
||||
expect(sessionContext.summary).toBe(null)
|
||||
})
|
||||
|
||||
it('should initialize with null summary when not provided', () => {
|
||||
const context = new SessionContext('session-id', mockMessages);
|
||||
const context = new SessionContext('session-id', mockMessages)
|
||||
|
||||
expect(context.sessionId).toBe('session-id');
|
||||
expect(context.messages).toEqual(mockMessages);
|
||||
expect(context.summary).toBe(null);
|
||||
});
|
||||
expect(context.sessionId).toBe('session-id')
|
||||
expect(context.messages).toEqual(mockMessages)
|
||||
expect(context.summary).toBe(null)
|
||||
})
|
||||
|
||||
it('should handle empty messages array', () => {
|
||||
const summary = createTestSummary('No messages');
|
||||
const context = new SessionContext('session-id', [], summary);
|
||||
const summary = createTestSummary('No messages')
|
||||
const context = new SessionContext('session-id', [], summary)
|
||||
|
||||
expect(context.sessionId).toBe('session-id');
|
||||
expect(context.messages).toEqual([]);
|
||||
expect(context.summary).toBe(summary);
|
||||
});
|
||||
expect(context.sessionId).toBe('session-id')
|
||||
expect(context.messages).toEqual([])
|
||||
expect(context.summary).toBe(summary)
|
||||
})
|
||||
|
||||
it('should handle null/undefined summary', () => {
|
||||
const context1 = new SessionContext('session-id', mockMessages, undefined as any);
|
||||
const context2 = new SessionContext('session-id', mockMessages, null);
|
||||
const context1 = new SessionContext(
|
||||
'session-id',
|
||||
mockMessages,
|
||||
undefined as any
|
||||
)
|
||||
const context2 = new SessionContext('session-id', mockMessages, null)
|
||||
|
||||
expect(context1.summary).toBe(null);
|
||||
expect(context2.summary).toBe(null);
|
||||
});
|
||||
});
|
||||
expect(context1.summary).toBe(null)
|
||||
expect(context2.summary).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('toOpenAI', () => {
|
||||
it('should convert messages to OpenAI format with string assistant', () => {
|
||||
const openAIMessages = sessionContext.toOpenAI('assistant');
|
||||
const openAIMessages = sessionContext.toOpenAI('assistant')
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello', name: 'assistant' },
|
||||
{ role: 'user', content: 'Hi there', name: 'user' },
|
||||
{ role: 'user', content: 'How are you?', name: 'user' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!', name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
{
|
||||
role: 'assistant',
|
||||
content: 'I am doing well, thank you!',
|
||||
name: 'assistant',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should convert messages to OpenAI format with Peer object', () => {
|
||||
const mockClient = {} as any;
|
||||
const assistantPeer = new Peer('assistant', 'test-workspace', mockClient);
|
||||
const mockClient = {} as any
|
||||
const assistantPeer = new Peer('assistant', 'test-workspace', mockClient)
|
||||
|
||||
const openAIMessages = sessionContext.toOpenAI(assistantPeer);
|
||||
const openAIMessages = sessionContext.toOpenAI(assistantPeer)
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello', name: 'assistant' },
|
||||
{ role: 'user', content: 'Hi there', name: 'user' },
|
||||
{ role: 'user', content: 'How are you?', name: 'user' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!', name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
{
|
||||
role: 'assistant',
|
||||
content: 'I am doing well, thank you!',
|
||||
name: 'assistant',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle messages where assistant is different peer', () => {
|
||||
const openAIMessages = sessionContext.toOpenAI('different-assistant');
|
||||
const openAIMessages = sessionContext.toOpenAI('different-assistant')
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'user', content: 'Hello', name: 'assistant' },
|
||||
{ role: 'user', content: 'Hi there', name: 'user' },
|
||||
{ role: 'user', content: 'How are you?', name: 'user' },
|
||||
{ role: 'user', content: 'I am doing well, thank you!', name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
{
|
||||
role: 'user',
|
||||
content: 'I am doing well, thank you!',
|
||||
name: 'assistant',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle empty messages array', () => {
|
||||
const emptyContext = new SessionContext('session-id', []);
|
||||
const openAIMessages = emptyContext.toOpenAI('assistant');
|
||||
const emptyContext = new SessionContext('session-id', [])
|
||||
const openAIMessages = emptyContext.toOpenAI('assistant')
|
||||
|
||||
expect(openAIMessages).toEqual([]);
|
||||
});
|
||||
expect(openAIMessages).toEqual([])
|
||||
})
|
||||
|
||||
it('should include summary message when summary exists', () => {
|
||||
const summary = createTestSummary('This is a summary');
|
||||
const contextWithSummary = new SessionContext('test-session', mockMessages, summary);
|
||||
const openAIMessages = contextWithSummary.toOpenAI('assistant');
|
||||
const summary = createTestSummary('This is a summary')
|
||||
const contextWithSummary = new SessionContext(
|
||||
'test-session',
|
||||
mockMessages,
|
||||
summary
|
||||
)
|
||||
const openAIMessages = contextWithSummary.toOpenAI('assistant')
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'system', content: '<summary>This is a summary</summary>' },
|
||||
{ role: 'assistant', content: 'Hello', name: 'assistant' },
|
||||
{ role: 'user', content: 'Hi there', name: 'user' },
|
||||
{ role: 'user', content: 'How are you?', name: 'user' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!', name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
{
|
||||
role: 'assistant',
|
||||
content: 'I am doing well, thank you!',
|
||||
name: 'assistant',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle messages with missing peer_id', () => {
|
||||
const messagesWithMissingPeer = [
|
||||
createTestMessage('msg1', 'Hello', 'assistant'),
|
||||
createTestMessage('msg2', 'No peer', ''), // missing peer_id
|
||||
createTestMessage('msg3', 'Another message', ''), // null peer_id
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithMissingPeer);
|
||||
]
|
||||
const context = new SessionContext('test', messagesWithMissingPeer)
|
||||
|
||||
const openAIMessages = context.toOpenAI('assistant');
|
||||
const openAIMessages = context.toOpenAI('assistant')
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello', name: 'assistant' },
|
||||
{ role: 'user', content: 'No peer', name: '' },
|
||||
{ role: 'user', content: 'Another message', name: '' },
|
||||
]);
|
||||
});
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle complex message content', () => {
|
||||
const complexMessages = [
|
||||
createTestMessage('msg1', 'Message with\nnewlines and special chars!@#$%', 'assistant'),
|
||||
createTestMessage(
|
||||
'msg1',
|
||||
'Message with\nnewlines and special chars!@#$%',
|
||||
'assistant'
|
||||
),
|
||||
createTestMessage('msg2', '', 'user'), // empty content
|
||||
createTestMessage('msg3', ' whitespace ', 'assistant'),
|
||||
];
|
||||
const context = new SessionContext('test', complexMessages);
|
||||
]
|
||||
const context = new SessionContext('test', complexMessages)
|
||||
|
||||
const openAIMessages = context.toOpenAI('assistant');
|
||||
const openAIMessages = context.toOpenAI('assistant')
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Message with\nnewlines and special chars!@#$%', name: 'assistant' },
|
||||
{
|
||||
role: 'assistant',
|
||||
content: 'Message with\nnewlines and special chars!@#$%',
|
||||
name: 'assistant',
|
||||
},
|
||||
{ role: 'user', content: '', name: 'user' },
|
||||
{ role: 'assistant', content: ' whitespace ', name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('toAnthropic', () => {
|
||||
it('should convert messages to Anthropic format with string assistant', () => {
|
||||
const anthropicMessages = sessionContext.toAnthropic('assistant');
|
||||
const anthropicMessages = sessionContext.toAnthropic('assistant')
|
||||
|
||||
expect(anthropicMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello' },
|
||||
{ role: 'user', content: 'user: Hi there' },
|
||||
{ role: 'user', content: 'user: How are you?' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!' },
|
||||
]);
|
||||
});
|
||||
])
|
||||
})
|
||||
|
||||
it('should convert messages to Anthropic format with Peer object', () => {
|
||||
const mockClient = {} as any;
|
||||
const assistantPeer = new Peer('assistant', 'test-workspace', mockClient);
|
||||
const mockClient = {} as any
|
||||
const assistantPeer = new Peer('assistant', 'test-workspace', mockClient)
|
||||
|
||||
const anthropicMessages = sessionContext.toAnthropic(assistantPeer);
|
||||
const anthropicMessages = sessionContext.toAnthropic(assistantPeer)
|
||||
|
||||
expect(anthropicMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello' },
|
||||
{ role: 'user', content: 'user: Hi there' },
|
||||
{ role: 'user', content: 'user: How are you?' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!' },
|
||||
]);
|
||||
});
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle messages where assistant is different peer', () => {
|
||||
const anthropicMessages = sessionContext.toAnthropic('different-assistant');
|
||||
const anthropicMessages = sessionContext.toAnthropic(
|
||||
'different-assistant'
|
||||
)
|
||||
|
||||
expect(anthropicMessages).toEqual([
|
||||
{ role: 'user', content: 'assistant: Hello' },
|
||||
{ role: 'user', content: 'user: Hi there' },
|
||||
{ role: 'user', content: 'user: How are you?' },
|
||||
{ role: 'user', content: 'assistant: I am doing well, thank you!' },
|
||||
]);
|
||||
});
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle empty messages array', () => {
|
||||
const emptyContext = new SessionContext('session-id', []);
|
||||
const anthropicMessages = emptyContext.toAnthropic('assistant');
|
||||
const emptyContext = new SessionContext('session-id', [])
|
||||
const anthropicMessages = emptyContext.toAnthropic('assistant')
|
||||
|
||||
expect(anthropicMessages).toEqual([]);
|
||||
});
|
||||
expect(anthropicMessages).toEqual([])
|
||||
})
|
||||
|
||||
it('should include summary message when summary exists', () => {
|
||||
const summary = createTestSummary('This is a summary');
|
||||
const contextWithSummary = new SessionContext('test-session', mockMessages, summary);
|
||||
const anthropicMessages = contextWithSummary.toAnthropic('assistant');
|
||||
const summary = createTestSummary('This is a summary')
|
||||
const contextWithSummary = new SessionContext(
|
||||
'test-session',
|
||||
mockMessages,
|
||||
summary
|
||||
)
|
||||
const anthropicMessages = contextWithSummary.toAnthropic('assistant')
|
||||
|
||||
expect(anthropicMessages).toEqual([
|
||||
{ role: 'user', content: '<summary>This is a summary</summary>' },
|
||||
|
|
@ -226,157 +269,169 @@ describe('SessionContext', () => {
|
|||
{ role: 'user', content: 'user: Hi there' },
|
||||
{ role: 'user', content: 'user: How are you?' },
|
||||
{ role: 'assistant', content: 'I am doing well, thank you!' },
|
||||
]);
|
||||
});
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle messages with missing peer_id', () => {
|
||||
const messagesWithMissingPeer = [
|
||||
createTestMessage('msg1', 'Hello', 'assistant'),
|
||||
createTestMessage('msg2', 'No peer', ''), // missing peer_id
|
||||
createTestMessage('msg3', 'Another message', ''), // undefined peer_id
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithMissingPeer);
|
||||
]
|
||||
const context = new SessionContext('test', messagesWithMissingPeer)
|
||||
|
||||
const anthropicMessages = context.toAnthropic('assistant');
|
||||
const anthropicMessages = context.toAnthropic('assistant')
|
||||
|
||||
expect(anthropicMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello' },
|
||||
{ role: 'user', content: ': No peer' },
|
||||
{ role: 'user', content: ': Another message' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('length getter', () => {
|
||||
it('should return correct message count', () => {
|
||||
expect(sessionContext.length).toBe(4);
|
||||
});
|
||||
expect(sessionContext.length).toBe(4)
|
||||
})
|
||||
|
||||
it('should return zero for empty messages', () => {
|
||||
const emptyContext = new SessionContext('session-id', []);
|
||||
expect(emptyContext.length).toBe(0);
|
||||
});
|
||||
const emptyContext = new SessionContext('session-id', [])
|
||||
expect(emptyContext.length).toBe(0)
|
||||
})
|
||||
|
||||
it('should return correct count for single message', () => {
|
||||
const singleMessageContext = new SessionContext('session-id', [mockMessages[0]]);
|
||||
expect(singleMessageContext.length).toBe(1);
|
||||
});
|
||||
});
|
||||
const singleMessageContext = new SessionContext('session-id', [
|
||||
mockMessages[0],
|
||||
])
|
||||
expect(singleMessageContext.length).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('toString', () => {
|
||||
it('should return correct string representation', () => {
|
||||
const result = sessionContext.toString();
|
||||
expect(result).toBe('SessionContext(messages=4, summary=none)');
|
||||
});
|
||||
const result = sessionContext.toString()
|
||||
expect(result).toBe('SessionContext(messages=4, summary=none)')
|
||||
})
|
||||
|
||||
it('should handle empty messages', () => {
|
||||
const emptyContext = new SessionContext('session-id', []);
|
||||
const result = emptyContext.toString();
|
||||
expect(result).toBe('SessionContext(messages=0, summary=none)');
|
||||
});
|
||||
const emptyContext = new SessionContext('session-id', [])
|
||||
const result = emptyContext.toString()
|
||||
expect(result).toBe('SessionContext(messages=0, summary=none)')
|
||||
})
|
||||
|
||||
it('should handle large number of messages', () => {
|
||||
const manyMessages = Array.from({ length: 1000 }, (_, i) =>
|
||||
createTestMessage(`msg${i}`, `Message ${i}`, i % 2 === 0 ? 'assistant' : 'user')
|
||||
);
|
||||
const context = new SessionContext('session-id', manyMessages);
|
||||
createTestMessage(
|
||||
`msg${i}`,
|
||||
`Message ${i}`,
|
||||
i % 2 === 0 ? 'assistant' : 'user'
|
||||
)
|
||||
)
|
||||
const context = new SessionContext('session-id', manyMessages)
|
||||
|
||||
const result = context.toString();
|
||||
expect(result).toBe('SessionContext(messages=1000, summary=none)');
|
||||
});
|
||||
});
|
||||
const result = context.toString()
|
||||
expect(result).toBe('SessionContext(messages=1000, summary=none)')
|
||||
})
|
||||
})
|
||||
|
||||
describe('edge cases and error handling', () => {
|
||||
it('should handle messages with null content', () => {
|
||||
const messagesWithNullContent = [
|
||||
createTestMessage('msg1', null as any, 'assistant'),
|
||||
createTestMessage('msg2', undefined as any, 'user'),
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithNullContent);
|
||||
]
|
||||
const context = new SessionContext('test', messagesWithNullContent)
|
||||
|
||||
const openAIMessages = context.toOpenAI('assistant');
|
||||
const openAIMessages = context.toOpenAI('assistant')
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: null, name: 'assistant' },
|
||||
{ role: 'user', content: undefined, name: 'user' },
|
||||
]);
|
||||
});
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle messages with non-string content', () => {
|
||||
const messagesWithNonStringContent = [
|
||||
createTestMessage('msg1', 123 as any, 'assistant'),
|
||||
createTestMessage('msg2', { text: 'object content' } as any, 'user'),
|
||||
createTestMessage('msg3', true as any, 'assistant'),
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithNonStringContent);
|
||||
]
|
||||
const context = new SessionContext('test', messagesWithNonStringContent)
|
||||
|
||||
const openAIMessages = context.toOpenAI('assistant');
|
||||
const openAIMessages = context.toOpenAI('assistant')
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: 123, name: 'assistant' },
|
||||
{ role: 'user', content: { text: 'object content' }, name: 'user' },
|
||||
{ role: 'assistant', content: true, name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle very long session IDs and summaries', () => {
|
||||
const longSessionId = 'x'.repeat(1000);
|
||||
const longSummaryText = 'Very long summary that goes on and on...'.repeat(100);
|
||||
const longSummary = createTestSummary(longSummaryText);
|
||||
const context = new SessionContext(longSessionId, mockMessages, longSummary);
|
||||
const longSessionId = 'x'.repeat(1000)
|
||||
const longSummaryText = 'Very long summary that goes on and on...'.repeat(
|
||||
100
|
||||
)
|
||||
const longSummary = createTestSummary(longSummaryText)
|
||||
const context = new SessionContext(
|
||||
longSessionId,
|
||||
mockMessages,
|
||||
longSummary
|
||||
)
|
||||
|
||||
expect(context.sessionId).toBe(longSessionId);
|
||||
expect(context.summary).toBe(longSummary);
|
||||
expect(context.summary?.content).toBe(longSummaryText);
|
||||
expect(context.length).toBe(5); // 4 messages + 1 summary
|
||||
});
|
||||
expect(context.sessionId).toBe(longSessionId)
|
||||
expect(context.summary).toBe(longSummary)
|
||||
expect(context.summary?.content).toBe(longSummaryText)
|
||||
expect(context.length).toBe(5) // 4 messages + 1 summary
|
||||
})
|
||||
|
||||
it('should handle messages with additional properties', () => {
|
||||
const messagesWithExtraProps = [
|
||||
createTestMessage('msg1', 'Hello', 'assistant', {
|
||||
timestamp: '2023-01-01T00:00:00Z',
|
||||
metadata: { important: true },
|
||||
extra_field: 'extra_value'
|
||||
extra_field: 'extra_value',
|
||||
}),
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithExtraProps);
|
||||
]
|
||||
const context = new SessionContext('test', messagesWithExtraProps)
|
||||
|
||||
const openAIMessages = context.toOpenAI('assistant');
|
||||
const openAIMessages = context.toOpenAI('assistant')
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'assistant', content: 'Hello', name: 'assistant' },
|
||||
]);
|
||||
});
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle case-sensitive peer names', () => {
|
||||
const caseMessages = [
|
||||
createTestMessage('msg1', 'Hello', 'Assistant'),
|
||||
createTestMessage('msg2', 'Hi', 'ASSISTANT'),
|
||||
createTestMessage('msg3', 'Hey', 'assistant'),
|
||||
];
|
||||
const context = new SessionContext('test', caseMessages);
|
||||
]
|
||||
const context = new SessionContext('test', caseMessages)
|
||||
|
||||
const openAIMessages = context.toOpenAI('assistant');
|
||||
const openAIMessages = context.toOpenAI('assistant')
|
||||
|
||||
expect(openAIMessages).toEqual([
|
||||
{ role: 'user', content: 'Hello', name: 'Assistant' }, // 'Assistant' != 'assistant'
|
||||
{ role: 'user', content: 'Hi', name: 'ASSISTANT' }, // 'ASSISTANT' != 'assistant'
|
||||
{ role: 'assistant', content: 'Hey', name: 'assistant' }, // exact match
|
||||
]);
|
||||
});
|
||||
])
|
||||
})
|
||||
|
||||
it('should handle messages without id field', () => {
|
||||
const messagesWithoutId = [
|
||||
createTestMessage('', 'Message without ID', 'assistant'),
|
||||
createTestMessage('', 'Another message', 'user'),
|
||||
];
|
||||
const context = new SessionContext('test', messagesWithoutId);
|
||||
]
|
||||
const context = new SessionContext('test', messagesWithoutId)
|
||||
|
||||
expect(context.length).toBe(2);
|
||||
expect(context.length).toBe(2)
|
||||
expect(context.toOpenAI('assistant')).toEqual([
|
||||
{ role: 'assistant', content: 'Message without ID', name: 'assistant' },
|
||||
{ role: 'user', content: 'Another message', name: 'user' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ app = FastAPI(
|
|||
title="Honcho API",
|
||||
summary="The Identity Layer for the Agentic World",
|
||||
description="""Honcho is a platform for giving agents user-centric memory and social cognition""",
|
||||
version="2.3.3",
|
||||
version="2.4.0",
|
||||
contact={
|
||||
"name": "Plastic Labs",
|
||||
"url": "https://honcho.dev",
|
||||
|
|
|
|||
Loading…
Reference in New Issue