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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions Packages/Sources/RxCodeCore/Backend/IDEToolRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,90 @@ public enum IDEToolRegistry {
"required": .array([.string("thread_id")]),
])
),
IDETool(
name: "ide__memory_search",
description: "Search durable RxCode memories. Use this to retrieve saved user preferences, project facts, or decisions relevant to the current task.",
visibility: .alwaysIDEOnly,
inputSchema: .object([
"type": .string("object"),
"properties": .object([
"query": .object([
"type": .string("string"),
"description": .string("Natural-language search query."),
]),
"project_id": .object([
"type": .string("string"),
"description": .string("Optional project UUID to prefer project-local memories while still including global memories."),
]),
"limit": .object([
"type": .string("integer"),
"description": .string("Maximum number of memories to return. Default 20, capped at 100."),
]),
]),
"required": .array([.string("query")]),
])
),
IDETool(
name: "ide__memory_add",
description: "Add a durable memory to RxCode. Use only for stable user preferences, project facts, or decisions that should help future work.",
visibility: .alwaysIDEOnly,
inputSchema: .object([
"type": .string("object"),
"properties": .object([
"content": .object(["type": .string("string")]),
"project_id": .object([
"type": .string("string"),
"description": .string("Optional project UUID. Ignored when scope is global."),
]),
"kind": .object([
"type": .string("string"),
"enum": .array([.string("preference"), .string("fact"), .string("decision")]),
]),
"scope": .object([
"type": .string("string"),
"enum": .array([.string("project"), .string("global")]),
]),
]),
"required": .array([.string("content")]),
])
),
IDETool(
name: "ide__memory_update",
description: "Update an existing RxCode memory by id.",
visibility: .alwaysIDEOnly,
inputSchema: .object([
"type": .string("object"),
"properties": .object([
"id": .object(["type": .string("string")]),
"content": .object(["type": .string("string")]),
"project_id": .object([
"type": .string("string"),
"description": .string("Optional project UUID. Ignored when scope is global."),
]),
"kind": .object([
"type": .string("string"),
"enum": .array([.string("preference"), .string("fact"), .string("decision")]),
]),
"scope": .object([
"type": .string("string"),
"enum": .array([.string("project"), .string("global")]),
]),
]),
"required": .array([.string("id"), .string("content")]),
])
),
IDETool(
name: "ide__memory_delete",
description: "Delete a durable RxCode memory by id.",
visibility: .alwaysIDEOnly,
inputSchema: .object([
"type": .string("object"),
"properties": .object([
"id": .object(["type": .string("string")]),
]),
"required": .array([.string("id")]),
])
),
IDETool(
name: "ide__send_to_thread",
description: "Send a chat prompt to a thread in any project — continue an existing thread by `thread_id`, or start a brand-new thread by passing `project_id`. Triggers a real agent run that may consume tokens. Returns the assistant's reply text (waits up to `timeout_seconds`).",
Expand Down
165 changes: 165 additions & 0 deletions Packages/Sources/RxCodeCore/Models/MemoryRecord.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import Foundation
import SwiftData

public struct MemoryItem: Identifiable, Sendable, Equatable {
public let id: String
public let content: String
public let projectId: UUID?
public let sessionId: String?
public let sourceMessageId: UUID?
public let createdAt: Date
public let updatedAt: Date
public let lastUsedAt: Date?
public let kind: String
public let scope: String

public init(
id: String,
content: String,
projectId: UUID?,
sessionId: String?,
sourceMessageId: UUID?,
createdAt: Date,
updatedAt: Date,
lastUsedAt: Date?,
kind: String,
scope: String
) {
self.id = id
self.content = content
self.projectId = projectId
self.sessionId = sessionId
self.sourceMessageId = sourceMessageId
self.createdAt = createdAt
self.updatedAt = updatedAt
self.lastUsedAt = lastUsedAt
self.kind = kind
self.scope = scope
}
}

public struct MemoryVectorSnapshot: Sendable, Equatable {
public let item: MemoryItem
public let vector: Data
public let dim: Int

public init(item: MemoryItem, vector: Data, dim: Int) {
self.item = item
self.vector = vector
self.dim = dim
}

public func floatVector() -> [Float] {
let count = vector.count / MemoryLayout<Float>.size
guard count == dim else { return [] }
return vector.withUnsafeBytes { raw -> [Float] in
let buf = raw.bindMemory(to: Float.self)
return Array(buf)
}
}
}

@Model
public final class MemoryRecord {
@Attribute(.unique) public var id: String
public var content: String
public var projectId: UUID?
public var sessionId: String?
public var sourceMessageId: UUID?
public var createdAt: Date
public var updatedAt: Date
public var lastUsedAt: Date?
public var kind: String
public var scope: String
/// L2-normalised `[Float]` packed as little-endian bytes. Length == dim * 4.
public var vector: Data
public var dim: Int

public init(
id: String = UUID().uuidString,
content: String,
projectId: UUID?,
sessionId: String?,
sourceMessageId: UUID?,
createdAt: Date = .now,
updatedAt: Date = .now,
lastUsedAt: Date? = nil,
kind: String = "fact",
scope: String = "project",
vector: Data,
dim: Int
) {
self.id = id
self.content = content
self.projectId = projectId
self.sessionId = sessionId
self.sourceMessageId = sourceMessageId
self.createdAt = createdAt
self.updatedAt = updatedAt
self.lastUsedAt = lastUsedAt
self.kind = kind
self.scope = scope
self.vector = vector
self.dim = dim
}

public func apply(
content: String,
projectId: UUID?,
sessionId: String?,
sourceMessageId: UUID?,
kind: String,
scope: String,
vector: Data,
dim: Int,
updatedAt: Date = .now
) {
self.content = content
self.projectId = projectId
self.sessionId = sessionId
self.sourceMessageId = sourceMessageId
self.kind = kind
self.scope = scope
self.vector = vector
self.dim = dim
self.updatedAt = updatedAt
}

public func touch(at date: Date = .now) {
lastUsedAt = date
}

public func toItem() -> MemoryItem {
MemoryItem(
id: id,
content: content,
projectId: projectId,
sessionId: sessionId,
sourceMessageId: sourceMessageId,
createdAt: createdAt,
updatedAt: updatedAt,
lastUsedAt: lastUsedAt,
kind: kind,
scope: scope
)
}

public func toVectorSnapshot() -> MemoryVectorSnapshot {
MemoryVectorSnapshot(item: toItem(), vector: vector, dim: dim)
}
}

extension MemoryRecord {
public func floatVector() -> [Float] {
let count = vector.count / MemoryLayout<Float>.size
guard count == dim else { return [] }
return vector.withUnsafeBytes { raw -> [Float] in
let buf = raw.bindMemory(to: Float.self)
return Array(buf)
}
}

public static func encode(_ vector: [Float]) -> Data {
vector.withUnsafeBufferPointer { Data(buffer: $0) }
}
}
Loading