Merge branch 'main' into ben/dialectic-two-phase-cost
This commit is contained in:
commit
26cddfe537
|
|
@ -85,6 +85,7 @@ Use this checklist to track migration progress. Copy into your working notes and
|
|||
|
||||
- [ ] `include_most_derived=` → `include_most_frequent=`
|
||||
- [ ] `max_observations=` → `max_conclusions=`
|
||||
- [ ] `last_user_message=` → `search_query=`
|
||||
|
||||
## Return Type Changes
|
||||
|
||||
|
|
|
|||
|
|
@ -225,6 +225,7 @@ if card:
|
|||
| `chat(stream=True)` | `chat_stream()` |
|
||||
| `include_most_derived=` | `include_most_frequent=` |
|
||||
| `max_observations=` | `max_conclusions=` |
|
||||
| `last_user_message=` | `search_query=` |
|
||||
| `config=` | `configuration=` |
|
||||
| `PeerContext` | `PeerContextResponse` |
|
||||
| `DeriverStatus` | `QueueStatusResponse` |
|
||||
|
|
|
|||
|
|
@ -186,6 +186,7 @@ const ctx = await session.getContext({
|
|||
summary: true,
|
||||
peerTarget: user,
|
||||
peerPerspective: assistant,
|
||||
lastUserMessage: "What are my preferences?",
|
||||
representationOptions: {
|
||||
maxObservations: 50,
|
||||
includeMostDerived: true
|
||||
|
|
@ -197,6 +198,7 @@ const ctx = await session.context({
|
|||
summary: true,
|
||||
peerTarget: user,
|
||||
peerPerspective: assistant,
|
||||
searchQuery: "What are my preferences?",
|
||||
representationOptions: {
|
||||
maxConclusions: 50,
|
||||
includeMostFrequent: true
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ Use this checklist to track migration progress. Copy into your working notes and
|
|||
|
||||
- [ ] Rename `maxObservations` → `maxConclusions`
|
||||
- [ ] Rename `includeMostDerived` → `includeMostFrequent`
|
||||
- [ ] Rename `lastUserMessage` → `searchQuery`
|
||||
- [ ] Rename `Observation` type → `Conclusion`
|
||||
- [ ] Rename `ObservationScope` type → `ConclusionScope`
|
||||
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ await session.updateMessage(message, metadata)
|
|||
| `{ timeoutMs: 60000 }` | `{ timeout: 60 }` |
|
||||
| `{ maxObservations: 50 }` | `{ maxConclusions: 50 }` |
|
||||
| `{ includeMostDerived }` | `{ includeMostFrequent }` |
|
||||
| `{ lastUserMessage }` | `{ searchQuery }` |
|
||||
| `{ config: ... }` | `{ configuration: ... }` |
|
||||
| `message.peer_id` | `message.peerId` |
|
||||
| `message.created_at` | `message.createdAt` |
|
||||
|
|
|
|||
|
|
@ -229,14 +229,10 @@ LLM_ANTHROPIC_API_KEY=your-anthropic-api-key-here
|
|||
# SENTRY_PROFILES_SAMPLE_RATE=0.1
|
||||
|
||||
# =============================================================================
|
||||
# OpenTelemetry Settings (Push-based metrics via OTLP)
|
||||
# Prometheus Metrics Settings (Pull-based metrics)
|
||||
# =============================================================================
|
||||
# OTEL_ENABLED=false
|
||||
# OTEL_ENDPOINT=https://mimir.example.com/otlp/v1/metrics
|
||||
# OTEL_HEADERS={"X-Scope-OrgID": "honcho"} # JSON string for auth headers
|
||||
# OTEL_EXPORT_INTERVAL_MILLIS=60000
|
||||
# OTEL_SERVICE_NAME=honcho
|
||||
# OTEL_SERVICE_NAMESPACE=honcho # Inherits from NAMESPACE if not set
|
||||
# METRICS_ENABLED=false
|
||||
# METRICS_NAMESPACE=honcho # Inherits from NAMESPACE if not set
|
||||
|
||||
# =============================================================================
|
||||
# CloudEvents Telemetry Settings (Analytics events)
|
||||
|
|
|
|||
|
|
@ -190,6 +190,4 @@ CRUSH.md
|
|||
metrics.jsonl
|
||||
AGENTS.md
|
||||
lancedb_data/
|
||||
|
||||
mimir-data/
|
||||
grafana-data/
|
||||
|
|
|
|||
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -5,6 +5,13 @@ 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/).
|
||||
|
||||
## [3.0.1] - 2026-01-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- Token counting in Explicit Agent Loop
|
||||
- Backwards compatibility of queue items
|
||||
|
||||
## [3.0.0] - 2026-01-19
|
||||
|
||||
### Added
|
||||
|
|
@ -14,18 +21,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||
- Reasoning levels configuration for dialectic (`minimal`, `low`, `medium`, `high`, `max`)
|
||||
- Prometheus token tracking for deriver and dialectic operations
|
||||
- n8n integration
|
||||
- Cloud Events for auditable telemetry
|
||||
- External Vector Store support for turbopuffer and lancedb with reconciliation flow
|
||||
|
||||
### Changed
|
||||
|
||||
- API route renaming for consistency
|
||||
- Dreamer and dialectic now respect peer card configuration settings
|
||||
- Observations renamed to Conclusions across API and SDKs
|
||||
- Deriver to buffer representation tasks to normalize workloads
|
||||
- Local Representation tasks to create singular QueueItems
|
||||
- getContext endpoint to use `search_query` rather than force `last_user_message`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Dream scheduling bugs
|
||||
- Summary creation when start_message_id > end_message_id
|
||||
- Cashews upgrade to prevent NoScriptError
|
||||
- Memory leak in `accumulate_metric` call
|
||||
|
||||
### Removed
|
||||
|
||||
|
|
|
|||
49
README.md
49
README.md
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
---
|
||||
|
||||

|
||||

|
||||
[](https://pypi.org/project/honcho-ai/)
|
||||
[](https://npmjs.org/package/@honcho-ai/sdk)
|
||||
[](https://discord.gg/plasticlabs)
|
||||
|
|
@ -44,26 +44,23 @@ poetry add honcho-ai
|
|||
```python
|
||||
from honcho import Honcho
|
||||
|
||||
####### Storing Data in Honcho
|
||||
|
||||
# 1. Initialize your Honcho client
|
||||
honcho = Honcho(workspace_id="my-app-testing")
|
||||
|
||||
# 2.. Initialize Peers
|
||||
# 2. Initialize peers
|
||||
alice = honcho.peer("alice")
|
||||
tutor = honcho.peer("tutor")
|
||||
|
||||
# 3. Make a Session and send messages
|
||||
# 3. Create a session and add messages
|
||||
|
||||
session = honcho.session("session-1")
|
||||
|
||||
session.add_messages([
|
||||
alice.message("Hey there can you help me with my math homework"),
|
||||
tutor.message("Absolutely send me your first problem!"),
|
||||
.
|
||||
.
|
||||
.
|
||||
])
|
||||
# Adding messages from a peer will automatically add them to the session
|
||||
session.add_messages(
|
||||
[
|
||||
alice.message("Hey there — can you help me with my math homework?"),
|
||||
tutor.message("Absolutely. Send me your first problem!"),
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
3. Leverage reasoning from Honcho to inform your agent's behavior
|
||||
|
|
@ -73,14 +70,14 @@ session.add_messages([
|
|||
### 1. Use the chat endpoint to ask questions about your users in natural language
|
||||
response = alice.chat("What learning styles does the user respond to best?")
|
||||
|
||||
### 2. Use Get context to get most recent messages and summaries to continue a conversation
|
||||
context = session.get_context(summary=True, tokens=10000)
|
||||
### 2. Use session context to continue a conversation with an LLM
|
||||
context = session.context(summary=True, tokens=10_000)
|
||||
|
||||
# Convert to a format to send to OpenAI and get the next message
|
||||
openai_messages = context.to_openai_messages(assistant=tutor)
|
||||
openai_messages = context.to_openai(assistant=tutor)
|
||||
|
||||
from openai import OpenAI
|
||||
client = Openai()
|
||||
client = OpenAI()
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-4",
|
||||
messages=openai_messages
|
||||
|
|
@ -89,8 +86,8 @@ response = client.chat.completions.create(
|
|||
### 3. Search for similar messages
|
||||
results = alice.search("Math Homework")
|
||||
|
||||
### 4. Get a cached representation of a Peer for the Session
|
||||
alice_representation = session.working_rep("alice")
|
||||
### 4. Get a session-scoped representation of a peer
|
||||
alice_representation = session.representation(alice)
|
||||
|
||||
```
|
||||
|
||||
|
|
@ -414,7 +411,7 @@ Then modify the values as needed. The TOML file is organized into sections:
|
|||
- `[summary]` - Session summarization settings
|
||||
- `[dream]` - Dream processing configuration (including specialist models and surprisal settings)
|
||||
- `[webhook]` - Webhook configuration
|
||||
- `[otel]` - OpenTelemetry push-based metrics via OTLP
|
||||
- `[metrics]` - Prometheus pull-based metrics
|
||||
- `[telemetry]` - CloudEvents telemetry for analytics
|
||||
- `[vector_store]` - Vector store configuration (pgvector, turbopuffer, or lancedb)
|
||||
- `[sentry]` - Error tracking and monitoring settings
|
||||
|
|
@ -434,7 +431,7 @@ Examples:
|
|||
- `DERIVER_PROVIDER` - Provider for background deriver
|
||||
- `SUMMARY_PROVIDER` - Summary generation provider
|
||||
- `LOG_LEVEL` - Application log level
|
||||
- `OTEL_ENABLED` - Enable OpenTelemetry metrics
|
||||
- `METRICS_ENABLED` - Enable Prometheus metrics
|
||||
- `TELEMETRY_ENABLED` - Enable CloudEvents telemetry
|
||||
|
||||
### Configuration Priority
|
||||
|
|
@ -593,7 +590,7 @@ serve the needs of any given application.
|
|||
#### Get Context
|
||||
|
||||
In long-running conversations with an LLM, the context window can fill up
|
||||
quickly. To address this, Honcho provides a `get_context`
|
||||
quickly. To address this, Honcho provides a `context`
|
||||
endpoint that returns a combination of messages, conclusions, summaries from a
|
||||
session up to a provided token limit.
|
||||
|
||||
|
|
@ -607,10 +604,10 @@ There are several search endpoints that let developers query messages at the
|
|||
Requests can include advanced filters to further refine
|
||||
the results.
|
||||
|
||||
#### Dialectic API
|
||||
#### Chat API
|
||||
|
||||
The flagship interface for using these insights is through
|
||||
the [Dialectic Endpoint](https://blog.plasticlabs.ai/archive/ARCHIVED;-Introducing-Honcho's-Dialectic-API).
|
||||
the [`Chat` Endpoint](https://blog.plasticlabs.ai/archive/ARCHIVED;-Introducing-Honcho's-Dialectic-API).
|
||||
|
||||
This is a regular API endpoint (`/peers/{peer_id}/chat`) that takes natural language requests to get data
|
||||
about the `Peer`. This robust design lets us use this single endpoint for all
|
||||
|
|
@ -625,10 +622,10 @@ API include:
|
|||
- Asking Honcho for a 2nd opinion or approach about how to respond to the Peer
|
||||
- Getting personalized responses that incorporate long-term facts and context
|
||||
|
||||
#### Working Representations
|
||||
#### Representations
|
||||
|
||||
For low-latency use cases,
|
||||
Honcho provides access to a `get_representation` endpoint that
|
||||
Honcho provides access to a `representation` endpoint that
|
||||
returns a static document with insights about a `Peer` in the context of a
|
||||
particular session.
|
||||
|
||||
|
|
|
|||
|
|
@ -187,14 +187,10 @@ INCLUDE_LEVELS = ["explicit", "deductive"]
|
|||
SECRET = ""
|
||||
MAX_WORKSPACE_LIMIT = 10
|
||||
|
||||
# OpenTelemetry settings (push-based metrics via OTLP)
|
||||
[otel]
|
||||
# Prometheus metrics settings (pull-based metrics)
|
||||
[metrics]
|
||||
ENABLED = false
|
||||
# ENDPOINT = "https://mimir.example.com/otlp/v1/metrics"
|
||||
# HEADERS = '{"X-Scope-OrgID": "honcho"}' # JSON string for auth headers
|
||||
EXPORT_INTERVAL_MILLIS = 60000
|
||||
SERVICE_NAME = "honcho"
|
||||
# SERVICE_NAMESPACE = "honcho" # Inherits from app.NAMESPACE if not set
|
||||
# NAMESPACE = "honcho" # Inherits from app.NAMESPACE if not set
|
||||
|
||||
# CloudEvents telemetry settings (analytics events)
|
||||
[telemetry]
|
||||
|
|
|
|||
|
|
@ -59,17 +59,6 @@ services:
|
|||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
mimir:
|
||||
image: grafana/mimir:2.14.0
|
||||
command:
|
||||
- -config.file=/etc/mimir/mimir.yaml
|
||||
ports:
|
||||
- 9009:9009
|
||||
volumes:
|
||||
- ./mimir-data:/data
|
||||
configs:
|
||||
- source: mimir_config
|
||||
target: /etc/mimir/mimir.yaml
|
||||
grafana:
|
||||
image: grafana/grafana:11.4.0
|
||||
ports:
|
||||
|
|
@ -81,66 +70,6 @@ services:
|
|||
- GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
|
||||
volumes:
|
||||
- ./grafana-data:/var/lib/grafana
|
||||
depends_on:
|
||||
- mimir
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
venv:
|
||||
|
||||
configs:
|
||||
mimir_config:
|
||||
content: |
|
||||
multitenancy_enabled: false
|
||||
|
||||
server:
|
||||
http_listen_port: 9009
|
||||
log_level: warn
|
||||
|
||||
common:
|
||||
storage:
|
||||
backend: filesystem
|
||||
filesystem:
|
||||
dir: /data
|
||||
|
||||
blocks_storage:
|
||||
backend: filesystem
|
||||
filesystem:
|
||||
dir: /data/blocks
|
||||
bucket_store:
|
||||
sync_dir: /data/tsdb-sync
|
||||
tsdb:
|
||||
dir: /data/tsdb
|
||||
|
||||
compactor:
|
||||
data_dir: /data/compactor
|
||||
sharding_ring:
|
||||
kvstore:
|
||||
store: memberlist
|
||||
|
||||
distributor:
|
||||
ring:
|
||||
instance_addr: 127.0.0.1
|
||||
kvstore:
|
||||
store: memberlist
|
||||
|
||||
ingester:
|
||||
ring:
|
||||
instance_addr: 127.0.0.1
|
||||
kvstore:
|
||||
store: memberlist
|
||||
replication_factor: 1
|
||||
|
||||
ruler_storage:
|
||||
backend: filesystem
|
||||
filesystem:
|
||||
dir: /data/rules
|
||||
|
||||
alertmanager_storage:
|
||||
backend: filesystem
|
||||
filesystem:
|
||||
dir: /data/alertmanager
|
||||
|
||||
store_gateway:
|
||||
sharding_ring:
|
||||
replication_factor: 1
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "honcho-docs",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ This guide helps you understand which versions of Honcho's API are compatible wi
|
|||
|
||||
## Version Compatibility
|
||||
|
||||
### Honcho API v3.0.0 (Current)
|
||||
### Honcho API v3.0.1 (Current)
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="TypeScript SDK" icon="js">
|
||||
|
|
@ -34,7 +34,8 @@ This guide helps you understand which versions of Honcho's API are compatible wi
|
|||
|
||||
| Honcho API Version | TypeScript SDK | Python SDK |
|
||||
|-------------------|---------------|------------|
|
||||
| v3.0.0 (Current) | v2.0.0 | v2.0.0 |
|
||||
| v3.0.1 (Current) | v2.0.0 | v2.0.0 |
|
||||
| v3.0.0 | v2.0.0 | v2.0.0 |
|
||||
| v2.5.1 | v1.6.0 | v1.6.0 |
|
||||
| v2.5.0 | v1.6.0 | v1.6.0 |
|
||||
| v2.4.3 | v1.5.0 | v1.5.0 |
|
||||
|
|
|
|||
|
|
@ -27,10 +27,43 @@ 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="v3.0.0 (Current)">
|
||||
<Update label="v3.0.1 (Current)">
|
||||
### Fixed
|
||||
|
||||
- Token counting in Explicit Agent Loop
|
||||
- Backwards compatibility of queue items
|
||||
</Update>
|
||||
|
||||
<Update label="v3.0.0">
|
||||
### Added
|
||||
|
||||
- Agentic Dreamer for intelligent memory consolidation using LLM agents
|
||||
- Agentic Dialectic for query answering using LLM agents with tool use
|
||||
- Reasoning levels configuration for dialectic (`minimal`, `low`, `medium`, `high`, `max`)
|
||||
- Prometheus token tracking for deriver and dialectic operations
|
||||
- n8n integration
|
||||
- Cloud Events for auditable telemetry
|
||||
- External Vector Store support for turbopuffer and lancedb with reconciliation flow
|
||||
|
||||
### Changed
|
||||
|
||||
- Major version release
|
||||
- API route renaming for consistency
|
||||
- Dreamer and dialectic now respect peer card configuration settings
|
||||
- Observations renamed to Conclusions across API and SDKs
|
||||
- Deriver to buffer representation tasks to normalize workloads
|
||||
- Local Representation tasks to create singular QueueItems
|
||||
- getContext endpoint to use `search_query` rather than force `last_user_message`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Dream scheduling bugs
|
||||
- Summary creation when start_message_id > end_message_id
|
||||
- Cashews upgrade to prevent NoScriptError
|
||||
- Memory leak in `accumulate_metric` call
|
||||
|
||||
### Removed
|
||||
|
||||
- Peer card configuration from message configuration; peer cards no longer created/updated in deriver process
|
||||
</Update>
|
||||
|
||||
<Update label="v2.5.1">
|
||||
|
|
@ -148,7 +181,7 @@ Welcome to the Honcho changelog! This section documents all notable changes to t
|
|||
<Update label="v2.3.2">
|
||||
### Added
|
||||
|
||||
- Get peer cards endpoint (`GET /v3/peers/{peer_id}/peer-card`) for retrieving targeted peer context information
|
||||
- Get peer cards endpoint (`GET /v2/peers/{peer_id}/card`) for retrieving targeted peer context information
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
432
docs/docs.json
432
docs/docs.json
|
|
@ -24,11 +24,226 @@
|
|||
},
|
||||
"navigation": {
|
||||
"versions": [
|
||||
{
|
||||
"version": "v3.0.1",
|
||||
"api": {
|
||||
"openapi": [
|
||||
"v3/openapi.json"
|
||||
]
|
||||
},
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Documentation",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Introduction",
|
||||
"pages": [
|
||||
"v3/documentation/introduction/overview",
|
||||
"v3/documentation/introduction/quickstart",
|
||||
"v3/documentation/introduction/vibecoding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Core Concepts",
|
||||
"pages": [
|
||||
"v3/documentation/core-concepts/architecture",
|
||||
"v3/documentation/core-concepts/reasoning",
|
||||
"v3/documentation/core-concepts/representation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Features",
|
||||
"pages": [
|
||||
"v3/documentation/features/get-context",
|
||||
"v3/documentation/features/chat",
|
||||
{
|
||||
"group": "Advanced",
|
||||
"pages": [
|
||||
"v3/documentation/features/advanced/overview",
|
||||
"v3/documentation/features/advanced/queue-status",
|
||||
"v3/documentation/features/advanced/reasoning-configuration",
|
||||
"v3/documentation/features/advanced/representation-scopes",
|
||||
"v3/documentation/features/advanced/summarizer",
|
||||
"v3/documentation/features/advanced/search",
|
||||
"v3/documentation/features/advanced/using-filters",
|
||||
"v3/documentation/features/advanced/streaming-response"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Reference",
|
||||
"pages": [
|
||||
"v3/documentation/reference/platform",
|
||||
"v3/documentation/reference/sdk"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Guides",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"pages": [
|
||||
"v3/guides/overview",
|
||||
"v3/guides/file-uploads",
|
||||
"v3/guides/storing-data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Integrations",
|
||||
"pages": [
|
||||
"v3/guides/integrations/crewai",
|
||||
"v3/guides/integrations/langgraph",
|
||||
"v3/guides/integrations/mcp",
|
||||
"v3/guides/integrations/n8n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Migrations",
|
||||
"pages": [
|
||||
"v3/guides/migrations/mem0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Chatbots",
|
||||
"pages": [
|
||||
"v3/guides/discord",
|
||||
"v3/guides/telegram"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Open Source",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Self-Hosting",
|
||||
"pages": [
|
||||
"v3/contributing/self-hosting",
|
||||
"v3/contributing/configuration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Contributing",
|
||||
"pages": [
|
||||
"v3/contributing/guidelines",
|
||||
"v3/contributing/license"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "API Reference",
|
||||
"groups": [
|
||||
{
|
||||
"group": "API Documentation",
|
||||
"pages": [
|
||||
"v3/api-reference/introduction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "workspaces",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/workspaces/get-or-create-workspace",
|
||||
"v3/api-reference/endpoint/workspaces/get-all-workspaces",
|
||||
"v3/api-reference/endpoint/workspaces/update-workspace",
|
||||
"v3/api-reference/endpoint/workspaces/delete-workspace",
|
||||
"v3/api-reference/endpoint/workspaces/search-workspace",
|
||||
"v3/api-reference/endpoint/workspaces/get-queue-status",
|
||||
"v3/api-reference/endpoint/workspaces/schedule-dream"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "peers",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/peers/get-peers",
|
||||
"v3/api-reference/endpoint/peers/get-or-create-peer",
|
||||
"v3/api-reference/endpoint/peers/update-peer",
|
||||
"v3/api-reference/endpoint/peers/get-sessions-for-peer",
|
||||
"v3/api-reference/endpoint/peers/chat",
|
||||
"v3/api-reference/endpoint/peers/get-representation",
|
||||
"v3/api-reference/endpoint/peers/get-peer-card",
|
||||
"v3/api-reference/endpoint/peers/set-peer-card",
|
||||
"v3/api-reference/endpoint/peers/get-peer-context",
|
||||
"v3/api-reference/endpoint/peers/search-peer"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "sessions",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/sessions/get-or-create-session",
|
||||
"v3/api-reference/endpoint/sessions/get-sessions",
|
||||
"v3/api-reference/endpoint/sessions/update-session",
|
||||
"v3/api-reference/endpoint/sessions/delete-session",
|
||||
"v3/api-reference/endpoint/sessions/clone-session",
|
||||
"v3/api-reference/endpoint/sessions/get-session-peers",
|
||||
"v3/api-reference/endpoint/sessions/set-session-peers",
|
||||
"v3/api-reference/endpoint/sessions/add-peers-to-session",
|
||||
"v3/api-reference/endpoint/sessions/remove-peers-from-session",
|
||||
"v3/api-reference/endpoint/sessions/get-peer-config",
|
||||
"v3/api-reference/endpoint/sessions/set-peer-config",
|
||||
"v3/api-reference/endpoint/sessions/get-session-context",
|
||||
"v3/api-reference/endpoint/sessions/get-session-summaries",
|
||||
"v3/api-reference/endpoint/sessions/search-session"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "messages",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/messages/create-messages-for-session",
|
||||
"v3/api-reference/endpoint/messages/get-messages",
|
||||
"v3/api-reference/endpoint/messages/get-message",
|
||||
"v3/api-reference/endpoint/messages/update-message",
|
||||
"v3/api-reference/endpoint/messages/create-messages-with-file"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "conclusions",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/conclusions/create-conclusions",
|
||||
"v3/api-reference/endpoint/conclusions/list-conclusions",
|
||||
"v3/api-reference/endpoint/conclusions/query-conclusions",
|
||||
"v3/api-reference/endpoint/conclusions/delete-conclusion"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "webhooks",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/webhooks/list-webhook-endpoints",
|
||||
"v3/api-reference/endpoint/webhooks/get-or-create-webhook-endpoint",
|
||||
"v3/api-reference/endpoint/webhooks/delete-webhook-endpoint",
|
||||
"v3/api-reference/endpoint/webhooks/test-emit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "miscellaneous",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/keys/create-key"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Changelog",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"pages": [
|
||||
"changelog/introduction",
|
||||
"changelog/compatibility-guide"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "v2.5.1",
|
||||
"api": {
|
||||
"openapi": [
|
||||
"openapi.json"
|
||||
"v2/openapi.json"
|
||||
]
|
||||
},
|
||||
"tabs": [
|
||||
|
|
@ -224,221 +439,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "v3.0.0",
|
||||
"api": {
|
||||
"openapi": [
|
||||
"openapi.json"
|
||||
]
|
||||
},
|
||||
"tabs": [
|
||||
{
|
||||
"tab": "Documentation",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Introduction",
|
||||
"pages": [
|
||||
"v3/documentation/introduction/overview",
|
||||
"v3/documentation/introduction/quickstart",
|
||||
"v3/documentation/introduction/vibecoding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Core Concepts",
|
||||
"pages": [
|
||||
"v3/documentation/core-concepts/architecture",
|
||||
"v3/documentation/core-concepts/reasoning",
|
||||
"v3/documentation/core-concepts/representation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Features",
|
||||
"pages": [
|
||||
"v3/documentation/features/get-context",
|
||||
"v3/documentation/features/chat",
|
||||
{
|
||||
"group": "Advanced",
|
||||
"pages": [
|
||||
"v3/documentation/features/advanced/overview",
|
||||
"v3/documentation/features/advanced/queue-status",
|
||||
"v3/documentation/features/advanced/reasoning-configuration",
|
||||
"v3/documentation/features/advanced/representation-scopes",
|
||||
"v3/documentation/features/advanced/summarizer",
|
||||
"v3/documentation/features/advanced/search",
|
||||
"v3/documentation/features/advanced/using-filters",
|
||||
"v3/documentation/features/advanced/streaming-response"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Reference",
|
||||
"pages": [
|
||||
"v3/documentation/reference/platform",
|
||||
"v3/documentation/reference/sdk"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Guides",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"pages": [
|
||||
"v3/guides/overview",
|
||||
"v3/guides/file-uploads",
|
||||
"v3/guides/storing-data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Integrations",
|
||||
"pages": [
|
||||
"v3/guides/integrations/crewai",
|
||||
"v3/guides/integrations/langgraph",
|
||||
"v3/guides/integrations/mcp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Migrations",
|
||||
"pages": [
|
||||
"v3/guides/migrations/mem0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Chatbots",
|
||||
"pages": [
|
||||
"v3/guides/discord",
|
||||
"v3/guides/telegram"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Open Source",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Self-Hosting",
|
||||
"pages": [
|
||||
"v3/contributing/self-hosting",
|
||||
"v3/contributing/configuration"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Contributing",
|
||||
"pages": [
|
||||
"v3/contributing/guidelines",
|
||||
"v3/contributing/license"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "API Reference",
|
||||
"groups": [
|
||||
{
|
||||
"group": "API Documentation",
|
||||
"pages": [
|
||||
"v3/api-reference/introduction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "workspaces",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/workspaces/get-or-create-workspace",
|
||||
"v3/api-reference/endpoint/workspaces/get-all-workspaces",
|
||||
"v3/api-reference/endpoint/workspaces/update-workspace",
|
||||
"v3/api-reference/endpoint/workspaces/delete-workspace",
|
||||
"v3/api-reference/endpoint/workspaces/search-workspace",
|
||||
"v3/api-reference/endpoint/workspaces/get-deriver-status",
|
||||
"v3/api-reference/endpoint/workspaces/trigger-dream"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "peers",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/peers/get-peers",
|
||||
"v3/api-reference/endpoint/peers/get-or-create-peer",
|
||||
"v3/api-reference/endpoint/peers/update-peer",
|
||||
"v3/api-reference/endpoint/peers/get-sessions-for-peer",
|
||||
"v3/api-reference/endpoint/peers/chat",
|
||||
"v3/api-reference/endpoint/peers/get-working-representation",
|
||||
"v3/api-reference/endpoint/peers/get-peer-card",
|
||||
"v3/api-reference/endpoint/peers/set-peer-card",
|
||||
"v3/api-reference/endpoint/peers/get-peer-context",
|
||||
"v3/api-reference/endpoint/peers/search-peer"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "sessions",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/sessions/get-or-create-session",
|
||||
"v3/api-reference/endpoint/sessions/get-sessions",
|
||||
"v3/api-reference/endpoint/sessions/update-session",
|
||||
"v3/api-reference/endpoint/sessions/delete-session",
|
||||
"v3/api-reference/endpoint/sessions/clone-session",
|
||||
"v3/api-reference/endpoint/sessions/get-session-peers",
|
||||
"v3/api-reference/endpoint/sessions/set-session-peers",
|
||||
"v3/api-reference/endpoint/sessions/add-peers-to-session",
|
||||
"v3/api-reference/endpoint/sessions/remove-peers-from-session",
|
||||
"v3/api-reference/endpoint/sessions/get-peer-config",
|
||||
"v3/api-reference/endpoint/sessions/set-peer-config",
|
||||
"v3/api-reference/endpoint/sessions/get-session-context",
|
||||
"v3/api-reference/endpoint/sessions/get-session-summaries",
|
||||
"v3/api-reference/endpoint/sessions/search-session"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "messages",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/messages/create-messages-for-session",
|
||||
"v3/api-reference/endpoint/messages/get-messages",
|
||||
"v3/api-reference/endpoint/messages/get-message",
|
||||
"v3/api-reference/endpoint/messages/update-message",
|
||||
"v3/api-reference/endpoint/messages/create-messages-with-file"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "observations",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/observations/create-observations",
|
||||
"v3/api-reference/endpoint/observations/list-observations",
|
||||
"v3/api-reference/endpoint/observations/query-observations",
|
||||
"v3/api-reference/endpoint/observations/delete-observation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "webhooks",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/webhooks/list-webhook-endpoints",
|
||||
"v3/api-reference/endpoint/webhooks/get-or-create-webhook-endpoint",
|
||||
"v3/api-reference/endpoint/webhooks/delete-webhook-endpoint",
|
||||
"v3/api-reference/endpoint/webhooks/test-emit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "miscellaneous",
|
||||
"pages": [
|
||||
"v3/api-reference/endpoint/keys/create-key",
|
||||
"v3/api-reference/endpoint/metrics"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tab": "Changelog",
|
||||
"groups": [
|
||||
{
|
||||
"group": "Overview",
|
||||
"pages": [
|
||||
"changelog/introduction",
|
||||
"changelog/compatibility-guide"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "v1.1.0",
|
||||
"api": {
|
||||
|
|
|
|||
|
|
@ -139,17 +139,17 @@ context = session.get_context(
|
|||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Semantic Search with Last Message
|
||||
### Semantic Search
|
||||
|
||||
Use `last_user_message` to fetch semantically relevant observations based on the most recent message:
|
||||
Use `search_query` to fetch semantically relevant observations based on a query string:
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
# Get context with semantic search based on last message
|
||||
# Get context with semantic search based on query
|
||||
context = session.get_context(
|
||||
tokens=2000,
|
||||
peer_target="user-123",
|
||||
last_user_message="What are my account preferences?",
|
||||
search_query="What are my account preferences?",
|
||||
search_top_k=10, # Number of relevant observations
|
||||
search_max_distance=0.8, # Max semantic distance (0.0-1.0)
|
||||
include_most_derived=True, # Include most recent observations
|
||||
|
|
@ -159,11 +159,11 @@ context = session.get_context(
|
|||
|
||||
```typescript TypeScript
|
||||
(async () => {
|
||||
// Get context with semantic search based on last message
|
||||
// Get context with semantic search based on query
|
||||
const context = await session.getContext({
|
||||
tokens: 2000,
|
||||
peerTarget: "user-123",
|
||||
lastUserMessage: "What are my account preferences?",
|
||||
searchQuery: "What are my account preferences?",
|
||||
searchTopK: 10, // Number of relevant observations
|
||||
searchMaxDistance: 0.8, // Max semantic distance (0.0-1.0)
|
||||
includeMostDerived: true, // Include most recent observations
|
||||
|
|
@ -207,7 +207,7 @@ context = session.get_context(
|
|||
| `tokens` | `int` | Maximum tokens to include |
|
||||
| `peer_target` | `str` | Peer ID to include representation for |
|
||||
| `peer_perspective` | `str` | Peer ID for perspective (requires peer_target) |
|
||||
| `last_user_message` | `str` | Message for semantic search (requires peer_target) |
|
||||
| `search_query` | `str` | Query for semantic search (requires peer_target) |
|
||||
| `limit_to_session` | `bool` | Limit to session observations only |
|
||||
| `search_top_k` | `int` | Semantic search results to include (1-100) |
|
||||
| `search_max_distance` | `float` | Max semantic distance (0.0-1.0) |
|
||||
|
|
|
|||
|
|
@ -533,7 +533,7 @@ context = session.get_context(
|
|||
tokens=2000,
|
||||
peer_target="user",
|
||||
peer_perspective="assistant",
|
||||
last_user_message="What are my preferences?",
|
||||
search_query="What are my preferences?",
|
||||
limit_to_session=True,
|
||||
search_top_k=10,
|
||||
search_max_distance=0.8,
|
||||
|
|
@ -612,7 +612,7 @@ const richContext = await session.getContext({
|
|||
tokens: 2000,
|
||||
peerTarget: "user",
|
||||
peerPerspective: "assistant",
|
||||
lastUserMessage: "What are my preferences?",
|
||||
searchQuery: "What are my preferences?",
|
||||
limitToSession: true,
|
||||
searchTopK: 10,
|
||||
searchMaxDistance: 0.8,
|
||||
|
|
@ -752,7 +752,7 @@ The SessionContext object has the following structure:
|
|||
| `tokens` | `int` | Maximum tokens to include |
|
||||
| `peer_target` | `str` | Peer ID to get representation for |
|
||||
| `peer_perspective` | `str` | Peer ID for perspective (requires peer_target) |
|
||||
| `last_user_message` | `str` | Most recent message for semantic search |
|
||||
| `search_query` | `str` | Query string for semantic search |
|
||||
| `limit_to_session` | `bool` | Limit representation to session only |
|
||||
| `search_top_k` | `int` | Number of semantic search results (1-100) |
|
||||
| `search_max_distance` | `float` | Max semantic distance (0.0-1.0) |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
openapi: post /v3/workspaces/{workspace_id}/conclusions
|
||||
---
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
openapi: delete /v3/workspaces/{workspace_id}/conclusions/{conclusion_id}
|
||||
---
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
openapi: post /v3/workspaces/{workspace_id}/conclusions/list
|
||||
---
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
openapi: post /v3/workspaces/{workspace_id}/conclusions/query
|
||||
---
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
openapi: post /v3/workspaces/{workspace_id}/sessions/{session_id}/messages/
|
||||
openapi: post /v3/workspaces/{workspace_id}/sessions/{session_id}/messages
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
openapi: get /metrics
|
||||
---
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
openapi: post /v3/workspaces/{workspace_id}/observations
|
||||
---
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
openapi: delete /v3/workspaces/{workspace_id}/observations/{observation_id}
|
||||
---
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
openapi: post /v3/workspaces/{workspace_id}/observations/list
|
||||
---
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
openapi: post /v3/workspaces/{workspace_id}/observations/query
|
||||
---
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
openapi: get /v3/workspaces/{workspace_id}/sessions/{session_id}/clone
|
||||
openapi: post /v3/workspaces/{workspace_id}/sessions/{session_id}/clone
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
openapi: post /v3/workspaces/{workspace_id}/sessions/{session_id}/peers/{peer_id}/config
|
||||
openapi: put /v3/workspaces/{workspace_id}/sessions/{session_id}/peers/{peer_id}/config
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
openapi: get /v3/workspaces/{workspace_id}/deriver/status
|
||||
---
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
openapi: get /v3/workspaces/{workspace_id}/queue/status
|
||||
---
|
||||
|
|
@ -57,7 +57,7 @@ Then modify the values as needed. The TOML file is organized into sections:
|
|||
- `[summary]` - Session summarization settings (frequency thresholds, provider, model, token limits for short and long summaries)
|
||||
- `[dream]` - Dream processing configuration (enable/disable, thresholds, idle timeouts, dream types, LLM settings, surprisal sampling)
|
||||
- `[webhook]` - Webhook configuration (webhook secret, workspace limits)
|
||||
- `[otel]` - OpenTelemetry settings for push-based metrics via OTLP
|
||||
- `[metrics]` - Prometheus pull-based metrics settings
|
||||
- `[telemetry]` - CloudEvents telemetry settings for analytics
|
||||
- `[vector_store]` - Vector store configuration (pgvector, Turbopuffer, LanceDB)
|
||||
- `[sentry]` - Error tracking and monitoring settings (enable/disable, DSN, environment, sample rates)
|
||||
|
|
@ -540,29 +540,19 @@ VECTOR_STORE_LANCEDB_PATH=./lancedb_data
|
|||
|
||||
## Monitoring Configuration
|
||||
|
||||
### OpenTelemetry (Push-based Metrics)
|
||||
### Prometheus Metrics (Pull-based)
|
||||
|
||||
Honcho supports push-based metrics via OpenTelemetry Protocol (OTLP) to any compatible backend (Mimir, Grafana Cloud, etc.).
|
||||
Honcho exposes Prometheus metrics via `/metrics` endpoints for scraping:
|
||||
- **API process**: Port 8000 at `/metrics`
|
||||
- **Deriver process**: Port 9090 at `/metrics`
|
||||
|
||||
**OpenTelemetry Settings:**
|
||||
**Metrics Settings:**
|
||||
```bash
|
||||
# Enable/disable OTel metrics
|
||||
OTEL_ENABLED=false
|
||||
# Enable/disable Prometheus metrics
|
||||
METRICS_ENABLED=false
|
||||
|
||||
# OTLP HTTP endpoint for metrics
|
||||
# For Mimir: <mimir-url>/otlp/v1/metrics
|
||||
# For Grafana Cloud: https://otlp-gateway-<region>.grafana.net/otlp/v1/metrics
|
||||
OTEL_ENDPOINT=https://mimir.example.com/otlp/v1/metrics
|
||||
|
||||
# Optional auth headers (JSON format in env var)
|
||||
OTEL_HEADERS='{"X-Scope-OrgID": "honcho"}'
|
||||
|
||||
# Export interval in milliseconds (default: 60 seconds)
|
||||
OTEL_EXPORT_INTERVAL_MILLIS=60000
|
||||
|
||||
# Service identification
|
||||
OTEL_SERVICE_NAME=honcho
|
||||
OTEL_SERVICE_NAMESPACE=honcho # Inherits from app.NAMESPACE if not set
|
||||
# Namespace label for all metrics (inherits from app.NAMESPACE if not set)
|
||||
METRICS_NAMESPACE=honcho
|
||||
```
|
||||
|
||||
### CloudEvents Telemetry (Analytics)
|
||||
|
|
@ -691,7 +681,7 @@ MODEL = "claude-sonnet-4-20250514"
|
|||
[webhook]
|
||||
MAX_WORKSPACE_LIMIT = 10
|
||||
|
||||
[otel]
|
||||
[metrics]
|
||||
ENABLED = false
|
||||
|
||||
[telemetry]
|
||||
|
|
@ -798,7 +788,7 @@ MODEL = "claude-sonnet-4-20250514"
|
|||
[webhook]
|
||||
MAX_WORKSPACE_LIMIT = 10
|
||||
|
||||
[otel]
|
||||
[metrics]
|
||||
ENABLED = true
|
||||
|
||||
[telemetry]
|
||||
|
|
@ -838,7 +828,7 @@ LLM_GROQ_API_KEY=your-prod-groq-key
|
|||
WEBHOOK_SECRET=your-webhook-signing-secret
|
||||
|
||||
# Monitoring
|
||||
OTEL_ENDPOINT=https://mimir.example.com/otlp/v1/metrics
|
||||
METRICS_ENABLED=true
|
||||
TELEMETRY_ENDPOINT=https://telemetry.honcho.dev/v1/events
|
||||
SENTRY_DSN=https://your-sentry-dsn@sentry.io/project-id
|
||||
SENTRY_ENVIRONMENT=production
|
||||
|
|
|
|||
|
|
@ -61,6 +61,6 @@ Humans reconstruct the past from imperfect recollections, then act on those reco
|
|||
Understand how representations fit into Honcho's architecture
|
||||
</Card>
|
||||
<Card title="Chat Endpoint" icon="comments" href="/v3/documentation/features/chat">
|
||||
Learn how to query representations with natural language
|
||||
Chat with Honcho about your users
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Advanced features give you fine-grained control over Honcho's behavior and imple
|
|||
## Configuration & Monitoring
|
||||
|
||||
- [Queue Status](/v3/documentation/features/advanced/queue-status) - Monitor background processing and reasoning tasks
|
||||
- [Configuration](/v3/documentation/features/advanced/toggle-reasoning) - Configure reasoning models and behavior
|
||||
- [Configuration](/v3/documentation/features/advanced/reasoning-configuration) - Configure reasoning models and behavior
|
||||
- [Summarizer](/v3/documentation/features/advanced/summarizer) - Automatic session summarization
|
||||
|
||||
## Querying & Filtering
|
||||
|
|
|
|||
|
|
@ -143,16 +143,16 @@ context = session.get_context(
|
|||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Semantic Search with Last Message
|
||||
### Semantic Search
|
||||
|
||||
Use `last_user_message` to fetch semantically relevant conclusions based on the most recent message (requires `peer_target`):
|
||||
Use `search_query` to fetch semantically relevant conclusions based on a query string (requires `peer_target`):
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
context = session.get_context(
|
||||
tokens=2000,
|
||||
peer_target="user-123",
|
||||
last_user_message="What are my coding preferences?",
|
||||
search_query="What are my coding preferences?",
|
||||
search_top_k=10, # Number of relevant conclusions to fetch
|
||||
search_max_distance=0.8, # Max semantic distance (0.0-1.0)
|
||||
include_most_frequent=True, # Include most frequent conclusions
|
||||
|
|
@ -165,7 +165,7 @@ context = session.get_context(
|
|||
const context = await session.getContext({
|
||||
tokens: 2000,
|
||||
peerTarget: "user-123",
|
||||
lastUserMessage: "What are my coding preferences?",
|
||||
searchQuery: "What are my coding preferences?",
|
||||
representationOptions: {
|
||||
searchTopK: 10, // Number of relevant conclusions to fetch
|
||||
searchMaxDistance: 0.8, // Max semantic distance (0.0-1.0)
|
||||
|
|
@ -211,7 +211,7 @@ context = session.get_context(
|
|||
| `tokens` | `int` | Maximum tokens to include |
|
||||
| `peer_target` | `str` | Peer ID to include representation for |
|
||||
| `peer_perspective` | `str` | Peer ID for perspective (requires peer_target) |
|
||||
| `last_user_message` | `str` | Message for semantic search (requires peer_target) |
|
||||
| `search_query` | `str` | Query for semantic search (requires peer_target) |
|
||||
| `limit_to_session` | `bool` | Limit to session conclusions only |
|
||||
| `search_top_k` | `int` | Semantic search results to include (1-100) |
|
||||
| `search_max_distance` | `float` | Max semantic distance (0.0-1.0) |
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: "Overview"
|
|||
Honcho is an open source memory library with a managed service for building stateful agents. Use it with any model, framework, or architecture. It enables agents to build and maintain state about any entity--users, agents, groups, ideas, and more. And because it's a continual learning system, it understands entities that change over time. Using Honcho as your memory system will earn your agents higher retention, more trust, and help you build data moats to out-compete incumbents.
|
||||
|
||||
<Note>
|
||||
Honcho has defined the Pareto Frontier of Agent Memory. Watch the [video](https://x.com/honchodotdev/status/2002090546521911703?s=20), check out our [evals page](https://evals.honcho.dev/), and read the [blog post](https://blog.plasticlabs.ai/research/Benchmarking-Honcho) for more detail.
|
||||
Honcho has defined the Pareto Frontier of Agent Memory. Watch the [video](https://x.com/honchodotdev/status/2002090546521911703?s=20), check out our [evals page](https://evals.honcho.dev/), and read the [blog post](https://blog.plasticlabs.ai/research/Benchmarking-Honcho) for more detail.
|
||||
</Note>
|
||||
|
||||
<CardGroup cols={2}>
|
||||
|
|
@ -42,7 +42,7 @@ Break free from this cycle. Honcho is a general solution to context engineering,
|
|||
## How Honcho Works
|
||||
|
||||
<Note>
|
||||
Honcho is a memory system that reasons. Read more on the approach [here](https://blog.plasticlabs.ai/blog/Memory-as-Reasoning).
|
||||
Honcho is a memory system that reasons. Read more on the approach [here](https://blog.plasticlabs.ai/blog/Memory-as-Reasoning).
|
||||
</Note>
|
||||
|
||||
Honcho has four storage primitives that work together:
|
||||
|
|
@ -65,9 +65,9 @@ Honcho has four storage primitives that work together:
|
|||
- **Workspaces** - Top-level containers that isolate different applications or environments
|
||||
- **Peers** - Any entity that persists but changes over time (users, agents, objects, and more)
|
||||
- **Sessions** - Interaction threads between peers with temporal boundaries
|
||||
- **Messages** - Units of data that trigger reasoning (conversations, events, activity, documents, and more)
|
||||
- **Messages** - Units of data that trigger reasoning (conversations, events, activity, documents, and more)
|
||||
|
||||
When you write messages to Honcho, they're stored and processed in the background. Custom reasoning models perform formal logical [*reasoning*](/v3/documentation/core-concepts/reasoning) to generate conclusions about each peer. These conclusions are stored as [*representations*](/v3/documentation/core-concepts/representation) that you can query to provide rich context for your agents.
|
||||
When you write messages to Honcho, they're stored and processed in the background. Custom reasoning models perform formal logical [_reasoning_](/v3/documentation/core-concepts/reasoning) to generate conclusions about each peer. These conclusions are stored as [_representations_](/v3/documentation/core-concepts/representation) that you can query to provide rich context for your agents.
|
||||
|
||||

|
||||
|
||||
|
|
@ -75,7 +75,7 @@ The diagram above shows the flow: agents write messages to Honcho, which trigger
|
|||
|
||||
## Why Reasoning?
|
||||
|
||||
Traditional RAG systems retrieve what was explicitly said, but they miss what matters most—the insights only accessible by *rigorously thinking* about your data. Without reasoning, you're leaving latent information on the table. Static retrieval can't surface implicit connections, struggles when new information contradicts old data, and fails when you need to make predictions under uncertainty.
|
||||
Traditional RAG systems retrieve what was explicitly said, but they miss what matters most—the insights only accessible by _rigorously thinking_ about your data. Without reasoning, you're leaving latent information on the table. Static retrieval can't surface implicit connections, struggles when new information contradicts old data, and fails when you need to make predictions under uncertainty.
|
||||
|
||||
Honcho uses formal logic to extract all that latent information. This reasoning is AI-native—it performs the rigorous, compute-intensive thinking that humans struggle with, instantly and consistently. The result is memory that goes beyond simple RAG recall to provide exhaustive context for statefulness.
|
||||
|
||||
|
|
@ -100,4 +100,4 @@ Welcome to Honcho. We're excited to have you at the frontier of AI with us 🫡.
|
|||
<Card title="Reasoning" icon="gears" href="/v3/documentation/core-concepts/reasoning">
|
||||
Learn how Honcho reasons about data to build memory
|
||||
</Card>
|
||||
</CardGroup>
|
||||
</CardGroup>
|
||||
|
|
@ -369,7 +369,7 @@ From here, you can explore how to use Honcho's features in your own applications
|
|||
Deep dive into how Honcho's primitives fit together
|
||||
</Card>
|
||||
<Card title="Chat Endpoint" icon="comments" href="/v3/documentation/features/chat">
|
||||
Query representations with natural language
|
||||
Chat with Honcho about your users
|
||||
</Card>
|
||||
<Card title="Guides" icon="book" href="/v3/guides/overview">
|
||||
Integration patterns and advanced use cases
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: "AI-Powered Honcho Setup"
|
||||
icon: "wand-magic-sparkles"
|
||||
description: "Universal starter prompt and Claude Code skill for building with Honcho"
|
||||
description: "Agent skills and starter prompt for building with Honcho"
|
||||
sidebarTitle: 'Vibecoding Setup'
|
||||
---
|
||||
|
||||
|
|
@ -14,38 +14,44 @@ We follow the llms.txt standard. There are both an llms.txt and llms-full.txt av
|
|||
|
||||
---
|
||||
|
||||
## Claude Code Skill
|
||||
## Agent Skills
|
||||
|
||||
If you're using [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview), you can install the Honcho integration skill for a guided, interactive setup experience. The skill will explore your codebase, ask targeted questions about your integration needs, and implement Honcho step by step.
|
||||
|
||||
### Installation
|
||||
We provide agent skills for coding assistants like Claude Code, Cursor, Windsurf, and others.
|
||||
|
||||
<CodeGroup>
|
||||
```bash Global Installation (all projects)
|
||||
# Add to your global skills directory
|
||||
curl -o ~/.claude/skills/honcho-integration.md https://raw.githubusercontent.com/plastic-labs/honcho/main/docs/SKILL.md
|
||||
```bash Install via npx (Recommended)
|
||||
npx skills add plastic-labs/honcho
|
||||
```
|
||||
|
||||
```bash Project-specific Installation
|
||||
# Add to your project's .claude directory
|
||||
mkdir -p .claude/skills
|
||||
curl -o .claude/skills/honcho-integration.md https://raw.githubusercontent.com/plastic-labs/honcho/main/docs/SKILL.md
|
||||
```bash Install as Claude Skill Manually
|
||||
curl -o ~/.claude/skills/honcho-integration.md https://raw.githubusercontent.com/plastic-labs/honcho/main/docs/SKILL.md
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Usage
|
||||
### Available Skills
|
||||
|
||||
Once installed, invoke the skill in Claude Code:
|
||||
#### honcho-integration
|
||||
|
||||
```
|
||||
/honcho-integration
|
||||
```
|
||||
**For new integrations.** This skill helps you add Honcho to an existing Python or TypeScript codebase. It provides a guided, interactive experience:
|
||||
|
||||
The skill will:
|
||||
1. **Explore your codebase** to understand your language, framework, and existing AI/LLM integrations
|
||||
2. **Interview you** about which entities should be peers, your preferred integration pattern, and session structure
|
||||
3. **Implement the integration** based on your answers
|
||||
4. **Verify the setup** to ensure everything is configured correctly
|
||||
1. **Explores your codebase** to understand your language, framework, and existing AI/LLM integrations
|
||||
2. **Interviews you** about which entities should be peers, your preferred integration pattern, and session structure
|
||||
3. **Implements the integration** based on your answers—installing the SDK, creating peers, configuring sessions, and wiring up the chat endpoint
|
||||
4. **Verifies the setup** to ensure everything is configured correctly
|
||||
|
||||
Invoke with `/honcho-integration` in your coding agent.
|
||||
|
||||
#### migrate-honcho-py / migrate-honcho-ts
|
||||
|
||||
**For SDK upgrades.** Migrates code from v1.6.0 to v2.0.0 (required for Honcho 3.0.0). Use when upgrading the SDK or seeing errors about removed APIs like `observations`, `Representation`, `.core`, or `get_config`.
|
||||
|
||||
Both skills handle: terminology changes (`Observation` → `Conclusion`), `Representation` class removal, method renames, and streaming API updates.
|
||||
|
||||
| Python | TypeScript |
|
||||
|--------|------------|
|
||||
| `/migrate-honcho-py` | `/migrate-honcho-ts` |
|
||||
| `AsyncHoncho` → `.aio` accessor | `@honcho-ai/core` removal |
|
||||
| | `snake_case` → `camelCase` |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ session.add_messages([
|
|||
assistant.message("It's sunny and 75°F outside!")
|
||||
])
|
||||
|
||||
# Query peer representations in natural language
|
||||
# Chat with Honcho about a peer
|
||||
response = alice.chat("What did the assistant tell this user about the weather?")
|
||||
|
||||
# Get conversation context for LLM completions
|
||||
|
|
@ -85,7 +85,7 @@ await session.addMessages([
|
|||
assistant.message("It's sunny and 75°F outside!")
|
||||
]);
|
||||
|
||||
// Query peer representations in natural language
|
||||
// Chat with Honcho about a peer
|
||||
const response = await alice.chat("What did the assistant tell this user about the weather?");
|
||||
|
||||
// Get conversation context for LLM completions
|
||||
|
|
@ -533,7 +533,7 @@ context = session.get_context(
|
|||
tokens=2000,
|
||||
peer_target="user",
|
||||
peer_perspective="assistant",
|
||||
last_user_message="What are my preferences?",
|
||||
search_query="What are my preferences?",
|
||||
limit_to_session=True,
|
||||
search_top_k=10,
|
||||
search_max_distance=0.8,
|
||||
|
|
@ -612,7 +612,7 @@ const richContext = await session.getContext({
|
|||
tokens: 2000,
|
||||
peerTarget: "user",
|
||||
peerPerspective: "assistant",
|
||||
lastUserMessage: "What are my preferences?",
|
||||
searchQuery: "What are my preferences?",
|
||||
limitToSession: true,
|
||||
searchTopK: 10,
|
||||
searchMaxDistance: 0.8,
|
||||
|
|
@ -752,7 +752,7 @@ The SessionContext object has the following structure:
|
|||
| `tokens` | `int` | Maximum tokens to include |
|
||||
| `peer_target` | `str` | Peer ID to get representation for |
|
||||
| `peer_perspective` | `str` | Peer ID for perspective (requires peer_target) |
|
||||
| `last_user_message` | `str` | Most recent message for semantic search |
|
||||
| `search_query` | `str` or `Message` | Query string or Message object for semantic search |
|
||||
| `limit_to_session` | `bool` | Limit representation to session only |
|
||||
| `search_top_k` | `int` | Number of semantic search results (1-100) |
|
||||
| `search_max_distance` | `float` | Max semantic distance (0.0-1.0) |
|
||||
|
|
@ -879,7 +879,7 @@ If `created_at` is not provided, messages will use the server's current timestam
|
|||
|
||||
### Metadata and Filtering
|
||||
|
||||
See [Using Filters](/v3/guides/using-filters) for more examples on how to use filters.
|
||||
See [Using Filters](/v3/documentation/features/advanced/using-filters) for more examples on how to use filters.
|
||||
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ and Bob. We:
|
|||
|
||||
As soon as you save a message in Honcho, it will start to reason about it to
|
||||
pull out insights and develop a profile of the user. This is the default
|
||||
behavior and can be toggled off via [the configuration](/v3/documentation/core-concepts/configuration).
|
||||
behavior and can be toggled off via [the configuration](/v3/documentation/features/advanced/reasoning-configuration).
|
||||
|
||||
## Next Steps
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ This feature is illustrated in the graphic below:
|
|||
|
||||
We can enable local representation for a `Peer` by setting `observe_others=True`.
|
||||
This is shown in the [Configure
|
||||
Reasoning](/v3/documentation/core-concepts/configuration) page.
|
||||
Reasoning](/v3/documentation/features/advanced/reasoning-configuration) page.
|
||||
|
||||
Now if we used Bob's local representation of Alice then Bob would only get
|
||||
insights on what they've seen Alice say to them.
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ Discord bots also offer slash command functionality. Here's an example using Hon
|
|||
```python
|
||||
@bot.slash_command(
|
||||
name="chat",
|
||||
description="Query the peer's representation in natural language.",
|
||||
description="Chat with Honcho about a peer.",
|
||||
)
|
||||
async def chat(ctx, query: str):
|
||||
await ctx.defer()
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ results = storage.search("query", filters={
|
|||
})
|
||||
```
|
||||
|
||||
For the full filter syntax including logical operators (AND, OR, NOT), comparison operators, and metadata filtering, see the [Using Filters](https://docs.honcho.dev/v3/documentation/core-concepts/features/using-filters) documentation.
|
||||
For the full filter syntax including logical operators (AND, OR, NOT), comparison operators, and metadata filtering, see the [Using Filters](https://docs.honcho.dev/v3/documentation/features/advanced/using-filters) documentation.
|
||||
|
||||
<Note>
|
||||
For comprehensive details about CrewAI's memory system, see the [official CrewAI Memory documentation](https://docs.crewai.com/en/concepts/memory).
|
||||
|
|
@ -282,13 +282,13 @@ Now that you have a working CrewAI integration with Honcho, you can:
|
|||
<Card title="Honcho Architecture" icon="sitemap" href="/v3/documentation/core-concepts/architecture">
|
||||
Understand Honcho's peer-based model and core primitives
|
||||
</Card>
|
||||
<Card title="Get Context" icon="messages" href="/v3/documentation/core-concepts/features/get-context">
|
||||
<Card title="Get Context" icon="messages" href="/v3/documentation/features/get-context">
|
||||
Learn about retrieving and formatting conversation context
|
||||
</Card>
|
||||
<Card title="Dialectic API" icon="brain" href="/v3/documentation/core-concepts/features/dialectic">
|
||||
<Card title="Chat API" icon="brain" href="/v3/documentation/features/chat">
|
||||
Query `peer` representations for deeper understanding
|
||||
</Card>
|
||||
<Card title="LangGraph Integration" icon="diagram-project" href="/v3/integrations/langgraph">
|
||||
<Card title="LangGraph Integration" icon="diagram-project" href="/v3/guides/integrations/langgraph">
|
||||
Build stateful agents with LangGraph and Honcho
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ const graph = new StateGraph(StateAnnotation)
|
|||
|
||||
### Understanding get_context()
|
||||
|
||||
The [`get_context()`](/v3/documentation/core-concepts/features/get-context) method retrieves comprehensive conversation context and formats it for your LLM. It automatically:
|
||||
The [`get_context()`](/v3/documentation/features/get-context) method retrieves comprehensive conversation context and formats it for your LLM. It automatically:
|
||||
|
||||
- **Manages conversation history** - Tracks all messages and determines what's relevant
|
||||
- **Respects token limits** - Stays within context window constraints without manual counting
|
||||
|
|
@ -248,7 +248,7 @@ That's it. Call `session.get_context().to_openai(assistant)` and you get properl
|
|||
</Tip>
|
||||
|
||||
<Note>
|
||||
For more details on all available parameters, see [`get_context() documentation`](/v3/documentation/core-concepts/features/get-context)
|
||||
For more details on all available parameters, see [`get_context() documentation`](/v3/documentation/features/get-context)
|
||||
</Note>
|
||||
|
||||
## Chat Loop
|
||||
|
|
@ -354,10 +354,10 @@ Now that you have a working LangGraph integration with Honcho, you can:
|
|||
## Related Resources
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Get Context" icon="messages" href="/v3/documentation/core-concepts/features/get-context">
|
||||
<Card title="Get Context" icon="messages" href="/v3/documentation/features/get-context">
|
||||
Learn more about retrieving and formatting conversation context
|
||||
</Card>
|
||||
<Card title="MCP Integration" icon="star-of-life" href="/v3/integrations/mcp">
|
||||
<Card title="MCP Integration" icon="star-of-life" href="/v3/guides/integrations/mcp">
|
||||
Use Honcho in Claude Desktop with MCP
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,761 @@
|
|||
---
|
||||
title: "n8n"
|
||||
icon: 'share-nodes'
|
||||
description: "Connect Honcho to your n8n workflows to build intelligent automation workflows and agents that leverage persistent memory across sessions."
|
||||
sidebarTitle: 'n8n'
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- n8n instance (self-hosted or cloud)
|
||||
- Honcho API key ([get one here](https://app.honcho.dev))
|
||||
- Basic understanding of n8n workflows
|
||||
- Basic understanding of [Honcho architecture](/v3/documentation/core-concepts/architecture). Specifically **workspaces**, **sessions**, **peers**, and **messages**.
|
||||
|
||||
### Before You Start
|
||||
|
||||
**This integration uses HTTP Request nodes.** There's no native Honcho node for n8n yet. While this requires more setup, it gives you full control over the API and works with any n8n version.
|
||||
|
||||
**This tutorial is instructional, not production-ready.** We load a single Gmail message with hardcoded IDs to demonstrate the concepts clearly. See [Next Steps](#next-steps) for handling multiple messages and dynamic configurations.
|
||||
|
||||
**Why Honcho over n8n's built-in memory?** n8n's "memory" nodes are vector databases for RAG-style retrieval. Honcho offers richer context and reasoning—it builds understanding of users over time, not just similarity search. [Learn more](https://blog.plasticlabs.ai/blog/Memory-as-Reasoning).
|
||||
|
||||
### Setting Up the HTTP Request Node
|
||||
|
||||
The Honcho integration in n8n uses the HTTP Request node to interact with the Honcho API. Here's how to configure it:
|
||||
|
||||
1. Add an **HTTP Request** node to your workflow (Core > HTTP Request)
|
||||
|
||||
<div style={{ maxWidth: "400px" }}>
|
||||
<Frame>
|
||||
<img src="/images/integrations/n8n/Http_request_core.png" alt="Adding HTTP Request node from Core nodes" />
|
||||
</Frame>
|
||||
</div>
|
||||
|
||||
2. Set the **Method** based on your operation (typically `POST` for creating resources, `GET` for retrieving)
|
||||
|
||||
3. Set the **URL** to the appropriate Honcho API endpoint. For example, create workspace is `https://api.honcho.dev/v3/workspaces`
|
||||
|
||||
4. For authentication, select **Generic Credential Type** and then **Bearer Auth**
|
||||
|
||||
5. Click **Create New Credential** and paste your Honcho API key in the Bearer Token field
|
||||
|
||||
<div style={{ maxWidth: "400px" }}>
|
||||
<Frame>
|
||||
<img src="/images/integrations/n8n/Bearer_auth_cred.png" alt="Bearer Auth credential setup in n8n" />
|
||||
</Frame>
|
||||
</div>
|
||||
|
||||
6. Check **Send Body** and select **JSON** as the Body Content Type when creating resources
|
||||
|
||||
## Step-by-Step Tutorial
|
||||
|
||||
We'll build a workflow that ingests Gmail emails into Honcho, then uses that memory to power a conversational AI chatbot.
|
||||
|
||||
The workflow has two parts (separated by sticky notes in the canvas):
|
||||
|
||||
1. **Data Ingestion**: Manual trigger → Workspace → Session → Gmail → Extract Peers → Create Peers → Add to Session → Create Messages
|
||||
|
||||
2. **AI Chat Interface**: Chat Trigger → Agent (with LLM and Honcho tools)
|
||||
|
||||
The Agent uses Honcho's `get_context()` endpoint to retrieve relevant information about email conversations, enabling contextual conversations about your email data.
|
||||
|
||||

|
||||
|
||||
### Part 1: Loading Email Data into Honcho
|
||||
|
||||
These nodes handle the initial setup and data ingestion:
|
||||
|
||||
<Tip>
|
||||
**Pro Tip: Copy from API Playground**
|
||||
|
||||
The fastest way to configure any Honcho endpoint is to copy the curl command directly from the [app.honcho.dev](https://app.honcho.dev) API playground:
|
||||
|
||||
1. Navigate to the endpoint you want to use in the API playground
|
||||
2. Fill in your parameters, verify the results and click **Copy as cURL**
|
||||
3. Then in n8n use the **import cURL** button to directly import the request (be sure to verify the bearer token imported correctly)
|
||||
|
||||
</Tip>
|
||||
|
||||
#### Step 1: Manual Trigger
|
||||
|
||||
Start with a **Manual Trigger** node to execute the workflow on demand. This is useful for initial setup and testing before automating with a Gmail trigger.
|
||||
|
||||
#### Step 2: Get or Create Workspace
|
||||
|
||||
1. Add an **HTTP Request** node
|
||||
2. **Method**: `POST`
|
||||
3. **URL**: `https://api.honcho.dev/v3/workspaces`
|
||||
4. **Body** (JSON): `{ "id": "email-test", "metadata": {} }`
|
||||
|
||||
<Tip>
|
||||
**Verify Your Data in Honcho**
|
||||
As you build the data ingestion workflow, verify everything is created correctly in your [Honcho instance](https://app.honcho.dev/).
|
||||
</Tip>
|
||||
|
||||
#### Step 3: Get or Create Session
|
||||
|
||||
1. Add another **HTTP Request** node
|
||||
2. **Method**: `POST`
|
||||
3. **URL**: `https://api.honcho.dev/v3/workspaces/{{ $('Get or Create Workspace').item.json.id }}/sessions`
|
||||
4. **Body** (JSON): `{ "id": "new_session" }`
|
||||
|
||||
#### Step 4: Get Gmail Message
|
||||
|
||||
1. Add a **Gmail** node
|
||||
2. **Operation**: Get
|
||||
3. **Message ID**: Your target message ID (a string of letters & numbers)
|
||||
4. Configure your Gmail OAuth2 credentials
|
||||
|
||||
<Note>
|
||||
**Finding the Gmail Message ID**
|
||||
The easiest way to find a Gmail message ID is to use n8n's Gmail Get Many operation. Temporarily add it, set the limit to 1, and execute. Use the message ID in the output for the message ID field.
|
||||
|
||||
In this tutorial, we load in only a single message to demonstrate the workflow.
|
||||
</Note>
|
||||
|
||||
#### Step 5: Extract Peers from Email
|
||||
|
||||
Use native n8n nodes to extract email participants as peers:
|
||||
|
||||
**5a. Add a Set node ("Combine Email Fields")**
|
||||
- Combines From, To, Cc, Bcc into an array of individual emails
|
||||
- **Field name**: `allEmails`
|
||||
- **Type**: Array
|
||||
- **Value**: `{{ [$json.From, $json.To, $json.Cc, $json.Bcc].filter(Boolean).flatMap(field => field.split(',').map(e => e.trim())).filter(Boolean) }}`
|
||||
|
||||
**5b. Add a Split Out node**
|
||||
- Splits the array into individual items (one per email address)
|
||||
- **Field to Split Out**: `allEmails`
|
||||
|
||||
**5c. Add a Set node ("Clean Names")**
|
||||
- Extracts the display name from each email and formats it
|
||||
- **Field name**: `name`
|
||||
- **Value**: `{{ $json.allEmails.split('<')[0].trim().replace(/ /g, '_') }}`
|
||||
|
||||
#### Step 6: Get or Create Peer
|
||||
|
||||
1. Add an **HTTP Request** node
|
||||
2. **Method**: `POST`
|
||||
3. **URL**: `https://api.honcho.dev/v3/workspaces/{{ $('Get or Create Workspace').item.json.id }}/peers`
|
||||
4. **Body**: `{ "id": "{{ $json.name }}" }`
|
||||
|
||||
This creates a peer for each email participant, allowing Honcho to build understanding of each person.
|
||||
|
||||
#### Step 7: Add Peers to Session
|
||||
|
||||
1. Add an **HTTP Request** node
|
||||
2. **Method**: `POST`
|
||||
3. **URL**: `https://api.honcho.dev/v3/workspaces/{{ $('Get or Create Workspace').item.json.id }}/sessions/{{ $json.id }}/peers`
|
||||
4. **Body**: `{ "{{ $json.id }}": {} }`
|
||||
|
||||
#### Step 8: Limit Node
|
||||
|
||||
Add a **Limit** node to control the flow so the message is only added once to the session.
|
||||
|
||||
#### Step 9: Create Message for Session
|
||||
|
||||
1. Add an **HTTP Request** node
|
||||
2. **Method**: `POST`
|
||||
3. **URL**: `https://api.honcho.dev/v3/workspaces/{{ $('Get or Create Workspace').item.json.id }}/sessions/{{ $('Get or Create Session').item.json.id }}/messages/`
|
||||
4. **Body** (JSON): `{ "messages": [{ "content": "{{ $('Get a message').item.json.snippet }}", "peer_id": "{{ $('Get a message').item.json.From.split('<')[0].trim().replace(/ /g, '_') }}" }] }`
|
||||
|
||||
The `peer_id` must exactly match a peer created in Step 6. The expression above uses the same cleaning logic as the Clean Names node (`split('<')[0].trim().replace(/ /g, '_')`).
|
||||
|
||||
### Part 2: Building a Stateful AI Chatbot
|
||||
|
||||
Now that data is loaded into Honcho, create a chat interface that leverages this memory:
|
||||
|
||||
#### Step 1: Chat Trigger
|
||||
|
||||
Add a **When chat message received** node (from LangChain nodes) to create an interactive chat interface.
|
||||
|
||||
#### Step 2: AI Agent
|
||||
|
||||
1. Add an **Agent** node (LangChain)
|
||||
2. Configure the system message:
|
||||
```
|
||||
You are a helpful assistant that retrieves context about email conversations.
|
||||
|
||||
Use the Get_Context tool to retrieve session context.
|
||||
|
||||
Today's date: {{ $now }}
|
||||
```
|
||||
|
||||
#### Step 3: Connect LLM
|
||||
|
||||
Add an **OpenAI Chat Model** node (or your preferred LLM) and connect it to the Agent.
|
||||
|
||||
#### Step 4: Add Honcho Tools
|
||||
|
||||
Create an HTTP Request Tool node for Honcho's context retrieval:
|
||||
|
||||
**Get Context Tool:**
|
||||
- **Method**: `GET`
|
||||
- **URL**: `https://api.honcho.dev/v3/workspaces/email-test/sessions/new_session/context`
|
||||
- Returns formatted context for the entire session including all messages and peer interactions
|
||||
|
||||
Connect the tool to the Agent node. The URL uses the same workspace (`email-test`) and session (`new_session`) IDs created during data ingestion.
|
||||
|
||||
---
|
||||
|
||||
## Import the Workflow
|
||||
|
||||
Want to skip the manual setup? Import this workflow directly into n8n. In n8n, go to **Workflows** → **Import from URL** (use the raw JSON link below) or **Import from File**.
|
||||
|
||||
[Import from URL (raw JSON)](https://raw.githubusercontent.com/plastic-labs/honcho/main/examples/n8n/n8n.json) or expand below to copy:
|
||||
|
||||
<Accordion title="Click to expand workflow JSON">
|
||||
```json
|
||||
{
|
||||
"name": "Honcho Empowered Email AI Agent",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"content": "## Data Ingestion\nLoads Gmail email into Honcho.\n\n**Run this section first** by clicking 'Execute workflow'.",
|
||||
"height": 356,
|
||||
"width": 2008
|
||||
},
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
-16,
|
||||
-64
|
||||
],
|
||||
"id": "53f767a8-8df9-4ad7-8a3d-37c145495627",
|
||||
"name": "Sticky Note - Data Ingestion"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"content": "## AI Chat With Honcho get_context()\nQuery your email data using natural language.\n\n**Run after data ingestion** to chat with the agent.",
|
||||
"height": 480,
|
||||
"width": 752
|
||||
},
|
||||
"type": "n8n-nodes-base.stickyNote",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
32,
|
||||
464
|
||||
],
|
||||
"id": "34066961-d29e-4fe8-93bf-8f7043e142b0",
|
||||
"name": "Sticky Note - AI Chat"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"model": "gpt-4o",
|
||||
"options": {}
|
||||
},
|
||||
"id": "16da7a98-5622-427a-bbab-1be39f828d0b",
|
||||
"name": "OpenAI Chat Model",
|
||||
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
|
||||
"position": [
|
||||
240,
|
||||
800
|
||||
],
|
||||
"typeVersion": 1,
|
||||
"credentials": {
|
||||
"openAiApi": {
|
||||
"id": "qrvGphL3ydUODxQZ",
|
||||
"name": "OpenAi account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {
|
||||
"systemMessage": "You are a helpful assistant that retrieves context about email conversations.\n\nUse the Get_Context tool to retrieve session context.\n\nToday's date: {{ $now }}"
|
||||
}
|
||||
},
|
||||
"id": "049c3c19-756c-4755-88d9-94312857d9bb",
|
||||
"name": "AI Agent",
|
||||
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||
"position": [
|
||||
368,
|
||||
576
|
||||
],
|
||||
"typeVersion": 1.7
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"options": {}
|
||||
},
|
||||
"id": "b9a2ef6a-0e81-45c9-a5ea-16e74a9aa77d",
|
||||
"name": "When chat message received",
|
||||
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
|
||||
"position": [
|
||||
80,
|
||||
576
|
||||
],
|
||||
"webhookId": "c91764c2-0b51-4025-ad74-d5f44127aa5a",
|
||||
"typeVersion": 1.1
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.honcho.dev/v3/workspaces/{{ $('Get or Create Workspace').item.json.id }}/peers",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpBearerAuth",
|
||||
"sendBody": true,
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"value": "={{ $json.name }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
1296,
|
||||
96
|
||||
],
|
||||
"id": "f2e81445-a866-4d9f-9a8a-b2dc8cbaea8b",
|
||||
"name": "Get or Create Peer",
|
||||
"credentials": {
|
||||
"httpBearerAuth": {
|
||||
"id": "NbrkGo1GdYWQY3OX",
|
||||
"name": "Bearer Auth account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "get",
|
||||
"messageId": "19b8fee837985953"
|
||||
},
|
||||
"type": "n8n-nodes-base.gmail",
|
||||
"typeVersion": 2.2,
|
||||
"position": [
|
||||
608,
|
||||
96
|
||||
],
|
||||
"id": "ee5d8cc7-876b-4096-b6e5-14f1b92084a6",
|
||||
"name": "Get a message",
|
||||
"webhookId": "4ab02540-af03-405d-a6eb-dfcaa76477fc",
|
||||
"credentials": {
|
||||
"gmailOAuth2": {
|
||||
"id": "a2RvA5NMNjfOeHtd",
|
||||
"name": "Gmail account 2"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.honcho.dev/v3/workspaces/{{ $('Get or Create Workspace').item.json.id }}/sessions",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpBearerAuth",
|
||||
"sendBody": true,
|
||||
"bodyParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"value": "=new_session"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
448,
|
||||
96
|
||||
],
|
||||
"id": "eef01db2-3355-484d-97b8-34480c40fcf8",
|
||||
"name": "Get or Create Session",
|
||||
"credentials": {
|
||||
"httpBearerAuth": {
|
||||
"id": "NbrkGo1GdYWQY3OX",
|
||||
"name": "Bearer Auth account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
48,
|
||||
96
|
||||
],
|
||||
"id": "a9de9d8f-7911-444f-99bd-7d287f587eff",
|
||||
"name": "When clicking 'Execute workflow'"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "https://api.honcho.dev/v3/workspaces",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpBearerAuth",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "{\n \"id\": \"email-test\",\n \"metadata\": {}\n}",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
240,
|
||||
96
|
||||
],
|
||||
"id": "49b91f82-1cd8-49aa-85a5-57a8ad7b5128",
|
||||
"name": "Get or Create Workspace",
|
||||
"credentials": {
|
||||
"httpBearerAuth": {
|
||||
"id": "NbrkGo1GdYWQY3OX",
|
||||
"name": "Bearer Auth account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "allEmails",
|
||||
"name": "allEmails",
|
||||
"type": "array",
|
||||
"value": "={{ [$json.From, $json.To, $json.Cc, $json.Bcc].filter(Boolean).flatMap(field => field.split(',').map(e => e.trim())).filter(Boolean) }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
784,
|
||||
96
|
||||
],
|
||||
"id": "3eb4ce63-d95c-42d7-8a16-f35e2419d8c0",
|
||||
"name": "Combine Email Fields"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fieldToSplitOut": "allEmails",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.splitOut",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
960,
|
||||
96
|
||||
],
|
||||
"id": "4702001d-40f5-4b44-b443-6fb0b94e58a9",
|
||||
"name": "Split Out"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"assignments": {
|
||||
"assignments": [
|
||||
{
|
||||
"id": "name",
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"value": "={{ $json.allEmails.split('<')[0].trim().replace(/ /g, '_') }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.4,
|
||||
"position": [
|
||||
1136,
|
||||
96
|
||||
],
|
||||
"id": "93327201-bfbe-4385-bfcd-7646f0513a62",
|
||||
"name": "Clean Names"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.honcho.dev/v3/workspaces/{{ $('Get or Create Workspace').item.json.id }}/sessions/{{ $('Get or Create Session').item.json.id }}/messages/",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpBearerAuth",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\"messages\": [{\"content\": \"{{ $('Get a message').item.json.snippet }}\", \"peer_id\": \"{{ $('Get a message').item.json.From.split('<')[0].trim().replace(/ /g, '_') }}\"}]}",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
1808,
|
||||
96
|
||||
],
|
||||
"id": "cb6732d0-e5a9-4d6e-98ef-8fe1c290740b",
|
||||
"name": "Create Message for Session",
|
||||
"credentials": {
|
||||
"httpBearerAuth": {
|
||||
"id": "NbrkGo1GdYWQY3OX",
|
||||
"name": "Bearer Auth account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://api.honcho.dev/v3/workspaces/{{ $('Get or Create Workspace').item.json.id }}/sessions/{{ $('Get or Create Session').item.json.id }}/peers",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpBearerAuth",
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\"{{ $json.id }}\": {}}",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
1472,
|
||||
96
|
||||
],
|
||||
"id": "3c2065b6-bd68-4698-a740-db3cd52f2267",
|
||||
"name": "Add Peers to Session",
|
||||
"credentials": {
|
||||
"httpBearerAuth": {
|
||||
"id": "NbrkGo1GdYWQY3OX",
|
||||
"name": "Bearer Auth account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.limit",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1632,
|
||||
96
|
||||
],
|
||||
"id": "d7c38f49-1a76-4a67-a540-4bec7aae1d9f",
|
||||
"name": "Limit"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "https://api.honcho.dev/v3/workspaces/email-test/sessions/new_session/context",
|
||||
"authentication": "predefinedCredentialType",
|
||||
"nodeCredentialType": "httpBearerAuth",
|
||||
"options": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.httpRequestTool",
|
||||
"typeVersion": 4.3,
|
||||
"position": [
|
||||
656,
|
||||
784
|
||||
],
|
||||
"id": "095e62d6-aae3-4eb8-9b9c-c473bfe2716d",
|
||||
"name": "Get_Context",
|
||||
"credentials": {
|
||||
"httpBearerAuth": {
|
||||
"id": "NbrkGo1GdYWQY3OX",
|
||||
"name": "Bearer Auth account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {},
|
||||
"connections": {
|
||||
"OpenAI Chat Model": {
|
||||
"ai_languageModel": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_languageModel",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"When chat message received": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"When clicking 'Execute workflow'": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get or Create Workspace",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get or Create Workspace": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get or Create Session",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get a message": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Combine Email Fields",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get or Create Session": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get a message",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Combine Email Fields": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Split Out",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Split Out": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Clean Names",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Clean Names": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get or Create Peer",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get or Create Peer": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Add Peers to Session",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Add Peers to Session": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Limit",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Limit": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create Message for Session",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get_Context": {
|
||||
"ai_tool": [
|
||||
[
|
||||
{
|
||||
"node": "AI Agent",
|
||||
"type": "ai_tool",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"availableInMCP": false
|
||||
},
|
||||
"versionId": "ba0a3b77-cc19-49fd-9189-aaee65b35f99",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "4e34c96e55eb26be21fa69ca62c4851a5d09b678190481f5d47c084b6b327003"
|
||||
},
|
||||
"id": "dKOYeEOdrZOetmFRmIAUJ",
|
||||
"tags": []
|
||||
}
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Note>
|
||||
**Important:** After importing, you'll need to:
|
||||
- Add your Honcho API key to the Bearer Auth credential
|
||||
- Connect your Gmail OAuth2 credential
|
||||
- Add your OpenAI API key (or swap for your preferred LLM)
|
||||
- Update the Gmail Message ID in "Get a message" node
|
||||
|
||||
**Running the workflow:**
|
||||
1. First, execute the data ingestion section (click "Execute workflow")
|
||||
2. Then use the chat interface to query your email data
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once you have the basic workflow running, consider these enhancements:
|
||||
|
||||
- **Dynamic IDs**: Use n8n variables instead of hardcoding `email-test` and `new_session`
|
||||
- **Chat with Peers**: Add an HTTP Request Tool for natural language queries about peer representations. Read more in the [docs](/v3/documentation/features/chat).
|
||||
- **Load more messages**: Use Gmail's "Get All" operation to load entire conversation threads
|
||||
- **Make it real-time**: Add a **Gmail Trigger** node to automatically ingest new emails as they arrive
|
||||
- **Add error handling**: Connect an **Error Trigger** node with notifications (Email, Slack) and retry logic
|
||||
- **Expand to other data sources**: Honcho works with Slack messages, CRM interactions, support tickets, and more
|
||||
|
||||
---
|
||||
|
||||
## Related Resources
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Architecture" icon="sitemap" href="/v3/documentation/core-concepts/architecture">
|
||||
Understand workspaces, sessions, peers, and messages
|
||||
</Card>
|
||||
<Card title="Get Context" icon="messages" href="/v3/documentation/features/get-context">
|
||||
Learn about retrieving formatted conversation context
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
|
@ -259,14 +259,14 @@ Reference the [API Comparison](#api-comparison) to replace your Mem0 API calls w
|
|||
|
||||
Mem0 requires manual assembly of context from `search()` results. Honcho's `session.get_context()` returns a ready-to-use `SessionContext` object with built-in token limits, auto-included summaries, and format helpers (`.to_openai()`, `.to_anthropic()`).
|
||||
|
||||
<Card title="Get Context" icon="window-restore" href="../../documentation/core-concepts/features/get-context">
|
||||
<Card title="Get Context" icon="window-restore" href="../../documentation/features/get-context">
|
||||
Learn more about token-optimized context retrieval
|
||||
</Card>
|
||||
|
||||
|
||||
Mem0's `search()` returns basic vector, semantic, or raw memory matches. Honcho's `peer.chat()` enables your agent to *reason* about what it knows—returning synthesized natural language insights with streaming support and scoped queries.
|
||||
|
||||
<Card title="Dialectic Endpoint" icon="brain" href="../../documentation/core-concepts/features/dialectic-endpoint">
|
||||
<Card title="Chat Endpoint" icon="brain" href="../../documentation/features/chat">
|
||||
Learn more about inference-powered queries
|
||||
</Card>
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ Additional features with **no Mem0 equivalent**:
|
|||
<Card title="Architecture" icon="rocket" href="../../documentation/core-concepts/architecture">
|
||||
Understand peers and sessions
|
||||
</Card>
|
||||
<Card title="Dialectic API" icon="brain" href="../../documentation/core-concepts/features/dialectic-endpoint">
|
||||
<Card title="Chat API" icon="brain" href="../../documentation/features/chat">
|
||||
Inference responses
|
||||
</Card>
|
||||
<Card title="Guides" icon="book" href="../../guides/overview">
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ Each guide focuses on a specific use case with practical examples. The goal is t
|
|||
Quick integration guides to get up and running:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="MCP Integration" icon="link" href="/v3/integrations/mcp">
|
||||
<Card title="MCP Integration" icon="link" href="/v3/guides/integrations/mcp">
|
||||
Get Honcho running with a single prompt in Claude Code
|
||||
</Card>
|
||||
<Card title="LangGraph" icon="diagram-project" href="/v3/integrations/langgraph">
|
||||
<Card title="LangGraph" icon="diagram-project" href="/v3/guides/integrations/langgraph">
|
||||
Add persistent memory and theory of mind to your LangGraph agents
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ Once a `Message` is saved in Honcho, it will kick off a background task that
|
|||
looks at the new data to generate insights about the `Peer` that sent the `Message`
|
||||
|
||||
This is the default behavior of Honcho and can be turned off by [configuring the
|
||||
Peer or Session](/v3/documentation/core-concepts/configuration)
|
||||
Peer or Session](/v3/documentation/features/advanced/reasoning-configuration)
|
||||
|
||||
This pattern of having a Peer, Session, and Messages is highly flexible and
|
||||
works for many different use cases and agent setups. Some use cases may only
|
||||
|
|
|
|||
|
|
@ -259,14 +259,14 @@ Reference the [API Comparison](#api-comparison) to replace your Mem0 API calls w
|
|||
|
||||
Mem0 requires manual assembly of context from `search()` results. Honcho's `session.get_context()` returns a ready-to-use `SessionContext` object with built-in token limits, auto-included summaries, and format helpers (`.to_openai()`, `.to_anthropic()`).
|
||||
|
||||
<Card title="Get Context" icon="window-restore" href="../../documentation/core-concepts/features/get-context">
|
||||
<Card title="Get Context" icon="window-restore" href="../../documentation/features/get-context">
|
||||
Learn more about token-optimized context retrieval
|
||||
</Card>
|
||||
|
||||
|
||||
Mem0's `search()` returns basic vector, semantic, or raw memory matches. Honcho's `peer.chat()` enables your agent to *reason* about what it knows—returning synthesized natural language insights with streaming support and scoped queries.
|
||||
|
||||
<Card title="Dialectic Endpoint" icon="brain" href="../../documentation/core-concepts/features/dialectic-endpoint">
|
||||
<Card title="Chat Endpoint" icon="brain" href="../../documentation/features/chat">
|
||||
Learn more about inference-powered queries
|
||||
</Card>
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ Additional features with **no Mem0 equivalent**:
|
|||
<Card title="Architecture" icon="rocket" href="../../documentation/core-concepts/architecture">
|
||||
Understand peers and sessions
|
||||
</Card>
|
||||
<Card title="Dialectic API" icon="brain" href="../../documentation/core-concepts/features/dialectic-endpoint">
|
||||
<Card title="Chat API" icon="brain" href="../../documentation/features/chat">
|
||||
Inference responses
|
||||
</Card>
|
||||
<Card title="Guides" icon="book" href="../../guides/overview">
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
},
|
||||
"license": {
|
||||
"name": "GNU Affero General Public License v3.0",
|
||||
"identifier": "AGPL-3.0-only",
|
||||
"url": "https://github.com/plastic-labs/honcho/blob/main/LICENSE"
|
||||
},
|
||||
"version": "3.0.0"
|
||||
|
|
@ -82,7 +81,7 @@
|
|||
"workspaces"
|
||||
],
|
||||
"summary": "Get All Workspaces",
|
||||
"description": "Get all Workspaces, paginated with optional filters.",
|
||||
"description": "Get all Workspaces",
|
||||
"operationId": "get_all_workspaces_v3_workspaces_list_post",
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -434,7 +433,7 @@
|
|||
"workspaces"
|
||||
],
|
||||
"summary": "Schedule Dream",
|
||||
"description": "Manually schedule a dream task for a specific collection.\n\nThis endpoint bypasses all automatic dream conditions (document threshold,\nminimum hours between dreams) and schedules the dream task for a future execution.\n\nCurrently this endpoint only supports scheduling immediate dreams. In the future,\nusers may pass a cron-style expression to schedule dreams at specific times.",
|
||||
"description": "",
|
||||
"operationId": "schedule_dream_v3_workspaces__workspace_id__schedule_dream_post",
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -487,7 +486,7 @@
|
|||
"peers"
|
||||
],
|
||||
"summary": "Get Peers",
|
||||
"description": "Get all Peers for a Workspace, paginated with optional filters.",
|
||||
"description": "Get all Peers for a Workspace",
|
||||
"operationId": "get_peers_v3_workspaces__workspace_id__peers_list_post",
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -710,7 +709,7 @@
|
|||
"peers"
|
||||
],
|
||||
"summary": "Get Sessions For Peer",
|
||||
"description": "Get all Sessions for a Peer, paginated with optional filters.",
|
||||
"description": "Get all Sessions for a Peer",
|
||||
"operationId": "get_sessions_for_peer_v3_workspaces__workspace_id__peers__peer_id__sessions_post",
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -812,8 +811,8 @@
|
|||
"tags": [
|
||||
"peers"
|
||||
],
|
||||
"summary": "Query a Peer's representation using natural language",
|
||||
"description": "Query a Peer's representation using natural language. Performs agentic search and reasoning to comprehensively\nanswer the query based on all latent knowledge gathered about the peer from their messages and conclusions.",
|
||||
"summary": "Chat",
|
||||
"description": "",
|
||||
"operationId": "chat_v3_workspaces__workspace_id__peers__peer_id__chat_post",
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -1374,7 +1373,7 @@
|
|||
"sessions"
|
||||
],
|
||||
"summary": "Get Sessions",
|
||||
"description": "Get all Sessions for a Workspace, paginated with optional filters.",
|
||||
"description": "Get all Sessions for a Workspace",
|
||||
"operationId": "get_sessions_v3_workspaces__workspace_id__sessions_list_post",
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -1940,7 +1939,7 @@
|
|||
"sessions"
|
||||
],
|
||||
"summary": "Get Session Peers",
|
||||
"description": "Get all Peers in a Session. Results are paginated.",
|
||||
"description": "Get all Peers in a Session",
|
||||
"operationId": "get_session_peers_v3_workspaces__workspace_id__sessions__session_id__peers_get",
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -2208,7 +2207,7 @@
|
|||
"description": "Number of tokens to use for the context. Includes summary if set to true. Includes representation and peer card if they are included in the response. If not provided, the context will be exhaustive (within 100000 tokens)"
|
||||
},
|
||||
{
|
||||
"name": "last_message",
|
||||
"name": "search_query",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
|
|
@ -2220,10 +2219,10 @@
|
|||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "The most recent message, used to fetch semantically relevant conclusions",
|
||||
"title": "Last Message"
|
||||
"description": "A query string used to fetch semantically relevant conclusions",
|
||||
"title": "Search Query"
|
||||
},
|
||||
"description": "The most recent message, used to fetch semantically relevant conclusions"
|
||||
"description": "A query string used to fetch semantically relevant conclusions"
|
||||
},
|
||||
{
|
||||
"name": "summary",
|
||||
|
|
@ -2279,11 +2278,11 @@
|
|||
"required": false,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"description": "Only used if `last_message` is provided. Whether to limit the representation to the session (as opposed to everything known about the target peer)",
|
||||
"description": "Only used if `search_query` is provided. Whether to limit the representation to the session (as opposed to everything known about the target peer)",
|
||||
"default": false,
|
||||
"title": "Limit To Session"
|
||||
},
|
||||
"description": "Only used if `last_message` is provided. Whether to limit the representation to the session (as opposed to everything known about the target peer)"
|
||||
"description": "Only used if `search_query` is provided. Whether to limit the representation to the session (as opposed to everything known about the target peer)"
|
||||
},
|
||||
{
|
||||
"name": "search_top_k",
|
||||
|
|
@ -2300,10 +2299,10 @@
|
|||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Only used if `last_message` is provided. The number of semantic-search-retrieved conclusions to include in the representation",
|
||||
"description": "Only used if `search_query` is provided. The number of semantic-search-retrieved conclusions to include in the representation",
|
||||
"title": "Search Top K"
|
||||
},
|
||||
"description": "Only used if `last_message` is provided. The number of semantic-search-retrieved conclusions to include in the representation"
|
||||
"description": "Only used if `search_query` is provided. The number of semantic-search-retrieved conclusions to include in the representation"
|
||||
},
|
||||
{
|
||||
"name": "search_max_distance",
|
||||
|
|
@ -2320,10 +2319,10 @@
|
|||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Only used if `last_message` is provided. The maximum distance to search for semantically relevant conclusions",
|
||||
"description": "Only used if `search_query` is provided. The maximum distance to search for semantically relevant conclusions",
|
||||
"title": "Search Max Distance"
|
||||
},
|
||||
"description": "Only used if `last_message` is provided. The maximum distance to search for semantically relevant conclusions"
|
||||
"description": "Only used if `search_query` is provided. The maximum distance to search for semantically relevant conclusions"
|
||||
},
|
||||
{
|
||||
"name": "include_most_frequent",
|
||||
|
|
@ -2331,11 +2330,11 @@
|
|||
"required": false,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"description": "Only used if `last_message` is provided. Whether to include the most frequent conclusions in the representation",
|
||||
"description": "Only used if `search_query` is provided. Whether to include the most frequent conclusions in the representation",
|
||||
"default": false,
|
||||
"title": "Include Most Frequent"
|
||||
},
|
||||
"description": "Only used if `last_message` is provided. Whether to include the most frequent conclusions in the representation"
|
||||
"description": "Only used if `search_query` is provided. Whether to include the most frequent conclusions in the representation"
|
||||
},
|
||||
{
|
||||
"name": "max_conclusions",
|
||||
|
|
@ -2352,10 +2351,10 @@
|
|||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "Only used if `last_message` is provided. The maximum number of conclusions to include in the representation",
|
||||
"description": "Only used if `search_query` is provided. The maximum number of conclusions to include in the representation",
|
||||
"title": "Max Conclusions"
|
||||
},
|
||||
"description": "Only used if `last_message` is provided. The maximum number of conclusions to include in the representation"
|
||||
"description": "Only used if `search_query` is provided. The maximum number of conclusions to include in the representation"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
|
@ -2663,7 +2662,7 @@
|
|||
"messages"
|
||||
],
|
||||
"summary": "Get Messages",
|
||||
"description": "Get all messages for a Session with optional filters. Results are paginated.",
|
||||
"description": "Get all messages for a Session with optional filters",
|
||||
"operationId": "get_messages_v3_workspaces__workspace_id__sessions__session_id__messages_list_post",
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -2992,7 +2991,7 @@
|
|||
"conclusions"
|
||||
],
|
||||
"summary": "List Conclusions",
|
||||
"description": "List Conclusions using optional filters, ordered by recency unless `reverse` is true. Results are paginated.",
|
||||
"description": "List Conclusions using optional filters, ordered by recency unless `reverse` is true",
|
||||
"operationId": "list_conclusions_v3_workspaces__workspace_id__conclusions_list_post",
|
||||
"security": [
|
||||
{
|
||||
|
|
@ -3564,23 +3563,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/metrics": {
|
||||
"get": {
|
||||
"summary": "Metrics",
|
||||
"description": "Prometheus metrics endpoint",
|
||||
"operationId": "metrics_metrics_get",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
|
|
@ -3658,7 +3640,14 @@
|
|||
"description": "The peer the conclusion is about"
|
||||
},
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Session Id"
|
||||
},
|
||||
"created_at": {
|
||||
|
|
@ -3673,7 +3662,6 @@
|
|||
"content",
|
||||
"observer_id",
|
||||
"observed_id",
|
||||
"session_id",
|
||||
"created_at"
|
||||
],
|
||||
"title": "Conclusion",
|
||||
|
|
@ -3717,17 +3705,23 @@
|
|||
"description": "The peer the conclusion is about"
|
||||
},
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Session Id",
|
||||
"description": "The session this conclusion relates to"
|
||||
"description": "A session ID to store the conclusion in, if specified"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content",
|
||||
"observer_id",
|
||||
"observed_id",
|
||||
"session_id"
|
||||
"observed_id"
|
||||
],
|
||||
"title": "ConclusionCreate",
|
||||
"description": "Schema for creating a single conclusion."
|
||||
|
|
@ -4787,16 +4781,22 @@
|
|||
"description": "Type of dream to schedule"
|
||||
},
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Session Id",
|
||||
"description": "Session ID to scope the dream to"
|
||||
"description": "Session ID to scope the dream to if specified"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"observer",
|
||||
"dream_type",
|
||||
"session_id"
|
||||
"dream_type"
|
||||
],
|
||||
"title": "ScheduleDreamRequest"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ class HonchoGetContextTool(BaseTool):
|
|||
"""
|
||||
try:
|
||||
session = self._honcho.session(self._session_id)
|
||||
context = session.get_context(
|
||||
context = session.context(
|
||||
summary=summary,
|
||||
tokens=tokens,
|
||||
peer_target=peer_target,
|
||||
|
|
@ -220,9 +220,8 @@ class HonchoDialecticTool(BaseTool):
|
|||
# Query the dialectic API (non-streaming)
|
||||
response = peer.chat(
|
||||
query=query,
|
||||
stream=False,
|
||||
target=target,
|
||||
session_id=scope_session_id,
|
||||
session=scope_session_id,
|
||||
)
|
||||
|
||||
# Return the response or a default message
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ def chatbot(state: State):
|
|||
|
||||
# Get context in OpenAI format with token limit
|
||||
# tokens=2000 limits the context to 2000 tokens to manage costs and fit within model limits
|
||||
messages = session.get_context(tokens=2000).to_openai(assistant=assistant)
|
||||
messages = session.context(tokens=2000).to_openai(assistant=assistant)
|
||||
|
||||
# Generate response
|
||||
response = llm.chat.completions.create(
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ async function chatbot(state: State) {
|
|||
|
||||
// Get context in OpenAI format with token limit
|
||||
// tokens: 2000 limits the context to 2000 tokens to manage costs and fit within model limits
|
||||
const messages = (await session.getContext({ tokens: 2000 })).toOpenAI(assistant);
|
||||
const messages = (await session.context({ tokens: 2000 })).toOpenAI(assistant);
|
||||
|
||||
// Generate response
|
||||
const response = await llm.chat.completions.create({
|
||||
|
|
|
|||
14
fly.toml
14
fly.toml
|
|
@ -24,17 +24,17 @@ kill_timeout = '5s'
|
|||
soft_limit = 20
|
||||
|
||||
[[vm]]
|
||||
memory = '512mb'
|
||||
memory = '1gb'
|
||||
cpu_kind = 'shared'
|
||||
cpus = 1
|
||||
processes = ['api', 'deriver']
|
||||
|
||||
[[metrics]]
|
||||
port = 8000
|
||||
path = "/metrics"
|
||||
processes = ["api"]
|
||||
port = 8000
|
||||
path = "/metrics"
|
||||
processes = ["api"]
|
||||
|
||||
[[metrics]]
|
||||
port = 9090
|
||||
path = "/metrics"
|
||||
processes = ["deriver"]
|
||||
port = 9090
|
||||
path = "/metrics"
|
||||
processes = ["deriver"]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
"deploy:staging": "wrangler deploy --env staging"
|
||||
},
|
||||
"dependencies": {
|
||||
"@honcho-ai/sdk": "^1.6.0"
|
||||
"@honcho-ai/sdk": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20241002.0",
|
||||
|
|
|
|||
620
mcp/worker.ts
620
mcp/worker.ts
|
|
@ -223,17 +223,20 @@ class HonchoWorker {
|
|||
* Get personalization insights about the user, based on the query and the accumulated knowledge of the user across all conversations.
|
||||
* @param sessionId - The ID of the session for context
|
||||
* @param query - The question about the user's preferences, habits, etc.
|
||||
* @param reasoningLevel - Optional reasoning level: "minimal", "low", "medium", "high", or "max"
|
||||
* @returns A string with the personalization insights
|
||||
*/
|
||||
async getPersonalizationInsights(
|
||||
sessionId: string,
|
||||
query: string,
|
||||
reasoningLevel?: string,
|
||||
): Promise<string> {
|
||||
const userPeer = await this.honcho.peer(this.config.userName);
|
||||
|
||||
// Get the personalization insights (non-streaming returns string | null)
|
||||
const personalizationInsights = await userPeer.chat(query, {
|
||||
session: sessionId,
|
||||
reasoningLevel,
|
||||
});
|
||||
|
||||
if (!personalizationInsights || typeof personalizationInsights !== 'string') {
|
||||
|
|
@ -345,6 +348,7 @@ class HonchoWorker {
|
|||
* @param query - The natural language question to ask
|
||||
* @param targetPeerId - Optional target peer ID for local representation queries
|
||||
* @param sessionId - Optional session ID to scope the query to a specific session
|
||||
* @param reasoningLevel - Optional reasoning level: "minimal", "low", "medium", "high", or "max"
|
||||
* @returns Response string containing the answer to the query, or "None" if no relevant information
|
||||
*/
|
||||
async chat(
|
||||
|
|
@ -352,6 +356,7 @@ class HonchoWorker {
|
|||
query: string,
|
||||
targetPeerId?: string,
|
||||
sessionId?: string,
|
||||
reasoningLevel?: string,
|
||||
): Promise<string> {
|
||||
const peer = await this.honcho.peer(peerId);
|
||||
let targetPeer;
|
||||
|
|
@ -362,6 +367,7 @@ class HonchoWorker {
|
|||
const result = await peer.chat(query, {
|
||||
target: targetPeer,
|
||||
session: sessionId,
|
||||
reasoningLevel,
|
||||
});
|
||||
|
||||
if (!result || typeof result !== 'string') {
|
||||
|
|
@ -387,6 +393,247 @@ class HonchoWorker {
|
|||
return peers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the peer card for a peer.
|
||||
* @param peerId - The ID of the observer peer
|
||||
* @param targetPeerId - Optional target peer ID to get the card about
|
||||
* @returns The peer card content or null if not found
|
||||
*/
|
||||
async getPeerCard(
|
||||
peerId: string,
|
||||
targetPeerId?: string,
|
||||
): Promise<string | null> {
|
||||
const peer = await this.honcho.peer(peerId);
|
||||
let targetPeer;
|
||||
if (targetPeerId) {
|
||||
targetPeer = await this.honcho.peer(targetPeerId);
|
||||
}
|
||||
return await peer.card(targetPeer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the peer context (combined representation and peer card).
|
||||
* @param peerId - The ID of the observer peer
|
||||
* @param targetPeerId - Optional target peer ID
|
||||
* @param sessionId - Optional session ID to scope the context
|
||||
* @param searchQuery - Optional semantic search query
|
||||
* @param maxConclusions - Optional maximum number of conclusions to include
|
||||
* @returns Context object with representation and peer card
|
||||
*/
|
||||
async getPeerContext(
|
||||
peerId: string,
|
||||
targetPeerId?: string,
|
||||
sessionId?: string,
|
||||
searchQuery?: string,
|
||||
maxConclusions?: number,
|
||||
): Promise<Record<string, any>> {
|
||||
const peer = await this.honcho.peer(peerId);
|
||||
let targetPeer;
|
||||
if (targetPeerId) {
|
||||
targetPeer = await this.honcho.peer(targetPeerId);
|
||||
}
|
||||
const context = await peer.context({
|
||||
target: targetPeer,
|
||||
session: sessionId,
|
||||
searchQuery,
|
||||
maxConclusions,
|
||||
});
|
||||
return {
|
||||
peer_id: context.peerId,
|
||||
target_id: context.targetId,
|
||||
representation: context.representation,
|
||||
peer_card: context.peerCard,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted representation for a peer.
|
||||
* @param peerId - The ID of the observer peer
|
||||
* @param targetPeerId - Optional target peer ID
|
||||
* @param sessionId - Optional session ID to scope the representation
|
||||
* @param searchQuery - Optional semantic search query
|
||||
* @param maxConclusions - Optional maximum number of conclusions
|
||||
* @returns Formatted representation string
|
||||
*/
|
||||
async getRepresentation(
|
||||
peerId: string,
|
||||
targetPeerId?: string,
|
||||
sessionId?: string,
|
||||
searchQuery?: string,
|
||||
maxConclusions?: number,
|
||||
): Promise<string> {
|
||||
const peer = await this.honcho.peer(peerId);
|
||||
let targetPeer;
|
||||
if (targetPeerId) {
|
||||
targetPeer = await this.honcho.peer(targetPeerId);
|
||||
}
|
||||
return await peer.representation({
|
||||
target: targetPeer,
|
||||
session: sessionId,
|
||||
searchQuery,
|
||||
maxConclusions,
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
/// ///
|
||||
/// Conclusions operations ///
|
||||
/// ///
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* List conclusions for a peer.
|
||||
* @param peerId - The ID of the observer peer
|
||||
* @param targetPeerId - Optional target peer to get conclusions about
|
||||
* @returns List of conclusions
|
||||
*/
|
||||
async listConclusions(
|
||||
peerId: string,
|
||||
targetPeerId?: string,
|
||||
): Promise<any[]> {
|
||||
const peer = await this.honcho.peer(peerId);
|
||||
let conclusionScope;
|
||||
if (targetPeerId) {
|
||||
const targetPeer = await this.honcho.peer(targetPeerId);
|
||||
conclusionScope = peer.conclusionsOf(targetPeer);
|
||||
} else {
|
||||
conclusionScope = peer.conclusions;
|
||||
}
|
||||
|
||||
const conclusionsPage = await conclusionScope.list();
|
||||
const conclusions = [];
|
||||
for await (const conclusion of conclusionsPage) {
|
||||
conclusions.push({
|
||||
id: conclusion.id,
|
||||
content: conclusion.content,
|
||||
observer_id: conclusion.observerId,
|
||||
observed_id: conclusion.observedId,
|
||||
session_id: conclusion.sessionId,
|
||||
created_at: conclusion.createdAt,
|
||||
});
|
||||
}
|
||||
return conclusions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query conclusions using semantic search.
|
||||
* @param peerId - The ID of the observer peer
|
||||
* @param query - The semantic search query
|
||||
* @param targetPeerId - Optional target peer to search conclusions about
|
||||
* @param topK - Maximum number of results to return
|
||||
* @returns List of matching conclusions
|
||||
*/
|
||||
async queryConclusions(
|
||||
peerId: string,
|
||||
query: string,
|
||||
targetPeerId?: string,
|
||||
topK?: number,
|
||||
): Promise<any[]> {
|
||||
const peer = await this.honcho.peer(peerId);
|
||||
let conclusionScope;
|
||||
if (targetPeerId) {
|
||||
const targetPeer = await this.honcho.peer(targetPeerId);
|
||||
conclusionScope = peer.conclusionsOf(targetPeer);
|
||||
} else {
|
||||
conclusionScope = peer.conclusions;
|
||||
}
|
||||
|
||||
// SDK query returns Conclusion[] directly, not a Page
|
||||
const conclusions = await conclusionScope.query(query, topK);
|
||||
return conclusions.map((conclusion) => ({
|
||||
id: conclusion.id,
|
||||
content: conclusion.content,
|
||||
observer_id: conclusion.observerId,
|
||||
observed_id: conclusion.observedId,
|
||||
session_id: conclusion.sessionId,
|
||||
created_at: conclusion.createdAt,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create conclusions manually.
|
||||
* @param peerId - The ID of the observer peer
|
||||
* @param targetPeerId - The target peer the conclusions are about
|
||||
* @param conclusions - List of conclusion content strings
|
||||
* @param sessionId - Optional session ID to associate with conclusions
|
||||
* @returns Number of conclusions created
|
||||
*/
|
||||
async createConclusions(
|
||||
peerId: string,
|
||||
targetPeerId: string,
|
||||
conclusions: string[],
|
||||
sessionId?: string,
|
||||
): Promise<number> {
|
||||
const peer = await this.honcho.peer(peerId);
|
||||
const targetPeer = await this.honcho.peer(targetPeerId);
|
||||
const conclusionScope = peer.conclusionsOf(targetPeer);
|
||||
|
||||
// Convert string array to ConclusionCreateParams array
|
||||
const conclusionParams = conclusions.map((content) => ({
|
||||
content,
|
||||
sessionId,
|
||||
}));
|
||||
|
||||
await conclusionScope.create(conclusionParams);
|
||||
return conclusions.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a conclusion.
|
||||
* @param peerId - The ID of the observer peer
|
||||
* @param targetPeerId - The target peer the conclusion is about
|
||||
* @param conclusionId - The ID of the conclusion to delete
|
||||
*/
|
||||
async deleteConclusion(
|
||||
peerId: string,
|
||||
targetPeerId: string,
|
||||
conclusionId: string,
|
||||
): Promise<void> {
|
||||
const peer = await this.honcho.peer(peerId);
|
||||
const targetPeer = await this.honcho.peer(targetPeerId);
|
||||
const conclusionScope = peer.conclusionsOf(targetPeer);
|
||||
|
||||
await conclusionScope.delete(conclusionId);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
/// ///
|
||||
/// System operations ///
|
||||
/// ///
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Schedule a dream (memory consolidation) for a peer.
|
||||
* @param peerId - The ID of the observer peer
|
||||
* @param targetPeerId - Optional target peer to dream about (defaults to observer for self-reflection)
|
||||
* @param sessionId - Optional session ID to scope the dream to
|
||||
* @returns Confirmation message
|
||||
*/
|
||||
async scheduleDream(
|
||||
peerId: string,
|
||||
targetPeerId?: string,
|
||||
sessionId?: string,
|
||||
): Promise<string> {
|
||||
const peer = await this.honcho.peer(peerId);
|
||||
const session = sessionId ? await this.honcho.session(sessionId) : undefined;
|
||||
const targetPeer = targetPeerId ? await this.honcho.peer(targetPeerId) : undefined;
|
||||
|
||||
await this.honcho.scheduleDream({
|
||||
observer: peer,
|
||||
session: session,
|
||||
observed: targetPeer,
|
||||
});
|
||||
return "Dream scheduled successfully";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current queue status for the deriver.
|
||||
* @returns Queue status information
|
||||
*/
|
||||
async getQueueStatus(): Promise<Record<string, any>> {
|
||||
return await this.honcho.queueStatus();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
/// ///
|
||||
/// Session operations ///
|
||||
|
|
@ -739,6 +986,12 @@ const tools: Tool[] = [
|
|||
description:
|
||||
"The question about the user's preferences, habits, etc.",
|
||||
},
|
||||
reasoning_level: {
|
||||
type: "string",
|
||||
enum: ["minimal", "low", "medium", "high", "max"],
|
||||
description:
|
||||
"The reasoning level for the response. Higher levels provide more detailed analysis.",
|
||||
},
|
||||
},
|
||||
required: ["session_id", "query"],
|
||||
},
|
||||
|
|
@ -879,6 +1132,12 @@ const tools: Tool[] = [
|
|||
description:
|
||||
"Optional session ID to scope the query to a specific session.",
|
||||
},
|
||||
reasoning_level: {
|
||||
type: "string",
|
||||
enum: ["minimal", "low", "medium", "high", "max"],
|
||||
description:
|
||||
"The reasoning level for the response. Higher levels provide more detailed analysis.",
|
||||
},
|
||||
},
|
||||
required: ["peer_id", "query"],
|
||||
},
|
||||
|
|
@ -892,6 +1151,223 @@ const tools: Tool[] = [
|
|||
required: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get_peer_card",
|
||||
description:
|
||||
"Get the peer card for a peer. The peer card contains compact biographical facts about the peer.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
peer_id: {
|
||||
type: "string",
|
||||
description: "The ID of the observer peer.",
|
||||
},
|
||||
target_peer_id: {
|
||||
type: "string",
|
||||
description:
|
||||
"Optional target peer ID to get the card about. If not provided, returns the peer's own card.",
|
||||
},
|
||||
},
|
||||
required: ["peer_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get_peer_context",
|
||||
description:
|
||||
"Get the peer context, which combines the representation and peer card for comprehensive peer information.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
peer_id: {
|
||||
type: "string",
|
||||
description: "The ID of the observer peer.",
|
||||
},
|
||||
target_peer_id: {
|
||||
type: "string",
|
||||
description: "Optional target peer ID to get context about.",
|
||||
},
|
||||
session_id: {
|
||||
type: "string",
|
||||
description: "Optional session ID to scope the context.",
|
||||
},
|
||||
search_query: {
|
||||
type: "string",
|
||||
description: "Optional semantic search query to filter conclusions.",
|
||||
},
|
||||
max_conclusions: {
|
||||
type: "integer",
|
||||
description: "Maximum number of conclusions to include.",
|
||||
},
|
||||
},
|
||||
required: ["peer_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get_representation",
|
||||
description:
|
||||
"Get the formatted representation for a peer, containing their conclusions and observations.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
peer_id: {
|
||||
type: "string",
|
||||
description: "The ID of the observer peer.",
|
||||
},
|
||||
target_peer_id: {
|
||||
type: "string",
|
||||
description: "Optional target peer ID to get representation about.",
|
||||
},
|
||||
session_id: {
|
||||
type: "string",
|
||||
description: "Optional session ID to scope the representation.",
|
||||
},
|
||||
search_query: {
|
||||
type: "string",
|
||||
description: "Optional semantic search query to filter conclusions.",
|
||||
},
|
||||
max_conclusions: {
|
||||
type: "integer",
|
||||
description: "Maximum number of conclusions to include.",
|
||||
},
|
||||
},
|
||||
required: ["peer_id"],
|
||||
},
|
||||
},
|
||||
|
||||
// Conclusions operations
|
||||
{
|
||||
name: "list_conclusions",
|
||||
description:
|
||||
"List conclusions for a peer. Conclusions are facts and observations derived from conversations.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
peer_id: {
|
||||
type: "string",
|
||||
description: "The ID of the observer peer.",
|
||||
},
|
||||
target_peer_id: {
|
||||
type: "string",
|
||||
description:
|
||||
"Optional target peer ID to get conclusions about. If not provided, returns conclusions about self.",
|
||||
},
|
||||
},
|
||||
required: ["peer_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "query_conclusions",
|
||||
description: "Query conclusions using semantic search.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
peer_id: {
|
||||
type: "string",
|
||||
description: "The ID of the observer peer.",
|
||||
},
|
||||
query: {
|
||||
type: "string",
|
||||
description: "The semantic search query.",
|
||||
},
|
||||
target_peer_id: {
|
||||
type: "string",
|
||||
description: "Optional target peer ID to search conclusions about.",
|
||||
},
|
||||
top_k: {
|
||||
type: "integer",
|
||||
description: "Maximum number of results to return.",
|
||||
},
|
||||
},
|
||||
required: ["peer_id", "query"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create_conclusions",
|
||||
description: "Create conclusions manually for a peer.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
peer_id: {
|
||||
type: "string",
|
||||
description: "The ID of the observer peer.",
|
||||
},
|
||||
target_peer_id: {
|
||||
type: "string",
|
||||
description: "The target peer the conclusions are about.",
|
||||
},
|
||||
conclusions: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "List of conclusion content strings to create.",
|
||||
},
|
||||
session_id: {
|
||||
type: "string",
|
||||
description:
|
||||
"Optional session ID to associate with conclusions. If not provided, conclusions will be global.",
|
||||
},
|
||||
},
|
||||
required: ["peer_id", "target_peer_id", "conclusions"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "delete_conclusion",
|
||||
description: "Delete a specific conclusion.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
peer_id: {
|
||||
type: "string",
|
||||
description: "The ID of the observer peer.",
|
||||
},
|
||||
target_peer_id: {
|
||||
type: "string",
|
||||
description: "The target peer the conclusion is about.",
|
||||
},
|
||||
conclusion_id: {
|
||||
type: "string",
|
||||
description: "The ID of the conclusion to delete.",
|
||||
},
|
||||
},
|
||||
required: ["peer_id", "target_peer_id", "conclusion_id"],
|
||||
},
|
||||
},
|
||||
|
||||
// System operations
|
||||
{
|
||||
name: "schedule_dream",
|
||||
description:
|
||||
"Schedule a dream (memory consolidation) for a peer. Dreams help consolidate and improve memory quality.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
peer_id: {
|
||||
type: "string",
|
||||
description: "The ID of the observer peer.",
|
||||
},
|
||||
target_peer_id: {
|
||||
type: "string",
|
||||
description:
|
||||
"Optional target peer to dream about. If not provided, defaults to the observer peer (self-reflection).",
|
||||
},
|
||||
session_id: {
|
||||
type: "string",
|
||||
description:
|
||||
"Optional session ID to scope the dream to. If not provided, the dream will be global.",
|
||||
},
|
||||
},
|
||||
required: ["peer_id"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get_queue_status",
|
||||
description:
|
||||
"Get the current queue status for the deriver (background processing system).",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: [],
|
||||
},
|
||||
},
|
||||
|
||||
// Session operations
|
||||
{
|
||||
|
|
@ -1170,6 +1646,7 @@ async function executeToolCall(
|
|||
result = await honcho.getPersonalizationInsights(
|
||||
toolArguments.session_id,
|
||||
toolArguments.query,
|
||||
toolArguments.reasoning_level,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
|
@ -1272,6 +1749,7 @@ async function executeToolCall(
|
|||
toolArguments.query,
|
||||
toolArguments.target_peer_id,
|
||||
toolArguments.session_id,
|
||||
toolArguments.reasoning_level,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
|
@ -1280,6 +1758,146 @@ async function executeToolCall(
|
|||
result = await honcho.listPeers();
|
||||
break;
|
||||
|
||||
case "get_peer_card": {
|
||||
const validation = validateArguments(
|
||||
toolArguments,
|
||||
["peer_id"],
|
||||
requestId,
|
||||
);
|
||||
if (validation) return validation;
|
||||
|
||||
result = await honcho.getPeerCard(
|
||||
toolArguments.peer_id,
|
||||
toolArguments.target_peer_id,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case "get_peer_context": {
|
||||
const validation = validateArguments(
|
||||
toolArguments,
|
||||
["peer_id"],
|
||||
requestId,
|
||||
);
|
||||
if (validation) return validation;
|
||||
|
||||
result = await honcho.getPeerContext(
|
||||
toolArguments.peer_id,
|
||||
toolArguments.target_peer_id,
|
||||
toolArguments.session_id,
|
||||
toolArguments.search_query,
|
||||
toolArguments.max_conclusions,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case "get_representation": {
|
||||
const validation = validateArguments(
|
||||
toolArguments,
|
||||
["peer_id"],
|
||||
requestId,
|
||||
);
|
||||
if (validation) return validation;
|
||||
|
||||
result = await honcho.getRepresentation(
|
||||
toolArguments.peer_id,
|
||||
toolArguments.target_peer_id,
|
||||
toolArguments.session_id,
|
||||
toolArguments.search_query,
|
||||
toolArguments.max_conclusions,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
// Conclusions operations
|
||||
case "list_conclusions": {
|
||||
const validation = validateArguments(
|
||||
toolArguments,
|
||||
["peer_id"],
|
||||
requestId,
|
||||
);
|
||||
if (validation) return validation;
|
||||
|
||||
result = await honcho.listConclusions(
|
||||
toolArguments.peer_id,
|
||||
toolArguments.target_peer_id,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case "query_conclusions": {
|
||||
const validation = validateArguments(
|
||||
toolArguments,
|
||||
["peer_id", "query"],
|
||||
requestId,
|
||||
);
|
||||
if (validation) return validation;
|
||||
|
||||
result = await honcho.queryConclusions(
|
||||
toolArguments.peer_id,
|
||||
toolArguments.query,
|
||||
toolArguments.target_peer_id,
|
||||
toolArguments.top_k,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case "create_conclusions": {
|
||||
const validation = validateArguments(
|
||||
toolArguments,
|
||||
["peer_id", "target_peer_id", "conclusions"],
|
||||
requestId,
|
||||
);
|
||||
if (validation) return validation;
|
||||
|
||||
const count = await honcho.createConclusions(
|
||||
toolArguments.peer_id,
|
||||
toolArguments.target_peer_id,
|
||||
toolArguments.conclusions,
|
||||
toolArguments.session_id,
|
||||
);
|
||||
result = `Created ${count} conclusions successfully`;
|
||||
break;
|
||||
}
|
||||
|
||||
case "delete_conclusion": {
|
||||
const validation = validateArguments(
|
||||
toolArguments,
|
||||
["peer_id", "target_peer_id", "conclusion_id"],
|
||||
requestId,
|
||||
);
|
||||
if (validation) return validation;
|
||||
|
||||
await honcho.deleteConclusion(
|
||||
toolArguments.peer_id,
|
||||
toolArguments.target_peer_id,
|
||||
toolArguments.conclusion_id,
|
||||
);
|
||||
result = "Conclusion deleted successfully";
|
||||
break;
|
||||
}
|
||||
|
||||
// System operations
|
||||
case "schedule_dream": {
|
||||
const validation = validateArguments(
|
||||
toolArguments,
|
||||
["peer_id"],
|
||||
requestId,
|
||||
);
|
||||
if (validation) return validation;
|
||||
|
||||
result = await honcho.scheduleDream(
|
||||
toolArguments.peer_id,
|
||||
toolArguments.target_peer_id,
|
||||
toolArguments.session_id,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case "get_queue_status":
|
||||
result = await honcho.getQueueStatus();
|
||||
break;
|
||||
|
||||
// Session operations
|
||||
case "create_session": {
|
||||
const validation = validateArguments(
|
||||
|
|
@ -1553,7 +2171,7 @@ export default {
|
|||
},
|
||||
serverInfo: {
|
||||
name: "Honcho MCP Server",
|
||||
version: "1.0.0",
|
||||
version: "3.0.0",
|
||||
},
|
||||
}),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
"""make document session_name nullable for sessionless dreams
|
||||
|
||||
Allow dreams to run without a session_id by making the session_name
|
||||
column nullable on the documents table.
|
||||
|
||||
Revision ID: e4eba9cfaa6f
|
||||
Revises: 119a52b73c60
|
||||
Create Date: 2026-01-26
|
||||
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from nanoid import generate as generate_nanoid
|
||||
|
||||
from migrations.utils import get_schema
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "e4eba9cfaa6f"
|
||||
down_revision: str | None = "119a52b73c60"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
schema = get_schema()
|
||||
|
||||
|
||||
def ensure_orphaned_sessions_exist(schema: str) -> None:
|
||||
"""Ensure a per-workspace `__orphaned__` session exists for downgrade.
|
||||
|
||||
During downgrade we backfill NULL `documents.session_name` values to a
|
||||
sentinel value (`__orphaned__`) and then make the column non-nullable. The
|
||||
`documents` table has a composite foreign key (`session_name`,
|
||||
`workspace_name`) that references `sessions` (`name`, `workspace_name`).
|
||||
If we set `session_name` to a non-null sentinel without ensuring a matching
|
||||
session row exists for each affected workspace, the UPDATE can violate the
|
||||
foreign key.
|
||||
|
||||
This helper inserts missing placeholder sessions scoped by workspace using
|
||||
`ON CONFLICT DO NOTHING` to remain safe and idempotent.
|
||||
"""
|
||||
conn = op.get_bind()
|
||||
orphaned_session_name = "__orphaned__"
|
||||
|
||||
workspaces = conn.execute(
|
||||
sa.text(
|
||||
f"""
|
||||
SELECT DISTINCT workspace_name
|
||||
FROM "{schema}".documents
|
||||
WHERE session_name IS NULL
|
||||
"""
|
||||
)
|
||||
).fetchall()
|
||||
|
||||
for (workspace_name,) in workspaces:
|
||||
conn.execute(
|
||||
sa.text(
|
||||
f"""
|
||||
INSERT INTO "{schema}".sessions (id, name, workspace_name, is_active)
|
||||
VALUES (:id, :name, :workspace_name, true)
|
||||
ON CONFLICT (name, workspace_name) DO NOTHING
|
||||
"""
|
||||
),
|
||||
{
|
||||
"id": generate_nanoid(),
|
||||
"name": orphaned_session_name,
|
||||
"workspace_name": workspace_name,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Make session_name nullable on documents table."""
|
||||
op.alter_column("documents", "session_name", nullable=True, schema=schema)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Make session_name non-nullable again, backfilling NULLs first."""
|
||||
ensure_orphaned_sessions_exist(schema)
|
||||
op.execute(
|
||||
f"UPDATE \"{schema}\".documents SET session_name = '__orphaned__' WHERE session_name IS NULL"
|
||||
)
|
||||
op.alter_column("documents", "session_name", nullable=False, schema=schema)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "honcho"
|
||||
version = "3.0.0"
|
||||
version = "3.0.1"
|
||||
description = "Honcho Server"
|
||||
authors = [
|
||||
{name = "Plastic Labs", email = "hello@plasticlabs.ai"},
|
||||
|
|
@ -37,8 +37,7 @@ dependencies = [
|
|||
"redis>=7.0.0,<8.0.0",
|
||||
"cashews[redis]==7.4.4",
|
||||
"scikit-learn>=1.6.0",
|
||||
"opentelemetry-sdk>=1.36.0",
|
||||
"opentelemetry-exporter-otlp-proto-http>=1.36.0",
|
||||
"prometheus_client>=0.21.0",
|
||||
"cloudevents>=1.12.0",
|
||||
]
|
||||
[dependency-groups]
|
||||
|
|
@ -116,7 +115,7 @@ reportUnusedCallResult = false
|
|||
reportCallInDefaultInitializer = false
|
||||
reportAny = false
|
||||
reportExplicitAny = false
|
||||
allowedUntypedLibraries = ["langfuse", "lancedb", "pyarrow", "opentelemetry"]
|
||||
allowedUntypedLibraries = ["langfuse", "lancedb", "pyarrow"]
|
||||
reportImplicitOverride = false
|
||||
reportImportCycles = false
|
||||
|
||||
|
|
|
|||
|
|
@ -372,7 +372,7 @@ class HonchoAio(AsyncMetadataConfigMixin):
|
|||
async def schedule_dream(
|
||||
self,
|
||||
observer: str | PeerBase,
|
||||
session: str | SessionBase,
|
||||
session: str | SessionBase | None = None,
|
||||
observed: str | PeerBase | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -385,7 +385,7 @@ class HonchoAio(AsyncMetadataConfigMixin):
|
|||
Args:
|
||||
observer: The observer peer (ID string or Peer object) whose perspective
|
||||
to use for the dream.
|
||||
session: The session (ID string or Session object) to scope the dream to.
|
||||
session: Optional session (ID string or Session object) to scope the dream to.
|
||||
observed: Optional observed peer (ID string or Peer object). If not provided,
|
||||
defaults to the observer (self-reflection).
|
||||
"""
|
||||
|
|
@ -934,9 +934,9 @@ class SessionAio(AsyncMetadataConfigMixin):
|
|||
None,
|
||||
description="A peer ID to get context for.",
|
||||
),
|
||||
last_user_message: str | Message | None = Field(
|
||||
search_query: str | Message | None = Field(
|
||||
None,
|
||||
description="The most recent message text (string or Message object), used to fetch semantically relevant conclusions.",
|
||||
description="A query string (or Message object) used to fetch semantically relevant conclusions.",
|
||||
),
|
||||
peer_perspective: str | None = Field(
|
||||
None,
|
||||
|
|
@ -976,15 +976,13 @@ class SessionAio(AsyncMetadataConfigMixin):
|
|||
"You must provide a `peer_target` when `peer_perspective` is provided"
|
||||
)
|
||||
|
||||
if peer_target is None and last_user_message is not None:
|
||||
if peer_target is None and search_query is not None:
|
||||
raise ValueError(
|
||||
"You must provide a `peer_target` when `last_user_message` is provided"
|
||||
"You must provide a `peer_target` when `search_query` is provided"
|
||||
)
|
||||
|
||||
last_user_message_text = (
|
||||
last_user_message.content
|
||||
if isinstance(last_user_message, Message)
|
||||
else last_user_message
|
||||
search_query_text = (
|
||||
search_query.content if isinstance(search_query, Message) else search_query
|
||||
)
|
||||
|
||||
query: dict[str, Any] = {
|
||||
|
|
@ -993,8 +991,8 @@ class SessionAio(AsyncMetadataConfigMixin):
|
|||
}
|
||||
if tokens is not None:
|
||||
query["tokens"] = tokens
|
||||
if last_user_message_text is not None:
|
||||
query["last_message"] = last_user_message_text
|
||||
if search_query_text is not None:
|
||||
query["search_query"] = search_query_text
|
||||
if peer_target is not None:
|
||||
query["peer_target"] = peer_target
|
||||
if peer_perspective is not None:
|
||||
|
|
@ -1319,19 +1317,28 @@ class ConclusionScopeAio:
|
|||
) -> list[Conclusion]:
|
||||
"""Create conclusions in this scope asynchronously."""
|
||||
await self._scope._honcho._ensure_workspace_async()
|
||||
conclusion_params = [
|
||||
{
|
||||
"content": c.content
|
||||
if isinstance(c, ConclusionCreateParams)
|
||||
else c["content"],
|
||||
"session_id": c.session_id
|
||||
if isinstance(c, ConclusionCreateParams)
|
||||
else c["session_id"],
|
||||
|
||||
def build_conclusion_payload(
|
||||
item: ConclusionCreateParams | dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Build a single conclusion create payload."""
|
||||
payload: dict[str, Any] = {
|
||||
"observer_id": self._scope.observer,
|
||||
"observed_id": self._scope.observed,
|
||||
}
|
||||
for c in conclusions
|
||||
]
|
||||
if isinstance(item, ConclusionCreateParams):
|
||||
payload["content"] = item.content
|
||||
if item.session_id is not None:
|
||||
payload["session_id"] = item.session_id
|
||||
return payload
|
||||
|
||||
payload["content"] = item["content"]
|
||||
session_id = item.get("session_id")
|
||||
if session_id is not None:
|
||||
payload["session_id"] = session_id
|
||||
return payload
|
||||
|
||||
conclusion_params = [build_conclusion_payload(c) for c in conclusions]
|
||||
|
||||
data = await self._scope._honcho._async_http_client.post(
|
||||
routes.conclusions(self._scope.workspace_id),
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@ class ConclusionResponse(BaseModel):
|
|||
content: str
|
||||
observer_id: str
|
||||
observed_id: str
|
||||
session_id: str
|
||||
session_id: str | None = None
|
||||
created_at: datetime.datetime
|
||||
|
||||
|
||||
|
|
@ -365,7 +365,7 @@ class ConclusionCreateParams(BaseModel):
|
|||
content: str = Field(min_length=1, max_length=65535)
|
||||
observer_id: str
|
||||
observed_id: str
|
||||
session_id: str
|
||||
session_id: str | None = None
|
||||
|
||||
|
||||
class ConclusionBatchCreateParams(BaseModel):
|
||||
|
|
|
|||
|
|
@ -561,7 +561,7 @@ class Honcho(BaseModel, MetadataConfigMixin): # pyright: ignore[reportUnsafeMul
|
|||
def schedule_dream(
|
||||
self,
|
||||
observer: str | PeerBase,
|
||||
session: str | SessionBase,
|
||||
session: str | SessionBase | None = None,
|
||||
observed: str | PeerBase | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
|
|
@ -574,7 +574,7 @@ class Honcho(BaseModel, MetadataConfigMixin): # pyright: ignore[reportUnsafeMul
|
|||
Args:
|
||||
observer: The observer peer (ID string or Peer object) whose perspective
|
||||
to use for the dream.
|
||||
session: The session (ID string or Session object) to scope the dream to.
|
||||
session: Optional session (ID string or Session object) to scope the dream to.
|
||||
observed: Optional observed peer (ID string or Peer object). If not provided,
|
||||
defaults to the observer (self-reflection).
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ __all__ = [
|
|||
|
||||
class ConclusionCreateParams(BaseModel):
|
||||
content: str
|
||||
session_id: str
|
||||
session_id: str | None = None
|
||||
|
||||
|
||||
class Conclusion:
|
||||
|
|
@ -50,7 +50,7 @@ class Conclusion:
|
|||
content: str
|
||||
observer_id: str
|
||||
observed_id: str
|
||||
session_id: str
|
||||
session_id: str | None = None
|
||||
created_at: datetime.datetime
|
||||
|
||||
def __init__(
|
||||
|
|
@ -59,7 +59,7 @@ class Conclusion:
|
|||
content: str,
|
||||
observer_id: str,
|
||||
observed_id: str,
|
||||
session_id: str,
|
||||
session_id: str | None,
|
||||
created_at: datetime.datetime,
|
||||
) -> None:
|
||||
self.id = id
|
||||
|
|
@ -269,7 +269,8 @@ class ConclusionScope:
|
|||
|
||||
Args:
|
||||
conclusions: List of conclusions to create.
|
||||
Each conclusion can be a ConclusionCreateParams object or a dictionary with 'content' and 'session_id' keys.
|
||||
Each conclusion can be a ConclusionCreateParams object or a dictionary
|
||||
with a required 'content' key and an optional 'session_id' key.
|
||||
|
||||
Returns:
|
||||
List of created Conclusion objects
|
||||
|
|
@ -278,24 +279,51 @@ class ConclusionScope:
|
|||
```python
|
||||
conclusions = peer.conclusions.create([
|
||||
{"content": "User prefers dark mode", "session_id": "session1"},
|
||||
{"content": "User is interested in AI", "session_id": "session1"},
|
||||
{"content": "User is interested in AI"},
|
||||
])
|
||||
```
|
||||
"""
|
||||
self._honcho._ensure_workspace()
|
||||
conclusion_params = [
|
||||
{
|
||||
"content": c.content
|
||||
if isinstance(c, ConclusionCreateParams)
|
||||
else c["content"],
|
||||
"session_id": c.session_id
|
||||
if isinstance(c, ConclusionCreateParams)
|
||||
else c["session_id"],
|
||||
|
||||
def build_conclusion_payload(
|
||||
item: ConclusionCreateParams | dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Build a single conclusion create payload.
|
||||
|
||||
This normalizes both `ConclusionCreateParams` instances and plain dictionaries
|
||||
into the wire format expected by the Honcho API.
|
||||
|
||||
Notes:
|
||||
- `content` is required.
|
||||
- `session_id` is optional; when not provided it is omitted from the payload.
|
||||
- `observer_id` and `observed_id` are always injected from this scope.
|
||||
|
||||
Args:
|
||||
item: A `ConclusionCreateParams` instance or a dict with a required
|
||||
`content` key and an optional `session_id` key.
|
||||
|
||||
Returns:
|
||||
A dictionary suitable for inclusion in the `conclusions` array for the
|
||||
create conclusions endpoint.
|
||||
"""
|
||||
payload: dict[str, Any] = {
|
||||
"observer_id": self.observer,
|
||||
"observed_id": self.observed,
|
||||
}
|
||||
for c in conclusions
|
||||
]
|
||||
if isinstance(item, ConclusionCreateParams):
|
||||
payload["content"] = item.content
|
||||
if item.session_id is not None:
|
||||
payload["session_id"] = item.session_id
|
||||
return payload
|
||||
|
||||
payload["content"] = item["content"]
|
||||
session_id = item.get("session_id")
|
||||
if session_id is not None:
|
||||
payload["session_id"] = session_id
|
||||
return payload
|
||||
|
||||
conclusion_params = [build_conclusion_payload(c) for c in conclusions]
|
||||
|
||||
data = self._honcho._http.post(
|
||||
routes.conclusions(self.workspace_id),
|
||||
|
|
|
|||
|
|
@ -517,9 +517,9 @@ class Session(SessionBase, MetadataConfigMixin):
|
|||
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(
|
||||
search_query: str | Message | None = Field(
|
||||
None,
|
||||
description="The most recent message text (string or Message object), used to fetch semantically relevant conclusions. Use this alongside `peer_target` to get a more focused context -- does nothing if `peer_target` is not provided.",
|
||||
description="A query string (or Message object) used to fetch semantically relevant conclusions. 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,
|
||||
|
|
@ -533,13 +533,13 @@ class Session(SessionBase, MetadataConfigMixin):
|
|||
None,
|
||||
ge=1,
|
||||
le=100,
|
||||
description="Number of semantically relevant facts to return when searching with `last_user_message`.",
|
||||
description="Number of semantically relevant facts to return when searching with `search_query`.",
|
||||
),
|
||||
search_max_distance: float | None = Field(
|
||||
None,
|
||||
ge=0.0,
|
||||
le=1.0,
|
||||
description="Maximum semantic distance for search results (0.0-1.0) when searching with `last_user_message`.",
|
||||
description="Maximum semantic distance for search results (0.0-1.0) when searching with `search_query`.",
|
||||
),
|
||||
include_most_frequent: bool | None = Field(
|
||||
None,
|
||||
|
|
@ -565,7 +565,7 @@ class Session(SessionBase, MetadataConfigMixin):
|
|||
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.
|
||||
last_user_message: The most recent message for semantic search.
|
||||
search_query: A query string for semantic search.
|
||||
peer_perspective: A peer ID to get context from the perspective of.
|
||||
limit_to_session: Whether to limit the representation to this session only.
|
||||
search_top_k: Number of semantically relevant facts to return.
|
||||
|
|
@ -589,15 +589,13 @@ class Session(SessionBase, MetadataConfigMixin):
|
|||
"You must provide a `peer_target` when `peer_perspective` is provided"
|
||||
)
|
||||
|
||||
if peer_target is None and last_user_message is not None:
|
||||
if peer_target is None and search_query is not None:
|
||||
raise ValueError(
|
||||
"You must provide a `peer_target` when `last_user_message` is provided"
|
||||
"You must provide a `peer_target` when `search_query` is provided"
|
||||
)
|
||||
|
||||
last_user_message_text = (
|
||||
last_user_message.content
|
||||
if isinstance(last_user_message, Message)
|
||||
else last_user_message
|
||||
search_query_text = (
|
||||
search_query.content if isinstance(search_query, Message) else search_query
|
||||
)
|
||||
|
||||
query: dict[str, Any] = {
|
||||
|
|
@ -606,8 +604,8 @@ class Session(SessionBase, MetadataConfigMixin):
|
|||
}
|
||||
if tokens is not None:
|
||||
query["tokens"] = tokens
|
||||
if last_user_message_text is not None:
|
||||
query["last_message"] = last_user_message_text
|
||||
if search_query_text is not None:
|
||||
query["search_query"] = search_query_text
|
||||
if peer_target is not None:
|
||||
query["peer_target"] = peer_target
|
||||
if peer_perspective is not None:
|
||||
|
|
|
|||
|
|
@ -423,4 +423,110 @@ describe('Conclusions', () => {
|
|||
expect(str).toContain(client.workspaceId)
|
||||
})
|
||||
})
|
||||
|
||||
// ===========================================================================
|
||||
// Sessionless Conclusions (Optional session_id)
|
||||
// ===========================================================================
|
||||
|
||||
describe('sessionless conclusions (optional session_id)', () => {
|
||||
test('create conclusion without sessionId', async () => {
|
||||
const peer = await client.peer('sessionless-create-peer', { metadata: {} })
|
||||
|
||||
// Create conclusion without sessionId
|
||||
const conclusions = await peer.conclusions.create({
|
||||
content: 'Global conclusion without session',
|
||||
// No sessionId - this is the key test
|
||||
})
|
||||
|
||||
expect(conclusions.length).toBe(1)
|
||||
expect(conclusions[0]).toBeInstanceOf(Conclusion)
|
||||
expect(conclusions[0].content).toBe('Global conclusion without session')
|
||||
expect(conclusions[0].sessionId).toBeNull()
|
||||
})
|
||||
|
||||
test('create multiple conclusions without sessionId', async () => {
|
||||
const peer = await client.peer('sessionless-multi-peer', { metadata: {} })
|
||||
|
||||
const conclusions = await peer.conclusions.create([
|
||||
{ content: 'Global observation 1' },
|
||||
{ content: 'Global observation 2' },
|
||||
{ content: 'Global observation 3' },
|
||||
])
|
||||
|
||||
expect(conclusions.length).toBe(3)
|
||||
for (const c of conclusions) {
|
||||
expect(c.sessionId).toBeNull()
|
||||
}
|
||||
})
|
||||
|
||||
test('create mixed session and sessionless conclusions', async () => {
|
||||
const peer = await client.peer('sessionless-mixed-peer', { metadata: {} })
|
||||
const session = await client.session('sessionless-mixed-session', {
|
||||
metadata: {},
|
||||
})
|
||||
|
||||
const conclusions = await peer.conclusions.create([
|
||||
{ content: 'Session-scoped conclusion', sessionId: session },
|
||||
{ content: 'Global conclusion without session' },
|
||||
])
|
||||
|
||||
expect(conclusions.length).toBe(2)
|
||||
|
||||
const sessionConclusion = conclusions.find(
|
||||
(c) => c.content === 'Session-scoped conclusion'
|
||||
)
|
||||
const globalConclusion = conclusions.find(
|
||||
(c) => c.content === 'Global conclusion without session'
|
||||
)
|
||||
|
||||
expect(sessionConclusion?.sessionId).toBe(session.id)
|
||||
expect(globalConclusion?.sessionId).toBeNull()
|
||||
})
|
||||
|
||||
test('list includes sessionless conclusions', async () => {
|
||||
const peer = await client.peer('sessionless-list-peer', { metadata: {} })
|
||||
|
||||
// Create a sessionless conclusion
|
||||
const [created] = await peer.conclusions.create({
|
||||
content: 'Sessionless conclusion for list test',
|
||||
})
|
||||
|
||||
// List all conclusions (no session filter)
|
||||
const page = await peer.conclusions.list()
|
||||
|
||||
const found = page.items.find((c) => c.id === created.id)
|
||||
expect(found).toBeDefined()
|
||||
expect(found?.sessionId).toBeNull()
|
||||
})
|
||||
|
||||
test('Conclusion.fromApiResponse handles null session_id', () => {
|
||||
const response = {
|
||||
id: 'test-id',
|
||||
content: 'Test content',
|
||||
observer_id: 'observer',
|
||||
observed_id: 'observed',
|
||||
session_id: null,
|
||||
created_at: '2024-01-15T10:00:00Z',
|
||||
}
|
||||
|
||||
const conclusion = Conclusion.fromApiResponse(response)
|
||||
|
||||
expect(conclusion.sessionId).toBeNull()
|
||||
expect(conclusion.id).toBe('test-id')
|
||||
expect(conclusion.content).toBe('Test content')
|
||||
})
|
||||
|
||||
test('Conclusion constructor accepts null sessionId', () => {
|
||||
const conclusion = new Conclusion(
|
||||
'id',
|
||||
'content',
|
||||
'observer',
|
||||
'observed',
|
||||
null,
|
||||
'2024-01-01'
|
||||
)
|
||||
|
||||
expect(conclusion.sessionId).toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,90 +5,17 @@
|
|||
"": {
|
||||
"name": "@honcho-ai/sdk",
|
||||
"dependencies": {
|
||||
"@honcho-ai/core": "2.2.0",
|
||||
"@types/node": "^24.0.1",
|
||||
"zod": "4.0.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.1.2",
|
||||
"@types/jest": "^29.5.14",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.0",
|
||||
"@types/bun": "latest",
|
||||
"typescript": "^5.0.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
|
||||
|
||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
|
||||
|
||||
"@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="],
|
||||
|
||||
"@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="],
|
||||
|
||||
"@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="],
|
||||
|
||||
"@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="],
|
||||
|
||||
"@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww=="],
|
||||
|
||||
"@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="],
|
||||
|
||||
"@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="],
|
||||
|
||||
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w=="],
|
||||
|
||||
"@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="],
|
||||
|
||||
"@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="],
|
||||
|
||||
"@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="],
|
||||
|
||||
"@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="],
|
||||
|
||||
"@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="],
|
||||
|
||||
"@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="],
|
||||
|
||||
"@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="],
|
||||
|
||||
"@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="],
|
||||
|
||||
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||
|
||||
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
|
||||
|
||||
"@biomejs/biome": ["@biomejs/biome@2.3.8", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.8", "@biomejs/cli-darwin-x64": "2.3.8", "@biomejs/cli-linux-arm64": "2.3.8", "@biomejs/cli-linux-arm64-musl": "2.3.8", "@biomejs/cli-linux-x64": "2.3.8", "@biomejs/cli-linux-x64-musl": "2.3.8", "@biomejs/cli-win32-arm64": "2.3.8", "@biomejs/cli-win32-x64": "2.3.8" }, "bin": { "biome": "bin/biome" } }, "sha512-Qjsgoe6FEBxWAUzwFGFrB+1+M8y/y5kwmg5CHac+GSVOdmOIqsAiXM5QMVGZJ1eCUCLlPZtq4aFAQ0eawEUuUA=="],
|
||||
|
||||
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HM4Zg9CGQ3txTPflxD19n8MFPrmUAjaC7PQdLkugeeC0cQ+PiVrd7i09gaBS/11QKsTDBJhVg85CEIK9f50Qww=="],
|
||||
|
|
@ -107,564 +34,16 @@
|
|||
|
||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.8", "", { "os": "win32", "cpu": "x64" }, "sha512-RguzimPoZWtBapfKhKjcWXBVI91tiSprqdBYu7tWhgN8pKRZhw24rFeNZTNf6UiBfjCYCi9eFQs/JzJZIhuK4w=="],
|
||||
|
||||
"@honcho-ai/core": ["@honcho-ai/core@2.2.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-3lDBdruXlg2m8OEGe5mSD6pt4EgZulgvT0tbaM7k61Mi+m8H56aQNzlCv5yPLdw5G/FIEjyJJcFfHknqdxoyrg=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="],
|
||||
|
||||
"@jest/console": ["@jest/console@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0" } }, "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg=="],
|
||||
|
||||
"@jest/core": ["@jest/core@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-changed-files": "^29.7.0", "jest-config": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-resolve-dependencies": "^29.7.0", "jest-runner": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg=="],
|
||||
|
||||
"@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="],
|
||||
|
||||
"@jest/expect": ["@jest/expect@29.7.0", "", { "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" } }, "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ=="],
|
||||
|
||||
"@jest/expect-utils": ["@jest/expect-utils@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3" } }, "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA=="],
|
||||
|
||||
"@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="],
|
||||
|
||||
"@jest/globals": ["@jest/globals@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", "jest-mock": "^29.7.0" } }, "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ=="],
|
||||
|
||||
"@jest/reporters": ["@jest/reporters@29.7.0", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", "v8-to-istanbul": "^9.0.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg=="],
|
||||
|
||||
"@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
|
||||
|
||||
"@jest/source-map": ["@jest/source-map@29.6.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" } }, "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw=="],
|
||||
|
||||
"@jest/test-result": ["@jest/test-result@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA=="],
|
||||
|
||||
"@jest/test-sequencer": ["@jest/test-sequencer@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "slash": "^3.0.0" } }, "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw=="],
|
||||
|
||||
"@jest/transform": ["@jest/transform@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" } }, "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw=="],
|
||||
|
||||
"@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="],
|
||||
|
||||
"@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="],
|
||||
|
||||
"@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="],
|
||||
|
||||
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||
|
||||
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
|
||||
|
||||
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
|
||||
|
||||
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
||||
|
||||
"@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="],
|
||||
|
||||
"@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
|
||||
|
||||
"@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="],
|
||||
|
||||
"@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="],
|
||||
|
||||
"@types/jest": ["@types/jest@29.5.14", "", { "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ=="],
|
||||
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
||||
|
||||
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
|
||||
|
||||
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
|
||||
|
||||
"@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="],
|
||||
|
||||
"@types/yargs": ["@types/yargs@17.0.35", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg=="],
|
||||
|
||||
"@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
|
||||
|
||||
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
|
||||
|
||||
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
|
||||
|
||||
"ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||
|
||||
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||
|
||||
"argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
|
||||
"babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="],
|
||||
|
||||
"babel-plugin-istanbul": ["babel-plugin-istanbul@6.1.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA=="],
|
||||
|
||||
"babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="],
|
||||
|
||||
"babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.2.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg=="],
|
||||
|
||||
"babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.32", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="],
|
||||
|
||||
"bs-logger": ["bs-logger@0.2.6", "", { "dependencies": { "fast-json-stable-stringify": "2.x" } }, "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog=="],
|
||||
|
||||
"bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001757", "", {}, "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="],
|
||||
|
||||
"ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
|
||||
|
||||
"cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="],
|
||||
|
||||
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="],
|
||||
|
||||
"collect-v8-coverage": ["collect-v8-coverage@1.0.3", "", {}, "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"create-jest": ["create-jest@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "prompts": "^2.0.1" }, "bin": { "create-jest": "bin/create-jest.js" } }, "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"dedent": ["dedent@1.7.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ=="],
|
||||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
||||
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||
|
||||
"detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="],
|
||||
|
||||
"diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="],
|
||||
|
||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.263", "", {}, "sha512-DrqJ11Knd+lo+dv+lltvfMDLU27g14LMdH2b0O3Pio4uk0x+z7OR+JrmyacTPN2M8w3BrZ7/RTwG3R9B7irPlg=="],
|
||||
|
||||
"emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="],
|
||||
|
||||
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
|
||||
|
||||
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
||||
|
||||
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
|
||||
|
||||
"execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
|
||||
|
||||
"exit": ["exit@0.1.2", "", {}, "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="],
|
||||
|
||||
"expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
|
||||
"fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
|
||||
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
|
||||
|
||||
"form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
|
||||
|
||||
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
|
||||
|
||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||
|
||||
"get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
|
||||
"get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
|
||||
|
||||
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||
|
||||
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
|
||||
|
||||
"human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
|
||||
|
||||
"humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
|
||||
|
||||
"import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-generator-fn": ["is-generator-fn@2.1.0", "", {}, "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
|
||||
|
||||
"istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q=="],
|
||||
|
||||
"istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="],
|
||||
|
||||
"istanbul-lib-source-maps": ["istanbul-lib-source-maps@4.0.1", "", { "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" } }, "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw=="],
|
||||
|
||||
"istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="],
|
||||
|
||||
"jest": ["jest@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", "import-local": "^3.0.2", "jest-cli": "^29.7.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw=="],
|
||||
|
||||
"jest-changed-files": ["jest-changed-files@29.7.0", "", { "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0" } }, "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w=="],
|
||||
|
||||
"jest-circus": ["jest-circus@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", "jest-each": "^29.7.0", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0", "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw=="],
|
||||
|
||||
"jest-cli": ["jest-cli@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "chalk": "^4.0.0", "create-jest": "^29.7.0", "exit": "^0.1.2", "import-local": "^3.0.2", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg=="],
|
||||
|
||||
"jest-config": ["jest-config@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-circus": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-runner": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "peerDependencies": { "@types/node": "*", "ts-node": ">=9.0.0" }, "optionalPeers": ["@types/node", "ts-node"] }, "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ=="],
|
||||
|
||||
"jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="],
|
||||
|
||||
"jest-docblock": ["jest-docblock@29.7.0", "", { "dependencies": { "detect-newline": "^3.0.0" } }, "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g=="],
|
||||
|
||||
"jest-each": ["jest-each@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "jest-util": "^29.7.0", "pretty-format": "^29.7.0" } }, "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ=="],
|
||||
|
||||
"jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="],
|
||||
|
||||
"jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="],
|
||||
|
||||
"jest-haste-map": ["jest-haste-map@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA=="],
|
||||
|
||||
"jest-leak-detector": ["jest-leak-detector@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw=="],
|
||||
|
||||
"jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="],
|
||||
|
||||
"jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="],
|
||||
|
||||
"jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="],
|
||||
|
||||
"jest-pnp-resolver": ["jest-pnp-resolver@1.2.3", "", { "peerDependencies": { "jest-resolve": "*" }, "optionalPeers": ["jest-resolve"] }, "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w=="],
|
||||
|
||||
"jest-regex-util": ["jest-regex-util@29.6.3", "", {}, "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg=="],
|
||||
|
||||
"jest-resolve": ["jest-resolve@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" } }, "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA=="],
|
||||
|
||||
"jest-resolve-dependencies": ["jest-resolve-dependencies@29.7.0", "", { "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" } }, "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA=="],
|
||||
|
||||
"jest-runner": ["jest-runner@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-leak-detector": "^29.7.0", "jest-message-util": "^29.7.0", "jest-resolve": "^29.7.0", "jest-runtime": "^29.7.0", "jest-util": "^29.7.0", "jest-watcher": "^29.7.0", "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ=="],
|
||||
|
||||
"jest-runtime": ["jest-runtime@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/globals": "^29.7.0", "@jest/source-map": "^29.6.3", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ=="],
|
||||
|
||||
"jest-snapshot": ["jest-snapshot@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", "@jest/expect-utils": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", "expect": "^29.7.0", "graceful-fs": "^4.2.9", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "natural-compare": "^1.4.0", "pretty-format": "^29.7.0", "semver": "^7.5.3" } }, "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw=="],
|
||||
|
||||
"jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="],
|
||||
|
||||
"jest-validate": ["jest-validate@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.7.0" } }, "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw=="],
|
||||
|
||||
"jest-watcher": ["jest-watcher@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", "jest-util": "^29.7.0", "string-length": "^4.0.1" } }, "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g=="],
|
||||
|
||||
"jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
||||
|
||||
"leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
|
||||
|
||||
"make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="],
|
||||
|
||||
"makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
|
||||
|
||||
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||
|
||||
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
||||
|
||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||
|
||||
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
|
||||
|
||||
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
|
||||
|
||||
"pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="],
|
||||
|
||||
"pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
|
||||
|
||||
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
|
||||
|
||||
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
|
||||
|
||||
"react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
|
||||
|
||||
"resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="],
|
||||
|
||||
"resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
|
||||
|
||||
"resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="],
|
||||
|
||||
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
|
||||
|
||||
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="],
|
||||
|
||||
"sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
|
||||
|
||||
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
|
||||
|
||||
"string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="],
|
||||
|
||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="],
|
||||
|
||||
"strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="],
|
||||
|
||||
"tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"ts-jest": ["ts-jest@29.4.6", "", { "dependencies": { "bs-logger": "^0.2.6", "fast-json-stable-stringify": "^2.1.0", "handlebars": "^4.7.8", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", "semver": "^7.7.3", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", "@jest/transform": "^29.0.0 || ^30.0.0", "@jest/types": "^29.0.0 || ^30.0.0", "babel-jest": "^29.0.0 || ^30.0.0", "jest": "^29.0.0 || ^30.0.0", "jest-util": "^29.0.0 || ^30.0.0", "typescript": ">=4.3 <6" }, "optionalPeers": ["@babel/core", "@jest/transform", "@jest/types", "babel-jest", "jest-util"], "bin": { "ts-jest": "cli.js" } }, "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA=="],
|
||||
|
||||
"type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="],
|
||||
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="],
|
||||
|
||||
"v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="],
|
||||
|
||||
"walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="],
|
||||
|
||||
"web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
|
||||
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
|
||||
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"zod": ["zod@4.0.0", "", {}, "sha512-9diLdTPc/L7w/5jI4C3gHYNiGHDV9IZYxo1e5LSD8cabi65WVTWWb+g2BGPEpUUCOxR4D+6O5B0AzyMdUAXwrw=="],
|
||||
|
||||
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@honcho-ai/core/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
|
||||
|
||||
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
|
||||
|
||||
"babel-plugin-istanbul/istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="],
|
||||
|
||||
"chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||
|
||||
"p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"@honcho-ai/core/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
|
||||
|
||||
"babel-plugin-istanbul/istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -775,12 +775,12 @@ export class Honcho {
|
|||
*/
|
||||
async scheduleDream(options: {
|
||||
observer: string | Peer
|
||||
session: string | Session
|
||||
session?: string | Session
|
||||
observed?: string | Peer
|
||||
}): Promise<void> {
|
||||
await this._ensureWorkspace()
|
||||
const observerId = resolveId(options.observer)
|
||||
const sessionId = resolveId(options.session)
|
||||
const sessionId = options.session ? resolveId(options.session) : undefined
|
||||
const observedId = options.observed
|
||||
? resolveId(options.observed)
|
||||
: observerId
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export interface ConclusionCreateParams {
|
|||
/** The conclusion content/text */
|
||||
content: string
|
||||
/** The session this conclusion relates to (ID string or Session object) */
|
||||
sessionId: string | Session
|
||||
sessionId?: string | Session
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -30,7 +30,7 @@ export class Conclusion {
|
|||
readonly content: string
|
||||
readonly observerId: string
|
||||
readonly observedId: string
|
||||
readonly sessionId: string
|
||||
readonly sessionId: string | null
|
||||
readonly createdAt: string
|
||||
|
||||
constructor(
|
||||
|
|
@ -38,7 +38,7 @@ export class Conclusion {
|
|||
content: string,
|
||||
observerId: string,
|
||||
observedId: string,
|
||||
sessionId: string,
|
||||
sessionId: string | null,
|
||||
createdAt: string
|
||||
) {
|
||||
this.id = id
|
||||
|
|
@ -128,7 +128,7 @@ export class ConclusionScope {
|
|||
private async _create(params: {
|
||||
conclusions: Array<{
|
||||
content: string
|
||||
session_id: string
|
||||
session_id: string | null
|
||||
observer_id: string
|
||||
observed_id: string
|
||||
}>
|
||||
|
|
@ -259,7 +259,11 @@ export class ConclusionScope {
|
|||
const requestConclusions = conclusionArray.map((obs) => ({
|
||||
content: obs.content,
|
||||
session_id:
|
||||
typeof obs.sessionId === 'string' ? obs.sessionId : obs.sessionId.id,
|
||||
obs.sessionId === undefined
|
||||
? null
|
||||
: typeof obs.sessionId === 'string'
|
||||
? obs.sessionId
|
||||
: obs.sessionId.id,
|
||||
observer_id: this.observer,
|
||||
observed_id: this.observed,
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ export class Session {
|
|||
private async _getContext(params: {
|
||||
tokens?: number
|
||||
summary?: boolean
|
||||
last_message?: string
|
||||
search_query?: string
|
||||
peer_target?: string
|
||||
peer_perspective?: string
|
||||
limit_to_session?: boolean
|
||||
|
|
@ -699,7 +699,7 @@ export class Session {
|
|||
summary?: boolean
|
||||
tokens?: number
|
||||
peerTarget?: string | Peer
|
||||
lastUserMessage?: string | Message
|
||||
searchQuery?: string | Message
|
||||
peerPerspective?: string | Peer
|
||||
limitToSession?: boolean
|
||||
representationOptions?: RepresentationOptions
|
||||
|
|
@ -713,30 +713,30 @@ export class Session {
|
|||
typeof opts.peerPerspective === 'object'
|
||||
? opts.peerPerspective.id
|
||||
: opts.peerPerspective
|
||||
const lastUserMessageText =
|
||||
typeof opts.lastUserMessage === 'string'
|
||||
? opts.lastUserMessage
|
||||
: opts.lastUserMessage?.content
|
||||
const searchQueryText =
|
||||
typeof opts.searchQuery === 'string'
|
||||
? opts.searchQuery
|
||||
: opts.searchQuery?.content
|
||||
|
||||
const contextParams = ContextParamsSchema.parse({
|
||||
summary: opts.summary,
|
||||
tokens: opts.tokens,
|
||||
peerTarget: peerTargetId,
|
||||
lastUserMessage: lastUserMessageText,
|
||||
searchQuery: searchQueryText,
|
||||
peerPerspective: peerPerspectiveId,
|
||||
limitToSession: opts.limitToSession,
|
||||
representationOptions: opts.representationOptions,
|
||||
})
|
||||
|
||||
const lastMessageText =
|
||||
typeof contextParams.lastUserMessage === 'string'
|
||||
? contextParams.lastUserMessage
|
||||
: contextParams.lastUserMessage?.content
|
||||
const searchQueryParsed =
|
||||
typeof contextParams.searchQuery === 'string'
|
||||
? contextParams.searchQuery
|
||||
: contextParams.searchQuery?.content
|
||||
|
||||
const context = await this._getContext({
|
||||
tokens: contextParams.tokens,
|
||||
summary: contextParams.summary,
|
||||
last_message: lastMessageText,
|
||||
search_query: searchQueryParsed,
|
||||
peer_target: contextParams.peerTarget,
|
||||
peer_perspective: contextParams.peerPerspective,
|
||||
limit_to_session: contextParams.limitToSession,
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ export interface SessionPeerConfigParams {
|
|||
export interface SessionContextParams {
|
||||
tokens?: number
|
||||
summary?: boolean
|
||||
last_message?: string
|
||||
search_query?: string
|
||||
peer_target?: string
|
||||
peer_perspective?: string
|
||||
limit_to_session?: boolean
|
||||
|
|
@ -243,7 +243,7 @@ export interface ConclusionResponse {
|
|||
content: string
|
||||
observer_id: string
|
||||
observed_id: string
|
||||
session_id: string
|
||||
session_id: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
|
|
@ -251,7 +251,7 @@ export interface ConclusionCreateParams {
|
|||
content: string
|
||||
observer_id: string
|
||||
observed_id: string
|
||||
session_id: string
|
||||
session_id: string | null
|
||||
}
|
||||
|
||||
export interface ConclusionBatchCreateParams {
|
||||
|
|
|
|||
|
|
@ -271,9 +271,9 @@ export const ContextParamsSchema = z
|
|||
.object({
|
||||
summary: z.boolean().optional(),
|
||||
tokens: z.int('Token limit must be an integer').optional(),
|
||||
lastUserMessage: z
|
||||
searchQuery: z
|
||||
.union([
|
||||
z.string().min(1, 'Last user message must be a non-empty string'),
|
||||
z.string().min(1, 'Search query must be a non-empty string'),
|
||||
MessageResponseSchema,
|
||||
])
|
||||
.optional(),
|
||||
|
|
@ -283,11 +283,11 @@ export const ContextParamsSchema = z
|
|||
representationOptions: RepresentationOptionsSchema.optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.lastUserMessage && !data.peerTarget) {
|
||||
if (data.searchQuery && !data.peerTarget) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'peerTarget is required when lastUserMessage is provided',
|
||||
path: ['lastUserMessage'],
|
||||
message: 'peerTarget is required when searchQuery is provided',
|
||||
path: ['searchQuery'],
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ from __future__ import annotations
|
|||
import asyncio
|
||||
import logging
|
||||
from typing import cast
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
|
||||
import redis.asyncio as aioredis
|
||||
import sentry_sdk
|
||||
from cashews import cache
|
||||
from cashews.picklers import PicklerType
|
||||
|
|
@ -126,9 +128,11 @@ async def is_deriver_flush_enabled() -> bool:
|
|||
if not is_cache_enabled():
|
||||
return False
|
||||
try:
|
||||
import redis.asyncio as aioredis
|
||||
# Strip query parameters - redis-py doesn't support custom params like ?suppress=true
|
||||
parsed = urlparse(settings.CACHE.URL)
|
||||
clean_url = urlunparse(parsed._replace(query=""))
|
||||
|
||||
redis_client = aioredis.from_url(settings.CACHE.URL) # pyright: ignore[reportUnknownMemberType]
|
||||
redis_client = aioredis.from_url(clean_url) # pyright: ignore[reportUnknownMemberType]
|
||||
try:
|
||||
result = await redis_client.get(DERIVER_FLUSH_KEY)
|
||||
return result == b"1"
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class TomlConfigSettingsSource(PydanticBaseSettingsSource):
|
|||
"WEBHOOK": "webhook",
|
||||
"DREAM": "dream",
|
||||
"VECTOR_STORE": "vector_store",
|
||||
"OTEL": "otel",
|
||||
"METRICS": "metrics",
|
||||
"TELEMETRY": "telemetry",
|
||||
"": "app", # For AppSettings with no prefix
|
||||
}
|
||||
|
|
@ -537,33 +537,10 @@ class WebhookSettings(HonchoSettings):
|
|||
MAX_WORKSPACE_LIMIT: int = 10
|
||||
|
||||
|
||||
class OpenTelemetrySettings(HonchoSettings):
|
||||
"""OpenTelemetry settings for push-based metrics via OTLP.
|
||||
|
||||
These settings configure the OTel SDK to push metrics via OTLP HTTP
|
||||
to any compatible backend (Mimir, Grafana Cloud, etc.).
|
||||
"""
|
||||
|
||||
model_config = SettingsConfigDict(env_prefix="OTEL_", extra="ignore") # pyright: ignore
|
||||
|
||||
# Master toggle for OTel metrics
|
||||
class MetricsSettings(HonchoSettings):
|
||||
model_config = SettingsConfigDict(env_prefix="METRICS_", extra="ignore") # pyright: ignore
|
||||
ENABLED: bool = False
|
||||
|
||||
# OTLP HTTP endpoint for metrics (e.g., "https://mimir.example.com/otlp/v1/metrics")
|
||||
# For Mimir, the endpoint is typically: <mimir-url>/otlp/v1/metrics
|
||||
# For Grafana Cloud: https://otlp-gateway-<region>.grafana.net/otlp/v1/metrics
|
||||
ENDPOINT: str | None = None
|
||||
|
||||
HEADERS: dict[str, str] | None = None
|
||||
|
||||
# Export interval in milliseconds (default: 60 seconds)
|
||||
EXPORT_INTERVAL_MILLIS: int = 60000
|
||||
|
||||
# Service name for resource attributes (identifies what this service is)
|
||||
SERVICE_NAME: str = "honcho"
|
||||
|
||||
# Service namespace for resource attributes (defaults to top-level NAMESPACE if not set)
|
||||
SERVICE_NAMESPACE: str | None = None
|
||||
NAMESPACE: str | None = None
|
||||
|
||||
|
||||
class TelemetrySettings(HonchoSettings):
|
||||
|
|
@ -768,7 +745,7 @@ class AppSettings(HonchoSettings):
|
|||
PEER_CARD: PeerCardSettings = Field(default_factory=PeerCardSettings)
|
||||
SUMMARY: SummarySettings = Field(default_factory=SummarySettings)
|
||||
WEBHOOK: WebhookSettings = Field(default_factory=WebhookSettings)
|
||||
OTEL: OpenTelemetrySettings = Field(default_factory=OpenTelemetrySettings)
|
||||
METRICS: MetricsSettings = Field(default_factory=MetricsSettings)
|
||||
TELEMETRY: TelemetrySettings = Field(default_factory=TelemetrySettings)
|
||||
CACHE: CacheSettings = Field(default_factory=CacheSettings)
|
||||
DREAM: DreamSettings = Field(default_factory=DreamSettings)
|
||||
|
|
@ -783,20 +760,15 @@ class AppSettings(HonchoSettings):
|
|||
|
||||
@model_validator(mode="after")
|
||||
def propagate_namespace(self) -> "AppSettings":
|
||||
"""Propagate top-level NAMESPACE to nested settings if not explicitly set.
|
||||
|
||||
After this validator runs, CACHE.NAMESPACE,
|
||||
VECTOR_STORE.NAMESPACE, TELEMETRY.NAMESPACE, and OTEL.SERVICE_NAMESPACE
|
||||
are guaranteed to exist. Explicitly provided nested namespaces are preserved.
|
||||
"""
|
||||
"""Propagate top-level NAMESPACE to nested settings if not explicitly set."""
|
||||
if "NAMESPACE" not in self.CACHE.model_fields_set:
|
||||
self.CACHE.NAMESPACE = self.NAMESPACE
|
||||
if "NAMESPACE" not in self.VECTOR_STORE.model_fields_set:
|
||||
self.VECTOR_STORE.NAMESPACE = self.NAMESPACE
|
||||
if "NAMESPACE" not in self.TELEMETRY.model_fields_set:
|
||||
self.TELEMETRY.NAMESPACE = self.NAMESPACE
|
||||
if "SERVICE_NAMESPACE" not in self.OTEL.model_fields_set:
|
||||
self.OTEL.SERVICE_NAMESPACE = self.NAMESPACE
|
||||
if "NAMESPACE" not in self.METRICS.model_fields_set:
|
||||
self.METRICS.NAMESPACE = self.NAMESPACE
|
||||
|
||||
return self
|
||||
|
||||
|
|
|
|||
|
|
@ -610,7 +610,8 @@ async def create_observations(
|
|||
collection_pairs: set[tuple[str, str]] = set()
|
||||
|
||||
for obs in observations:
|
||||
sessions_to_validate.add(obs.session_id)
|
||||
if obs.session_id is not None:
|
||||
sessions_to_validate.add(obs.session_id)
|
||||
peers_to_validate.add(obs.observer_id)
|
||||
peers_to_validate.add(obs.observed_id)
|
||||
collection_pairs.add((obs.observer_id, obs.observed_id))
|
||||
|
|
|
|||
|
|
@ -3,17 +3,20 @@ import logging
|
|||
import os
|
||||
|
||||
import uvloop
|
||||
from prometheus_client import start_http_server
|
||||
|
||||
from src.config import settings
|
||||
from src.telemetry import (
|
||||
initialize_telemetry,
|
||||
initialize_telemetry_async,
|
||||
shutdown_telemetry,
|
||||
)
|
||||
from src.telemetry import initialize_telemetry_async, shutdown_telemetry
|
||||
|
||||
from .queue_manager import main
|
||||
|
||||
|
||||
def start_metrics_server() -> None:
|
||||
"""Start the Prometheus metrics HTTP server on port 9090."""
|
||||
start_http_server(9090)
|
||||
print("[DERIVER] Prometheus metrics server started on port 9090")
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""
|
||||
Configure logging for the deriver process.
|
||||
|
|
@ -55,7 +58,7 @@ async def run_deriver():
|
|||
try:
|
||||
await main()
|
||||
finally:
|
||||
# Shutdown telemetry (flush CloudEvents buffer, shutdown OTel metrics)
|
||||
# Shutdown telemetry (flush CloudEvents buffer)
|
||||
await shutdown_telemetry()
|
||||
|
||||
|
||||
|
|
@ -67,8 +70,10 @@ if __name__ == "__main__":
|
|||
|
||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||
try:
|
||||
# Initialize sync telemetry (OTel metrics)
|
||||
initialize_telemetry()
|
||||
# Start Prometheus metrics server if enabled
|
||||
if settings.METRICS.ENABLED:
|
||||
start_metrics_server()
|
||||
|
||||
print("[DERIVER] Running main loop")
|
||||
asyncio.run(run_deriver())
|
||||
except KeyboardInterrupt:
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ async def process_representation_batch(
|
|||
*,
|
||||
observers: list[str] | None,
|
||||
observed: str | None,
|
||||
queue_items_count: int,
|
||||
queue_item_message_ids: list[int],
|
||||
) -> None:
|
||||
"""
|
||||
Prepares and processes a batch of messages for representation tasks.
|
||||
|
|
@ -168,6 +168,7 @@ async def process_representation_batch(
|
|||
message_level_configuration: Resolved configuration for this batch
|
||||
observers: List of observers for the messages
|
||||
observed: The observed of the messages
|
||||
queue_item_message_ids: Message IDs from queue items
|
||||
"""
|
||||
if not messages or not messages[0]:
|
||||
logger.debug("process_representation_batch received no messages")
|
||||
|
|
@ -181,7 +182,7 @@ async def process_representation_batch(
|
|||
message_level_configuration,
|
||||
observers=observers,
|
||||
observed=observed,
|
||||
queue_items_count=queue_items_count,
|
||||
queue_item_message_ids=queue_item_message_ids,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,16 +7,20 @@ from src.crud.representation import RepresentationManager
|
|||
from src.dependencies import tracked_db
|
||||
from src.models import Message
|
||||
from src.schemas import ResolvedConfiguration
|
||||
from src.telemetry import otel_metrics
|
||||
from src.telemetry import prometheus_metrics
|
||||
from src.telemetry.events import RepresentationCompletedEvent, emit
|
||||
from src.telemetry.logging import accumulate_metric, log_performance_metrics
|
||||
from src.telemetry.otel.metrics import DeriverComponents, DeriverTaskTypes, TokenTypes
|
||||
from src.telemetry.prometheus.metrics import (
|
||||
DeriverComponents,
|
||||
DeriverTaskTypes,
|
||||
TokenTypes,
|
||||
)
|
||||
from src.telemetry.sentry import with_sentry_transaction
|
||||
from src.utils.clients import honcho_llm_call
|
||||
from src.utils.config_helpers import get_configuration
|
||||
from src.utils.formatting import format_new_turn_with_timestamp
|
||||
from src.utils.representation import PromptRepresentation, Representation
|
||||
from src.utils.tokens import estimate_tokens, track_deriver_input_tokens
|
||||
from src.utils.tokens import track_deriver_input_tokens
|
||||
|
||||
from .prompts import estimate_minimal_deriver_prompt_tokens, minimal_deriver_prompt
|
||||
|
||||
|
|
@ -30,7 +34,7 @@ async def process_representation_tasks_batch(
|
|||
*,
|
||||
observers: list[str],
|
||||
observed: str,
|
||||
queue_items_count: int,
|
||||
queue_item_message_ids: list[int],
|
||||
) -> None:
|
||||
"""
|
||||
Process messages with minimal overhead - single LLM call, save to multiple collections.
|
||||
|
|
@ -40,7 +44,7 @@ async def process_representation_tasks_batch(
|
|||
message_level_configuration: Optional configuration override.
|
||||
observers: List of observer peer IDs (collections to save to).
|
||||
observed: The observed peer ID.
|
||||
queue_items_count: Number of QueueItem records being processed in this batch.
|
||||
queue_item_message_ids: Message IDs from queue items being processed
|
||||
"""
|
||||
if not messages:
|
||||
return
|
||||
|
|
@ -89,9 +93,12 @@ async def process_representation_tasks_batch(
|
|||
for msg in messages
|
||||
)
|
||||
|
||||
# Track token usage
|
||||
# Track token usage - count only tokens from messages being processed
|
||||
prompt_tokens = estimate_minimal_deriver_prompt_tokens()
|
||||
messages_tokens = estimate_tokens(formatted_messages)
|
||||
queue_item_message_ids_set = set(queue_item_message_ids)
|
||||
messages_tokens = sum(
|
||||
msg.token_count for msg in messages if msg.id in queue_item_message_ids_set
|
||||
)
|
||||
track_deriver_input_tokens(
|
||||
task_type=DeriverTaskTypes.INGESTION,
|
||||
components={
|
||||
|
|
@ -141,9 +148,9 @@ async def process_representation_tasks_batch(
|
|||
"ms",
|
||||
)
|
||||
|
||||
# OTel metrics (push-based)
|
||||
if settings.OTEL.ENABLED:
|
||||
otel_metrics.record_deriver_tokens(
|
||||
# Prometheus metrics
|
||||
if settings.METRICS.ENABLED:
|
||||
prometheus_metrics.record_deriver_tokens(
|
||||
count=response.output_tokens,
|
||||
task_type=DeriverTaskTypes.INGESTION.value,
|
||||
token_type=TokenTypes.OUTPUT.value,
|
||||
|
|
@ -231,7 +238,7 @@ async def process_representation_tasks_batch(
|
|||
workspace_name=latest_message.workspace_name,
|
||||
session_name=latest_message.session_name,
|
||||
observed=observed,
|
||||
queue_items_processed=queue_items_count,
|
||||
queue_items_processed=len(queue_item_message_ids),
|
||||
earliest_message_id=earliest_message.public_id,
|
||||
latest_message_id=latest_message.public_id,
|
||||
message_count=len(messages),
|
||||
|
|
@ -239,7 +246,7 @@ async def process_representation_tasks_batch(
|
|||
context_preparation_ms=context_prep_duration,
|
||||
llm_call_ms=llm_duration,
|
||||
total_duration_ms=overall_duration,
|
||||
input_tokens=response.input_tokens,
|
||||
input_tokens=messages_tokens,
|
||||
output_tokens=response.output_tokens,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -399,7 +399,7 @@ def create_dream_record(
|
|||
observer: str,
|
||||
observed: str,
|
||||
dream_type: schemas.DreamType,
|
||||
session_name: str,
|
||||
session_name: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Create a queue record for a dream task.
|
||||
|
|
@ -409,7 +409,7 @@ def create_dream_record(
|
|||
observer: Name of the observer peer
|
||||
observed: Name of the observed peer
|
||||
dream_type: Type of dream to execute
|
||||
session_name: Name of the session to scope the dream to
|
||||
session_name: Name of the session to scope the dream to if specified
|
||||
|
||||
Returns:
|
||||
Queue record dictionary with workspace_name and other fields
|
||||
|
|
@ -437,7 +437,7 @@ async def enqueue_dream(
|
|||
observed: str,
|
||||
dream_type: schemas.DreamType,
|
||||
document_count: int,
|
||||
session_name: str,
|
||||
session_name: str | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Enqueue a dream task for immediate processing by the deriver.
|
||||
|
|
@ -452,7 +452,7 @@ async def enqueue_dream(
|
|||
observed: Name of the observed peer
|
||||
dream_type: Type of dream to execute
|
||||
document_count: Current document count for metadata update
|
||||
session_name: Name of the session to scope the dream to
|
||||
session_name: Name of the session to scope the dream to if specified
|
||||
"""
|
||||
async with tracked_db("dream_enqueue") as db_session:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ from src.reconciler import (
|
|||
set_reconciler_scheduler,
|
||||
)
|
||||
from src.schemas import ResolvedConfiguration
|
||||
from src.telemetry import otel_metrics
|
||||
from src.telemetry import prometheus_metrics
|
||||
from src.telemetry.sentry import initialize_sentry
|
||||
from src.utils.work_unit import parse_work_unit_key
|
||||
from src.webhooks.events import (
|
||||
|
|
@ -450,12 +450,17 @@ class QueueManager:
|
|||
else:
|
||||
observers = []
|
||||
|
||||
queue_item_message_ids = [
|
||||
item.message_id
|
||||
for item in items_to_process
|
||||
if item.message_id is not None
|
||||
]
|
||||
await process_representation_batch(
|
||||
messages_context,
|
||||
message_level_configuration,
|
||||
observers=observers,
|
||||
observed=work_unit.observed,
|
||||
queue_items_count=len(items_to_process),
|
||||
queue_item_message_ids=queue_item_message_ids,
|
||||
)
|
||||
await self.mark_queue_items_as_processed(
|
||||
items_to_process, work_unit_key
|
||||
|
|
@ -791,9 +796,9 @@ class QueueManager:
|
|||
if (
|
||||
work_unit.task_type in ["representation", "summary"]
|
||||
and work_unit.workspace_name is not None
|
||||
and settings.OTEL.ENABLED
|
||||
and settings.METRICS.ENABLED
|
||||
):
|
||||
otel_metrics.record_deriver_queue_item(
|
||||
prometheus_metrics.record_deriver_queue_item(
|
||||
count=len(items),
|
||||
workspace_name=work_unit.workspace_name,
|
||||
task_type=work_unit.task_type,
|
||||
|
|
|
|||
|
|
@ -17,14 +17,14 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
from src import crud
|
||||
from src.config import ReasoningLevel, settings
|
||||
from src.dialectic import prompts
|
||||
from src.telemetry import otel_metrics
|
||||
from src.telemetry import prometheus_metrics
|
||||
from src.telemetry.events import DialecticCompletedEvent, DialecticPhaseMetrics, emit
|
||||
from src.telemetry.logging import (
|
||||
accumulate_metric,
|
||||
log_performance_metrics,
|
||||
log_token_usage_metrics,
|
||||
)
|
||||
from src.telemetry.otel.metrics import DialecticComponents, TokenTypes
|
||||
from src.telemetry.prometheus.metrics import DialecticComponents, TokenTypes
|
||||
from src.utils.agent_tools import (
|
||||
DIALECTIC_TOOLS,
|
||||
DIALECTIC_TOOLS_MINIMAL,
|
||||
|
|
@ -338,15 +338,15 @@ class DialecticAgent:
|
|||
if not self.metric_key and run_id is not None:
|
||||
log_performance_metrics("dialectic_chat", run_id)
|
||||
|
||||
# OTel metrics (push-based)
|
||||
if settings.OTEL.ENABLED:
|
||||
otel_metrics.record_dialectic_tokens(
|
||||
# Prometheus metrics
|
||||
if settings.METRICS.ENABLED:
|
||||
prometheus_metrics.record_dialectic_tokens(
|
||||
count=input_tokens,
|
||||
token_type=TokenTypes.INPUT.value,
|
||||
component=DialecticComponents.TOTAL.value,
|
||||
reasoning_level=self.reasoning_level,
|
||||
)
|
||||
otel_metrics.record_dialectic_tokens(
|
||||
prometheus_metrics.record_dialectic_tokens(
|
||||
count=output_tokens,
|
||||
token_type=TokenTypes.OUTPUT.value,
|
||||
component=DialecticComponents.TOTAL.value,
|
||||
|
|
@ -615,21 +615,6 @@ class DialecticAgent:
|
|||
if not self.metric_key and run_id is not None:
|
||||
log_performance_metrics("dialectic_chat", run_id)
|
||||
|
||||
# OTel metrics (push-based)
|
||||
if settings.OTEL.ENABLED:
|
||||
otel_metrics.record_dialectic_tokens(
|
||||
count=total_input_tokens,
|
||||
token_type=TokenTypes.INPUT.value,
|
||||
component=DialecticComponents.TOTAL.value,
|
||||
reasoning_level=self.reasoning_level,
|
||||
)
|
||||
otel_metrics.record_dialectic_tokens(
|
||||
count=total_output_tokens,
|
||||
token_type=TokenTypes.OUTPUT.value,
|
||||
component=DialecticComponents.TOTAL.value,
|
||||
reasoning_level=self.reasoning_level,
|
||||
)
|
||||
|
||||
# Get model/provider info for phase metrics
|
||||
level_settings = settings.DIALECTIC.LEVELS[self.reasoning_level]
|
||||
synthesis_settings = level_settings.SYNTHESIS
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ async def run_dream(
|
|||
workspace_name: str,
|
||||
observer: str,
|
||||
observed: str,
|
||||
session_name: str,
|
||||
session_name: str | None = None,
|
||||
) -> DreamResult | None:
|
||||
"""
|
||||
Run a full dream cycle with optional surprisal-based sampling.
|
||||
|
|
@ -101,7 +101,7 @@ async def run_dream(
|
|||
workspace_name: Workspace identifier
|
||||
observer: Observer peer name
|
||||
observed: Observed peer name
|
||||
session_name: Session identifier
|
||||
session_name: Session identifier if specified
|
||||
"""
|
||||
if not settings.DREAM.ENABLED:
|
||||
return None
|
||||
|
|
@ -114,9 +114,13 @@ async def run_dream(
|
|||
f"[{run_id}] Starting dream cycle for {workspace_name}/{observer}/{observed}"
|
||||
)
|
||||
|
||||
session = await crud.get_session(
|
||||
db, workspace_name=workspace_name, session_name=session_name
|
||||
)
|
||||
if session_name is not None:
|
||||
session = await crud.get_session(
|
||||
db, workspace_name=workspace_name, session_name=session_name
|
||||
)
|
||||
else:
|
||||
session = None
|
||||
|
||||
workspace = await crud.get_workspace(db, workspace_name=workspace_name)
|
||||
|
||||
configuration = get_configuration(None, session, workspace)
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
|
||||
from src.config import settings
|
||||
from src.schemas import ResolvedConfiguration
|
||||
from src.telemetry import otel_metrics
|
||||
from src.telemetry import prometheus_metrics
|
||||
from src.telemetry.events import DreamSpecialistEvent, emit
|
||||
from src.telemetry.logging import accumulate_metric, log_performance_metrics
|
||||
from src.telemetry.otel.metrics import TokenTypes
|
||||
from src.telemetry.prometheus.metrics import TokenTypes
|
||||
from src.utils.agent_tools import (
|
||||
DEDUCTION_SPECIALIST_TOOLS,
|
||||
INDUCTION_SPECIALIST_TOOLS,
|
||||
|
|
@ -96,7 +96,7 @@ class BaseSpecialist(ABC):
|
|||
workspace_name: str,
|
||||
observer: str,
|
||||
observed: str,
|
||||
session_name: str,
|
||||
session_name: str | None,
|
||||
probing_questions: list[str],
|
||||
configuration: ResolvedConfiguration | None = None,
|
||||
parent_run_id: str | None = None,
|
||||
|
|
@ -186,14 +186,14 @@ class BaseSpecialist(ABC):
|
|||
accumulate_metric(task_name, "input_tokens", response.input_tokens, "count")
|
||||
accumulate_metric(task_name, "output_tokens", response.output_tokens, "count")
|
||||
|
||||
# OTel metrics (push-based)
|
||||
if settings.OTEL.ENABLED:
|
||||
otel_metrics.record_dreamer_tokens(
|
||||
# Prometheus metrics
|
||||
if settings.METRICS.ENABLED:
|
||||
prometheus_metrics.record_dreamer_tokens(
|
||||
count=response.input_tokens,
|
||||
specialist_name=self.name,
|
||||
token_type=TokenTypes.INPUT.value,
|
||||
)
|
||||
otel_metrics.record_dreamer_tokens(
|
||||
prometheus_metrics.record_dreamer_tokens(
|
||||
count=response.output_tokens,
|
||||
specialist_name=self.name,
|
||||
token_type=TokenTypes.OUTPUT.value,
|
||||
|
|
|
|||
|
|
@ -4,19 +4,22 @@ Graph-theoretic surprisal using k-NN graph and random walk.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
from sklearn.neighbors import NearestNeighbors
|
||||
|
||||
from .base import SurprisalTree
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sklearn.neighbors import NearestNeighbors
|
||||
|
||||
|
||||
def _knn_indices(
|
||||
points: NDArray[np.floating[Any]], n_neighbors: int
|
||||
) -> NDArray[np.intp]:
|
||||
"""Get k-nearest neighbor indices for each point."""
|
||||
from sklearn.neighbors import NearestNeighbors
|
||||
|
||||
knn: NearestNeighbors = NearestNeighbors(n_neighbors=n_neighbors, algorithm="auto")
|
||||
knn.fit(points) # pyright: ignore[reportUnknownMemberType]
|
||||
|
|
@ -26,6 +29,7 @@ def _knn_indices(
|
|||
|
||||
def _nearest_index(points: NDArray[np.floating[Any]], query: np.ndarray) -> int:
|
||||
"""Find index of nearest point to query."""
|
||||
from sklearn.neighbors import NearestNeighbors
|
||||
|
||||
knn: NearestNeighbors = NearestNeighbors(n_neighbors=1)
|
||||
knn.fit(points) # pyright: ignore[reportUnknownMemberType]
|
||||
|
|
|
|||
|
|
@ -2,17 +2,19 @@
|
|||
Wrapper for sklearn's KDTree and BallTree.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import numpy as np
|
||||
from numpy.typing import NDArray
|
||||
from sklearn.neighbors import (
|
||||
BallTree, # pyright: ignore[reportUnknownVariableType]
|
||||
KDTree, # pyright: ignore[reportUnknownVariableType]
|
||||
)
|
||||
|
||||
from .base import SurprisalTree
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sklearn.neighbors import (
|
||||
BallTree, # pyright: ignore[reportUnknownVariableType]
|
||||
KDTree, # pyright: ignore[reportUnknownVariableType]
|
||||
)
|
||||
|
||||
|
||||
class SklearnTreeWrapper(SurprisalTree):
|
||||
"""
|
||||
|
|
@ -23,7 +25,7 @@ class SklearnTreeWrapper(SurprisalTree):
|
|||
tree_type: str
|
||||
k: int
|
||||
points: list[NDArray[np.floating[Any]]]
|
||||
tree: KDTree | BallTree | None
|
||||
tree: "KDTree | BallTree | None"
|
||||
total_points: int
|
||||
|
||||
def __init__(
|
||||
|
|
@ -50,6 +52,11 @@ class SklearnTreeWrapper(SurprisalTree):
|
|||
if len(self.points) == 0:
|
||||
return
|
||||
|
||||
from sklearn.neighbors import (
|
||||
BallTree, # pyright: ignore[reportUnknownVariableType]
|
||||
KDTree, # pyright: ignore[reportUnknownVariableType]
|
||||
)
|
||||
|
||||
points_array: NDArray[np.floating[Any]] = np.array(self.points)
|
||||
if self.tree_type == "kd":
|
||||
self.tree = KDTree(points_array)
|
||||
|
|
|
|||
18
src/main.py
18
src/main.py
|
|
@ -30,9 +30,9 @@ from src.routers import (
|
|||
)
|
||||
from src.security import create_admin_jwt
|
||||
from src.telemetry import (
|
||||
initialize_telemetry,
|
||||
initialize_telemetry_async,
|
||||
otel_metrics,
|
||||
metrics_endpoint,
|
||||
prometheus_metrics,
|
||||
shutdown_telemetry,
|
||||
)
|
||||
from src.telemetry.logging import get_route_template
|
||||
|
|
@ -120,8 +120,7 @@ if SENTRY_ENABLED:
|
|||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_: FastAPI):
|
||||
# Initialize telemetry (OTel metrics + CloudEvents emitter)
|
||||
initialize_telemetry()
|
||||
# Initialize CloudEvents telemetry
|
||||
await initialize_telemetry_async()
|
||||
|
||||
try:
|
||||
|
|
@ -140,7 +139,7 @@ async def lifespan(_: FastAPI):
|
|||
await close_external_vector_store()
|
||||
await close_cache()
|
||||
await engine.dispose()
|
||||
# Shutdown telemetry (flush CloudEvents buffer, shutdown OTel metrics)
|
||||
# Shutdown telemetry (flush CloudEvents buffer)
|
||||
await shutdown_telemetry()
|
||||
|
||||
|
||||
|
|
@ -153,7 +152,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="3.0.0",
|
||||
version="3.0.1",
|
||||
contact={
|
||||
"name": "Plastic Labs",
|
||||
"url": "https://honcho.dev",
|
||||
|
|
@ -191,6 +190,9 @@ app.include_router(conclusions.router, prefix="/v3")
|
|||
app.include_router(keys.router, prefix="/v3")
|
||||
app.include_router(webhooks.router, prefix="/v3")
|
||||
|
||||
# Prometheus metrics endpoint
|
||||
app.add_route("/metrics", metrics_endpoint, methods=["GET"])
|
||||
|
||||
|
||||
# Global exception handlers
|
||||
@app.exception_handler(HonchoException)
|
||||
|
|
@ -233,9 +235,9 @@ async def track_request(
|
|||
response = await call_next(request)
|
||||
|
||||
# Track metrics if enabled
|
||||
if settings.OTEL.ENABLED:
|
||||
if settings.METRICS.ENABLED:
|
||||
template = get_route_template(request)
|
||||
otel_metrics.record_api_request(
|
||||
prometheus_metrics.record_api_request(
|
||||
method=request.method,
|
||||
endpoint=template,
|
||||
status_code=str(response.status_code),
|
||||
|
|
|
|||
|
|
@ -399,7 +399,7 @@ class Document(Base):
|
|||
workspace_name: Mapped[str] = mapped_column(
|
||||
ForeignKey("workspaces.name"), nullable=False, index=True
|
||||
)
|
||||
session_name: Mapped[str] = mapped_column(TEXT, index=True)
|
||||
session_name: Mapped[str | None] = mapped_column(TEXT, nullable=True, index=True)
|
||||
deleted_at: Mapped[datetime.datetime | None] = mapped_column(
|
||||
DateTime(timezone=True), nullable=True, index=True, default=None
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ from src.dependencies import db
|
|||
from src.deriver import enqueue
|
||||
from src.exceptions import FileTooLargeError, ResourceNotFoundException
|
||||
from src.security import require_auth
|
||||
from src.telemetry import otel_metrics
|
||||
from src.telemetry import prometheus_metrics
|
||||
from src.utils.files import process_file_uploads_for_messages
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -100,9 +100,9 @@ async def create_messages_for_session(
|
|||
session_name=session_id,
|
||||
)
|
||||
|
||||
# OTel metrics (push-based)
|
||||
if settings.OTEL.ENABLED:
|
||||
otel_metrics.record_messages_created(
|
||||
# Prometheus metrics
|
||||
if settings.METRICS.ENABLED:
|
||||
prometheus_metrics.record_messages_created(
|
||||
count=len(created_messages),
|
||||
workspace_name=workspace_id,
|
||||
)
|
||||
|
|
@ -199,9 +199,9 @@ async def create_messages_with_file(
|
|||
len(created_messages),
|
||||
)
|
||||
|
||||
# OTel metrics (push-based)
|
||||
if settings.OTEL.ENABLED:
|
||||
otel_metrics.record_messages_created(
|
||||
# Prometheus metrics
|
||||
if settings.METRICS.ENABLED:
|
||||
prometheus_metrics.record_messages_created(
|
||||
count=len(created_messages),
|
||||
workspace_name=workspace_id,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from src.dependencies import db, tracked_db
|
|||
from src.dialectic.chat import agentic_chat, agentic_chat_stream
|
||||
from src.exceptions import AuthenticationException, ResourceNotFoundException
|
||||
from src.security import JWTParams, require_auth
|
||||
from src.telemetry import otel_metrics
|
||||
from src.telemetry import prometheus_metrics
|
||||
from src.utils.search import search
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -184,9 +184,9 @@ async def chat(
|
|||
yield f"data: {json.dumps({'delta': {'content': chunk}, 'done': False})}\n\n"
|
||||
yield f"data: {json.dumps({'done': True})}\n\n"
|
||||
|
||||
# OTel metrics (push-based)
|
||||
if settings.OTEL.ENABLED:
|
||||
otel_metrics.record_dialectic_call(
|
||||
# Prometheus metrics
|
||||
if settings.METRICS.ENABLED:
|
||||
prometheus_metrics.record_dialectic_call(
|
||||
workspace_name=workspace_id,
|
||||
reasoning_level=options.reasoning_level,
|
||||
)
|
||||
|
|
@ -216,9 +216,9 @@ async def chat(
|
|||
reasoning_level=options.reasoning_level,
|
||||
)
|
||||
|
||||
# OTel metrics (push-based)
|
||||
if settings.OTEL.ENABLED:
|
||||
otel_metrics.record_dialectic_call(
|
||||
# Prometheus metrics
|
||||
if settings.METRICS.ENABLED:
|
||||
prometheus_metrics.record_dialectic_call(
|
||||
workspace_name=workspace_id,
|
||||
reasoning_level=options.reasoning_level,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -508,9 +508,9 @@ async def get_session_context(
|
|||
description=f"Number of tokens to use for the context. Includes summary if set to true. Includes representation and peer card if they are included in the response. If not provided, the context will be exhaustive (within {config.settings.GET_CONTEXT_MAX_TOKENS} tokens)",
|
||||
),
|
||||
*,
|
||||
last_message: str | None = Query(
|
||||
search_query: str | None = Query(
|
||||
None,
|
||||
description="The most recent message, used to fetch semantically relevant conclusions",
|
||||
description="A query string used to fetch semantically relevant conclusions",
|
||||
),
|
||||
include_summary: bool = Query(
|
||||
default=True,
|
||||
|
|
@ -527,29 +527,29 @@ async def get_session_context(
|
|||
),
|
||||
limit_to_session: bool = Query(
|
||||
default=False,
|
||||
description="Only used if `last_message` is provided. Whether to limit the representation to the session (as opposed to everything known about the target peer)",
|
||||
description="Only used if `search_query` is provided. Whether to limit the representation to the session (as opposed to everything known about the target peer)",
|
||||
),
|
||||
search_top_k: int | None = Query(
|
||||
None,
|
||||
ge=1,
|
||||
le=100,
|
||||
description="Only used if `last_message` is provided. The number of semantic-search-retrieved conclusions to include in the representation",
|
||||
description="Only used if `search_query` is provided. The number of semantic-search-retrieved conclusions to include in the representation",
|
||||
),
|
||||
search_max_distance: float | None = Query(
|
||||
None,
|
||||
ge=0.0,
|
||||
le=1.0,
|
||||
description="Only used if `last_message` is provided. The maximum distance to search for semantically relevant conclusions",
|
||||
description="Only used if `search_query` is provided. The maximum distance to search for semantically relevant conclusions",
|
||||
),
|
||||
include_most_frequent: bool = Query(
|
||||
default=False,
|
||||
description="Only used if `last_message` is provided. Whether to include the most frequent conclusions in the representation",
|
||||
description="Only used if `search_query` is provided. Whether to include the most frequent conclusions in the representation",
|
||||
),
|
||||
max_conclusions: int | None = Query(
|
||||
None,
|
||||
ge=1,
|
||||
le=100,
|
||||
description="Only used if `last_message` is provided. The maximum number of conclusions to include in the representation",
|
||||
description="Only used if `search_query` is provided. The maximum number of conclusions to include in the representation",
|
||||
),
|
||||
):
|
||||
"""
|
||||
|
|
@ -585,7 +585,7 @@ async def get_session_context(
|
|||
# with tracked_db creating separate database sessions
|
||||
representation = await _get_working_representation_task(
|
||||
workspace_id,
|
||||
last_message,
|
||||
search_query,
|
||||
observer=observer,
|
||||
observed=observed,
|
||||
session_name=session_id if limit_to_session else None,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import datetime
|
||||
import ipaddress
|
||||
from enum import Enum
|
||||
from typing import Annotated, Any, Self
|
||||
from typing import Annotated, Any, Self, cast
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import tiktoken
|
||||
|
|
@ -173,6 +173,20 @@ class ResolvedConfiguration(BaseModel):
|
|||
summary: ResolvedSummaryConfiguration
|
||||
dream: ResolvedDreamConfiguration
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def migrate_deriver_to_reasoning(cls, data: Any) -> Any:
|
||||
"""Handle v3.0.0 migration: 'deriver' was renamed to 'reasoning'."""
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
|
||||
config = cast(dict[str, Any], data)
|
||||
|
||||
if "deriver" in config and "reasoning" not in config:
|
||||
config["reasoning"] = config.pop("deriver")
|
||||
|
||||
return config
|
||||
|
||||
|
||||
class PeerConfig(BaseModel):
|
||||
# TODO: Update description - should say "Whether honcho forms a representation of the peer itself"
|
||||
|
|
@ -526,8 +540,9 @@ class DocumentMetadata(BaseModel):
|
|||
|
||||
class DocumentCreate(DocumentBase):
|
||||
content: Annotated[str, Field(min_length=1, max_length=100000)]
|
||||
session_name: str = Field(
|
||||
description="The session from which the document was derived"
|
||||
session_name: str | None = Field(
|
||||
default=None,
|
||||
description="The session from which the document was derived (NULL for global observations)",
|
||||
)
|
||||
level: DocumentLevel = Field(
|
||||
default="explicit",
|
||||
|
|
@ -566,7 +581,7 @@ class Conclusion(BaseModel):
|
|||
description="The peer the conclusion is about",
|
||||
serialization_alias="observed_id",
|
||||
)
|
||||
session_name: str = Field(serialization_alias="session_id")
|
||||
session_name: str | None = Field(default=None, serialization_alias="session_id")
|
||||
created_at: datetime.datetime
|
||||
|
||||
model_config = ConfigDict( # pyright: ignore
|
||||
|
|
@ -603,7 +618,10 @@ class ConclusionCreate(BaseModel):
|
|||
content: Annotated[str, Field(min_length=1, max_length=65535)]
|
||||
observer_id: str = Field(..., description="The peer making the conclusion")
|
||||
observed_id: str = Field(..., description="The peer the conclusion is about")
|
||||
session_id: str = Field(..., description="The session this conclusion relates to")
|
||||
session_id: str | None = Field(
|
||||
default=None,
|
||||
description="A session ID to store the conclusion in, if specified",
|
||||
)
|
||||
|
||||
_token_count: int = PrivateAttr(default=0)
|
||||
|
||||
|
|
@ -767,7 +785,9 @@ class ScheduleDreamRequest(BaseModel):
|
|||
None, description="Observed peer name (defaults to observer if not specified)"
|
||||
)
|
||||
dream_type: DreamType = Field(..., description="Type of dream to schedule")
|
||||
session_id: str = Field(..., description="Session ID to scope the dream to")
|
||||
session_id: str | None = Field(
|
||||
None, description="Session ID to scope the dream to if specified"
|
||||
)
|
||||
|
||||
|
||||
# Webhook endpoint schemas
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Telemetry module for Honcho.
|
|||
|
||||
This module consolidates all telemetry, metrics, and observability functionality:
|
||||
- Sentry: Error tracking and performance tracing
|
||||
- OTel: Push-based metrics via OTLP to any compatible backend (e.g., Mimir)
|
||||
- Prometheus: Pull-based metrics scraped by Fly.io
|
||||
- CloudEvents: Structured events for analytics (push-based)
|
||||
- Logging: Langfuse integration, Rich console output, metric accumulation
|
||||
- Tracing: Sentry transaction decorators
|
||||
|
|
@ -12,54 +12,27 @@ This module consolidates all telemetry, metrics, and observability functionality
|
|||
"""
|
||||
|
||||
from src.telemetry.events import emit
|
||||
from src.telemetry.otel import get_meter, initialize_otel_metrics, shutdown_otel_metrics
|
||||
from src.telemetry.otel.metrics import otel_metrics
|
||||
from src.telemetry.prometheus import metrics_endpoint, prometheus_metrics
|
||||
|
||||
__all__ = [
|
||||
"emit",
|
||||
"get_meter",
|
||||
"initialize_otel_metrics",
|
||||
"initialize_telemetry",
|
||||
"initialize_telemetry_async",
|
||||
"otel_metrics",
|
||||
"shutdown_otel_metrics",
|
||||
"metrics_endpoint",
|
||||
"prometheus_metrics",
|
||||
"shutdown_telemetry",
|
||||
]
|
||||
|
||||
|
||||
def initialize_telemetry() -> None:
|
||||
"""
|
||||
Initialize all telemetry systems based on configuration.
|
||||
|
||||
This should be called once at application startup (in main.py lifespan).
|
||||
It reads configuration from settings and initializes:
|
||||
- OTel metrics (if OTEL_ENABLED=true)
|
||||
|
||||
Note: CloudEvents telemetry requires async initialization and should be
|
||||
initialized separately using initialize_telemetry_async().
|
||||
|
||||
Sentry is initialized separately in sentry.py as it has its own lifecycle.
|
||||
"""
|
||||
from src.config import settings
|
||||
|
||||
if settings.OTEL.ENABLED:
|
||||
initialize_otel_metrics(
|
||||
endpoint=settings.OTEL.ENDPOINT,
|
||||
headers=settings.OTEL.HEADERS,
|
||||
export_interval_millis=settings.OTEL.EXPORT_INTERVAL_MILLIS,
|
||||
service_name=settings.OTEL.SERVICE_NAME,
|
||||
service_namespace=settings.OTEL.SERVICE_NAMESPACE,
|
||||
enabled=True,
|
||||
)
|
||||
|
||||
|
||||
async def initialize_telemetry_async() -> None:
|
||||
"""
|
||||
Initialize async telemetry systems based on configuration.
|
||||
|
||||
This should be called once at application startup (in main.py lifespan),
|
||||
after initialize_telemetry(). It initializes:
|
||||
This should be called once at application startup (in main.py lifespan).
|
||||
It initializes:
|
||||
- CloudEvents emitter (if TELEMETRY_ENABLED=true)
|
||||
|
||||
Note: Prometheus metrics are pull-based and require no initialization.
|
||||
Sentry is initialized separately in sentry.py as it has its own lifecycle.
|
||||
"""
|
||||
from src.config import settings
|
||||
from src.telemetry.events import initialize_telemetry_events
|
||||
|
|
@ -73,13 +46,9 @@ async def shutdown_telemetry() -> None:
|
|||
Shutdown all telemetry systems gracefully.
|
||||
|
||||
This should be called during application shutdown to ensure:
|
||||
- OTel metrics are flushed
|
||||
- CloudEvents buffer is flushed
|
||||
"""
|
||||
from src.telemetry.events import shutdown_telemetry_events
|
||||
|
||||
# Shutdown CloudEvents emitter (flushes buffer)
|
||||
await shutdown_telemetry_events()
|
||||
|
||||
# Shutdown OTel metrics
|
||||
shutdown_otel_metrics()
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class DreamRunEvent(BaseEvent):
|
|||
workspace_name: str = Field(..., description="Workspace name")
|
||||
|
||||
# Session context
|
||||
session_name: str = Field(..., description="Most recent session name")
|
||||
session_name: str | None = Field(None, description="Session name if specified")
|
||||
|
||||
# Peer context
|
||||
observer: str = Field(..., description="Observer peer name")
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
"""
|
||||
OpenTelemetry metrics module for Honcho.
|
||||
|
||||
This module provides push-based metrics using OpenTelemetry SDK
|
||||
with OTLP HTTP export to any compatible backend (Mimir, Grafana Cloud, etc.).
|
||||
"""
|
||||
|
||||
from src.telemetry.otel.metrics import (
|
||||
DeriverComponents,
|
||||
DeriverTaskTypes,
|
||||
DialecticComponents,
|
||||
TokenTypes,
|
||||
get_meter,
|
||||
initialize_otel_metrics,
|
||||
shutdown_otel_metrics,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"DeriverComponents",
|
||||
"DeriverTaskTypes",
|
||||
"DialecticComponents",
|
||||
"TokenTypes",
|
||||
"get_meter",
|
||||
"initialize_otel_metrics",
|
||||
"shutdown_otel_metrics",
|
||||
]
|
||||
|
|
@ -1,481 +0,0 @@
|
|||
"""
|
||||
OpenTelemetry metrics implementation with OTLP export.
|
||||
|
||||
This module provides push-based metrics that replace the pull-based Prometheus
|
||||
/metrics endpoint. Metrics are pushed via OTLP to any compatible backend
|
||||
(Mimir, Grafana Cloud, etc.) at configurable intervals.
|
||||
|
||||
Usage:
|
||||
from src.telemetry.otel import get_meter, initialize_otel_metrics
|
||||
|
||||
# Initialize once at startup
|
||||
initialize_otel_metrics()
|
||||
|
||||
# Get a meter for your component
|
||||
meter = get_meter("honcho.deriver")
|
||||
|
||||
# Create instruments
|
||||
counter = meter.create_counter("tokens_processed", unit="tokens")
|
||||
counter.add(100, {"task_type": "ingestion"})
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
import logging
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, final
|
||||
|
||||
from opentelemetry import metrics
|
||||
from opentelemetry.sdk.metrics import MeterProvider
|
||||
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
|
||||
from opentelemetry.sdk.resources import Resource
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from opentelemetry.metrics import Counter, Meter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# =============================================================================
|
||||
# Metric label enums
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TokenTypes(Enum):
|
||||
INPUT = "input"
|
||||
OUTPUT = "output"
|
||||
|
||||
|
||||
class DeriverTaskTypes(Enum):
|
||||
INGESTION = "ingestion"
|
||||
SUMMARY = "summary"
|
||||
|
||||
|
||||
class DeriverComponents(Enum):
|
||||
PROMPT = "prompt" # used in ingestion and summary
|
||||
MESSAGES = "messages" # used in ingestion and summary
|
||||
PREVIOUS_SUMMARY = "previous_summary" # only used for summary
|
||||
OUTPUT_TOTAL = "output_total"
|
||||
|
||||
|
||||
class DialecticComponents(Enum):
|
||||
TOTAL = "total"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# OTel metrics infrastructure
|
||||
# =============================================================================
|
||||
|
||||
# Global state
|
||||
_meter_provider: MeterProvider | None = None
|
||||
_initialized: bool = False
|
||||
|
||||
|
||||
def initialize_otel_metrics(
|
||||
*,
|
||||
endpoint: str | None = None,
|
||||
headers: dict[str, str] | None = None,
|
||||
export_interval_millis: int = 60000,
|
||||
service_name: str = "honcho",
|
||||
service_namespace: str | None = None,
|
||||
enabled: bool = True,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize OpenTelemetry metrics with OTLP export.
|
||||
|
||||
This should be called once at application startup. If already initialized,
|
||||
subsequent calls are no-ops.
|
||||
|
||||
Args:
|
||||
endpoint: OTLP HTTP endpoint URL (e.g., "https://mimir.example.com/otlp/v1/metrics").
|
||||
If None, metrics are collected but not exported (useful for testing).
|
||||
headers: Optional headers to include in requests (e.g., {"X-Scope-OrgID": "tenant"}).
|
||||
export_interval_millis: How often to export metrics (default: 60 seconds).
|
||||
service_name: Service name for resource attributes (default: "honcho").
|
||||
service_namespace: Optional namespace for the service.
|
||||
enabled: If False, metrics are no-ops (default: True).
|
||||
"""
|
||||
global _meter_provider, _initialized
|
||||
|
||||
if _initialized:
|
||||
logger.debug("OTel metrics already initialized, skipping")
|
||||
return
|
||||
|
||||
if not enabled:
|
||||
logger.info("OTel metrics disabled")
|
||||
_initialized = True
|
||||
return
|
||||
|
||||
# Build resource attributes
|
||||
resource_attributes = {
|
||||
"service.name": service_name,
|
||||
}
|
||||
if service_namespace:
|
||||
resource_attributes["service.namespace"] = service_namespace
|
||||
|
||||
resource = Resource.create(resource_attributes)
|
||||
|
||||
# Create metric reader
|
||||
readers: list[PeriodicExportingMetricReader] = []
|
||||
|
||||
if endpoint:
|
||||
try:
|
||||
from opentelemetry.exporter.otlp.proto.http.metric_exporter import (
|
||||
OTLPMetricExporter,
|
||||
)
|
||||
|
||||
exporter = OTLPMetricExporter(
|
||||
endpoint=endpoint,
|
||||
headers=headers or {},
|
||||
)
|
||||
reader = PeriodicExportingMetricReader(
|
||||
exporter,
|
||||
export_interval_millis=export_interval_millis,
|
||||
)
|
||||
readers.append(reader)
|
||||
logger.info(f"OTel metrics configured to push via OTLP to {endpoint}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to configure OTLP metrics exporter: {e}")
|
||||
# Continue without exporter - metrics still work locally
|
||||
else:
|
||||
logger.info(
|
||||
"OTel metrics initialized without remote export (no endpoint configured)"
|
||||
)
|
||||
|
||||
# Create and set the meter provider
|
||||
# Note: empty list is valid for metric_readers (metrics still work, just not exported)
|
||||
_meter_provider = MeterProvider(
|
||||
resource=resource,
|
||||
metric_readers=readers,
|
||||
)
|
||||
metrics.set_meter_provider(_meter_provider)
|
||||
|
||||
# Register shutdown handler
|
||||
atexit.register(shutdown_otel_metrics)
|
||||
|
||||
_initialized = True
|
||||
logger.info("OTel metrics initialized successfully")
|
||||
|
||||
|
||||
def shutdown_otel_metrics() -> None:
|
||||
"""
|
||||
Shutdown the OTel metrics provider, flushing any pending metrics.
|
||||
|
||||
This is automatically called at process exit via atexit, but can be
|
||||
called manually for graceful shutdown.
|
||||
"""
|
||||
global _meter_provider, _initialized
|
||||
|
||||
if _meter_provider is not None:
|
||||
try:
|
||||
_meter_provider.shutdown()
|
||||
logger.info("OTel metrics shutdown complete")
|
||||
except Exception as e:
|
||||
logger.error(f"Error during OTel metrics shutdown: {e}")
|
||||
finally:
|
||||
_meter_provider = None
|
||||
_initialized = False
|
||||
|
||||
|
||||
def get_meter(name: str, version: str = "") -> Meter:
|
||||
"""
|
||||
Get an OTel Meter for creating instruments.
|
||||
|
||||
Args:
|
||||
name: The name of the instrumentation scope (e.g., "honcho.deriver").
|
||||
version: Optional version of the instrumentation scope.
|
||||
|
||||
Returns:
|
||||
An OTel Meter instance for creating counters, histograms, etc.
|
||||
|
||||
Example:
|
||||
meter = get_meter("honcho.deriver")
|
||||
counter = meter.create_counter("tokens_processed", unit="tokens")
|
||||
counter.add(100, {"task_type": "ingestion"})
|
||||
"""
|
||||
return metrics.get_meter(name, version)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Pre-defined metrics that mirror existing Prometheus counters
|
||||
# =============================================================================
|
||||
|
||||
# These are created lazily on first use to avoid issues with initialization order
|
||||
|
||||
|
||||
@final
|
||||
class OTelMetrics:
|
||||
"""
|
||||
Container for OTel metrics that mirror existing Prometheus counters.
|
||||
|
||||
This class provides a bridge during migration - the same metrics are
|
||||
available via both Prometheus (pull) and OTel (push).
|
||||
|
||||
Namespace is managed at the instance level (from settings), not per-call.
|
||||
"""
|
||||
|
||||
_instance: OTelMetrics | None = None
|
||||
_is_initialized: bool = False
|
||||
_namespace: str = "honcho"
|
||||
|
||||
# Meters (lazily initialized)
|
||||
_api_meter: Meter | None = None
|
||||
_deriver_meter: Meter | None = None
|
||||
_dialectic_meter: Meter | None = None
|
||||
_dreamer_meter: Meter | None = None
|
||||
|
||||
# Counters (lazily initialized)
|
||||
_api_requests: Counter | None = None
|
||||
_messages_created: Counter | None = None
|
||||
_dialectic_calls: Counter | None = None
|
||||
_deriver_queue_items: Counter | None = None
|
||||
_deriver_tokens: Counter | None = None
|
||||
_dialectic_tokens: Counter | None = None
|
||||
_dreamer_tokens: Counter | None = None
|
||||
|
||||
def __new__(cls) -> OTelMetrics:
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def _ensure_initialized(self) -> None:
|
||||
"""Lazily initialize meters and instruments."""
|
||||
if self._is_initialized:
|
||||
return
|
||||
|
||||
# Get namespace from settings (same as Prometheus)
|
||||
from src.config import settings
|
||||
|
||||
self._namespace = settings.OTEL.SERVICE_NAMESPACE or "honcho"
|
||||
|
||||
# Get meters for different components
|
||||
self._api_meter = get_meter("honcho.api")
|
||||
self._deriver_meter = get_meter("honcho.deriver")
|
||||
self._dialectic_meter = get_meter("honcho.dialectic")
|
||||
self._dreamer_meter = get_meter("honcho.dreamer")
|
||||
|
||||
# Create counters that mirror Prometheus metrics
|
||||
# API requests
|
||||
self._api_requests = self._api_meter.create_counter(
|
||||
name="api_requests",
|
||||
unit="requests",
|
||||
description="Total API requests",
|
||||
)
|
||||
|
||||
# Messages created
|
||||
self._messages_created = self._api_meter.create_counter(
|
||||
name="messages_created",
|
||||
unit="messages",
|
||||
description="Total messages created",
|
||||
)
|
||||
|
||||
# Dialectic calls
|
||||
self._dialectic_calls = self._dialectic_meter.create_counter(
|
||||
name="dialectic_calls",
|
||||
unit="calls",
|
||||
description="Total dialectic calls",
|
||||
)
|
||||
|
||||
# Deriver queue items processed
|
||||
self._deriver_queue_items = self._deriver_meter.create_counter(
|
||||
name="deriver_queue_items_processed",
|
||||
unit="items",
|
||||
description="Total deriver queue items processed",
|
||||
)
|
||||
|
||||
# Token counters
|
||||
self._deriver_tokens = self._deriver_meter.create_counter(
|
||||
name="deriver_tokens_processed",
|
||||
unit="tokens",
|
||||
description="Total tokens processed by the deriver",
|
||||
)
|
||||
|
||||
self._dialectic_tokens = self._dialectic_meter.create_counter(
|
||||
name="dialectic_tokens_processed",
|
||||
unit="tokens",
|
||||
description="Total tokens processed by the dialectic",
|
||||
)
|
||||
|
||||
self._dreamer_tokens = self._dreamer_meter.create_counter(
|
||||
name="dreamer_tokens_processed",
|
||||
unit="tokens",
|
||||
description="Total tokens processed by the dreamer",
|
||||
)
|
||||
|
||||
self._is_initialized = True
|
||||
|
||||
def _handle_metric_error(self, method_name: str, error: Exception) -> None:
|
||||
"""Handle errors from metric recording by logging to Sentry."""
|
||||
import sentry_sdk
|
||||
|
||||
sentry_sdk.capture_exception(error)
|
||||
logger.warning(
|
||||
"Failed to record OTel metric in %s: %s", method_name, str(error)
|
||||
)
|
||||
|
||||
def record_api_request(
|
||||
self,
|
||||
*,
|
||||
method: str,
|
||||
endpoint: str,
|
||||
status_code: str,
|
||||
) -> None:
|
||||
"""Record an API request metric."""
|
||||
try:
|
||||
self._ensure_initialized()
|
||||
if self._api_requests is None:
|
||||
return # Not initialized, skip silently
|
||||
self._api_requests.add(
|
||||
1,
|
||||
{
|
||||
"method": method,
|
||||
"endpoint": endpoint,
|
||||
"status_code": status_code,
|
||||
"namespace": self._namespace,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_api_request", e)
|
||||
|
||||
def record_messages_created(
|
||||
self,
|
||||
*,
|
||||
count: int,
|
||||
workspace_name: str,
|
||||
) -> None:
|
||||
"""Record messages created metric."""
|
||||
try:
|
||||
self._ensure_initialized()
|
||||
if self._messages_created is None:
|
||||
return # Not initialized, skip silently
|
||||
self._messages_created.add(
|
||||
count,
|
||||
{
|
||||
"workspace_name": workspace_name,
|
||||
"namespace": self._namespace,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_messages_created", e)
|
||||
|
||||
def record_dialectic_call(
|
||||
self,
|
||||
*,
|
||||
workspace_name: str,
|
||||
reasoning_level: str,
|
||||
) -> None:
|
||||
"""Record a dialectic call metric."""
|
||||
try:
|
||||
self._ensure_initialized()
|
||||
if self._dialectic_calls is None:
|
||||
return # Not initialized, skip silently
|
||||
self._dialectic_calls.add(
|
||||
1,
|
||||
{
|
||||
"workspace_name": workspace_name,
|
||||
"reasoning_level": reasoning_level,
|
||||
"namespace": self._namespace,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_dialectic_call", e)
|
||||
|
||||
def record_deriver_queue_item(
|
||||
self,
|
||||
*,
|
||||
count: int,
|
||||
workspace_name: str,
|
||||
task_type: str,
|
||||
) -> None:
|
||||
"""Record deriver queue items processed metric."""
|
||||
try:
|
||||
self._ensure_initialized()
|
||||
if self._deriver_queue_items is None:
|
||||
return # Not initialized, skip silently
|
||||
self._deriver_queue_items.add(
|
||||
count,
|
||||
{
|
||||
"workspace_name": workspace_name,
|
||||
"task_type": task_type,
|
||||
"namespace": self._namespace,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_deriver_queue_item", e)
|
||||
|
||||
def record_deriver_tokens(
|
||||
self,
|
||||
*,
|
||||
count: int,
|
||||
task_type: str,
|
||||
token_type: str,
|
||||
component: str,
|
||||
) -> None:
|
||||
"""Record deriver token usage metric."""
|
||||
try:
|
||||
self._ensure_initialized()
|
||||
if self._deriver_tokens is None:
|
||||
return # Not initialized, skip silently
|
||||
self._deriver_tokens.add(
|
||||
count,
|
||||
{
|
||||
"task_type": task_type,
|
||||
"token_type": token_type,
|
||||
"component": component,
|
||||
"namespace": self._namespace,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_deriver_tokens", e)
|
||||
|
||||
def record_dialectic_tokens(
|
||||
self,
|
||||
*,
|
||||
count: int,
|
||||
token_type: str,
|
||||
component: str,
|
||||
reasoning_level: str,
|
||||
) -> None:
|
||||
"""Record dialectic token usage metric."""
|
||||
try:
|
||||
self._ensure_initialized()
|
||||
if self._dialectic_tokens is None:
|
||||
return # Not initialized, skip silently
|
||||
self._dialectic_tokens.add(
|
||||
count,
|
||||
{
|
||||
"token_type": token_type,
|
||||
"component": component,
|
||||
"reasoning_level": reasoning_level,
|
||||
"namespace": self._namespace,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_dialectic_tokens", e)
|
||||
|
||||
def record_dreamer_tokens(
|
||||
self,
|
||||
*,
|
||||
count: int,
|
||||
specialist_name: str,
|
||||
token_type: str,
|
||||
) -> None:
|
||||
"""Record dreamer token usage metric."""
|
||||
try:
|
||||
self._ensure_initialized()
|
||||
if self._dreamer_tokens is None:
|
||||
return # Not initialized, skip silently
|
||||
self._dreamer_tokens.add(
|
||||
count,
|
||||
{
|
||||
"specialist_name": specialist_name,
|
||||
"token_type": token_type,
|
||||
"namespace": self._namespace,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_dreamer_tokens", e)
|
||||
|
||||
|
||||
# Singleton instance
|
||||
otel_metrics = OTelMetrics()
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
"""
|
||||
Prometheus telemetry module.
|
||||
|
||||
Exports:
|
||||
- prometheus_metrics: Singleton for recording metrics
|
||||
- metrics_endpoint: Async endpoint for /metrics route
|
||||
- Label enums for metric values
|
||||
"""
|
||||
|
||||
from src.telemetry.prometheus.metrics import (
|
||||
DeriverComponents,
|
||||
DeriverTaskTypes,
|
||||
DialecticComponents,
|
||||
TokenTypes,
|
||||
metrics_endpoint,
|
||||
prometheus_metrics,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"DeriverComponents",
|
||||
"DeriverTaskTypes",
|
||||
"DialecticComponents",
|
||||
"TokenTypes",
|
||||
"metrics_endpoint",
|
||||
"prometheus_metrics",
|
||||
]
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
"""Prometheus metrics for Honcho."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from enum import Enum
|
||||
from typing import cast, final
|
||||
|
||||
from prometheus_client import (
|
||||
CONTENT_TYPE_LATEST,
|
||||
REGISTRY,
|
||||
Counter,
|
||||
disable_created_metrics,
|
||||
generate_latest,
|
||||
)
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import Response
|
||||
|
||||
from src.config import settings
|
||||
|
||||
disable_created_metrics()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NamespacedCounter(Counter):
|
||||
def labels(self, **kwargs: str) -> NamespacedCounter:
|
||||
kwargs["namespace"] = cast(str, settings.METRICS.NAMESPACE)
|
||||
return super().labels(**kwargs) # type: ignore[return-value]
|
||||
|
||||
|
||||
class TokenTypes(Enum):
|
||||
INPUT = "input"
|
||||
OUTPUT = "output"
|
||||
|
||||
|
||||
class DeriverTaskTypes(Enum):
|
||||
INGESTION = "ingestion"
|
||||
SUMMARY = "summary"
|
||||
|
||||
|
||||
class DeriverComponents(Enum):
|
||||
PROMPT = "prompt"
|
||||
MESSAGES = "messages"
|
||||
PREVIOUS_SUMMARY = "previous_summary"
|
||||
OUTPUT_TOTAL = "output_total"
|
||||
|
||||
|
||||
class DialecticComponents(Enum):
|
||||
TOTAL = "total"
|
||||
|
||||
|
||||
api_requests_counter = NamespacedCounter(
|
||||
"api_requests",
|
||||
"Total API requests",
|
||||
["namespace", "method", "endpoint", "status_code"],
|
||||
)
|
||||
|
||||
messages_created_counter = NamespacedCounter(
|
||||
"messages_created",
|
||||
"Total messages created",
|
||||
["namespace", "workspace_name"],
|
||||
)
|
||||
|
||||
dialectic_calls_counter = NamespacedCounter(
|
||||
"dialectic_calls",
|
||||
"Total dialectic calls",
|
||||
["namespace", "workspace_name", "reasoning_level"],
|
||||
)
|
||||
|
||||
deriver_queue_items_processed_counter = NamespacedCounter(
|
||||
"deriver_queue_items_processed",
|
||||
"Total deriver queue items processed",
|
||||
["namespace", "workspace_name", "task_type"],
|
||||
)
|
||||
|
||||
deriver_tokens_processed_counter = NamespacedCounter(
|
||||
"deriver_tokens_processed",
|
||||
"Total tokens processed by the deriver",
|
||||
["namespace", "task_type", "token_type", "component"],
|
||||
)
|
||||
|
||||
dialectic_tokens_processed_counter = NamespacedCounter(
|
||||
"dialectic_tokens_processed",
|
||||
"Total tokens processed by the dialectic",
|
||||
["namespace", "token_type", "component", "reasoning_level"],
|
||||
)
|
||||
|
||||
dreamer_tokens_processed_counter = NamespacedCounter(
|
||||
"dreamer_tokens_processed",
|
||||
"Total tokens processed by the dreamer",
|
||||
["namespace", "specialist_name", "token_type"],
|
||||
)
|
||||
|
||||
|
||||
@final
|
||||
class PrometheusMetrics:
|
||||
_instance: PrometheusMetrics | None = None
|
||||
|
||||
def __new__(cls) -> PrometheusMetrics:
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def _handle_metric_error(self, method_name: str, error: Exception) -> None:
|
||||
import sentry_sdk
|
||||
|
||||
sentry_sdk.capture_exception(error)
|
||||
logger.warning(
|
||||
"Failed to record Prometheus metric in %s: %s", method_name, str(error)
|
||||
)
|
||||
|
||||
def record_api_request(
|
||||
self,
|
||||
*,
|
||||
method: str,
|
||||
endpoint: str,
|
||||
status_code: str,
|
||||
) -> None:
|
||||
try:
|
||||
api_requests_counter.labels(
|
||||
method=method,
|
||||
endpoint=endpoint,
|
||||
status_code=status_code,
|
||||
).inc()
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_api_request", e)
|
||||
|
||||
def record_messages_created(
|
||||
self,
|
||||
*,
|
||||
count: int,
|
||||
workspace_name: str,
|
||||
) -> None:
|
||||
try:
|
||||
messages_created_counter.labels(
|
||||
workspace_name=workspace_name,
|
||||
).inc(count)
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_messages_created", e)
|
||||
|
||||
def record_dialectic_call(
|
||||
self,
|
||||
*,
|
||||
workspace_name: str,
|
||||
reasoning_level: str,
|
||||
) -> None:
|
||||
try:
|
||||
dialectic_calls_counter.labels(
|
||||
workspace_name=workspace_name,
|
||||
reasoning_level=reasoning_level,
|
||||
).inc()
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_dialectic_call", e)
|
||||
|
||||
def record_deriver_queue_item(
|
||||
self,
|
||||
*,
|
||||
count: int,
|
||||
workspace_name: str,
|
||||
task_type: str,
|
||||
) -> None:
|
||||
try:
|
||||
deriver_queue_items_processed_counter.labels(
|
||||
workspace_name=workspace_name,
|
||||
task_type=task_type,
|
||||
).inc(count)
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_deriver_queue_item", e)
|
||||
|
||||
def record_deriver_tokens(
|
||||
self,
|
||||
*,
|
||||
count: int,
|
||||
task_type: str,
|
||||
token_type: str,
|
||||
component: str,
|
||||
) -> None:
|
||||
try:
|
||||
deriver_tokens_processed_counter.labels(
|
||||
task_type=task_type,
|
||||
token_type=token_type,
|
||||
component=component,
|
||||
).inc(count)
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_deriver_tokens", e)
|
||||
|
||||
def record_dialectic_tokens(
|
||||
self,
|
||||
*,
|
||||
count: int,
|
||||
token_type: str,
|
||||
component: str,
|
||||
reasoning_level: str,
|
||||
) -> None:
|
||||
try:
|
||||
dialectic_tokens_processed_counter.labels(
|
||||
token_type=token_type,
|
||||
component=component,
|
||||
reasoning_level=reasoning_level,
|
||||
).inc(count)
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_dialectic_tokens", e)
|
||||
|
||||
def record_dreamer_tokens(
|
||||
self,
|
||||
*,
|
||||
count: int,
|
||||
specialist_name: str,
|
||||
token_type: str,
|
||||
) -> None:
|
||||
try:
|
||||
dreamer_tokens_processed_counter.labels(
|
||||
specialist_name=specialist_name,
|
||||
token_type=token_type,
|
||||
).inc(count)
|
||||
except Exception as e:
|
||||
self._handle_metric_error("record_dreamer_tokens", e)
|
||||
|
||||
|
||||
prometheus_metrics = PrometheusMetrics()
|
||||
|
||||
|
||||
async def metrics_endpoint(_request: Request) -> Response:
|
||||
if not settings.METRICS.ENABLED:
|
||||
return Response("Metrics are disabled", status_code=404)
|
||||
try:
|
||||
return Response(
|
||||
content=generate_latest(REGISTRY),
|
||||
media_type=CONTENT_TYPE_LATEST,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to generate metrics: {e}", exc_info=True)
|
||||
return Response("Failed to generate metrics", status_code=500)
|
||||
|
|
@ -558,7 +558,7 @@ async def create_observations(
|
|||
observations: list[dict[str, Any]],
|
||||
observer: str,
|
||||
observed: str,
|
||||
session_name: str,
|
||||
session_name: str | None,
|
||||
workspace_name: str,
|
||||
message_ids: list[int],
|
||||
message_created_at: str,
|
||||
|
|
@ -941,12 +941,9 @@ async def _handle_create_observations(
|
|||
|
||||
message_ids = [msg.id for msg in ctx.current_messages]
|
||||
message_created_at = str(ctx.current_messages[-1].created_at)
|
||||
obs_session_name = ctx.session_name or ctx.current_messages[0].session_name
|
||||
obs_session_name = ctx.session_name
|
||||
else:
|
||||
# Dreamer/Dialectic agent: allow deductive and inductive, no source messages
|
||||
if not ctx.session_name:
|
||||
return "ERROR: Cannot create observations without a session context"
|
||||
|
||||
for i, obs in enumerate(observations):
|
||||
if "content" not in obs:
|
||||
return f"ERROR: observation {i} missing 'content' field"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import logging
|
|||
from io import BytesIO
|
||||
from typing import Any, Protocol
|
||||
|
||||
import pdfplumber
|
||||
from fastapi import UploadFile
|
||||
from nanoid import generate as generate_nanoid
|
||||
from sqlalchemy import Integer, select
|
||||
|
|
@ -27,6 +26,8 @@ class PDFProcessor:
|
|||
return content_type == "application/pdf"
|
||||
|
||||
async def extract_text(self, content: bytes) -> str:
|
||||
import pdfplumber
|
||||
|
||||
with pdfplumber.open(BytesIO(content)) as pdf_reader:
|
||||
text_parts: list[str] = []
|
||||
for page_num, page in enumerate(pdf_reader.pages):
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class DreamPayload(BasePayload):
|
|||
dream_type: DreamType
|
||||
observer: str
|
||||
observed: str
|
||||
session_name: str
|
||||
session_name: str | None = None
|
||||
|
||||
|
||||
class DeletionPayload(BasePayload):
|
||||
|
|
@ -89,7 +89,7 @@ def create_dream_payload(
|
|||
*,
|
||||
observer: str,
|
||||
observed: str,
|
||||
session_name: str,
|
||||
session_name: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Create a dream payload."""
|
||||
return DreamPayload(
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue