syntropy 0.39.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/TODO.md +44 -142
- data/cmd/console.rb +0 -2
- data/cmd/new/template/config/production.rb +2 -1
- data/cmd/new.rb +1 -1
- data/cmd/serve.rb +26 -0
- data/examples/agent/Dockerfile +25 -0
- data/examples/agent/Gemfile +3 -0
- data/examples/agent/README.md +40 -0
- data/examples/agent/TODO.md +5 -0
- data/examples/agent/app/_layout/default.rb +15 -0
- data/examples/agent/app/_lib/storage.rb +13 -0
- data/examples/agent/app/_schema/2026-01-01-initial.rb +9 -0
- data/examples/agent/app/assets/agent.js +63 -0
- data/examples/agent/app/assets/minigfm.js +207 -0
- data/examples/agent/app/assets/model.js +45 -0
- data/examples/agent/app/assets/style.css +25 -0
- data/examples/agent/app/assets/tools.js +74 -0
- data/examples/agent/app/assets/ui.js +50 -0
- data/examples/agent/app/index.rb +19 -0
- data/examples/agent/app/test.rb +7 -0
- data/examples/agent/config/Caddyfile +5 -0
- data/examples/agent/config/development.rb +5 -0
- data/examples/agent/config/production.rb +5 -0
- data/examples/agent/config/test.rb +5 -0
- data/examples/agent/docker-compose.yml +71 -0
- data/examples/agent/response_example.json +108 -0
- data/examples/agent/test/test_app.rb +14 -0
- data/examples/blog/app/foo.rb +1 -0
- data/examples/blog/config/production.rb +2 -1
- data/lib/syntropy/app.rb +6 -2
- data/lib/syntropy/applets/builtin/auto_refresh/watch.js +1 -1
- data/lib/syntropy/applets/builtin/auto_refresh/watch.sse.rb +7 -0
- data/lib/syntropy/dev_mode.rb +8 -3
- data/lib/syntropy/http/client.rb +17 -1
- data/lib/syntropy/http/client_connection.rb +16 -0
- data/lib/syntropy/http/io_extensions.rb +32 -4
- data/lib/syntropy/http/server.rb +55 -24
- data/lib/syntropy/http/server_connection.rb +72 -2
- data/lib/syntropy/logger.rb +8 -0
- data/lib/syntropy/markdown.rb +44 -20
- data/lib/syntropy/module_loader.rb +29 -16
- data/lib/syntropy/request/request_info.rb +1 -1
- data/lib/syntropy/request/response.rb +75 -6
- data/lib/syntropy/request/validation.rb +6 -0
- data/lib/syntropy/storage/connection_pool.rb +34 -0
- data/lib/syntropy/test.rb +4 -4
- data/lib/syntropy/version.rb +1 -1
- data/lib/syntropy.rb +7 -1
- data/syntropy.gemspec +4 -4
- data/test/test_markdown.rb +3 -3
- data/test/test_module_loader.rb +4 -1
- metadata +34 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 96429a90f9e886ffafc25c1e4c88a46157fb79751d21fdbed7af10252ff5d16e
|
|
4
|
+
data.tar.gz: 040ebeaf507ada9e986847b7630739f9451c88d5384c736fd632f55d4928bff2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f75c54e095c15c1742786f712062142619a0e7c23ee22295f3dc93825d476027ab26a962054ad3cf868f8d89045af2c3c29733b648d9042ba2594595783ab234
|
|
7
|
+
data.tar.gz: d60a7f2d439b615db027c9f678ab4fba2aae38deb1c68b9d9e5b29bdbe22d282f7d14e1111458c97e3b7ad004eef1a428714376d40bd1370fa668e08765fe739
|
data/CHANGELOG.md
CHANGED
data/TODO.md
CHANGED
|
@@ -1,10 +1,43 @@
|
|
|
1
1
|
## Immediate
|
|
2
2
|
|
|
3
|
+
- [ ] Nicer logging in development mode:
|
|
4
|
+
|
|
5
|
+
Nice terminal formatting with colors:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
sharon@nf1:~/tmp/capatest2$ npx serve dist
|
|
9
|
+
|
|
10
|
+
┌───────────────────────────────────────────┐
|
|
11
|
+
│ │
|
|
12
|
+
│ Serving! │
|
|
13
|
+
│ │
|
|
14
|
+
│ - Local: http://localhost:3000 │
|
|
15
|
+
│ - Network: http://192.168.0.106:3000 │
|
|
16
|
+
│ │
|
|
17
|
+
│ Copied local address to clipboard! │
|
|
18
|
+
│ │
|
|
19
|
+
└───────────────────────────────────────────┘
|
|
20
|
+
|
|
21
|
+
HTTP 7/6/2026 8:04:23 PM 127.0.0.1 GET /
|
|
22
|
+
HTTP 7/6/2026 8:04:23 PM 127.0.0.1 Returned 200 in 42 ms
|
|
23
|
+
HTTP 7/6/2026 8:04:23 PM 127.0.0.1 GET /assets/index-edfizW3i.js
|
|
24
|
+
HTTP 7/6/2026 8:04:23 PM 127.0.0.1 GET /assets/index-PVjztr5e.css
|
|
25
|
+
HTTP 7/6/2026 8:04:23 PM 127.0.0.1 Returned 200 in 3 ms
|
|
26
|
+
HTTP 7/6/2026 8:04:23 PM 127.0.0.1 Returned 200 in 12 ms
|
|
27
|
+
HTTP 7/6/2026 8:04:23 PM 127.0.0.1 GET /assets/p-Sh0ICmPV-D227nRX-.js
|
|
28
|
+
HTTP 7/6/2026 8:04:23 PM 127.0.0.1 Returned 200 in 3 ms
|
|
29
|
+
HTTP 7/6/2026 8:04:23 PM 127.0.0.1 GET /assets/p-C4t5ymfq-4gquBJ2r.js
|
|
30
|
+
HTTP 7/6/2026 8:04:23 PM 127.0.0.1 Returned 200 in 6 ms
|
|
31
|
+
HTTP 7/6/2026 8:04:23 PM 127.0.0.1 GET /assets/p-D6Ynv7Xh-CRAF8up3.js
|
|
32
|
+
HTTP 7/6/2026 8:04:23 PM 127.0.0.1 Returned 200 in 4 ms
|
|
33
|
+
^C
|
|
34
|
+
INFO Gracefully shutting down. Please wait...
|
|
35
|
+
```
|
|
36
|
+
|
|
3
37
|
- [ ] Ability to load modules from builtin applet
|
|
4
38
|
- [ ] Can we mount them on the app's module loader?
|
|
5
39
|
|
|
6
|
-
|
|
7
|
-
- [ ] add dispatch_json_rpc, dispatch_json_api
|
|
40
|
+
Why we need that? The use case is making use of a default pub/sub instance
|
|
8
41
|
|
|
9
42
|
- [ ] Pub/sub
|
|
10
43
|
- [ ] Ruby side
|
|
@@ -12,6 +45,9 @@
|
|
|
12
45
|
- [ ] Reimplement `auto_refresh` using a *default* event bus provided by
|
|
13
46
|
Syntropy
|
|
14
47
|
|
|
48
|
+
- [ ] An alternative to the pub/sub design - add streaming responses to the
|
|
49
|
+
JSON/jS API (using SSE).
|
|
50
|
+
|
|
15
51
|
## Collections
|
|
16
52
|
|
|
17
53
|
- [ ] Collection - treat directories and files as collections of data.
|
|
@@ -52,21 +88,18 @@
|
|
|
52
88
|
- [ ] Website
|
|
53
89
|
- [v] Frontend part of JSON API
|
|
54
90
|
- [v] Auto-refresh page when file changes
|
|
55
|
-
- [ ] SQLite database capabilities
|
|
56
|
-
- [ ] Stores
|
|
57
|
-
- [ ] KV store (with TTL)
|
|
58
91
|
- [v] Examples
|
|
59
92
|
- [v] Reactive app - counter or some other simple app showing interaction with
|
|
60
93
|
server
|
|
61
|
-
- [
|
|
94
|
+
- [v] blog
|
|
62
95
|
|
|
63
96
|
## Testing facilities
|
|
64
97
|
|
|
65
98
|
- What do we need to test?
|
|
66
|
-
- Routes
|
|
67
|
-
- Route responses
|
|
68
|
-
- Changes to state / DB
|
|
69
|
-
- Rendered HTML - presence of certain markup / elements / text
|
|
99
|
+
- [v] Routes
|
|
100
|
+
- [v] Route responses
|
|
101
|
+
- [v] Changes to state / DB
|
|
102
|
+
- [ ] Rendered HTML - presence of certain markup / elements / text
|
|
70
103
|
|
|
71
104
|
## Support for applets
|
|
72
105
|
|
|
@@ -102,138 +135,7 @@ class Syntropy::Admin < Syntropy::App
|
|
|
102
135
|
end
|
|
103
136
|
```
|
|
104
137
|
|
|
105
|
-
##
|
|
106
|
-
|
|
107
|
-
We need a way to inject cookies into the response. This probably should be done
|
|
108
|
-
in the TP2 code:
|
|
109
|
-
|
|
110
|
-
```ruby
|
|
111
|
-
@@default_set_cookie_attr = 'HttpOnly'
|
|
112
|
-
def self.default_set_cookie_attr=(v)
|
|
113
|
-
@@default_set_cookie_attr = v
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def set_cookie(key, value, attr = @@default_set_cookie_attr)
|
|
117
|
-
@buffered_headers ||= +''
|
|
118
|
-
@buffered_headers << format(
|
|
119
|
-
"Set-Cookie: %<key>s=%<value>s; %<attr>s\n",
|
|
120
|
-
key:, value:, attr:
|
|
121
|
-
)
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def set_headers(headers)
|
|
125
|
-
@buffered_headers ||= +''
|
|
126
|
-
@buffered_headers << format_headers(headers)
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
...
|
|
130
|
-
|
|
131
|
-
req.set_cookie('at', 'foobar', 'SameSite=none; Secure; HttpOnly')
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
## Middleware
|
|
135
|
-
|
|
136
|
-
Some standard middleware:
|
|
137
|
-
|
|
138
|
-
- request rewriter
|
|
139
|
-
- logger
|
|
140
|
-
- auth
|
|
141
|
-
- selector + terminator
|
|
142
|
-
|
|
143
|
-
```Ruby
|
|
144
|
-
# For the chainable DSL shown below, we need to create a custom class:
|
|
145
|
-
class Syntropy::Middleware::Selector
|
|
146
|
-
def initialize(select_proc, terminator_proc = nil)
|
|
147
|
-
@select_proc = select_proc
|
|
148
|
-
@terminator_proc = terminator_proc
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def to_proc
|
|
152
|
-
->(req, proc) {
|
|
153
|
-
@select_proc.(req) ? @terminator_proc.(req) : proc(req)
|
|
154
|
-
}
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def terminate(&proc)
|
|
158
|
-
@terminator_proc = proc
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
```Ruby
|
|
164
|
-
# a _site.rb file can be used to wrap a whole app
|
|
165
|
-
# site/_site.rb
|
|
166
|
-
|
|
167
|
-
# this means we route according to the host header, with each
|
|
168
|
-
export Syntropy.route_by_host
|
|
169
|
-
|
|
170
|
-
# we can also rewrite requests:
|
|
171
|
-
rewriter = Syntropy
|
|
172
|
-
.select { it.host =~ /^tolkora\.(org|com)$/ }
|
|
173
|
-
.terminate { it.redirect_permanent('https://tolkora.net') }
|
|
174
|
-
|
|
175
|
-
# This is actuall a pretty interesting DSL design:
|
|
176
|
-
# a chain of operations that compose functions. So, we can select a
|
|
177
|
-
export rewriter.wrap(default_app)
|
|
178
|
-
|
|
179
|
-
# composing
|
|
180
|
-
export rewriter.wrap(Syntropy.some_custom_app.wrap(app))
|
|
181
|
-
|
|
182
|
-
# or maybe
|
|
183
|
-
export rewriter << some_other_middleware << app
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
## CLI tool for setting up a site repo:
|
|
187
|
-
|
|
188
|
-
```bash
|
|
189
|
-
# clone a newly created repo
|
|
190
|
-
~/repo$ git clone https://github.com/foo/bar
|
|
191
|
-
...
|
|
192
|
-
~/repo$ syntropy setup bar
|
|
193
|
-
|
|
194
|
-
(syntropy banner)
|
|
195
|
-
|
|
196
|
-
Setting up Syntropy project in /home/sharon/repo/bar:
|
|
197
|
-
|
|
198
|
-
bar/
|
|
199
|
-
bin/
|
|
200
|
-
start
|
|
201
|
-
stop
|
|
202
|
-
restart
|
|
203
|
-
console
|
|
204
|
-
server
|
|
205
|
-
docker-compose.yml
|
|
206
|
-
Dockerfile
|
|
207
|
-
Gemfile
|
|
208
|
-
proxy/
|
|
209
|
-
README.md
|
|
210
|
-
site/
|
|
211
|
-
_layout/
|
|
212
|
-
default.rb
|
|
213
|
-
_lib/
|
|
214
|
-
about.md
|
|
215
|
-
articles/
|
|
216
|
-
long-form.md
|
|
217
|
-
assets/
|
|
218
|
-
js/
|
|
219
|
-
css/
|
|
220
|
-
style.css
|
|
221
|
-
img/
|
|
222
|
-
syntropy.png
|
|
223
|
-
index.rb
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
Some of the files might need templating, but we can maybe do without, or at
|
|
227
|
-
least make it as generic as possible.
|
|
228
|
-
|
|
229
|
-
`syntropy setup` steps:
|
|
230
|
-
|
|
231
|
-
1. Verify existence of target directory
|
|
232
|
-
2. Copy files from Syntropy template to target directory
|
|
233
|
-
3. Do chmod +x for bin/*
|
|
234
|
-
4. Do bundle install in the target directory
|
|
235
|
-
5. Show some information with regard to how to get started working with the
|
|
236
|
-
repo
|
|
138
|
+
## Some more CLI commands
|
|
237
139
|
|
|
238
140
|
`syntropy provision` steps:
|
|
239
141
|
|
data/cmd/console.rb
CHANGED
|
@@ -72,8 +72,6 @@ env[:logger] = env[:logger] && Syntropy::Logger.new(env[:machine], **env)
|
|
|
72
72
|
@app = Syntropy::App.load(env)
|
|
73
73
|
@env = env
|
|
74
74
|
@machine = env[:machine]
|
|
75
|
-
@connection_pool = @app.connection_pool if @app.respond_to?(:connection_pool)
|
|
76
|
-
@schema = @app.schema if @app.respond_to?(:schema)
|
|
77
75
|
@module_loader = @app.module_loader
|
|
78
76
|
|
|
79
77
|
require 'uringmachine/fiber_scheduler'
|
data/cmd/new.rb
CHANGED
|
@@ -44,7 +44,7 @@ template_path = File.join(__dir__, 'new/template')
|
|
|
44
44
|
begin
|
|
45
45
|
`mkdir -p "#{path}"`
|
|
46
46
|
system("cp -rv#{opts[:yes] ? '' : 'i'} #{template_path}/* \"#{path}/\"")
|
|
47
|
-
puts "Your app is ready in #{
|
|
47
|
+
puts "Your app is ready in #{full_path}"
|
|
48
48
|
rescue => e
|
|
49
49
|
p e
|
|
50
50
|
p e.backtrace
|
data/cmd/serve.rb
CHANGED
|
@@ -99,4 +99,30 @@ Fiber.set_scheduler(UM::FiberScheduler.new(env[:machine]))
|
|
|
99
99
|
require 'syntropy/dev_mode' if Syntropy.dev_mode
|
|
100
100
|
|
|
101
101
|
app = Syntropy::App.load(env)
|
|
102
|
+
|
|
103
|
+
if env[:mode] == 'development'
|
|
104
|
+
@main_fiber = Fiber.current
|
|
105
|
+
env[:machine].spin do
|
|
106
|
+
@app = app
|
|
107
|
+
@env = env
|
|
108
|
+
@machine = env[:machine]
|
|
109
|
+
@module_loader = @app.module_loader
|
|
110
|
+
|
|
111
|
+
loop do
|
|
112
|
+
puts "Enter command: (c + Enter to open console, q + Enter to quit)"
|
|
113
|
+
command = $stdin.gets.chomp
|
|
114
|
+
case command
|
|
115
|
+
when 'c'
|
|
116
|
+
t = Thread.new {
|
|
117
|
+
require 'irb'
|
|
118
|
+
IRB.start
|
|
119
|
+
}
|
|
120
|
+
t.join
|
|
121
|
+
when 'q'
|
|
122
|
+
@machine.schedule(@main_fiber, UM::Terminate.new)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
102
128
|
Syntropy.run(env) { app.call(it) }
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
FROM ruby:4.0-slim AS base
|
|
2
|
+
WORKDIR /syntropy
|
|
3
|
+
RUN apt-get update -qq && apt-get install -y \
|
|
4
|
+
curl \
|
|
5
|
+
build-essential \
|
|
6
|
+
libpq-dev \
|
|
7
|
+
libsqlite3-dev \
|
|
8
|
+
libssl-dev \
|
|
9
|
+
libyaml-dev \
|
|
10
|
+
pkg-config && \
|
|
11
|
+
rm -rf /var/lib/apt/lists /var/cache/apt/archives
|
|
12
|
+
|
|
13
|
+
ENV BUNDLE_PATH="/usr/local/bundle"
|
|
14
|
+
COPY Gemfile Gemfile.lock ./
|
|
15
|
+
RUN bundle install
|
|
16
|
+
|
|
17
|
+
RUN groupadd --system --gid 1000 syntropy && \
|
|
18
|
+
useradd syntropy --uid 1000 --gid 1000 --create-home --shell /bin/bash
|
|
19
|
+
USER 1000:1000
|
|
20
|
+
|
|
21
|
+
EXPOSE 1234
|
|
22
|
+
|
|
23
|
+
# Start the main process
|
|
24
|
+
# CMD ["bash"]
|
|
25
|
+
CMD ["bundle", "exec", "syntropy", "serve"]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# My awesome Syntropy app
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
$ bundle install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Running the web server
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
$ bundle exec syntropy serve
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Starting the app console
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
$ bundle exec syntropy console
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Running tests
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
$ bundle exec syntropy tests
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Usage with Docker Compose
|
|
28
|
+
|
|
29
|
+
You can also run the app on Docker Compose:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# run the app server
|
|
33
|
+
$ docker compose up
|
|
34
|
+
|
|
35
|
+
# run the console
|
|
36
|
+
$ docker compose run --remove-orphans console
|
|
37
|
+
|
|
38
|
+
# run tests
|
|
39
|
+
$ docker compose run --remove-orphans test
|
|
40
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export template { |**props|
|
|
2
|
+
html {
|
|
3
|
+
head {
|
|
4
|
+
title "My awesome Syntropy web app"
|
|
5
|
+
meta charset: 'utf-8'
|
|
6
|
+
meta name: 'viewport', content: 'width=device-width, initial-scale=1.0'
|
|
7
|
+
link rel: "stylesheet", href: 'https://cdn.simplecss.org/simple.min.css'
|
|
8
|
+
# link rel: "stylesheet", type: "text/css", href: "/assets/style.css"
|
|
9
|
+
}
|
|
10
|
+
body {
|
|
11
|
+
render_children(**props)
|
|
12
|
+
auto_refresh!
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export self
|
|
2
|
+
|
|
3
|
+
def connection_pool
|
|
4
|
+
@connection_pool ||= Storage::ConnectionPool.new(@machine, @env[:config][:storage][:path], 4)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def schema
|
|
8
|
+
Storage::Schema.new(module_loader: @module_loader, schema_root: '_schema')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def migrate!
|
|
12
|
+
schema.apply(connection_pool)
|
|
13
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as UI from './ui.js';
|
|
2
|
+
import * as Model from './model.js';
|
|
3
|
+
import * as Tools from './tools.js';
|
|
4
|
+
|
|
5
|
+
export async function runPromptExchange(callHandler) {
|
|
6
|
+
const section = UI.createNewSection();
|
|
7
|
+
const prompt = await UI.askPrompt(section);
|
|
8
|
+
UI.addSectionEntryMarkdown(section, `*Prompt*: ${prompt}`);
|
|
9
|
+
const ctx = Model.buildExchangeBody(prompt);
|
|
10
|
+
|
|
11
|
+
while (true) {
|
|
12
|
+
UI.addPendingEntry(section);
|
|
13
|
+
const response = await Model.query(ctx);
|
|
14
|
+
UI.removePendingEntry(section);
|
|
15
|
+
if (!(await processResponse(section, ctx, response, callHandler)))
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function processResponse(section, ctx, response, callHandler) {
|
|
21
|
+
const first_choice = response.choices[0];
|
|
22
|
+
if (first_choice.finish_reason == "tool_calls") {
|
|
23
|
+
Model.addMessage(ctx, first_choice.message);
|
|
24
|
+
const tool_calls = first_choice.message.tool_calls;
|
|
25
|
+
const tool_results = await performToolCalls(section, ctx, tool_calls, callHandler);
|
|
26
|
+
console.log("tool results", tool_results);
|
|
27
|
+
Model.mergeToolResults(ctx, tool_results);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
else if (first_choice.finish_reason == "stop") {
|
|
31
|
+
UI.addSectionEntryMarkdown(section, first_choice.message.content);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function performToolCalls(section, ctx, tool_calls, callHandler) {
|
|
38
|
+
const pending = tool_calls.map(async (c) => await runTool(section, ctx, callHandler, c));
|
|
39
|
+
return await Promise.all(pending);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function runTool(section, ctx, callHandler, call) {
|
|
43
|
+
UI.addSectionEntryMarkdown(section, `Running \`${call.function.name}(${call.function.arguments})\``);
|
|
44
|
+
const name = call.function.name;
|
|
45
|
+
const args = JSON.parse(call.function.arguments);
|
|
46
|
+
try {
|
|
47
|
+
const res = await callHandler(name, args);
|
|
48
|
+
return {
|
|
49
|
+
id: call.id,
|
|
50
|
+
result: res
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
return {
|
|
55
|
+
id: call.id,
|
|
56
|
+
error: e.message
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
runPromptExchange(Tools.toolHandler);
|