railcode 0.1.26 → 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/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 auto 1fr; gap:8px; align-items:baseline;
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; align-self:start; margin-top:2px; }
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 (!log) return;
92
- if (!entries.length) {
93
- log.innerHTML = '<div class="empty">No calls yet. Interact with the app to see SDK activity.</div>';
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
- log.innerHTML = entries.map((e) => {
97
- 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>`;
98
- return `<div class="row ${e.status}"><span class="dot"></span>
99
- <span class="kind k-${e.kind}">${e.kind}</span>
100
- <span class="body"><code>${escHtml(e.label)}</code>${res}</span></div>`;
101
- }).join("");
102
- log.scrollTop = log.scrollHeight;
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") textLen += event.text.length;
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 = (engine, connection, query2, params) => {
318
- const trimmed = shorten(query2.replace(/\s+/g, " ").trim(), QUERY_LABEL_MAX);
319
- const label3 = `${engine ?? "data"}(${q(connection)}).runSQL(${q(trimmed)}${params && params.length ? `, ${shorten(JSON.stringify(params), PARAMS_LABEL_MAX)}` : ""})`;
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: { ...engine ? { engine } : {}, connection, query: query2, params: params || [] }
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 = (engine) => {
624
+ var namespace = (engine2) => {
331
625
  const handle = (connection) => ({
332
- runSQL: (query2, params) => runSQL(engine, connection, query2, params)
626
+ runSQL: (query2, params) => runSQL(engine2, connection, query2, params)
333
627
  });
334
628
  const ns = handle;
335
- ns.runSQL = (query2, params) => runSQL(engine, "default", query2, params);
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
- return track("db", this.label(`put(${q(key)}, ${shorten(JSON.stringify(value), 40)})`), async () => {
446
- const record = await call("PUT", `/kv/${encPath(this.name)}/${encPath(key)}`, {
447
- body: { value },
448
- query: scopeParams(this.scope)
449
- });
450
- return record.value;
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 now = Date.now();
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 > now + URL_EXPIRY_MARGIN_MS) items.set(name, cached.item);
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 : now + item.expires_in * 1e3;
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 preview = typeof input === "string" ? shorten(input.replace(/\s+/g, " ").trim(), 70) : `${input.length} message${input.length === 1 ? "" : "s"}`;
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
- return `llm.${method}(${q(preview)})${output}`;
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.temperature !== void 0) out.temperature = opts.temperature;
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
- return track("llm", label2("generate", input, opts), () => doGenerate(input, opts));
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
- return trackStream("llm", label2("stream", input, opts), () => doStream(input, opts));
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"));
@@ -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 label3 = `query(${q(name)}${params && Object.keys(params).length ? `, ${shorten(JSON.stringify(params), PARAMS_LABEL_MAX2)}` : ""})`;
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) => ({