-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDemoTools.swift
More file actions
196 lines (174 loc) · 7.72 KB
/
Copy pathDemoTools.swift
File metadata and controls
196 lines (174 loc) · 7.72 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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import Foundation
import ManifoldInference
import ManifoldTools
/// Composition root for the demo's tool layer.
///
/// Resolves an app-owned sandbox directory, seeds it with a small fixture
/// "workspace" on first launch, and registers the reference toolset on a
/// ``ToolRegistry``. The seeded fixture exists so the demo's hero tool
/// (``SampleRepoSearchTool``) has something to search without the user
/// having to drop their own files in.
enum DemoTools {
/// Names of the tools registered by ``register(on:root:)``. Used by the
/// scenario runner to clear out variant executors and snap back to the
/// baseline set when a scenario starts.
static let baselineNames: [String] = [
"calc",
"now",
"read_file",
"list_dir",
"sample_repo_search",
"write_file"
] + FailureDemoTools.names
/// Registers the full reference toolset on `registry`.
///
/// Runs synchronously on the main actor because ``ToolRegistry`` is
/// MainActor-isolated. Seeding is idempotent — the on-disk fixture is
/// only written when absent, so repeated launches are cheap.
@MainActor
static func register(on registry: ToolRegistry, root: URL = DemoToolRoot.resolve()) {
do {
try DemoToolRoot.seedIfNeeded(at: root)
} catch {
// Fail open: the tools still work for files the user adds later,
// and the seed content is a nice-to-have rather than a contract.
Log.inference.warning("DemoTools: failed to seed fixture workspace at \(root.path, privacy: .public): \(String(describing: error), privacy: .public)")
}
registry.register(CalcTool.makeExecutor())
registry.register(DemoNowTool.makeExecutor())
registry.register(ReadFileTool.makeExecutor(root: root))
registry.register(ListDirTool.makeExecutor(root: root))
registry.register(SampleRepoSearchTool.makeExecutor(root: root))
registry.register(WriteFileTool.makeExecutor(root: root))
FailureDemoTools.register(on: registry)
}
/// Restores `registry` to the baseline tool set.
///
/// Used by ``DemoScenarioRunner`` to drop any variant executors a previous
/// scenario may have installed (e.g. a deliberately-slow `sample_repo_search`
/// for the cancellation scenario) before starting a new one. Idempotent.
@MainActor
static func resetToDefaults(on registry: ToolRegistry, root: URL = DemoToolRoot.resolve()) {
for name in baselineNames {
registry.unregister(name: name)
}
register(on: registry, root: root)
}
}
/// Resolves and seeds the demo's filesystem-tool sandbox.
enum DemoToolRoot {
/// Returns the app-owned sandbox root. Creates the parent directory lazily.
static func resolve() -> URL {
let fm = FileManager.default
let base = (try? fm.url(
for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true
)) ?? fm.temporaryDirectory
let root = base
.appendingPathComponent("ManifoldDemo", isDirectory: true)
.appendingPathComponent("ToolRoot", isDirectory: true)
try? fm.createDirectory(at: root, withIntermediateDirectories: true)
return root
}
/// Writes the bundled fixture workspace under `root` when it isn't there.
///
/// The seed is keyed by a marker file; we do not diff contents across
/// launches. If the user edits a seeded file we respect their choice. If
/// they delete the entire workspace (leaving only the marker, or nothing
/// at all) we re-seed so demo scenarios that search the fixture don't
/// silently look broken.
static func seedIfNeeded(at root: URL) throws {
let fm = FileManager.default
let marker = root.appendingPathComponent(".seeded", isDirectory: false)
if fm.fileExists(atPath: marker.path) {
// Re-seed only when the marker is the sole survivor — not when
// user content lives alongside it.
let contents = (try? fm.contentsOfDirectory(atPath: root.path)) ?? []
let nonMarker = contents.filter { $0 != ".seeded" }
if !nonMarker.isEmpty { return }
}
try fm.createDirectory(at: root, withIntermediateDirectories: true)
for (path, contents) in Self.fixture {
let url = root.appendingPathComponent(path)
try fm.createDirectory(
at: url.deletingLastPathComponent(),
withIntermediateDirectories: true
)
try contents.write(to: url, atomically: true, encoding: .utf8)
}
try Data("1".utf8).write(to: marker)
}
private static let fixture: [(String, String)] = [
("README.md", """
# Sample Workspace
This is a small fixture workspace the demo uses to showcase tool calling.
Ask the assistant to summarize the README files, search for a keyword,
or read a specific file — it will invoke the sandboxed filesystem tools.
"""),
("notes/ideas.md", """
# Product Ideas
- Offline-first note app with local embeddings.
- A CLI that replays a chat transcript through a different model.
- Voice memos transcribed via on-device speech, summarized at close of day.
- A fuzzer harness for long-context chat backends.
"""),
("notes/meeting-2026-04-15.md", """
# Planning meeting — April 15 2026
Attendees: Rory, Claude
Decisions:
- Ship tool-calling UI before 1.0.
- Defer voice module until post-launch.
- Unify deep-link / AppIntent / share-extension handoff behind InboundPayload.
"""),
("docs/architecture.md", """
# Architecture Overview
ManifoldKit exposes these core products:
- ManifoldInference — protocols and orchestration.
- ManifoldRuntime — persistence-free runtime services and ports.
- ManifoldPersistenceSwiftData — SwiftData persistence and bootstrap.
- ManifoldBackends — MLX, llama.cpp, Foundation, and cloud backends.
- ManifoldUI — SwiftUI views and view models.
- ManifoldTools — reference tools and the fuzzing harness.
"""),
("docs/tool-calling.md", """
# Tool Calling
Register a ToolExecutor on the ToolRegistry you pass to InferenceService.
The GenerationCoordinator dispatches ToolCall events through the registry
and threads the ToolResult back into the conversation.
"""),
("projects/scout/spec.md", """
# Scout — local research companion (draft)
Scout is a Mac-first agent app built on ManifoldKit. It surfaces:
- Filesystem tool calling with per-call approval.
- Live thinking streams from reasoning models.
- MCP server integration for third-party tools.
"""),
("projects/scout/roadmap.md", """
# Roadmap
- [ ] Tool approval UI
- [ ] MCP client
- [ ] Share Extension summarize-selection flow
- [ ] App Intents for Spotlight and Siri
"""),
("shopping-list.txt", """
milk
eggs
coffee
olive oil
"""),
("journal/2026-04-22.md", """
# 22 Apr 2026
Shipped the thinking-tokens super-session. 11 PRs merged, four reviews
came back in parallel, scope held. Next up: tool-calling demo in the
example app.
"""),
("journal/2026-04-23.md", """
# 23 Apr 2026
Pivoted on the demo strategy — instead of a third example app, we're
upgrading ManifoldDemo in place. Plan approved after PM / architect /
engineer / QA reviews.
"""),
]
}