railcode 0.1.25 → 0.1.27
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.
- package/README.md +28 -7
- package/dist/index.js +201 -75
- package/dist/manifest.js +2 -2
- package/package.json +4 -1
- package/static/sdk.js +755 -54
package/static/sdk.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// ../sdk/src/inspector.ts
|
|
5
5
|
var entries = [];
|
|
6
6
|
var isOpen = localStorage.getItem("__sdk_open") === "1";
|
|
7
|
+
var selected = null;
|
|
7
8
|
var host = document.createElement("div");
|
|
8
9
|
host.id = "__sdk_inspector";
|
|
9
10
|
var root = host.attachShadow({ mode: "open" });
|
|
@@ -25,26 +26,42 @@
|
|
|
25
26
|
font-size:12px; padding:4px 9px; cursor:pointer; }
|
|
26
27
|
header button:hover { background:#232a33; color:#fff; }
|
|
27
28
|
.log { flex:1; overflow:auto; padding:6px 0; }
|
|
28
|
-
.row { display:grid; grid-template-columns: 12px
|
|
29
|
-
padding:7px 14px; border-bottom:1px solid #1d2127; }
|
|
29
|
+
.row { display:grid; grid-template-columns: 12px 1fr; gap:8px; align-items:start;
|
|
30
|
+
padding:7px 14px; border-bottom:1px solid #1d2127; cursor:pointer; }
|
|
31
|
+
.row:hover { background:#181c22; }
|
|
30
32
|
.row .dot { width:8px; height:8px; border-radius:50%; margin-top:5px; }
|
|
31
33
|
.row.pending .dot { background:#f59e0b; animation:pulse 1s infinite; }
|
|
32
34
|
.row.ok .dot { background:#34d399; } .row.error .dot { background:#f87171; }
|
|
33
35
|
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.3} }
|
|
34
|
-
.kind { font-size:9.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase;
|
|
35
|
-
padding:2px 6px; border-radius:4px; white-space:nowrap;
|
|
36
|
+
.kind { display:inline-block; font-size:9.5px; font-weight:700; letter-spacing:.05em; text-transform:uppercase;
|
|
37
|
+
padding:2px 6px; border-radius:4px; white-space:nowrap; margin-bottom:4px; }
|
|
36
38
|
.k-sql { background:#1e3a5f; color:#7cc4ff; } .k-db { background:#3a2752; color:#c79bff; }
|
|
37
39
|
.k-files { background:#13413c; color:#5fd6c4; } .k-llm { background:#4a2b16; color:#f6b26b; }
|
|
38
40
|
.k-me, .k-connections, .k-app-users, .k-roles { background:#2a2f38; color:#9aa4b2; }
|
|
39
41
|
.body { min-width:0; }
|
|
40
42
|
code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size:11.5px; color:#e6e8eb;
|
|
41
43
|
word-break:break-word; display:block; line-height:1.45; }
|
|
42
|
-
.res { color:#7b8493; font-size:11px; margin-top:2px; }
|
|
44
|
+
.res { color:#7b8493; font-size:11px; margin-top:2px; display:block; }
|
|
43
45
|
.res.error { color:#f87171; }
|
|
44
46
|
.res i { font-style:normal; color:#5b6472; }
|
|
45
47
|
.empty { color:#5b6472; text-align:center; padding:30px 16px; font-size:12px; }
|
|
46
48
|
footer { padding:9px 14px; border-top:1px solid #262b33; color:#5b6472; font-size:10.5px; }
|
|
47
49
|
footer code { display:inline; color:#7b8493; font-size:10.5px; }
|
|
50
|
+
.backdrop { position:fixed; inset:0; background:rgba(0,0,0,.5); z-index:2147483002;
|
|
51
|
+
display:none; align-items:center; justify-content:center; padding:24px; }
|
|
52
|
+
.backdrop.open { display:flex; }
|
|
53
|
+
.detail { background:#14171c; border:1px solid #262b33; border-radius:10px; width:560px;
|
|
54
|
+
max-width:100%; max-height:85vh; display:flex; flex-direction:column;
|
|
55
|
+
box-shadow:0 20px 60px rgba(0,0,0,.5); }
|
|
56
|
+
.detail .content { padding:14px; overflow:auto; }
|
|
57
|
+
.meta { display:flex; align-items:center; gap:10px; margin-bottom:12px; color:#7b8493; font-size:11.5px; }
|
|
58
|
+
.section { margin-bottom:14px; } .section:last-child { margin-bottom:0; }
|
|
59
|
+
.section .label { font-size:10px; text-transform:uppercase; letter-spacing:.05em; color:#5b6472;
|
|
60
|
+
margin-bottom:6px; font-weight:700; }
|
|
61
|
+
.detail pre { margin:0; white-space:pre-wrap; word-break:break-word; font-family: ui-monospace,
|
|
62
|
+
"SF Mono", Menlo, monospace; font-size:11.5px; line-height:1.5; color:#e6e8eb; background:#0e1013;
|
|
63
|
+
border:1px solid #1d2127; border-radius:6px; padding:10px 12px; max-height:320px; overflow:auto; }
|
|
64
|
+
.detail pre.error-text { color:#f87171; }
|
|
48
65
|
</style>
|
|
49
66
|
<div class="panel" id="panel">
|
|
50
67
|
<header>
|
|
@@ -55,22 +72,52 @@
|
|
|
55
72
|
</header>
|
|
56
73
|
<div class="log" id="log"></div>
|
|
57
74
|
<footer>Calls made via <code>postgres()</code>, <code>llm</code>, <code>db</code>, <code>files</code>, <code>me()</code>, <code>appUsers()</code>, <code>roles()</code></footer>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="backdrop" id="backdrop">
|
|
77
|
+
<div class="detail">
|
|
78
|
+
<header>
|
|
79
|
+
<div class="t">Call detail</div>
|
|
80
|
+
<div class="grow"></div>
|
|
81
|
+
<button id="detail-close">Close</button>
|
|
82
|
+
</header>
|
|
83
|
+
<div class="content" id="detail-content"></div>
|
|
84
|
+
</div>
|
|
58
85
|
</div>`;
|
|
59
86
|
function mount() {
|
|
60
87
|
(document.body || document.documentElement).appendChild(host);
|
|
61
88
|
root.getElementById("collapse").onclick = () => setOpen(false);
|
|
62
89
|
root.getElementById("clear").onclick = () => {
|
|
63
90
|
entries.length = 0;
|
|
91
|
+
selected = null;
|
|
64
92
|
render();
|
|
65
93
|
};
|
|
94
|
+
root.getElementById("log").addEventListener("click", (ev) => {
|
|
95
|
+
const rowEl = ev.target.closest(".row");
|
|
96
|
+
if (!rowEl) return;
|
|
97
|
+
const idx = Number(rowEl.dataset.idx);
|
|
98
|
+
const entry = entries[idx];
|
|
99
|
+
if (!entry) return;
|
|
100
|
+
selected = entry;
|
|
101
|
+
renderDetail();
|
|
102
|
+
});
|
|
103
|
+
root.getElementById("detail-close").onclick = () => closeDetail();
|
|
104
|
+
root.getElementById("backdrop").addEventListener("click", (ev) => {
|
|
105
|
+
if (ev.target === root.getElementById("backdrop")) closeDetail();
|
|
106
|
+
});
|
|
66
107
|
applyWidth();
|
|
67
108
|
}
|
|
68
109
|
if (document.body) mount();
|
|
69
110
|
else document.addEventListener("DOMContentLoaded", mount);
|
|
111
|
+
function closeDetail() {
|
|
112
|
+
selected = null;
|
|
113
|
+
renderDetail();
|
|
114
|
+
}
|
|
70
115
|
window.addEventListener("keydown", (e) => {
|
|
71
116
|
if (e.ctrlKey && !e.metaKey && !e.altKey && e.code === "Backquote") {
|
|
72
117
|
e.preventDefault();
|
|
73
118
|
setOpen(!isOpen);
|
|
119
|
+
} else if (e.code === "Escape" && selected) {
|
|
120
|
+
closeDetail();
|
|
74
121
|
}
|
|
75
122
|
});
|
|
76
123
|
function applyWidth() {
|
|
@@ -88,18 +135,69 @@
|
|
|
88
135
|
var escHtml = (s) => String(s == null ? "" : s).replace(/[&<>]/g, (c) => escMap[c]);
|
|
89
136
|
function render() {
|
|
90
137
|
const log = root.getElementById("log");
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
138
|
+
if (log) {
|
|
139
|
+
if (!entries.length) {
|
|
140
|
+
log.innerHTML = '<div class="empty">No calls yet. Interact with the app to see SDK activity.</div>';
|
|
141
|
+
} else {
|
|
142
|
+
log.innerHTML = entries.map((e, i) => {
|
|
143
|
+
const res = e.status === "pending" ? '<span class="res">running\u2026</span>' : e.status === "error" ? `<span class="res error">\u2715 ${escHtml(e.error)} <i>${e.ms}ms</i></span>` : `<span class="res">${escHtml(e.result)} <i>${e.ms}ms</i></span>`;
|
|
144
|
+
return `<div class="row ${e.status}" data-idx="${i}"><span class="dot"></span>
|
|
145
|
+
<div class="body">
|
|
146
|
+
<span class="kind k-${e.kind}">${e.kind}</span>
|
|
147
|
+
<code>${escHtml(e.label)}</code>${res}
|
|
148
|
+
</div></div>`;
|
|
149
|
+
}).join("");
|
|
150
|
+
log.scrollTop = log.scrollHeight;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
renderDetail();
|
|
154
|
+
}
|
|
155
|
+
function prettyPrint(value) {
|
|
156
|
+
if (value === void 0) return "";
|
|
157
|
+
if (typeof value === "string") {
|
|
158
|
+
try {
|
|
159
|
+
return JSON.stringify(JSON.parse(value), null, 2);
|
|
160
|
+
} catch {
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
return JSON.stringify(value, null, 2);
|
|
166
|
+
} catch {
|
|
167
|
+
return String(value);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function renderDetail() {
|
|
171
|
+
const backdrop = root.getElementById("backdrop");
|
|
172
|
+
const box = root.getElementById("detail-content");
|
|
173
|
+
if (!backdrop || !box) return;
|
|
174
|
+
if (!selected) {
|
|
175
|
+
backdrop.classList.remove("open");
|
|
176
|
+
box.innerHTML = "";
|
|
94
177
|
return;
|
|
95
178
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
179
|
+
const e = selected;
|
|
180
|
+
const statusLabel = e.status === "pending" ? "pending\u2026" : e.status === "error" ? `failed \xB7 ${e.ms}ms` : `ok \xB7 ${e.ms}ms`;
|
|
181
|
+
const sections = [
|
|
182
|
+
`<div class="section"><div class="label">Call</div><pre>${escHtml(e.full ?? e.label)}</pre></div>`
|
|
183
|
+
];
|
|
184
|
+
if (e.status === "error") {
|
|
185
|
+
sections.push(
|
|
186
|
+
`<div class="section"><div class="label">Error</div><pre class="error-text">${escHtml(prettyPrint(e.error))}</pre></div>`
|
|
187
|
+
);
|
|
188
|
+
if (e.errorStack) {
|
|
189
|
+
sections.push(
|
|
190
|
+
`<div class="section"><div class="label">Stack</div><pre class="error-text">${escHtml(e.errorStack)}</pre></div>`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
} else if (e.status === "ok") {
|
|
194
|
+
const result = e.resultFull !== void 0 ? prettyPrint(e.resultFull) : e.result ?? "";
|
|
195
|
+
sections.push(`<div class="section"><div class="label">Result</div><pre>${escHtml(result)}</pre></div>`);
|
|
196
|
+
}
|
|
197
|
+
box.innerHTML = `
|
|
198
|
+
<div class="meta"><span class="kind k-${e.kind}">${e.kind}</span><span>${statusLabel}</span></div>
|
|
199
|
+
${sections.join("")}`;
|
|
200
|
+
backdrop.classList.add("open");
|
|
103
201
|
}
|
|
104
202
|
var Inspector = {
|
|
105
203
|
add(entry) {
|
|
@@ -178,14 +276,15 @@
|
|
|
178
276
|
}
|
|
179
277
|
return shorten(String(res), 48);
|
|
180
278
|
}
|
|
181
|
-
function track(kind, label3, thunk) {
|
|
182
|
-
const entry = { kind, label: label3, status: "pending", t0: performance.now() };
|
|
279
|
+
function track(kind, label3, thunk, full) {
|
|
280
|
+
const entry = { kind, label: label3, full, status: "pending", t0: performance.now() };
|
|
183
281
|
Inspector.add(entry);
|
|
184
282
|
return thunk().then(
|
|
185
283
|
(res) => {
|
|
186
284
|
entry.status = "ok";
|
|
187
285
|
entry.ms = Math.round(performance.now() - entry.t0);
|
|
188
286
|
entry.result = summarize(res);
|
|
287
|
+
entry.resultFull = res;
|
|
189
288
|
Inspector.update();
|
|
190
289
|
return res;
|
|
191
290
|
},
|
|
@@ -193,29 +292,36 @@
|
|
|
193
292
|
entry.status = "error";
|
|
194
293
|
entry.ms = Math.round(performance.now() - entry.t0);
|
|
195
294
|
entry.error = err.message;
|
|
295
|
+
entry.errorStack = err.stack;
|
|
196
296
|
Inspector.update();
|
|
197
297
|
throw err;
|
|
198
298
|
}
|
|
199
299
|
);
|
|
200
300
|
}
|
|
201
|
-
function trackStream(kind, label3, streamFactory) {
|
|
202
|
-
const entry = { kind, label: label3, status: "pending", t0: performance.now() };
|
|
301
|
+
function trackStream(kind, label3, streamFactory, full) {
|
|
302
|
+
const entry = { kind, label: label3, full, status: "pending", t0: performance.now() };
|
|
203
303
|
Inspector.add(entry);
|
|
204
304
|
return async function* () {
|
|
205
305
|
let textLen = 0;
|
|
306
|
+
let streamed = "";
|
|
206
307
|
try {
|
|
207
308
|
for await (const event of streamFactory()) {
|
|
208
|
-
if (event && event.type === "text")
|
|
309
|
+
if (event && event.type === "text") {
|
|
310
|
+
textLen += event.text.length;
|
|
311
|
+
streamed += event.text;
|
|
312
|
+
}
|
|
209
313
|
yield event;
|
|
210
314
|
}
|
|
211
315
|
entry.status = "ok";
|
|
212
316
|
entry.ms = Math.round(performance.now() - entry.t0);
|
|
213
317
|
entry.result = textLen ? `${textLen} chars streamed` : "done";
|
|
318
|
+
entry.resultFull = streamed || void 0;
|
|
214
319
|
Inspector.update();
|
|
215
320
|
} catch (err) {
|
|
216
321
|
entry.status = "error";
|
|
217
322
|
entry.ms = Math.round(performance.now() - entry.t0);
|
|
218
323
|
entry.error = err.message;
|
|
324
|
+
entry.errorStack = err.stack;
|
|
219
325
|
Inspector.update();
|
|
220
326
|
throw err;
|
|
221
327
|
}
|
|
@@ -265,6 +371,191 @@
|
|
|
265
371
|
}).catch(() => {
|
|
266
372
|
});
|
|
267
373
|
|
|
374
|
+
// ../sdk/src/app-bar.ts
|
|
375
|
+
var BAR_H = 46;
|
|
376
|
+
function hostUrls(who) {
|
|
377
|
+
const host2 = location.host;
|
|
378
|
+
const proto = location.protocol;
|
|
379
|
+
const two = `${who.app.slug}.${who.org.slug}.`;
|
|
380
|
+
const one = `${who.app.slug}.`;
|
|
381
|
+
if (host2.startsWith(two)) {
|
|
382
|
+
const parent = host2.slice(two.length);
|
|
383
|
+
return { login: `${proto}//${parent}/login`, launcher: `${proto}//${who.org.slug}.${parent}/` };
|
|
384
|
+
}
|
|
385
|
+
if (host2.startsWith(one)) {
|
|
386
|
+
const parent = host2.slice(one.length);
|
|
387
|
+
return { login: `${proto}//${parent}/login`, launcher: `${proto}//${parent}/` };
|
|
388
|
+
}
|
|
389
|
+
return { login: "/login", launcher: "/" };
|
|
390
|
+
}
|
|
391
|
+
function initials(name, email2) {
|
|
392
|
+
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
393
|
+
if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
394
|
+
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
395
|
+
return (email2.trim()[0] || "?").toUpperCase();
|
|
396
|
+
}
|
|
397
|
+
function mount3(who) {
|
|
398
|
+
const urls2 = hostUrls(who);
|
|
399
|
+
const displayName = who.user.name?.trim() || who.user.email;
|
|
400
|
+
const host2 = document.createElement("div");
|
|
401
|
+
host2.id = "__rc_appbar";
|
|
402
|
+
const root2 = host2.attachShadow({ mode: "open" });
|
|
403
|
+
root2.innerHTML = `
|
|
404
|
+
<style>
|
|
405
|
+
:host { all: initial; }
|
|
406
|
+
* { box-sizing: border-box; font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; }
|
|
407
|
+
|
|
408
|
+
/* Railcode tokens \u2014 light. */
|
|
409
|
+
.rc {
|
|
410
|
+
--h: ${BAR_H}px; --radius: 8px;
|
|
411
|
+
--bg: #ffffff; --fg: #1a1f36; --muted: #687385; --accent: #f0f1f4;
|
|
412
|
+
--border: #e6e8eb; --primary: #2563eb; --ring: #2563eb;
|
|
413
|
+
}
|
|
414
|
+
@media (prefers-color-scheme: dark) {
|
|
415
|
+
.rc {
|
|
416
|
+
--bg: #1f1f23; --fg: #ededee; --muted: #9b9ba3; --accent: #27272b;
|
|
417
|
+
--border: #2c2c31; --primary: #60a5fa; --ring: #60a5fa;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.bar {
|
|
422
|
+
position: fixed; top: 0; left: 0; right: 0; z-index: 2147483000;
|
|
423
|
+
height: var(--h); display: grid; grid-template-columns: 1fr auto 1fr;
|
|
424
|
+
align-items: center; gap: 12px; padding: 0 12px;
|
|
425
|
+
background: var(--bg); color: var(--fg);
|
|
426
|
+
border-bottom: 1px solid var(--border);
|
|
427
|
+
transition: transform 180ms ease;
|
|
428
|
+
-webkit-font-smoothing: antialiased;
|
|
429
|
+
}
|
|
430
|
+
.rc.collapsed .bar { transform: translateY(-100%); }
|
|
431
|
+
|
|
432
|
+
/* Left: Railcode \u2192 launcher */
|
|
433
|
+
.brand {
|
|
434
|
+
justify-self: start; display: inline-flex; align-items: center;
|
|
435
|
+
padding: 4px 6px; border: 0; background: transparent; cursor: pointer;
|
|
436
|
+
color: inherit; border-radius: var(--radius); font-size: 14.5px;
|
|
437
|
+
font-weight: 600; letter-spacing: -0.01em; line-height: 1;
|
|
438
|
+
transition: background-color 120ms ease;
|
|
439
|
+
}
|
|
440
|
+
.brand:hover { background: color-mix(in srgb, var(--fg) 4%, transparent); }
|
|
441
|
+
|
|
442
|
+
/* Center: app name */
|
|
443
|
+
.app {
|
|
444
|
+
justify-self: center; min-width: 0; font-size: 13px; font-weight: 500;
|
|
445
|
+
letter-spacing: -0.005em; white-space: nowrap; overflow: hidden;
|
|
446
|
+
text-overflow: ellipsis; color: var(--fg);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/* Right: user + logout + collapse */
|
|
450
|
+
.right { justify-self: end; display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
|
|
451
|
+
.user { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
|
|
452
|
+
.avatar {
|
|
453
|
+
width: 24px; height: 24px; border-radius: 999px; display: grid;
|
|
454
|
+
place-items: center; font-size: 10.5px; font-weight: 600; flex: none;
|
|
455
|
+
color: var(--primary); background: color-mix(in srgb, var(--primary) 14%, transparent);
|
|
456
|
+
}
|
|
457
|
+
.name {
|
|
458
|
+
font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden;
|
|
459
|
+
text-overflow: ellipsis; max-width: 160px; color: var(--fg);
|
|
460
|
+
}
|
|
461
|
+
.sep { width: 1px; height: 18px; background: var(--border); flex: none; }
|
|
462
|
+
|
|
463
|
+
.logout {
|
|
464
|
+
display: inline-flex; align-items: center; gap: 6px; height: 28px;
|
|
465
|
+
padding: 0 10px; border: 1px solid transparent; border-radius: var(--radius);
|
|
466
|
+
background: transparent; color: var(--muted); font: inherit; font-size: 12.5px;
|
|
467
|
+
font-weight: 500; cursor: pointer; transition: background-color 120ms ease, color 120ms ease;
|
|
468
|
+
}
|
|
469
|
+
.logout:hover { background: var(--accent); color: var(--fg); }
|
|
470
|
+
.logout:focus-visible { outline: 2px solid color-mix(in srgb, var(--ring) 45%, transparent); outline-offset: 1px; }
|
|
471
|
+
|
|
472
|
+
.collapse {
|
|
473
|
+
display: grid; place-items: center; width: 28px; height: 28px;
|
|
474
|
+
border: 1px solid transparent; border-radius: var(--radius); background: transparent;
|
|
475
|
+
color: var(--muted); cursor: pointer; transition: background-color 120ms ease, color 120ms ease;
|
|
476
|
+
}
|
|
477
|
+
.collapse:hover { background: var(--accent); color: var(--fg); }
|
|
478
|
+
.collapse:focus-visible { outline: 2px solid color-mix(in srgb, var(--ring) 45%, transparent); outline-offset: 1px; }
|
|
479
|
+
|
|
480
|
+
/* Reopen caret \u2014 near-transparent while it sits over the app; solidifies on
|
|
481
|
+
hover. Click only (no hover-to-open). A light blur keeps it legible over
|
|
482
|
+
busy content without reading as a solid chip. */
|
|
483
|
+
.reveal {
|
|
484
|
+
position: fixed; top: 0; left: 50%; z-index: 2147482999;
|
|
485
|
+
transform: translateX(-50%) translateY(-100%);
|
|
486
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
487
|
+
height: 20px; padding: 0 16px 1px;
|
|
488
|
+
border: 1px solid color-mix(in srgb, var(--border) 45%, transparent); border-top: 0;
|
|
489
|
+
border-radius: 0 0 var(--radius) var(--radius);
|
|
490
|
+
background: color-mix(in srgb, var(--bg) 10%, transparent);
|
|
491
|
+
color: color-mix(in srgb, var(--muted) 60%, transparent);
|
|
492
|
+
-webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
|
|
493
|
+
cursor: pointer;
|
|
494
|
+
transition: transform 180ms ease, background-color 120ms ease, color 120ms ease, border-color 120ms ease;
|
|
495
|
+
}
|
|
496
|
+
.rc.collapsed .reveal { transform: translateX(-50%) translateY(0); }
|
|
497
|
+
.reveal:hover { background: var(--bg); border-color: var(--border); color: var(--fg); }
|
|
498
|
+
|
|
499
|
+
svg { display: block; }
|
|
500
|
+
</style>
|
|
501
|
+
|
|
502
|
+
<div class="rc collapsed">
|
|
503
|
+
<header class="bar">
|
|
504
|
+
<button class="brand" type="button" title="Back to apps">Railcode</button>
|
|
505
|
+
<span class="app"></span>
|
|
506
|
+
<div class="right">
|
|
507
|
+
<div class="user">
|
|
508
|
+
<span class="avatar"></span>
|
|
509
|
+
<span class="name"></span>
|
|
510
|
+
</div>
|
|
511
|
+
<span class="sep"></span>
|
|
512
|
+
<button class="logout" type="button">
|
|
513
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
514
|
+
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/>
|
|
515
|
+
<polyline points="16 17 21 12 16 7"/>
|
|
516
|
+
<line x1="21" y1="12" x2="9" y2="12"/>
|
|
517
|
+
</svg>
|
|
518
|
+
Log out
|
|
519
|
+
</button>
|
|
520
|
+
<button class="collapse" type="button" title="Collapse bar">
|
|
521
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
522
|
+
<polyline points="18 15 12 9 6 15"/>
|
|
523
|
+
</svg>
|
|
524
|
+
</button>
|
|
525
|
+
</div>
|
|
526
|
+
</header>
|
|
527
|
+
|
|
528
|
+
<button class="reveal" type="button" title="Show bar">
|
|
529
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
530
|
+
<polyline points="6 9 12 15 18 9"/>
|
|
531
|
+
</svg>
|
|
532
|
+
</button>
|
|
533
|
+
</div>`;
|
|
534
|
+
const rc = root2.querySelector(".rc");
|
|
535
|
+
root2.querySelector(".app").textContent = who.app.name || who.app.slug;
|
|
536
|
+
root2.querySelector(".name").textContent = displayName;
|
|
537
|
+
root2.querySelector(".avatar").textContent = initials(who.user.name || "", who.user.email);
|
|
538
|
+
const docEl = document.documentElement;
|
|
539
|
+
docEl.style.transition = "padding-top 180ms ease";
|
|
540
|
+
const setCollapsed = (collapsed) => {
|
|
541
|
+
rc.classList.toggle("collapsed", collapsed);
|
|
542
|
+
docEl.style.paddingTop = collapsed ? "" : `${BAR_H}px`;
|
|
543
|
+
};
|
|
544
|
+
root2.querySelector(".brand").onclick = () => location.assign(urls2.launcher);
|
|
545
|
+
root2.querySelector(".collapse").onclick = () => setCollapsed(true);
|
|
546
|
+
root2.querySelector(".reveal").onclick = () => setCollapsed(false);
|
|
547
|
+
root2.querySelector(".logout").onclick = async () => {
|
|
548
|
+
try {
|
|
549
|
+
await fetch("/_api/logout", { method: "POST", credentials: "include" });
|
|
550
|
+
} catch {
|
|
551
|
+
}
|
|
552
|
+
location.assign(urls2.login);
|
|
553
|
+
};
|
|
554
|
+
(document.body || document.documentElement).appendChild(host2);
|
|
555
|
+
}
|
|
556
|
+
me().then(mount3).catch(() => {
|
|
557
|
+
});
|
|
558
|
+
|
|
268
559
|
// ../sdk/src/agents.ts
|
|
269
560
|
var TERMINAL = ["success", "failed", "cancelled", "limit_exceeded"];
|
|
270
561
|
var POLL_MIN_MS = 400;
|
|
@@ -314,25 +605,28 @@
|
|
|
314
605
|
rows.truncated = r.truncated;
|
|
315
606
|
return rows;
|
|
316
607
|
};
|
|
317
|
-
var runSQL = (
|
|
318
|
-
const
|
|
319
|
-
const
|
|
608
|
+
var runSQL = (engine2, connection, query2, params) => {
|
|
609
|
+
const trimmedQuery = query2.replace(/\s+/g, " ").trim();
|
|
610
|
+
const paramsJson = params && params.length ? JSON.stringify(params) : "";
|
|
611
|
+
const label3 = `${engine2 ?? "data"}(${q(connection)}).runSQL(${q(shorten(trimmedQuery, QUERY_LABEL_MAX))}${paramsJson ? `, ${shorten(paramsJson, PARAMS_LABEL_MAX)}` : ""})`;
|
|
612
|
+
const full = `${engine2 ?? "data"}(${q(connection)}).runSQL(${q(trimmedQuery)}${paramsJson ? `, ${paramsJson}` : ""})`;
|
|
320
613
|
return track(
|
|
321
614
|
"sql",
|
|
322
615
|
label3,
|
|
323
616
|
() => call("POST", "/sql", {
|
|
324
617
|
// engine is advisory; omit it for the generic data() namespace so the backend
|
|
325
618
|
// dispatches on the connection's stored kind. postgres() pins engine="postgres".
|
|
326
|
-
body: { ...
|
|
327
|
-
}).then(toSqlRows)
|
|
619
|
+
body: { ...engine2 ? { engine: engine2 } : {}, connection, query: query2, params: params || [] }
|
|
620
|
+
}).then(toSqlRows),
|
|
621
|
+
full
|
|
328
622
|
);
|
|
329
623
|
};
|
|
330
|
-
var namespace = (
|
|
624
|
+
var namespace = (engine2) => {
|
|
331
625
|
const handle = (connection) => ({
|
|
332
|
-
runSQL: (query2, params) => runSQL(
|
|
626
|
+
runSQL: (query2, params) => runSQL(engine2, connection, query2, params)
|
|
333
627
|
});
|
|
334
628
|
const ns = handle;
|
|
335
|
-
ns.runSQL = (query2, params) => runSQL(
|
|
629
|
+
ns.runSQL = (query2, params) => runSQL(engine2, "default", query2, params);
|
|
336
630
|
return ns;
|
|
337
631
|
};
|
|
338
632
|
var data = namespace();
|
|
@@ -442,13 +736,19 @@
|
|
|
442
736
|
}
|
|
443
737
|
/** Upsert ``key`` → ``value``; returns the stored value. */
|
|
444
738
|
put(key, value) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
739
|
+
const valueJson = JSON.stringify(value);
|
|
740
|
+
return track(
|
|
741
|
+
"db",
|
|
742
|
+
this.label(`put(${q(key)}, ${shorten(valueJson, 40)})`),
|
|
743
|
+
async () => {
|
|
744
|
+
const record = await call("PUT", `/kv/${encPath(this.name)}/${encPath(key)}`, {
|
|
745
|
+
body: { value },
|
|
746
|
+
query: scopeParams(this.scope)
|
|
747
|
+
});
|
|
748
|
+
return record.value;
|
|
749
|
+
},
|
|
750
|
+
this.label(`put(${q(key)}, ${valueJson})`)
|
|
751
|
+
);
|
|
452
752
|
}
|
|
453
753
|
delete(key) {
|
|
454
754
|
return track(
|
|
@@ -579,12 +879,12 @@
|
|
|
579
879
|
async function urls(scope, names) {
|
|
580
880
|
return track("files", `${scopeLabel2(scope)}.urls(<${names.length} names>)`, async () => {
|
|
581
881
|
const unique = [...new Set(names)];
|
|
582
|
-
const
|
|
882
|
+
const now2 = Date.now();
|
|
583
883
|
const items = /* @__PURE__ */ new Map();
|
|
584
884
|
const unresolved = [];
|
|
585
885
|
for (const name of unique) {
|
|
586
886
|
const cached = urlCache.get(cacheKey(scope, name));
|
|
587
|
-
if (cached && cached.expiresAt >
|
|
887
|
+
if (cached && cached.expiresAt > now2 + URL_EXPIRY_MARGIN_MS) items.set(name, cached.item);
|
|
588
888
|
else {
|
|
589
889
|
urlCache.delete(cacheKey(scope, name));
|
|
590
890
|
unresolved.push(name);
|
|
@@ -599,7 +899,7 @@
|
|
|
599
899
|
missing = resolved.missing;
|
|
600
900
|
for (const item of resolved.items) {
|
|
601
901
|
const normalized = { ...item, url: new URL(item.url, location.origin).toString() };
|
|
602
|
-
const expiresAt = item.expires_in === null ? Number.POSITIVE_INFINITY :
|
|
902
|
+
const expiresAt = item.expires_in === null ? Number.POSITIVE_INFINITY : now2 + item.expires_in * 1e3;
|
|
603
903
|
urlCache.set(cacheKey(scope, item.name), { item: normalized, expiresAt });
|
|
604
904
|
items.set(item.name, normalized);
|
|
605
905
|
}
|
|
@@ -640,11 +940,383 @@
|
|
|
640
940
|
delete: shared2.delete
|
|
641
941
|
};
|
|
642
942
|
|
|
943
|
+
// ../sdk/src/tool-loop.ts
|
|
944
|
+
var OBSERVATION_CHARS = 6e3;
|
|
945
|
+
var DEFAULT_LIMITS = {
|
|
946
|
+
maxSteps: 8,
|
|
947
|
+
maxToolCalls: 30,
|
|
948
|
+
timeoutMs: 12e4
|
|
949
|
+
};
|
|
950
|
+
var LlmRunError = class extends Error {
|
|
951
|
+
constructor(message, step, cause) {
|
|
952
|
+
super(message);
|
|
953
|
+
this.name = "LlmRunError";
|
|
954
|
+
this.step = step;
|
|
955
|
+
this.cause = cause;
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
var now = () => typeof performance !== "undefined" && performance.now ? performance.now() : Date.now();
|
|
959
|
+
var toMessages = (input) => typeof input === "string" ? [{ role: "user", content: input }] : input.map((m) => ({ ...m }));
|
|
960
|
+
var errMessage = (err) => err instanceof Error ? err.message : typeof err === "string" ? err : JSON.stringify(err);
|
|
961
|
+
function clip(text, n) {
|
|
962
|
+
return text.length > n ? `${text.slice(0, n)}\u2026 (${text.length - n} more chars)` : text;
|
|
963
|
+
}
|
|
964
|
+
function defaultStringify(value) {
|
|
965
|
+
if (value === void 0) return "undefined";
|
|
966
|
+
try {
|
|
967
|
+
const s = JSON.stringify(value);
|
|
968
|
+
return s === void 0 ? String(value) : s;
|
|
969
|
+
} catch {
|
|
970
|
+
return String(value);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
function summarizeResult(tool, raw) {
|
|
974
|
+
if (tool.summarize) {
|
|
975
|
+
try {
|
|
976
|
+
return clip(String(tool.summarize(raw)), OBSERVATION_CHARS);
|
|
977
|
+
} catch {
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return clip(defaultStringify(raw), OBSERVATION_CHARS);
|
|
981
|
+
}
|
|
982
|
+
var JS_TYPE_OK = {
|
|
983
|
+
string: (v) => typeof v === "string",
|
|
984
|
+
number: (v) => typeof v === "number" && !Number.isNaN(v),
|
|
985
|
+
integer: (v) => typeof v === "number" && Number.isInteger(v),
|
|
986
|
+
boolean: (v) => typeof v === "boolean",
|
|
987
|
+
object: (v) => typeof v === "object" && v !== null && !Array.isArray(v),
|
|
988
|
+
array: (v) => Array.isArray(v),
|
|
989
|
+
null: (v) => v === null
|
|
990
|
+
};
|
|
991
|
+
function typeMatches(type, value) {
|
|
992
|
+
const allowed = Array.isArray(type) ? type : [type];
|
|
993
|
+
return allowed.some((t) => typeof t === "string" && JS_TYPE_OK[t]?.(value));
|
|
994
|
+
}
|
|
995
|
+
function validateArgs(schema, args) {
|
|
996
|
+
if (!schema || typeof schema !== "object") return null;
|
|
997
|
+
if (schema.type && schema.type !== "object") {
|
|
998
|
+
return typeMatches(schema.type, args) ? null : `arguments must be of type ${schema.type}`;
|
|
999
|
+
}
|
|
1000
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
1001
|
+
return "arguments must be an object";
|
|
1002
|
+
}
|
|
1003
|
+
const obj = args;
|
|
1004
|
+
for (const key of schema.required ?? []) {
|
|
1005
|
+
if (!(key in obj)) return `missing required property "${key}"`;
|
|
1006
|
+
}
|
|
1007
|
+
const props = schema.properties ?? {};
|
|
1008
|
+
for (const [key, spec] of Object.entries(props)) {
|
|
1009
|
+
if (!(key in obj) || spec == null) continue;
|
|
1010
|
+
const value = obj[key];
|
|
1011
|
+
if (spec.type && !typeMatches(spec.type, value)) {
|
|
1012
|
+
return `property "${key}" must be of type ${JSON.stringify(spec.type)}`;
|
|
1013
|
+
}
|
|
1014
|
+
if (Array.isArray(spec.enum) && !spec.enum.includes(value)) {
|
|
1015
|
+
return `property "${key}" must be one of ${JSON.stringify(spec.enum)}`;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
function assistantTurn(text, calls) {
|
|
1021
|
+
const decided = calls.map((c) => `${c.name}(${defaultStringify(c.arguments)})`).join(", ");
|
|
1022
|
+
const content = text ? `${text}
|
|
1023
|
+
|
|
1024
|
+
[calling tools: ${decided}]` : `[calling tools: ${decided}]`;
|
|
1025
|
+
return { role: "assistant", content };
|
|
1026
|
+
}
|
|
1027
|
+
function observationTurn(lines) {
|
|
1028
|
+
return { role: "user", content: `Tool results:
|
|
1029
|
+
${lines.join("\n")}` };
|
|
1030
|
+
}
|
|
1031
|
+
var RunLedger = class {
|
|
1032
|
+
constructor() {
|
|
1033
|
+
this.usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
1034
|
+
this.model = "";
|
|
1035
|
+
this.provider = "";
|
|
1036
|
+
this.finishReason = null;
|
|
1037
|
+
this.requestId = "";
|
|
1038
|
+
this.costSum = 0;
|
|
1039
|
+
this.costSeen = false;
|
|
1040
|
+
this.costMissing = false;
|
|
1041
|
+
}
|
|
1042
|
+
turn(t) {
|
|
1043
|
+
this.usage.inputTokens += t.usage.inputTokens;
|
|
1044
|
+
this.usage.outputTokens += t.usage.outputTokens;
|
|
1045
|
+
this.usage.totalTokens += t.usage.totalTokens;
|
|
1046
|
+
if (t.model) this.model = t.model;
|
|
1047
|
+
if (t.provider) this.provider = t.provider;
|
|
1048
|
+
if (t.finishReason !== void 0) this.finishReason = t.finishReason;
|
|
1049
|
+
if (t.requestId) this.requestId = t.requestId;
|
|
1050
|
+
const parsed = t.cost == null ? NaN : Number(t.cost);
|
|
1051
|
+
if (Number.isFinite(parsed)) {
|
|
1052
|
+
this.costSum += parsed;
|
|
1053
|
+
this.costSeen = true;
|
|
1054
|
+
} else {
|
|
1055
|
+
this.costMissing = true;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
/** Sum of per-turn costs; null unless every turn reported one. */
|
|
1059
|
+
get cost() {
|
|
1060
|
+
if (!this.costSeen || this.costMissing) return null;
|
|
1061
|
+
return this.costSum.toFixed(10).replace(/0+$/, "").replace(/\.$/, "");
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
var toDefs = (tools2) => tools2.map((t) => ({ name: t.name, description: t.description, schema: t.schema }));
|
|
1065
|
+
async function* engine(input, opts, wire2, streamFinal) {
|
|
1066
|
+
const tools2 = opts.tools ?? [];
|
|
1067
|
+
const toolsByName = new Map(tools2.map((t) => [t.name, t]));
|
|
1068
|
+
const toolDefs = toDefs(tools2);
|
|
1069
|
+
const limits = { ...DEFAULT_LIMITS, ...opts.limits };
|
|
1070
|
+
const { system: baseSystem, model, provider, output, maxOutputTokens, metadata } = opts;
|
|
1071
|
+
const messages = toMessages(input);
|
|
1072
|
+
const steps = [];
|
|
1073
|
+
const ledger = new RunLedger();
|
|
1074
|
+
if (model) ledger.model = model;
|
|
1075
|
+
let toolExecs = 0;
|
|
1076
|
+
let turn = 0;
|
|
1077
|
+
let finalText = "";
|
|
1078
|
+
let finalOutput = null;
|
|
1079
|
+
let hasStructuredOutput = false;
|
|
1080
|
+
let stopReason = "end";
|
|
1081
|
+
const controller = new AbortController();
|
|
1082
|
+
let abortReason = "aborted";
|
|
1083
|
+
const onExternalAbort = () => {
|
|
1084
|
+
abortReason = "aborted";
|
|
1085
|
+
controller.abort();
|
|
1086
|
+
};
|
|
1087
|
+
if (opts.signal) {
|
|
1088
|
+
if (opts.signal.aborted) controller.abort();
|
|
1089
|
+
else opts.signal.addEventListener("abort", onExternalAbort, { once: true });
|
|
1090
|
+
}
|
|
1091
|
+
const timer = setTimeout(() => {
|
|
1092
|
+
abortReason = "timeout";
|
|
1093
|
+
controller.abort();
|
|
1094
|
+
}, limits.timeoutMs);
|
|
1095
|
+
const signal = controller.signal;
|
|
1096
|
+
const budgetSystem = () => {
|
|
1097
|
+
if (!limits.maxSteps) return baseSystem;
|
|
1098
|
+
const line = `You have used ${turn} of ${limits.maxSteps} tool steps.`;
|
|
1099
|
+
return baseSystem ? `${baseSystem}
|
|
1100
|
+
|
|
1101
|
+
${line}` : line;
|
|
1102
|
+
};
|
|
1103
|
+
async function* planTurn(system) {
|
|
1104
|
+
const turnOpts = {
|
|
1105
|
+
system,
|
|
1106
|
+
tools: toolDefs,
|
|
1107
|
+
model,
|
|
1108
|
+
provider,
|
|
1109
|
+
maxOutputTokens,
|
|
1110
|
+
metadata,
|
|
1111
|
+
signal
|
|
1112
|
+
};
|
|
1113
|
+
if (!streamFinal) {
|
|
1114
|
+
try {
|
|
1115
|
+
const result = await wire2.generate(messages, turnOpts);
|
|
1116
|
+
ledger.turn(result);
|
|
1117
|
+
return { text: result.text, toolCalls: result.toolCalls ?? [] };
|
|
1118
|
+
} catch (err) {
|
|
1119
|
+
if (signal.aborted) return { text: "", toolCalls: [] };
|
|
1120
|
+
throw err;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
let turnText = "";
|
|
1124
|
+
let toolCalls = [];
|
|
1125
|
+
try {
|
|
1126
|
+
for await (const event of wire2.stream(messages, turnOpts)) {
|
|
1127
|
+
if (signal.aborted) break;
|
|
1128
|
+
if (event.type === "text") {
|
|
1129
|
+
turnText += event.text;
|
|
1130
|
+
yield { type: "text", text: event.text };
|
|
1131
|
+
} else if (event.type === "done") {
|
|
1132
|
+
ledger.turn(event);
|
|
1133
|
+
toolCalls = event.toolCalls ?? [];
|
|
1134
|
+
} else if (event.type === "error") {
|
|
1135
|
+
throw new LlmRunError(event.message, turn, event);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
} catch (err) {
|
|
1139
|
+
if (!signal.aborted) throw err;
|
|
1140
|
+
}
|
|
1141
|
+
return { text: turnText, toolCalls };
|
|
1142
|
+
}
|
|
1143
|
+
try {
|
|
1144
|
+
planning: while (true) {
|
|
1145
|
+
if (signal.aborted) {
|
|
1146
|
+
stopReason = abortReason;
|
|
1147
|
+
break;
|
|
1148
|
+
}
|
|
1149
|
+
if (turn >= limits.maxSteps) {
|
|
1150
|
+
stopReason = "max_steps";
|
|
1151
|
+
break;
|
|
1152
|
+
}
|
|
1153
|
+
const system = budgetSystem();
|
|
1154
|
+
turn += 1;
|
|
1155
|
+
const result = yield* planTurn(system);
|
|
1156
|
+
if (signal.aborted) {
|
|
1157
|
+
finalText = result.text;
|
|
1158
|
+
stopReason = abortReason;
|
|
1159
|
+
break;
|
|
1160
|
+
}
|
|
1161
|
+
if (result.toolCalls.length === 0) {
|
|
1162
|
+
finalText = result.text;
|
|
1163
|
+
stopReason = "end";
|
|
1164
|
+
break;
|
|
1165
|
+
}
|
|
1166
|
+
messages.push(assistantTurn(result.text, result.toolCalls));
|
|
1167
|
+
const observations = [];
|
|
1168
|
+
for (const call2 of result.toolCalls) {
|
|
1169
|
+
if (signal.aborted) {
|
|
1170
|
+
messages.push(observationTurn(observations.length ? observations : ["(cancelled)"]));
|
|
1171
|
+
stopReason = abortReason;
|
|
1172
|
+
break planning;
|
|
1173
|
+
}
|
|
1174
|
+
if (toolExecs >= limits.maxToolCalls) {
|
|
1175
|
+
observations.push(`- ${call2.name}: skipped (tool-call budget exhausted)`);
|
|
1176
|
+
stopReason = "max_tool_calls";
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
toolExecs += 1;
|
|
1180
|
+
const step = {
|
|
1181
|
+
id: call2.id || `step_${toolExecs}`,
|
|
1182
|
+
index: toolExecs,
|
|
1183
|
+
tool: call2.name,
|
|
1184
|
+
args: call2.arguments,
|
|
1185
|
+
status: "running",
|
|
1186
|
+
result: null,
|
|
1187
|
+
error: null,
|
|
1188
|
+
ms: null
|
|
1189
|
+
};
|
|
1190
|
+
steps.push(step);
|
|
1191
|
+
yield { type: "step", step: { ...step } };
|
|
1192
|
+
const started = now();
|
|
1193
|
+
let observation;
|
|
1194
|
+
const tool = toolsByName.get(call2.name);
|
|
1195
|
+
if (!tool) {
|
|
1196
|
+
step.status = "error";
|
|
1197
|
+
step.error = `Unknown tool "${call2.name}"`;
|
|
1198
|
+
observation = step.error;
|
|
1199
|
+
} else {
|
|
1200
|
+
const invalid = validateArgs(tool.schema, call2.arguments);
|
|
1201
|
+
if (invalid) {
|
|
1202
|
+
step.status = "error";
|
|
1203
|
+
step.error = invalid;
|
|
1204
|
+
observation = `Invalid arguments: ${invalid}`;
|
|
1205
|
+
} else {
|
|
1206
|
+
try {
|
|
1207
|
+
const raw = await tool.run(call2.arguments, { signal, step: turn });
|
|
1208
|
+
step.status = "ok";
|
|
1209
|
+
step.result = raw;
|
|
1210
|
+
observation = summarizeResult(tool, raw);
|
|
1211
|
+
} catch (err) {
|
|
1212
|
+
step.status = "error";
|
|
1213
|
+
step.error = errMessage(err);
|
|
1214
|
+
observation = `Error: ${step.error}`;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
step.ms = Math.round(now() - started);
|
|
1219
|
+
observations.push(`- ${call2.name} [${step.id}]: ${observation}`);
|
|
1220
|
+
yield { type: "step", step: { ...step } };
|
|
1221
|
+
}
|
|
1222
|
+
messages.push(observationTurn(observations));
|
|
1223
|
+
if (stopReason === "max_tool_calls") break;
|
|
1224
|
+
}
|
|
1225
|
+
if (output && output.type === "json" && stopReason === "end" && !hasStructuredOutput) {
|
|
1226
|
+
const result = await wire2.generate(messages, {
|
|
1227
|
+
system: baseSystem,
|
|
1228
|
+
output,
|
|
1229
|
+
model,
|
|
1230
|
+
provider,
|
|
1231
|
+
maxOutputTokens,
|
|
1232
|
+
metadata,
|
|
1233
|
+
signal
|
|
1234
|
+
});
|
|
1235
|
+
ledger.turn(result);
|
|
1236
|
+
finalText = result.text;
|
|
1237
|
+
finalOutput = result.output;
|
|
1238
|
+
hasStructuredOutput = true;
|
|
1239
|
+
if (streamFinal && finalText) yield { type: "text", text: finalText };
|
|
1240
|
+
}
|
|
1241
|
+
if (finalText) messages.push({ role: "assistant", content: finalText });
|
|
1242
|
+
yield {
|
|
1243
|
+
type: "done",
|
|
1244
|
+
usage: ledger.usage,
|
|
1245
|
+
cost: ledger.cost,
|
|
1246
|
+
provider: ledger.provider,
|
|
1247
|
+
model: ledger.model,
|
|
1248
|
+
finishReason: ledger.finishReason,
|
|
1249
|
+
requestId: ledger.requestId,
|
|
1250
|
+
text: finalText,
|
|
1251
|
+
output: hasStructuredOutput ? finalOutput : null,
|
|
1252
|
+
steps,
|
|
1253
|
+
messages,
|
|
1254
|
+
stopReason
|
|
1255
|
+
};
|
|
1256
|
+
} catch (err) {
|
|
1257
|
+
const cause = err instanceof LlmRunError ? err.cause : err;
|
|
1258
|
+
const wireError = cause && typeof cause === "object" && "error" in cause ? cause : void 0;
|
|
1259
|
+
yield {
|
|
1260
|
+
type: "error",
|
|
1261
|
+
error: typeof wireError?.error === "string" ? wireError.error : "tool_loop_error",
|
|
1262
|
+
message: errMessage(err),
|
|
1263
|
+
...wireError?.retryable !== void 0 ? { retryable: wireError.retryable } : {},
|
|
1264
|
+
...wireError?.requestId !== void 0 ? { requestId: wireError.requestId } : {},
|
|
1265
|
+
step: err instanceof LlmRunError ? err.step : turn
|
|
1266
|
+
};
|
|
1267
|
+
} finally {
|
|
1268
|
+
clearTimeout(timer);
|
|
1269
|
+
opts.signal?.removeEventListener("abort", onExternalAbort);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
function streamToolLoop(input, opts, wire2) {
|
|
1273
|
+
return engine(input, opts, wire2, true);
|
|
1274
|
+
}
|
|
1275
|
+
async function runToolLoop(input, opts, wire2) {
|
|
1276
|
+
for await (const event of engine(input, opts, wire2, false)) {
|
|
1277
|
+
if (event.type === "done") {
|
|
1278
|
+
return {
|
|
1279
|
+
text: event.text ?? "",
|
|
1280
|
+
output: event.output ?? null,
|
|
1281
|
+
toolCalls: [],
|
|
1282
|
+
usage: event.usage,
|
|
1283
|
+
cost: event.cost,
|
|
1284
|
+
provider: event.provider,
|
|
1285
|
+
model: event.model,
|
|
1286
|
+
finishReason: event.finishReason,
|
|
1287
|
+
requestId: event.requestId,
|
|
1288
|
+
steps: event.steps,
|
|
1289
|
+
messages: event.messages,
|
|
1290
|
+
stopReason: event.stopReason
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
if (event.type === "error") {
|
|
1294
|
+
throw new LlmRunError(event.message, event.step ?? null, event);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
throw new LlmRunError("tool loop produced no result", null, null);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
643
1300
|
// ../sdk/src/llm.ts
|
|
1301
|
+
function toolMode(tools2) {
|
|
1302
|
+
if (!tools2 || tools2.length === 0) return "none";
|
|
1303
|
+
const withRun = tools2.filter((t) => typeof t.run === "function").length;
|
|
1304
|
+
if (withRun === 0) return "defs";
|
|
1305
|
+
if (withRun === tools2.length) return "loop";
|
|
1306
|
+
throw new Error(
|
|
1307
|
+
"llm tools must either all define run() (the SDK runs the loop) or none of them (raw toolCalls passthrough)."
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
644
1310
|
function label2(method, input, opts = {}) {
|
|
645
|
-
const
|
|
1311
|
+
const trimmed = typeof input === "string" ? input.replace(/\s+/g, " ").trim() : "";
|
|
1312
|
+
const preview = typeof input === "string" ? shorten(trimmed, 70) : `${input.length} message${input.length === 1 ? "" : "s"}`;
|
|
1313
|
+
const fullPreview = typeof input === "string" ? trimmed : JSON.stringify(input);
|
|
646
1314
|
const output = opts.output?.type === "json" ? " \u2192 json" : "";
|
|
647
|
-
|
|
1315
|
+
const tools2 = opts.tools?.length ? ` +${opts.tools.length} tool${opts.tools.length === 1 ? "" : "s"}` : "";
|
|
1316
|
+
return {
|
|
1317
|
+
label: `llm.${method}(${q(preview)})${output}${tools2}`,
|
|
1318
|
+
full: `llm.${method}(${q(fullPreview)})${output}${tools2}`
|
|
1319
|
+
};
|
|
648
1320
|
}
|
|
649
1321
|
function body2(input, opts = {}) {
|
|
650
1322
|
const out = Array.isArray(input) ? { messages: input } : { input };
|
|
@@ -652,7 +1324,13 @@
|
|
|
652
1324
|
if (opts.provider !== void 0) out.provider = opts.provider;
|
|
653
1325
|
if (opts.system !== void 0) out.system = opts.system;
|
|
654
1326
|
if (opts.output !== void 0) out.output = opts.output;
|
|
655
|
-
if (opts.
|
|
1327
|
+
if (opts.tools !== void 0) {
|
|
1328
|
+
out.tools = opts.tools.map((t) => ({
|
|
1329
|
+
name: t.name,
|
|
1330
|
+
description: t.description,
|
|
1331
|
+
schema: t.schema
|
|
1332
|
+
}));
|
|
1333
|
+
}
|
|
656
1334
|
if (opts.maxOutputTokens !== void 0) out.maxOutputTokens = opts.maxOutputTokens;
|
|
657
1335
|
if (opts.metadata !== void 0) out.metadata = opts.metadata;
|
|
658
1336
|
return out;
|
|
@@ -662,20 +1340,19 @@
|
|
|
662
1340
|
method: "POST",
|
|
663
1341
|
credentials: "include",
|
|
664
1342
|
headers: { "Content-Type": "application/json" },
|
|
665
|
-
body: JSON.stringify(body2(input, opts))
|
|
1343
|
+
body: JSON.stringify(body2(input, opts)),
|
|
1344
|
+
signal: opts.signal ?? null
|
|
666
1345
|
});
|
|
667
1346
|
if (!resp.ok) throw new ApiError(resp.status, await resp.text());
|
|
668
1347
|
return await resp.json();
|
|
669
1348
|
}
|
|
670
1349
|
async function* doStream(input, opts = {}) {
|
|
671
|
-
if (opts.output?.type === "json") {
|
|
672
|
-
throw new Error("llm.stream() does not support JSON output; use llm.generate().");
|
|
673
|
-
}
|
|
674
1350
|
const resp = await send(buildUrl("/llm/stream"), {
|
|
675
1351
|
method: "POST",
|
|
676
1352
|
credentials: "include",
|
|
677
1353
|
headers: { "Content-Type": "application/json" },
|
|
678
|
-
body: JSON.stringify(body2(input, opts))
|
|
1354
|
+
body: JSON.stringify(body2(input, opts)),
|
|
1355
|
+
signal: opts.signal ?? null
|
|
679
1356
|
});
|
|
680
1357
|
if (!resp.ok) throw new ApiError(resp.status, await resp.text());
|
|
681
1358
|
if (!resp.body) throw new Error("LLM stream response has no body.");
|
|
@@ -702,11 +1379,30 @@
|
|
|
702
1379
|
reader.releaseLock();
|
|
703
1380
|
}
|
|
704
1381
|
}
|
|
1382
|
+
var wire = { generate: doGenerate, stream: doStream };
|
|
705
1383
|
function generate(input, opts = {}) {
|
|
706
|
-
|
|
1384
|
+
const mode = toolMode(opts.tools);
|
|
1385
|
+
if (opts.output?.type === "json" && mode === "defs") {
|
|
1386
|
+
throw new Error(
|
|
1387
|
+
"llm.generate() does not support JSON output with run-less tools; add run() handlers or omit tools."
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
const { label: lbl, full } = label2("generate", input, opts);
|
|
1391
|
+
if (mode === "loop") {
|
|
1392
|
+
return track("llm", lbl, () => runToolLoop(input, opts, wire), full);
|
|
1393
|
+
}
|
|
1394
|
+
return track("llm", lbl, () => doGenerate(input, opts), full);
|
|
707
1395
|
}
|
|
708
1396
|
function stream(input, opts = {}) {
|
|
709
|
-
|
|
1397
|
+
const mode = toolMode(opts.tools);
|
|
1398
|
+
if (opts.output?.type === "json" && mode !== "loop") {
|
|
1399
|
+
throw new Error("llm.stream() does not support JSON output; use llm.generate().");
|
|
1400
|
+
}
|
|
1401
|
+
const { label: lbl, full } = label2("stream", input, opts);
|
|
1402
|
+
if (mode === "loop") {
|
|
1403
|
+
return trackStream("llm", lbl, () => streamToolLoop(input, opts, wire), full);
|
|
1404
|
+
}
|
|
1405
|
+
return trackStream("llm", lbl, () => doStream(input, opts), full);
|
|
710
1406
|
}
|
|
711
1407
|
var llm = { generate, stream };
|
|
712
1408
|
var llmProviders = () => track("llm", "llmProviders()", () => call("GET", "/llm/providers"));
|
|
@@ -736,12 +1432,12 @@
|
|
|
736
1432
|
() => call("GET", `/personal-connections/${encodeURIComponent(toolkit)}/tools`)
|
|
737
1433
|
);
|
|
738
1434
|
}
|
|
739
|
-
function callTool(tool, args = {}) {
|
|
1435
|
+
function callTool(toolkit, tool, args = {}) {
|
|
740
1436
|
return track(
|
|
741
1437
|
"personalConnections",
|
|
742
|
-
`personalConnections.call(${q(tool)})`,
|
|
1438
|
+
`personalConnections.call(${q(toolkit)}, ${q(tool)})`,
|
|
743
1439
|
() => call("POST", "/personal-connections/call", {
|
|
744
|
-
body: { tool, arguments: args }
|
|
1440
|
+
body: { toolkit, tool, arguments: args }
|
|
745
1441
|
})
|
|
746
1442
|
);
|
|
747
1443
|
}
|
|
@@ -750,13 +1446,16 @@
|
|
|
750
1446
|
// ../sdk/src/queries.ts
|
|
751
1447
|
var PARAMS_LABEL_MAX2 = 40;
|
|
752
1448
|
var query = (name, params) => {
|
|
753
|
-
const
|
|
1449
|
+
const paramsJson = params && Object.keys(params).length ? JSON.stringify(params) : "";
|
|
1450
|
+
const label3 = `query(${q(name)}${paramsJson ? `, ${shorten(paramsJson, PARAMS_LABEL_MAX2)}` : ""})`;
|
|
1451
|
+
const full = `query(${q(name)}${paramsJson ? `, ${paramsJson}` : ""})`;
|
|
754
1452
|
return track(
|
|
755
1453
|
"sql",
|
|
756
1454
|
label3,
|
|
757
1455
|
() => call("POST", `/queries/${encPath(name)}`, {
|
|
758
1456
|
body: { params: params || {} }
|
|
759
|
-
}).then(toSqlRows)
|
|
1457
|
+
}).then(toSqlRows),
|
|
1458
|
+
full
|
|
760
1459
|
);
|
|
761
1460
|
};
|
|
762
1461
|
var savedQueries = () => track("queries", "savedQueries()", () => call("GET", "/queries"));
|
|
@@ -777,12 +1476,14 @@
|
|
|
777
1476
|
var request = (name, path, opts) => {
|
|
778
1477
|
const method = (opts?.method || "GET").toUpperCase();
|
|
779
1478
|
const label3 = `connector(${q(name)}).fetch(${q(method)} ${q(shorten(path, PATH_LABEL_MAX))})`;
|
|
1479
|
+
const full = `connector(${q(name)}).fetch(${q(method)} ${q(path)})`;
|
|
780
1480
|
return track(
|
|
781
1481
|
"connector",
|
|
782
1482
|
label3,
|
|
783
1483
|
() => call("POST", "/service-connectors/request", {
|
|
784
1484
|
body: { connector: name, method, path, body: opts?.body }
|
|
785
|
-
}).then(toResponse)
|
|
1485
|
+
}).then(toResponse),
|
|
1486
|
+
full
|
|
786
1487
|
);
|
|
787
1488
|
};
|
|
788
1489
|
var connector = (name) => ({
|