diff --git a/sdks/python/pyproject.toml b/sdks/python/pyproject.toml
index 669159b8..9d8c7544 100644
--- a/sdks/python/pyproject.toml
+++ b/sdks/python/pyproject.toml
@@ -8,7 +8,7 @@ authors = [
{ name = "Plastic Labs", email = "hello@plasticlabs.ai" },
]
dependencies = [
- "honcho-core>=1.4.1-alpha.0",
+ "honcho-core>=1.5.0",
"httpx>=0.28.0, <1",
"pydantic>=2.0.0, <3",
"typing-extensions>=4.12.0; python_version < \"3.12\"",
diff --git a/sdks/python/src/honcho/async_client/session.py b/sdks/python/src/honcho/async_client/session.py
index fb2bea00..3610367e 100644
--- a/sdks/python/src/honcho/async_client/session.py
+++ b/sdks/python/src/honcho/async_client/session.py
@@ -427,11 +427,23 @@ class AsyncSession(BaseModel):
tokens: int | None = Field(
None, gt=0, description="Maximum number of tokens to include in the context"
),
+ peer_target: str | None = Field(
+ None,
+ description="A peer ID to get context for. If given *without* `peer_perspective`, a representation and peer card will be included from the omniscient Honcho-level view of `peer_target`. If given *with* `peer_perspective`, will get the representation and card for `peer_target` *from the perspective of `peer_perspective`*.",
+ ),
+ last_user_message: str | Message | None = Field(
+ None,
+ description="The most recent message (string or Message object), used to fetch semantically relevant observations and returned as part of the context object. Use this alongside `peer_target` to get a more focused context -- does nothing if `peer_target` is not provided.",
+ ),
+ peer_perspective: str | None = Field(
+ None,
+ description="A peer ID to get context *from the perspective of*. If given, response will attempt to include representation and card from the perspective of `peer_perspective`. Must be provided with `peer_target`.",
+ ),
) -> SessionContext:
"""
Get optimized context for this session within a token limit.
- Makes an async API call to retrieve a curated list of messages that provides
+ Makes an API call to retrieve a curated list of messages that provides
optimal context for the conversation while staying within the specified
token limit. Uses tiktoken for token counting, so results should be
compatible with OpenAI models.
@@ -440,6 +452,9 @@ class AsyncSession(BaseModel):
summary: Whether to include summary information
tokens: Maximum number of tokens to include in the context. Will default
to Honcho server configuration if not provided.
+ peer_target: A peer ID to get context for. If given *without* `peer_perspective`, a representation and peer card will be included from the omniscient Honcho-level view of `peer_target`. If given *with* `peer_perspective`, will get the representation and card for `peer_target` *from the perspective of `peer_perspective`*.
+ last_user_message: The most recent message (string or Message object), used to fetch semantically relevant observations and returned as part of the context object. Use this alongside `peer_target` to get a more focused context -- does nothing if `peer_target` is not provided.
+ peer_perspective: A peer ID to get context *from the perspective of*. If given, response will attempt to include representation and card from the perspective of `peer_perspective`. Must be provided with `peer_target`.
Returns:
A SessionContext object containing the optimized message history and
@@ -450,11 +465,32 @@ class AsyncSession(BaseModel):
Token counting is performed using tiktoken. For models using different
tokenizers, you may need to adjust the token limit accordingly.
"""
+
+ if peer_target is None and peer_perspective is not None:
+ raise ValueError(
+ "You must provide a `peer_target` when `peer_perspective` is provided"
+ )
+
+ if peer_target is None and last_user_message is not None:
+ raise ValueError(
+ "You must provide a `peer_target` when `last_user_message` is provided"
+ )
+
+ last_user_message_id = (
+ last_user_message.id
+ if isinstance(last_user_message, Message)
+ else last_user_message
+ )
context = await self._client.workspaces.sessions.get_context(
session_id=self.id,
workspace_id=self.workspace_id,
tokens=tokens if tokens is not None else omit,
summary=summary,
+ last_message=last_user_message_id
+ if last_user_message_id is not None
+ else omit,
+ peer_target=peer_target if peer_target is not None else omit,
+ peer_perspective=peer_perspective if peer_perspective is not None else omit,
)
# Convert the honcho_core summary to our Summary if it exists
@@ -469,7 +505,13 @@ class AsyncSession(BaseModel):
)
return SessionContext(
- session_id=self.id, messages=context.messages, summary=session_summary
+ session_id=self.id,
+ messages=context.messages,
+ summary=session_summary,
+ peer_representation=str(context.peer_representation)
+ if context.peer_representation
+ else None,
+ peer_card=context.peer_card,
)
async def get_summaries(self) -> SessionSummaries:
diff --git a/sdks/python/src/honcho/session.py b/sdks/python/src/honcho/session.py
index 193887b5..51a7f20c 100644
--- a/sdks/python/src/honcho/session.py
+++ b/sdks/python/src/honcho/session.py
@@ -407,6 +407,18 @@ class Session(BaseModel):
tokens: int | None = Field(
None, gt=0, description="Maximum number of tokens to include in the context"
),
+ peer_target: str | None = Field(
+ None,
+ description="A peer ID to get context for. If given *without* `peer_perspective`, a representation and peer card will be included from the omniscient Honcho-level view of `peer_target`. If given *with* `peer_perspective`, will get the representation and card for `peer_target` *from the perspective of `peer_perspective`*.",
+ ),
+ last_user_message: str | Message | None = Field(
+ None,
+ description="The most recent message (string or Message object), used to fetch semantically relevant observations and returned as part of the context object. Use this alongside `peer_target` to get a more focused context -- does nothing if `peer_target` is not provided.",
+ ),
+ peer_perspective: str | None = Field(
+ None,
+ description="A peer ID to get context *from the perspective of*. If given, response will attempt to include representation and card from the perspective of `peer_perspective`. Must be provided with `peer_target`.",
+ ),
) -> SessionContext:
"""
Get optimized context for this session within a token limit.
@@ -420,6 +432,9 @@ class Session(BaseModel):
summary: Whether to include summary information
tokens: Maximum number of tokens to include in the context. Will default
to Honcho server configuration if not provided.
+ peer_target: A peer ID to get context for. If given *without* `peer_perspective`, a representation and peer card will be included from the omniscient Honcho-level view of `peer_target`. If given *with* `peer_perspective`, will get the representation and card for `peer_target` *from the perspective of `peer_perspective`*.
+ last_user_message: The most recent message (string or Message object), used to fetch semantically relevant observations and returned as part of the context object. Use this alongside `peer_target` to get a more focused context -- does nothing if `peer_target` is not provided.
+ peer_perspective: A peer ID to get context *from the perspective of*. If given, response will attempt to include representation and card from the perspective of `peer_perspective`. Must be provided with `peer_target`.
Returns:
A SessionContext object containing the optimized message history and
@@ -430,11 +445,32 @@ class Session(BaseModel):
Token counting is performed using tiktoken. For models using different
tokenizers, you may need to adjust the token limit accordingly.
"""
+
+ if peer_target is None and peer_perspective is not None:
+ raise ValueError(
+ "You must provide a `peer_target` when `peer_perspective` is provided"
+ )
+
+ if peer_target is None and last_user_message is not None:
+ raise ValueError(
+ "You must provide a `peer_target` when `last_user_message` is provided"
+ )
+
+ last_user_message_id = (
+ last_user_message.id
+ if isinstance(last_user_message, Message)
+ else last_user_message
+ )
context = self._client.workspaces.sessions.get_context(
session_id=self.id,
workspace_id=self.workspace_id,
tokens=tokens if tokens is not None else omit,
summary=summary,
+ last_message=last_user_message_id
+ if last_user_message_id is not None
+ else omit,
+ peer_target=peer_target if peer_target is not None else omit,
+ peer_perspective=peer_perspective if peer_perspective is not None else omit,
)
# Convert the honcho_core summary to our Summary if it exists
@@ -449,7 +485,13 @@ class Session(BaseModel):
)
return SessionContext(
- session_id=self.id, messages=context.messages, summary=session_summary
+ session_id=self.id,
+ messages=context.messages,
+ summary=session_summary,
+ peer_representation=str(context.peer_representation)
+ if context.peer_representation
+ else None,
+ peer_card=context.peer_card,
)
def get_summaries(self) -> SessionSummaries:
diff --git a/sdks/python/src/honcho/session_context.py b/sdks/python/src/honcho/session_context.py
index 45017075..0c657fe1 100644
--- a/sdks/python/src/honcho/session_context.py
+++ b/sdks/python/src/honcho/session_context.py
@@ -58,6 +58,14 @@ class SessionContext(BaseModel):
summary: Summary | None = Field(
None, description="Summary of the session history prior to the message cutoff"
)
+ peer_representation: str | None = Field(
+ None,
+ description="The peer representation, if context is requested from a specific perspective",
+ )
+ peer_card: list[str] | None = Field(
+ None,
+ description="The peer card, if context is requested from a specific perspective",
+ )
@validate_call
def __init__(
@@ -72,6 +80,14 @@ class SessionContext(BaseModel):
None,
description="Summary of the session history prior to the message cutoff",
),
+ peer_representation: str | None = Field(
+ None,
+ description="The peer representation, if context is requested from a specific perspective",
+ ),
+ peer_card: list[str] | None = Field(
+ None,
+ description="The peer card, if context is requested from a specific perspective",
+ ),
) -> None:
"""
Initialize a new SessionContext.
@@ -84,6 +100,8 @@ class SessionContext(BaseModel):
session_id=session_id,
messages=messages,
summary=summary,
+ peer_representation=peer_representation,
+ peer_card=peer_card,
)
def to_openai(
@@ -117,14 +135,30 @@ class SessionContext(BaseModel):
}
for message in self.messages
]
+ system_messages: list[dict[str, str]] = []
+
+ if self.peer_representation:
+ peer_representation_message = {
+ "role": "system",
+ "content": f"{self.peer_representation}",
+ }
+ system_messages.append(peer_representation_message)
+
+ if self.peer_card:
+ peer_card_message = {
+ "role": "system",
+ "content": f"{self.peer_card}",
+ }
+ system_messages.append(peer_card_message)
if self.summary:
summary_message = {
"role": "system",
"content": f"{self.summary.content}",
}
- return [summary_message, *messages]
- return messages
+ system_messages.append(summary_message)
+
+ return system_messages + messages
def to_anthropic(
self,
@@ -164,14 +198,30 @@ class SessionContext(BaseModel):
}
for message in self.messages
]
+ system_messages: list[dict[str, str]] = []
+
+ if self.peer_representation:
+ peer_representation_message = {
+ "role": "user",
+ "content": f"{self.peer_representation}",
+ }
+ system_messages.append(peer_representation_message)
+
+ if self.peer_card:
+ peer_card_message = {
+ "role": "user",
+ "content": f"{self.peer_card}",
+ }
+ system_messages.append(peer_card_message)
if self.summary:
summary_message = {
"role": "user",
"content": f"{self.summary.content}",
}
- return [summary_message, *messages]
- return messages
+ system_messages.append(summary_message)
+
+ return system_messages + messages
def __len__(self) -> int:
"""
diff --git a/sdks/typescript/__tests__/session_context.test.ts b/sdks/typescript/__tests__/session_context.test.ts
index 9c95dbce..5865c765 100644
--- a/sdks/typescript/__tests__/session_context.test.ts
+++ b/sdks/typescript/__tests__/session_context.test.ts
@@ -23,7 +23,7 @@ function createTestMessage(id: string, content: string, peer_id: string, additio
function createTestSummary(content: string): Summary {
return new Summary({
content,
- message_id: 1,
+ message_id: "1",
summary_type: 'short',
created_at: new Date().toISOString(),
token_count: content.length
diff --git a/sdks/typescript/bun.lock b/sdks/typescript/bun.lock
index 7ec73bd1..b4ad630e 100644
--- a/sdks/typescript/bun.lock
+++ b/sdks/typescript/bun.lock
@@ -4,7 +4,7 @@
"": {
"name": "@honcho-ai/sdk",
"dependencies": {
- "@honcho-ai/core": "^1.4.1-alpha.0",
+ "@honcho-ai/core": "^1.5.0",
"@types/node": "^24.0.1",
"zod": "4.0.0",
},
@@ -108,7 +108,7 @@
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
- "@honcho-ai/core": ["@honcho-ai/core@1.4.1-alpha.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-cRp3ug8cBe9E7NZDgCXkI0EHIgm8x5Jq/nQB0xA5629GuniS99yq6M786I//FjOZhB2YEZoOxd4Zkg412uk5Yg=="],
+ "@honcho-ai/core": ["@honcho-ai/core@1.5.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-kjYhCO0S9Ll3DfVpK8MetjY1PlD9FG6QGtudlMnSwfTWGzXFjL/OafpoE3SZ3c9BVY48JIbW9IttFUZFekv/xg=="],
"@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="],
diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json
index 1e9750d3..803b9d23 100644
--- a/sdks/typescript/package.json
+++ b/sdks/typescript/package.json
@@ -20,7 +20,7 @@
"test:coverage": "jest --coverage"
},
"dependencies": {
- "@honcho-ai/core": "^1.4.1-alpha.0",
+ "@honcho-ai/core": "^1.5.0",
"@types/node": "^24.0.1",
"zod": "4.0.0"
},
diff --git a/sdks/typescript/src/session.ts b/sdks/typescript/src/session.ts
index 6bc3e16f..aae0e309 100644
--- a/sdks/typescript/src/session.ts
+++ b/sdks/typescript/src/session.ts
@@ -451,10 +451,21 @@ export class Session {
* compatible with OpenAI models. The context optimization balances
* recency and relevance to provide the best conversational context.
*
- * @param summary - Whether to include summary information in the context.
- * When true, includes session summary if available. Defaults to true
- * @param tokens - Maximum number of tokens to include in the context. If not provided,
- * uses the server's default configuration
+ * @param options - Configuration options for context retrieval
+ * @param options.summary - Whether to include summary information in the context.
+ * When true, includes session summary if available. Defaults to true
+ * @param options.tokens - Maximum number of tokens to include in the context. If not provided,
+ * uses the server's default configuration
+ * @param options.peerTarget - The target of the perspective. If given without `peerPerspective`,
+ * will get the Honcho-level representation and peer card for this peer.
+ * If given with `peerPerspective`, will get the representation and card
+ * for this peer from the perspective of that peer.
+ * @param options.lastUserMessage - The most recent message, used to fetch semantically relevant
+ * observations and returned as part of the context object.
+ * Can be either a message ID string or a Message object.
+ * @param options.peerPerspective - A peer to get context for. If given, response will attempt to
+ * include representation and card from the perspective of that peer.
+ * Must be provided with `peerTarget`.
* @returns Promise resolving to a SessionContext object containing the optimized
* message history and summary (if available) that maximizes conversational
* context while respecting the token limit
@@ -462,25 +473,103 @@ export class Session {
* @note Token counting is performed using tiktoken. For models using different
* tokenizers, you may need to adjust the token limit accordingly.
*/
+ async getContext(
+ summary?: boolean,
+ tokens?: number,
+ peerTarget?: string | Peer,
+ lastUserMessage?: string | Message,
+ peerPerspective?: string | Peer
+ ): Promise
async getContext(options?: {
summary?: boolean
tokens?: number
- }): Promise {
+ peerTarget?: string | Peer
+ lastUserMessage?: string | Message
+ peerPerspective?: string | Peer
+ }): Promise
+ async getContext(
+ summaryOrOptions?:
+ | boolean
+ | {
+ summary?: boolean
+ tokens?: number
+ peerTarget?: string | Peer
+ lastUserMessage?: string | Message
+ peerPerspective?: string | Peer
+ },
+ tokens?: number,
+ peerTarget?: string | Peer,
+ lastUserMessage?: string | Message,
+ peerPerspective?: string | Peer
+ ): Promise {
+ // Normalize positional arguments into options object
+ let options: {
+ summary?: boolean
+ tokens?: number
+ peerTarget?: string
+ lastUserMessage?: string
+ peerPerspective?: string
+ }
+
+ if (
+ typeof summaryOrOptions === 'boolean' ||
+ (summaryOrOptions === undefined && arguments.length > 1)
+ ) {
+ // Positional arguments pattern
+ options = {
+ summary: summaryOrOptions as boolean | undefined,
+ tokens,
+ peerTarget: typeof peerTarget === 'object' ? peerTarget.id : peerTarget,
+ lastUserMessage:
+ typeof lastUserMessage === 'string'
+ ? lastUserMessage
+ : lastUserMessage?.id,
+ peerPerspective:
+ typeof peerPerspective === 'object'
+ ? peerPerspective.id
+ : peerPerspective,
+ }
+ } else {
+ // Options object pattern
+ options = (summaryOrOptions as typeof options) || {}
+ }
+
const contextParams = ContextParamsSchema.parse({
- summary: options?.summary,
- tokens: options?.tokens,
+ summary: options.summary,
+ tokens: options.tokens,
+ peerTarget: options.peerTarget,
+ lastUserMessage: options.lastUserMessage,
+ peerPerspective: options.peerPerspective,
})
+
+ // Extract message ID if lastUserMessage is a Message object
+ const lastMessageId =
+ typeof contextParams.lastUserMessage === 'string'
+ ? contextParams.lastUserMessage
+ : contextParams.lastUserMessage?.id
+
const context = await this._client.workspaces.sessions.getContext(
this.workspaceId,
this.id,
{
tokens: contextParams.tokens,
summary: contextParams.summary,
+ last_message: lastMessageId,
+ peer_target: contextParams.peerTarget,
+ peer_perspective: contextParams.peerPerspective,
}
)
// Convert the summary response to Summary object if present
const summary = context.summary ? new Summary(context.summary) : null
- return new SessionContext(this.id, context.messages, summary)
+ return new SessionContext(
+ this.id,
+ context.messages,
+ summary,
+ context.peer_representation
+ ? JSON.stringify(context.peer_representation)
+ : null,
+ context.peer_card ?? null
+ )
}
/**
diff --git a/sdks/typescript/src/session_context.ts b/sdks/typescript/src/session_context.ts
index 538844c2..6eca21e0 100644
--- a/sdks/typescript/src/session_context.ts
+++ b/sdks/typescript/src/session_context.ts
@@ -102,21 +102,37 @@ export class SessionContext {
*/
readonly summary: Summary | null
+ /**
+ * The peer representation, if context is requested from a specific perspective.
+ */
+ readonly peerRepresentation: string | null
+
+ /**
+ * The peer card, if context is requested from a specific perspective.
+ */
+ readonly peerCard: string[] | null
+
/**
* Initialize a new SessionContext.
*
* @param sessionId ID of the session this context belongs to
* @param messages List of Message objects to include in the context
* @param summary Summary of the session history prior to the message cutoff
+ * @param peerRepresentation The peer representation, if context is requested from a specific perspective
+ * @param peerCard The peer card, if context is requested from a specific perspective
*/
constructor(
sessionId: string,
messages: Message[],
- summary: Summary | null = null
+ summary: Summary | null = null,
+ peerRepresentation: string | null = null,
+ peerCard: string[] | null = null
) {
this.sessionId = sessionId
this.messages = messages
this.summary = summary
+ this.peerRepresentation = peerRepresentation
+ this.peerCard = peerCard
}
/**
@@ -136,18 +152,36 @@ export class SessionContext {
assistant: string | Peer
): Array<{ role: string; content: string; name?: string }> {
const assistantId = typeof assistant === 'string' ? assistant : assistant.id
- const summaryMessage = this.summary
- ? {
- role: 'system',
- content: `${this.summary.content}`,
- }
- : null
const messages = this.messages.map((message) => ({
role: message.peer_id === assistantId ? 'assistant' : 'user',
name: message.peer_id,
content: message.content,
}))
- return summaryMessage ? [summaryMessage, ...messages] : messages
+
+ const systemMessages: Array<{ role: string; content: string }> = []
+
+ if (this.peerRepresentation) {
+ systemMessages.push({
+ role: 'system',
+ content: `${this.peerRepresentation}`,
+ })
+ }
+
+ if (this.peerCard) {
+ systemMessages.push({
+ role: 'system',
+ content: `${this.peerCard}`,
+ })
+ }
+
+ if (this.summary) {
+ systemMessages.push({
+ role: 'system',
+ content: `${this.summary.content}`,
+ })
+ }
+
+ return [...systemMessages, ...messages]
}
/**
@@ -170,12 +204,6 @@ export class SessionContext {
assistant: string | Peer
): Array<{ role: string; content: string }> {
const assistantId = typeof assistant === 'string' ? assistant : assistant.id
- const summaryMessage = this.summary
- ? {
- role: 'user',
- content: `${this.summary.content}`,
- }
- : null
const messages = this.messages.map((message) =>
message.peer_id === assistantId
? {
@@ -187,7 +215,31 @@ export class SessionContext {
content: `${message.peer_id}: ${message.content}`,
}
)
- return summaryMessage ? [summaryMessage, ...messages] : messages
+
+ const systemMessages: Array<{ role: string; content: string }> = []
+
+ if (this.peerRepresentation) {
+ systemMessages.push({
+ role: 'user',
+ content: `${this.peerRepresentation}`,
+ })
+ }
+
+ if (this.peerCard) {
+ systemMessages.push({
+ role: 'user',
+ content: `${this.peerCard}`,
+ })
+ }
+
+ if (this.summary) {
+ systemMessages.push({
+ role: 'user',
+ content: `${this.summary.content}`,
+ })
+ }
+
+ return [...systemMessages, ...messages]
}
/**
diff --git a/sdks/typescript/src/validation.ts b/sdks/typescript/src/validation.ts
index 3c30943b..e5e405c8 100644
--- a/sdks/typescript/src/validation.ts
+++ b/sdks/typescript/src/validation.ts
@@ -1,3 +1,4 @@
+import type { Message } from '@honcho-ai/core/resources/workspaces/sessions/messages'
import { z } from 'zod'
/**
@@ -123,16 +124,56 @@ export const ChatQuerySchema = z.object({
sessionId: z.string().optional(),
})
+/**
+ * Schema for validating Message objects from the core SDK.
+ */
+const MessageSchema: z.ZodType = z.object({
+ id: z.string(),
+ content: z.string(),
+ created_at: z.string(),
+ peer_id: z.string(),
+ session_id: z.string(),
+ token_count: z.number(),
+ workspace_id: z.string(),
+ metadata: z.record(z.string(), z.unknown()).optional(),
+}) as z.ZodType
+
/**
* Schema for context retrieval parameters.
*/
-export const ContextParamsSchema = z.object({
- summary: z.boolean().optional(),
- tokens: z
- .number()
- .positive('Token limit must be a positive number')
- .optional(),
-})
+export const ContextParamsSchema = z
+ .object({
+ summary: z.boolean().optional(),
+ tokens: z
+ .number()
+ .positive('Token limit must be a positive number')
+ .optional(),
+ lastUserMessage: z
+ .union([
+ z.string().min(1, 'Last user message must be a non-empty string'),
+ MessageSchema,
+ ])
+ .optional(),
+ peerTarget: PeerIdSchema.optional(),
+ peerPerspective: PeerIdSchema.optional(),
+ })
+ .superRefine((data, ctx) => {
+ if (data.lastUserMessage && !data.peerTarget) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'peerTarget is required when lastUserMessage is provided',
+ path: ['lastUserMessage'],
+ })
+ }
+
+ if (data.peerPerspective && !data.peerTarget) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: 'peerTarget is required when peerPerspective is provided',
+ path: ['peerPerspective'],
+ })
+ }
+ })
/**
* Schema for deriver status options.
diff --git a/tests/bench/harness.py b/tests/bench/harness.py
index f7f235a0..321c79a3 100755
--- a/tests/bench/harness.py
+++ b/tests/bench/harness.py
@@ -436,11 +436,8 @@ try:
print_settings(value, full_key, max_depth, current_depth + 1)
else:
# Mask sensitive information
- if isinstance(value, str) and any(sensitive in value.lower() for sensitive in ['password', 'secret', 'key', 'token']):
- if 'testpwd' in value:
- masked_value = value.replace('testpwd', '***')
- else:
- masked_value = '***'
+ if isinstance(full_key, str) and any(sensitive in full_key.lower() for sensitive in ['password', 'secret', 'key', 'uri']):
+ masked_value = '*' * len(value) if value else 'None'
else:
masked_value = value
print(f" {{key}}: {{masked_value}}")
diff --git a/uv.lock b/uv.lock
index a8615c45..95765dbb 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1,5 +1,5 @@
version = 1
-revision = 3
+revision = 2
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.13'",
@@ -780,7 +780,7 @@ dev = [
[package.metadata]
requires-dist = [
- { name = "honcho-core", specifier = ">=1.4.1a0" },
+ { name = "honcho-core", specifier = ">=1.5.0" },
{ name = "httpx", specifier = ">=0.28.0,<1" },
{ name = "pydantic", specifier = ">=2.0.0,<3" },
{ name = "typing-extensions", marker = "python_full_version < '3.12'", specifier = ">=4.12.0" },
@@ -791,7 +791,7 @@ dev = [{ name = "ruff", specifier = ">=0.11.13" }]
[[package]]
name = "honcho-core"
-version = "1.4.1a0"
+version = "1.5.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -801,9 +801,9 @@ dependencies = [
{ name = "sniffio" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/2d/2f/fbf3ca017ffcdaef92e26fe84b77268c1c0b26f95e282d8684f50932d81a/honcho_core-1.4.1a0.tar.gz", hash = "sha256:e0023f46c65500d727e3fb1275781ae1e0b162c226a01352afd45c421c5f350b", size = 130826, upload-time = "2025-10-01T03:23:27.143Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/75/cd/05d2a21afd037673637e390de411cd66ca56b07e524cd7ead65c655a2b49/honcho_core-1.5.0.tar.gz", hash = "sha256:4876195dad16db437117d40a1d5e34ff88974e8eca6d093f3653b4ac2bda3c6d", size = 132236, upload-time = "2025-10-08T18:30:15.777Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/83/5f/2aa746b7fd92690ef782dcacc56a5c47a19a999b9729597c04c7cc509e52/honcho_core-1.4.1a0-py3-none-any.whl", hash = "sha256:8b4c86e3ac3964ff56fe6a449b9fa23be49530609c34bdf35c9ecde7cd871bba", size = 121704, upload-time = "2025-10-01T03:23:25.66Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/4e/9ef193bba00c521be1152791d2ba37a98bdb58ece8f8ce7863b710a4f511/honcho_core-1.5.0-py3-none-any.whl", hash = "sha256:01db345371d6e80230b202c797a73f9f086dc244936522dbc39efb05f4cee306", size = 123210, upload-time = "2025-10-08T18:30:14.277Z" },
]
[[package]]