forked from intitni/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathShortcutChatPlugin.swift
More file actions
147 lines (126 loc) · 5.4 KB
/
ShortcutChatPlugin.swift
File metadata and controls
147 lines (126 loc) · 5.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import ChatBasic
import Foundation
import Terminal
public final class ShortcutChatPlugin: ChatPlugin {
public static var id: String { "com.intii.shortcut" }
public static var command: String { "shortcut" }
public static var name: String { "Shortcut" }
public static var description: String { """
Run a shortcut and use message content as input. You need to provide the shortcut name as an argument, for example, `/shortcut(Shortcut Name)`.
""" }
let terminal: TerminalType
init(terminal: TerminalType) {
self.terminal = terminal
}
public init() {
terminal = Terminal()
}
public func sendForTextResponse(_ request: Request) async
-> AsyncThrowingStream<String, any Error>
{
let stream = await sendForComplicatedResponse(request)
return .init { continuation in
let task = Task {
do {
for try await response in stream {
switch response {
case let .content(.text(content)):
continuation.yield(content)
default:
break
}
}
continuation.finish()
} catch {
continuation.finish(throwing: error)
}
}
continuation.onTermination = { _ in
task.cancel()
}
}
}
public func sendForComplicatedResponse(_ request: Request) async
-> AsyncThrowingStream<Response, any Error>
{
return .init { continuation in
let task = Task {
let id = "\(Self.command)-\(UUID().uuidString)"
guard let shortcutName = request.arguments.first, !shortcutName.isEmpty else {
continuation.yield(.content(.text(
"Please provide the shortcut name in format: `/\(Self.command)(shortcut name)`"
)))
return
}
var input = String(request.text).trimmingCharacters(in: .whitespacesAndNewlines)
if input.isEmpty {
// if no input detected, use the previous message as input
input = request.history.last?.content ?? ""
}
do {
continuation.yield(.startAction(
id: "run",
task: "Run shortcut `\(shortcutName)`"
))
let env = ProcessInfo.processInfo.environment
let shell = env["SHELL"] ?? "/bin/bash"
let temporaryURL = FileManager.default.temporaryDirectory
let temporaryInputFileURL = temporaryURL
.appendingPathComponent("\(id)-input.txt")
let temporaryOutputFileURL = temporaryURL
.appendingPathComponent("\(id)-output")
try input.write(to: temporaryInputFileURL, atomically: true, encoding: .utf8)
let command = """
shortcuts run "\(shortcutName)" \
-i "\(temporaryInputFileURL.path)" \
-o "\(temporaryOutputFileURL.path)"
"""
continuation.yield(.startAction(
id: "run",
task: "Run shortcut \(shortcutName)"
))
do {
let result = try await terminal.runCommand(
shell,
arguments: ["-i", "-l", "-c", command],
currentDirectoryURL: nil,
environment: [:]
)
continuation.yield(.finishAction(id: "run", result: .success(result)))
} catch {
continuation.yield(.finishAction(
id: "run",
result: .failure(error.localizedDescription)
))
throw error
}
await Task.yield()
try Task.checkCancellation()
if FileManager.default.fileExists(atPath: temporaryOutputFileURL.path) {
let data = try Data(contentsOf: temporaryOutputFileURL)
if let text = String(data: data, encoding: .utf8) {
var response = text
if response.isEmpty {
response = "Finished"
}
continuation.yield(.content(.text(response)))
} else {
let content = """
[View File](\(temporaryOutputFileURL))
"""
continuation.yield(.content(.text(content)))
}
} else {
continuation.yield(.content(.text("Finished")))
}
} catch {
continuation.yield(.content(.text(error.localizedDescription)))
}
continuation.finish()
}
continuation.onTermination = { _ in
task.cancel()
}
}
}
}