| import Foundation |
|
|
| enum AgentPolicyDecision: Sendable, Equatable { |
| case allow |
| case requireApproval(String) |
| case deny(String) |
| } |
|
|
| struct AgentPolicy: Sendable { |
| static func decision( |
| for tool: AgentToolDefinition, |
| settings: AgentSettings |
| ) -> AgentPolicyDecision { |
| if let requiredCapability = tool.requiredCapability, |
| !settings.enabledSystemCapabilities.contains(requiredCapability) |
| { |
| return .deny( |
| "Enable this system capability in Dolphin Settings before using it." |
| ) |
| } |
|
|
| switch tool.risk { |
| case .readOnly: |
| return .allow |
| case .sensitiveRead: |
| return .requireApproval( |
| "Allow this one-time read of sensitive data from this iPhone?" |
| ) |
| case .localWrite: |
| if settings.requireApprovalForLocalWrites { |
| return .requireApproval( |
| "This action changes saved assistant data on this iPhone." |
| ) |
| } |
| return .allow |
| case .networkRead: |
| guard settings.networkAccessEnabled else { |
| return .deny("Network access is disabled in Settings.") |
| } |
| return .requireApproval( |
| "This action sends a request to an external HTTPS server." |
| ) |
| } |
| } |
| } |
|
|
| enum AgentPlanMode: String, Sendable, Equatable { |
| case conversation |
| case deterministic |
| case modelAssistedRead |
| } |
|
|
| |
| |
| |
| struct AgentRequestPlan: Sendable, Equatable { |
| let mode: AgentPlanMode |
| let plannedToolCalls: [AgentToolCall] |
| let eligibleToolIDs: Set<String> |
|
|
| var initialToolCall: AgentToolCall? { plannedToolCalls.first } |
|
|
| var requiredReceiptCallIDs: Set<UUID> { |
| Set(plannedToolCalls.map(\.id)) |
| } |
|
|
| var auditSummary: String { |
| switch mode { |
| case .conversation: |
| return "Conversation only · no tools eligible" |
| case .modelAssistedRead: |
| return "Model-assisted read · eligible tools: " |
| + eligibleToolIDs.sorted().joined(separator: ", ") |
| case .deterministic: |
| return "Deterministic sequence · " |
| + plannedToolCalls.map(\.name).joined(separator: " → ") |
| + " · \(requiredReceiptCallIDs.count) required receipt(s)" |
| } |
| } |
|
|
| init( |
| initialToolCall: AgentToolCall?, |
| eligibleToolIDs: Set<String> |
| ) { |
| plannedToolCalls = initialToolCall.map { [$0] } ?? [] |
| self.eligibleToolIDs = eligibleToolIDs |
| if initialToolCall != nil { |
| mode = .deterministic |
| } else if eligibleToolIDs.isEmpty { |
| mode = .conversation |
| } else { |
| mode = .modelAssistedRead |
| } |
| } |
|
|
| init(plannedToolCalls: [AgentToolCall]) { |
| mode = plannedToolCalls.isEmpty ? .conversation : .deterministic |
| self.plannedToolCalls = plannedToolCalls |
| eligibleToolIDs = [] |
| } |
|
|
| static let conversation = AgentRequestPlan( |
| initialToolCall: nil, |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| |
| |
| |
| |
| enum AgentRequestRouter: Sendable { |
| static let canonicalToolIDs: Set<String> = [ |
| "current_datetime", |
| "calculator", |
| "memory_search", |
| "memory_save", |
| "knowledge_search", |
| "knowledge_capture", |
| "knowledge_get", |
| "project_context", |
| "project_review", |
| "handoff_create", |
| "handoff_list", |
| "handoff_get", |
| "handoff_restore", |
| "handoff_deactivate", |
| "insight_list", |
| "code_analyze", |
| "task_list", |
| "task_search", |
| "task_add", |
| "task_complete", |
| "task_reopen", |
| "calendar_events", |
| ] |
|
|
| static func plan(for rawText: String) -> AgentRequestPlan { |
| let text = rawText.trimmingCharacters(in: .whitespacesAndNewlines) |
| guard !text.isEmpty else { return .conversation } |
|
|
| if let clauses = explicitThenClauses(in: text) { |
| let plans = clauses.map(singlePlan) |
| guard plans.allSatisfy({ |
| $0.mode == .deterministic && $0.plannedToolCalls.count == 1 |
| }) else { |
| return .conversation |
| } |
| return AgentRequestPlan( |
| plannedToolCalls: plans.flatMap(\.plannedToolCalls) |
| ) |
| } |
|
|
| |
| |
| |
| if let clauses = explicitConjunctionClauses(in: text) { |
| let plans = clauses.map(singlePlan) |
| if plans.allSatisfy({ |
| $0.mode == .deterministic && $0.plannedToolCalls.count == 1 |
| }) { |
| return AgentRequestPlan( |
| plannedToolCalls: plans.flatMap(\.plannedToolCalls) |
| ) |
| } |
| } |
|
|
| return singlePlan(text) |
| } |
|
|
| private static func singlePlan(_ text: String) -> AgentRequestPlan { |
| let lowercased = text.lowercased() |
|
|
| if let title = explicitTaskTitle(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "task_add", |
| arguments: ["title": .string(String(title.prefix(300)))] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let content = explicitMemoryContent(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "memory_save", |
| arguments: ["content": .string(String(content.prefix(1_500)))] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let id = explicitTaskCompletionID(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "task_complete", |
| arguments: ["id": .string(id.uuidString)] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let id = explicitTaskReopenID(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "task_reopen", |
| arguments: ["id": .string(id.uuidString)] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let capture = explicitKnowledgeCapture(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "knowledge_capture", |
| arguments: [ |
| "kind": .string(capture.kind), |
| "title": .string(capture.title), |
| "content": .string(capture.content), |
| "tags": .array([]), |
| "related_ids": .array([]), |
| ] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let query = explicitKnowledgeQuery(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "knowledge_search", |
| arguments: [ |
| "query": .string( |
| String(query.prefix(KnowledgeEngine.Limits.maxQueryCharacters)) |
| ), |
| "limit": .number(5), |
| ] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let id = explicitKnowledgeGetID(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "knowledge_get", |
| arguments: ["id": .string(id.uuidString)] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let focus = explicitHandoffFocus(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "handoff_create", |
| arguments: [ |
| "title": .string("Dolphin session handoff"), |
| "focus": .string(focus), |
| ] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let id = explicitHandoffRestoreID(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "handoff_restore", |
| arguments: ["id": .string(id.uuidString)] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let id = explicitHandoffDeactivateID(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "handoff_deactivate", |
| arguments: ["id": .string(id.uuidString)] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let id = explicitHandoffGetID(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "handoff_get", |
| arguments: ["id": .string(id.uuidString)] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let code = explicitCodeForAnalysis(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "code_analyze", |
| arguments: ["code": .string(String(code.prefix(5_000)))] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if isProjectContextRequest(text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall(name: "project_context", arguments: [:]), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if isProjectReviewRequest(text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "project_review", |
| arguments: ["limit": .number(5)] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if isHandoffListRequest(text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "handoff_list", |
| arguments: ["limit": .number(5)] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if isInsightRequest(text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "insight_list", |
| arguments: ["limit": .number(5)] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if isExplicitTaskListRequest(text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "task_list", |
| arguments: [:] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let query = explicitTaskQuery(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "task_search", |
| arguments: [ |
| "query": .string(String(query.prefix(200))), |
| "include_completed": .bool(true), |
| "limit": .number(10), |
| ] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let query = explicitMemoryQuery(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "memory_search", |
| arguments: [ |
| "query": .string(String(query.prefix(200))), |
| "limit": .number(5), |
| ] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let window = explicitCalendarWindow(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "calendar_events", |
| arguments: [ |
| "window": .string(window.rawValue), |
| "limit": .number(10), |
| ] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if let expression = explicitCalculation(in: text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "calculator", |
| arguments: ["expression": .string(expression)] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| if isExplicitDateTimeRequest(text) { |
| return AgentRequestPlan( |
| initialToolCall: AgentToolCall( |
| name: "current_datetime", |
| arguments: [:] |
| ), |
| eligibleToolIDs: [] |
| ) |
| } |
|
|
| |
| |
| |
| if containsCommandNegation(lowercased) { return .conversation } |
|
|
| let words = wordSet(in: lowercased) |
| var eligible: Set<String> = [] |
|
|
| if !words.isDisjoint(with: ["memory", "remember", "recall"]) { |
| eligible.insert("memory_search") |
| } |
| if !words.isDisjoint( |
| with: [ |
| "capture", "context", "decision", "goal", "knowledge", "preference", |
| "project", "synthesize", |
| ] |
| ) { |
| eligible.formUnion([ |
| "knowledge_search", "knowledge_get", "project_context", |
| "project_review", |
| ]) |
| } |
| if !words.isDisjoint(with: ["handoff", "handoffs", "resume", "restore"]) { |
| eligible.formUnion(["handoff_list", "handoff_get"]) |
| } |
| if !words.isDisjoint(with: ["insight", "insights", "suggestion", "suggestions"]) { |
| eligible.insert("insight_list") |
| } |
| if words.contains("code") |
| && !words.isDisjoint(with: ["analyze", "analyse", "review", "pattern"]) |
| { |
| eligible.insert("code_analyze") |
| } |
| if !words.isDisjoint(with: ["task", "tasks", "todo", "todos"]) { |
| eligible.formUnion(["task_list", "task_search"]) |
| } |
| if !words.isDisjoint( |
| with: ["calculate", "calculator", "arithmetic", "multiply", "divide"] |
| ) || lowercased.range( |
| of: #"[0-9]\s*[+\-*/%]\s*[0-9]"#, |
| options: .regularExpression |
| ) != nil { |
| eligible.insert("calculator") |
| } |
| if !words.isDisjoint( |
| with: ["time", "date", "day", "today", "tonight", "timezone"] |
| ) { |
| eligible.insert("current_datetime") |
| } |
| if !words.isDisjoint( |
| with: ["agenda", "calendar", "event", "events", "meeting", "meetings"] |
| ) { |
| eligible.insert("calendar_events") |
| } |
|
|
| return AgentRequestPlan( |
| initialToolCall: nil, |
| eligibleToolIDs: eligible |
| ) |
| } |
|
|
| private static func explicitMemoryContent(in text: String) -> String? { |
| let prefixes = [ |
| "please remember that ", |
| "please remember ", |
| "remember that ", |
| "remember ", |
| "please don't forget that ", |
| "please don’t forget that ", |
| "don't forget that ", |
| "don’t forget that ", |
| "save this to memory: ", |
| "save to memory: ", |
| "store this in memory: ", |
| "store in memory: ", |
| ] |
| for prefix in prefixes { |
| if let value = value(afterAnchoredPrefix: prefix, in: text) { |
| return cleanedPayload(value) |
| } |
| } |
| return nil |
| } |
|
|
| private struct KnowledgeCaptureRequest { |
| let kind: String |
| let title: String |
| let content: String |
| } |
|
|
| private static func explicitKnowledgeCapture( |
| in text: String |
| ) -> KnowledgeCaptureRequest? { |
| let prefixes: [(String, String)] = [ |
| ("capture a decision: ", "decision"), |
| ("capture decision: ", "decision"), |
| ("record decision: ", "decision"), |
| ("capture a goal: ", "goal"), |
| ("capture goal: ", "goal"), |
| ("capture a preference: ", "preference"), |
| ("capture preference: ", "preference"), |
| ("capture code context: ", "code"), |
| ("capture code: ", "code"), |
| ("capture an insight: ", "insight"), |
| ("capture insight: ", "insight"), |
| ("capture a fact: ", "fact"), |
| ("capture fact: ", "fact"), |
| ("capture context: ", "context"), |
| ("capture knowledge: ", "context"), |
| ] |
| for (prefix, kind) in prefixes { |
| guard let rawValue = value(afterAnchoredPrefix: prefix, in: text), |
| let content = cleanedPayload(rawValue) |
| else { continue } |
| let compact = content.split(whereSeparator: \Character.isWhitespace) |
| .joined(separator: " ") |
| let title = String(compact.prefix(80)) |
| return KnowledgeCaptureRequest( |
| kind: kind, |
| title: title, |
| content: String(content.prefix(4_000)) |
| ) |
| } |
| return nil |
| } |
|
|
| private static func explicitKnowledgeQuery(in text: String) -> String? { |
| let prefixes = [ |
| "search project knowledge for ", |
| "search knowledge for ", |
| "find knowledge about ", |
| "what do you know about ", |
| ] |
| for prefix in prefixes { |
| if let value = value(afterAnchoredPrefix: prefix, in: text) { |
| return cleanedPayload(value) |
| } |
| } |
| return nil |
| } |
|
|
| private static func explicitKnowledgeGetID(in text: String) -> UUID? { |
| let lowercased = text.lowercased() |
| guard !containsCommandNegation(lowercased) else { return nil } |
| guard lowercased.contains("knowledge") else { return nil } |
| guard ["show", "get", "open", "details", "inspect"].contains(where: { |
| wordSet(in: lowercased).contains($0) |
| }) else { return nil } |
| return firstUUID(in: text) |
| } |
|
|
| private static func isProjectContextRequest(_ text: String) -> Bool { |
| let lowercased = text.lowercased() |
| guard !containsCommandNegation(lowercased) else { return false } |
| return [ |
| "synthesize project context", |
| "synthesise project context", |
| "summarize project context", |
| "summarise project context", |
| "show project context", |
| "what are we working on", |
| "where did we leave off", |
| ].contains { lowercased.contains($0) } |
| } |
|
|
| private static func isProjectReviewRequest(_ text: String) -> Bool { |
| let lowercased = text.lowercased() |
| guard !containsCommandNegation(lowercased) else { return false } |
| return [ |
| "review project state", |
| "review the project state", |
| "project review", |
| "review my project", |
| "review this project", |
| "what needs attention", |
| ].contains { lowercased.contains($0) } |
| } |
|
|
| private static func explicitHandoffFocus(in text: String) -> String? { |
| let lowercased = text.lowercased() |
| if [ |
| "create a handoff", "create handoff", "save a handoff", "save handoff", |
| ].contains(lowercased.trimmingCharacters(in: .whitespacesAndNewlines)) { |
| return "Continue the current work" |
| } |
| let prefixes = [ |
| "create a handoff for ", |
| "create handoff for ", |
| "save a handoff for ", |
| "save handoff for ", |
| ] |
| for prefix in prefixes { |
| if let value = value(afterAnchoredPrefix: prefix, in: text) { |
| return cleanedPayload(value).map { String($0.prefix(500)) } |
| } |
| } |
| return nil |
| } |
|
|
| private static func explicitHandoffRestoreID(in text: String) -> UUID? { |
| let lowercased = text.lowercased() |
| guard !containsCommandNegation(lowercased) else { return nil } |
| guard lowercased.contains("handoff"), |
| lowercased.contains("restore") || lowercased.contains("resume") |
| else { return nil } |
| guard let range = text.range( |
| of: #"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}"#, |
| options: .regularExpression |
| ) else { return nil } |
| return UUID(uuidString: String(text[range])) |
| } |
|
|
| private static func explicitHandoffDeactivateID(in text: String) -> UUID? { |
| let lowercased = text.lowercased() |
| guard !containsCommandNegation(lowercased) else { return nil } |
| guard lowercased.contains("handoff"), lowercased.contains("deactivate") else { |
| return nil |
| } |
| return firstUUID(in: text) |
| } |
|
|
| private static func explicitHandoffGetID(in text: String) -> UUID? { |
| let lowercased = text.lowercased() |
| guard !containsCommandNegation(lowercased) else { return nil } |
| guard lowercased.contains("handoff") else { return nil } |
| let words = wordSet(in: lowercased) |
| guard !words.isDisjoint(with: ["show", "get", "open", "details", "inspect"]) else { |
| return nil |
| } |
| return firstUUID(in: text) |
| } |
|
|
| private static func isHandoffListRequest(_ text: String) -> Bool { |
| let lowercased = text.lowercased() |
| guard !containsCommandNegation(lowercased) else { return false } |
| return [ |
| "list handoffs", "list my handoffs", "show handoffs", "show my handoffs", |
| ].contains { lowercased.contains($0) } |
| } |
|
|
| private static func isInsightRequest(_ text: String) -> Bool { |
| let lowercased = text.lowercased() |
| guard !containsCommandNegation(lowercased) else { return false } |
| return [ |
| "show insights", "show proactive insights", "list insights", |
| "what should i do next", "what should we do next", |
| ].contains { lowercased.contains($0) } |
| } |
|
|
| private static func explicitCodeForAnalysis(in text: String) -> String? { |
| let prefixes = [ |
| "analyze this code: ", "analyse this code: ", "analyze code: ", |
| "analyse code: ", "review this code: ", |
| ] |
| for prefix in prefixes { |
| if let value = value(afterAnchoredPrefix: prefix, in: text) { |
| return cleanedPayload(value) |
| } |
| } |
| return nil |
| } |
|
|
| private static func containsCommandNegation(_ lowercased: String) -> Bool { |
| [ |
| "don't ", "don’t ", "dont ", "do not ", "never ", "stop ", "avoid ", |
| ].contains { lowercased.contains($0) } |
| } |
|
|
| private static func explicitCalendarWindow( |
| in text: String |
| ) -> CalendarQueryWindow? { |
| let lowercased = text.lowercased() |
| guard !containsCommandNegation(lowercased) else { return nil } |
| let words = wordSet(in: lowercased) |
| guard !words.isDisjoint( |
| with: ["agenda", "calendar", "event", "events", "meeting", "meetings"] |
| ) else { return nil } |
| if lowercased.contains("next 7 days") |
| || lowercased.contains("next seven days") |
| { |
| return .nextSevenDays |
| } |
| if words.contains("tomorrow") { return .tomorrow } |
| if words.contains("today") || words.contains("agenda") { return .today } |
| return nil |
| } |
|
|
| private static func explicitTaskTitle(in text: String) -> String? { |
| let prefixes = [ |
| "please add a task to ", |
| "add a task to ", |
| "please create a task to ", |
| "create a task to ", |
| "add a task: ", |
| "create a task: ", |
| "remember to ", |
| ] |
| for prefix in prefixes { |
| if let value = value(afterAnchoredPrefix: prefix, in: text) { |
| return cleanedPayload(value) |
| } |
| } |
|
|
| guard let remainder = value(afterAnchoredPrefix: "add ", in: text) else { |
| return nil |
| } |
| let suffixes = [ |
| " to my tasks", |
| " to my task list", |
| " to the task list", |
| " to tasks", |
| " to my todo list", |
| " to my to-do list", |
| ] |
| for suffix in suffixes { |
| if let range = remainder.range( |
| of: suffix, |
| options: [.caseInsensitive, .backwards] |
| ), range.upperBound == remainder.endIndex { |
| return cleanedPayload(String(remainder[..<range.lowerBound])) |
| } |
| } |
| return nil |
| } |
|
|
| private static func explicitTaskCompletionID(in text: String) -> UUID? { |
| let lowercased = text.lowercased() |
| guard !containsCommandNegation(lowercased) else { return nil } |
| guard |
| lowercased.contains("complete") |
| || lowercased.contains("finish") |
| || lowercased.contains("mark") |
| else { return nil } |
| guard let range = text.range( |
| of: #"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}"#, |
| options: .regularExpression |
| ) else { return nil } |
| return UUID(uuidString: String(text[range])) |
| } |
|
|
| private static func explicitTaskReopenID(in text: String) -> UUID? { |
| let lowercased = text.lowercased() |
| guard !containsCommandNegation(lowercased) else { return nil } |
| guard lowercased.contains("task"), lowercased.contains("reopen") else { |
| return nil |
| } |
| return firstUUID(in: text) |
| } |
|
|
| private static func isExplicitTaskListRequest(_ text: String) -> Bool { |
| let lowercased = text.lowercased() |
| guard !containsCommandNegation(lowercased) else { return false } |
| return [ |
| "list tasks", |
| "list my tasks", |
| "show my tasks", |
| "show tasks", |
| "what are my tasks", |
| "what's on my task list", |
| "whats on my task list", |
| ].contains { lowercased.contains($0) } |
| } |
|
|
| private static func explicitTaskQuery(in text: String) -> String? { |
| for prefix in [ |
| "search tasks for ", |
| "search my tasks for ", |
| "find task about ", |
| "find tasks about ", |
| "find my task about ", |
| ] { |
| if let value = value(afterAnchoredPrefix: prefix, in: text) { |
| return cleanedPayload(value) |
| } |
| } |
| return nil |
| } |
|
|
| private static func explicitMemoryQuery(in text: String) -> String? { |
| let lowercased = text.lowercased() |
| if !containsCommandNegation(lowercased), [ |
| "what is my name", |
| "what's my name", |
| "whats my name", |
| "do you remember my name", |
| "who am i", |
| ].contains(where: lowercased.contains) { |
| return "name" |
| } |
|
|
| let prefixes = [ |
| "what do you remember about ", |
| "search your memory for ", |
| "search memory for ", |
| "recall ", |
| ] |
| for prefix in prefixes { |
| if let value = value(afterAnchoredPrefix: prefix, in: text) { |
| return cleanedPayload(value) |
| } |
| } |
| return nil |
| } |
|
|
| private static func explicitCalculation(in text: String) -> String? { |
| for prefix in ["calculate: ", "calculate ", "calculator: "] { |
| if let value = value(afterAnchoredPrefix: prefix, in: text), |
| let cleaned = cleanedPayload(value) |
| { |
| return String(cleaned.prefix(256)) |
| } |
| } |
| return nil |
| } |
|
|
| private static func isExplicitDateTimeRequest(_ text: String) -> Bool { |
| let value = text.lowercased().trimmingCharacters( |
| in: .whitespacesAndNewlines |
| ) |
| return [ |
| "what time is it", |
| "what time is it?", |
| "current time", |
| "current date", |
| "what is today's date", |
| "what is today’s date", |
| "what's today's date", |
| "what’s today’s date", |
| ].contains(value) |
| } |
|
|
| private static func explicitThenClauses(in text: String) -> [String]? { |
| var separated = text.replacingOccurrences( |
| of: " and then ", |
| with: "\n", |
| options: .caseInsensitive |
| ) |
| separated = separated.replacingOccurrences( |
| of: " then ", |
| with: "\n", |
| options: .caseInsensitive |
| ) |
| let clauses = separated.split(separator: "\n").map { |
| String($0).trimmingCharacters(in: .whitespacesAndNewlines) |
| }.filter { !$0.isEmpty } |
| return clauses.count > 1 ? clauses : nil |
| } |
|
|
| private static func explicitConjunctionClauses( |
| in text: String |
| ) -> [String]? { |
| let separated = text.replacingOccurrences( |
| of: #"(?i)\s+and\s+"#, |
| with: "\n", |
| options: .regularExpression |
| ) |
| let clauses = separated.split(separator: "\n").map { |
| String($0).trimmingCharacters(in: .whitespacesAndNewlines) |
| }.filter { !$0.isEmpty } |
| return clauses.count > 1 ? clauses : nil |
| } |
|
|
| private static func firstUUID(in text: String) -> UUID? { |
| guard let range = text.range( |
| of: #"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}"#, |
| options: .regularExpression |
| ) else { return nil } |
| return UUID(uuidString: String(text[range])) |
| } |
|
|
| private static func value( |
| afterAnchoredPrefix prefix: String, |
| in text: String |
| ) -> String? { |
| guard let range = text.range( |
| of: prefix, |
| options: [.anchored, .caseInsensitive] |
| ) else { return nil } |
| return String(text[range.upperBound...]) |
| } |
|
|
| private static func cleanedPayload(_ rawValue: String) -> String? { |
| var value = rawValue.trimmingCharacters(in: .whitespacesAndNewlines) |
| for prefix in ["that ", "this: ", "this "] { |
| if let range = value.range( |
| of: prefix, |
| options: [.anchored, .caseInsensitive] |
| ) { |
| value = String(value[range.upperBound...]) |
| .trimmingCharacters(in: .whitespacesAndNewlines) |
| break |
| } |
| } |
| guard !value.isEmpty else { return nil } |
| return value |
| } |
|
|
| private static func wordSet(in text: String) -> Set<String> { |
| Set( |
| text.split { character in |
| !character.isLetter && !character.isNumber |
| }.map(String.init) |
| ) |
| } |
| } |
|
|