@tabbio-technologies/cli 1.2.8
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 +763 -0
- package/dist/chunks/chunk-7LNC3FIV.js +2019 -0
- package/dist/chunks/chunk-7LNC3FIV.js.map +7 -0
- package/dist/chunks/chunk-NAWA6NCZ.js +259 -0
- package/dist/chunks/chunk-NAWA6NCZ.js.map +7 -0
- package/dist/chunks/chunk-NK5RPNSV.js +15 -0
- package/dist/chunks/chunk-NK5RPNSV.js.map +7 -0
- package/dist/chunks/chunk-QVR5KIEQ.js +1976 -0
- package/dist/chunks/chunk-QVR5KIEQ.js.map +7 -0
- package/dist/chunks/chunk-VHHZFMIF.js +162 -0
- package/dist/chunks/chunk-VHHZFMIF.js.map +7 -0
- package/dist/chunks/chunk-Y6GWIFHI.js +227 -0
- package/dist/chunks/chunk-Y6GWIFHI.js.map +7 -0
- package/dist/chunks/entry-WENOOT6W.js +3492 -0
- package/dist/chunks/entry-WENOOT6W.js.map +7 -0
- package/dist/chunks/mount-NGUARMCL.js +79 -0
- package/dist/chunks/mount-NGUARMCL.js.map +7 -0
- package/dist/chunks/program-O2BA5L6Z.js +2755 -0
- package/dist/chunks/program-O2BA5L6Z.js.map +7 -0
- package/dist/chunks/status-I5UAQPBY.js +16 -0
- package/dist/chunks/status-I5UAQPBY.js.map +7 -0
- package/dist/cli.js +66 -0
- package/dist/cli.js.map +7 -0
- package/package.json +65 -0
|
@@ -0,0 +1,1976 @@
|
|
|
1
|
+
import { createRequire as __tabbioCreateRequire } from 'node:module';
|
|
2
|
+
const require = __tabbioCreateRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
CliError,
|
|
5
|
+
ExitCode,
|
|
6
|
+
debug,
|
|
7
|
+
interruptedError,
|
|
8
|
+
kebabCase,
|
|
9
|
+
loadConfig,
|
|
10
|
+
networkError,
|
|
11
|
+
parseJsonResponse,
|
|
12
|
+
relativeTime,
|
|
13
|
+
updateConfig,
|
|
14
|
+
usageError
|
|
15
|
+
} from "./chunk-7LNC3FIV.js";
|
|
16
|
+
import {
|
|
17
|
+
theme
|
|
18
|
+
} from "./chunk-VHHZFMIF.js";
|
|
19
|
+
|
|
20
|
+
// src/core/chat-model.ts
|
|
21
|
+
import { marked } from "marked";
|
|
22
|
+
|
|
23
|
+
// src/core/capabilities.ts
|
|
24
|
+
var TOGGLE_MODES = ["auto", "on", "off"];
|
|
25
|
+
var RESULT_MODES = ["auto", "summary", "document", "slides", "page", "image"];
|
|
26
|
+
var DEFAULT_CAPABILITIES = { research: "auto", image: "auto", result: "auto" };
|
|
27
|
+
var CAPABILITY_CHOICES = {
|
|
28
|
+
research: TOGGLE_MODES,
|
|
29
|
+
image: TOGGLE_MODES,
|
|
30
|
+
result: RESULT_MODES
|
|
31
|
+
};
|
|
32
|
+
var CAPABILITY_CHOICE_HELP = {
|
|
33
|
+
research: { auto: "Search the web when it helps", on: "Always search first", off: "Never search the web" },
|
|
34
|
+
image: { auto: "Only when you ask for one", on: "Make an image (uses image credits)", off: "Never make images" },
|
|
35
|
+
result: {
|
|
36
|
+
auto: "Let the message decide",
|
|
37
|
+
summary: "Answer in the chat",
|
|
38
|
+
document: "A document (HTML and PDF)",
|
|
39
|
+
slides: "A slide deck (PPTX)",
|
|
40
|
+
page: "A web page (HTML)",
|
|
41
|
+
image: "An image"
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
function isToggle(value) {
|
|
45
|
+
return typeof value === "string" && TOGGLE_MODES.includes(value);
|
|
46
|
+
}
|
|
47
|
+
function isResult(value) {
|
|
48
|
+
return typeof value === "string" && RESULT_MODES.includes(value);
|
|
49
|
+
}
|
|
50
|
+
function parseCapabilityMode(key, value) {
|
|
51
|
+
const normalized = value.trim().toLowerCase();
|
|
52
|
+
const alias = key === "result" && (normalized === "doc" || normalized === "pdf") ? "document" : key === "result" && normalized === "deck" ? "slides" : normalized;
|
|
53
|
+
if (key === "result" ? isResult(alias) : isToggle(alias)) return alias;
|
|
54
|
+
throw usageError(`--${key} must be one of ${CAPABILITY_CHOICES[key].join(", ")}; got "${value}"`);
|
|
55
|
+
}
|
|
56
|
+
function sanitizeCapabilities(raw) {
|
|
57
|
+
const record = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
58
|
+
return {
|
|
59
|
+
research: isToggle(record.research) ? record.research : DEFAULT_CAPABILITIES.research,
|
|
60
|
+
image: isToggle(record.image) ? record.image : DEFAULT_CAPABILITIES.image,
|
|
61
|
+
result: isResult(record.result) ? record.result : DEFAULT_CAPABILITIES.result
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function loadCapabilityPrefs(profileName) {
|
|
65
|
+
try {
|
|
66
|
+
const stored = loadConfig().profiles[profileName];
|
|
67
|
+
return sanitizeCapabilities(stored?.capabilities);
|
|
68
|
+
} catch {
|
|
69
|
+
return { ...DEFAULT_CAPABILITIES };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function saveCapabilityPrefs(profileName, capabilities) {
|
|
73
|
+
updateConfig((config) => {
|
|
74
|
+
config.profiles[profileName] = { ...config.profiles[profileName], capabilities: { ...capabilities } };
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function resolveCapabilities(stored, flags = {}) {
|
|
78
|
+
return {
|
|
79
|
+
research: flags.research !== void 0 ? parseCapabilityMode("research", flags.research) : stored.research,
|
|
80
|
+
image: flags.image !== void 0 ? parseCapabilityMode("image", flags.image) : stored.image,
|
|
81
|
+
result: flags.result !== void 0 ? parseCapabilityMode("result", flags.result) : stored.result
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function toWireCapabilities(capabilities) {
|
|
85
|
+
return { research: capabilities.research, image: capabilities.image, artifact: capabilities.result };
|
|
86
|
+
}
|
|
87
|
+
function deviceTimeZone() {
|
|
88
|
+
try {
|
|
89
|
+
const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
90
|
+
return typeof zone === "string" && zone ? zone : void 0;
|
|
91
|
+
} catch {
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function capabilityRequestFields(mode, capabilities, timezone = deviceTimeZone()) {
|
|
96
|
+
return {
|
|
97
|
+
...mode === "employer" ? {} : { capabilities: toWireCapabilities(capabilities) },
|
|
98
|
+
...timezone ? { timezone } : {}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
var REASON_TEXT = {
|
|
102
|
+
disabled: "switched off on this server",
|
|
103
|
+
not_allowlisted: "not enabled for this account yet (canary or plan tier)",
|
|
104
|
+
not_configured: "the provider is not configured on this server",
|
|
105
|
+
upgrade_required: "needs a paid plan"
|
|
106
|
+
};
|
|
107
|
+
function reasonText(reason) {
|
|
108
|
+
return reason && REASON_TEXT[reason] || reason || "unavailable";
|
|
109
|
+
}
|
|
110
|
+
function unavailableNotices(capabilities, availability) {
|
|
111
|
+
if (!availability) return [];
|
|
112
|
+
const notices = [];
|
|
113
|
+
if (capabilities.research === "on" && !availability.research.available) {
|
|
114
|
+
notices.push(`Research is unavailable: ${reasonText(availability.research.reason)}. This message runs without it.`);
|
|
115
|
+
}
|
|
116
|
+
const wantsImage = capabilities.image === "on" || capabilities.result === "image";
|
|
117
|
+
if (wantsImage && !availability.image.available) {
|
|
118
|
+
notices.push(`Images are unavailable: ${reasonText(availability.image.reason)}. Tabbio answers in the chat instead.`);
|
|
119
|
+
}
|
|
120
|
+
const wantsFile = capabilities.result === "document" || capabilities.result === "slides" || capabilities.result === "page";
|
|
121
|
+
if (wantsFile && !availability.artifacts.available) {
|
|
122
|
+
notices.push(`Documents, slides and pages are unavailable: ${reasonText(availability.artifacts.reason)}. Tabbio answers in the chat instead.`);
|
|
123
|
+
}
|
|
124
|
+
return notices;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/core/chat-model.ts
|
|
128
|
+
function initialChatState(opts) {
|
|
129
|
+
return {
|
|
130
|
+
mode: opts.mode,
|
|
131
|
+
threadId: opts.threadId,
|
|
132
|
+
modelId: opts.modelId,
|
|
133
|
+
entries: [],
|
|
134
|
+
turn: null,
|
|
135
|
+
approvals: [],
|
|
136
|
+
streaming: false,
|
|
137
|
+
seq: 0,
|
|
138
|
+
capabilities: opts.capabilities ?? { ...DEFAULT_CAPABILITIES },
|
|
139
|
+
links: opts.links ?? false
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
var FINAL_STAGES = /* @__PURE__ */ new Set(["completed", "failed", "declined"]);
|
|
143
|
+
function toolTitle(payload) {
|
|
144
|
+
const raw = payload.display?.title?.trim() || payload.name.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
145
|
+
return raw.split(/\s+/).map((w, i) => i === 0 ? w.charAt(0).toUpperCase() + w.slice(1) : /^[A-Z][a-z]/.test(w) ? w.toLowerCase() : w).join(" ");
|
|
146
|
+
}
|
|
147
|
+
function stageOf(payload, fallback) {
|
|
148
|
+
const stage = payload.stage ?? payload.status;
|
|
149
|
+
if (stage === "completed" || stage === "failed" || stage === "declined" || stage === "running") return stage;
|
|
150
|
+
if (stage === "pending_approval" || stage === "pending" || stage === "requires_approval") return "pending_approval";
|
|
151
|
+
return fallback;
|
|
152
|
+
}
|
|
153
|
+
function stableBoundary(text) {
|
|
154
|
+
let tokens;
|
|
155
|
+
try {
|
|
156
|
+
tokens = marked.lexer(text, { gfm: true });
|
|
157
|
+
} catch {
|
|
158
|
+
return 0;
|
|
159
|
+
}
|
|
160
|
+
let lastContent = -1;
|
|
161
|
+
tokens.forEach((token, i) => {
|
|
162
|
+
if (token.type !== "space") lastContent = i;
|
|
163
|
+
});
|
|
164
|
+
if (lastContent <= 0) return 0;
|
|
165
|
+
const prefix = tokens.slice(0, lastContent).map((t) => t.raw).join("");
|
|
166
|
+
if (!prefix || !text.startsWith(prefix) || !prefix.endsWith("\n")) return 0;
|
|
167
|
+
return prefix.length;
|
|
168
|
+
}
|
|
169
|
+
function normalizeText(text) {
|
|
170
|
+
return text.replace(/\s+/g, " ").trim();
|
|
171
|
+
}
|
|
172
|
+
function isDestructiveTool(name) {
|
|
173
|
+
return /(delete|remove|revoke|withdraw|unpublish|send|close|reject|archive)/i.test(name);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/core/schema-flags.ts
|
|
177
|
+
import { Option } from "commander";
|
|
178
|
+
|
|
179
|
+
// src/core/schema-spec.ts
|
|
180
|
+
var RESERVED_GLOBAL_FLAGS = [
|
|
181
|
+
"json",
|
|
182
|
+
"profile",
|
|
183
|
+
"debug",
|
|
184
|
+
"yes",
|
|
185
|
+
"quiet",
|
|
186
|
+
"color",
|
|
187
|
+
"no-color",
|
|
188
|
+
"api-url",
|
|
189
|
+
"app-url",
|
|
190
|
+
"help",
|
|
191
|
+
"version"
|
|
192
|
+
];
|
|
193
|
+
var RUN_LEVEL_FLAGS = ["input", "input-file", "fields", "wait", "timeout", "output"];
|
|
194
|
+
var RESERVED = /* @__PURE__ */ new Set([...RESERVED_GLOBAL_FLAGS, ...RUN_LEVEL_FLAGS]);
|
|
195
|
+
var TOOL_FLAGS_HELP_GROUP = "Tool flags:";
|
|
196
|
+
function commanderAttr(flag) {
|
|
197
|
+
return flag.split("-").reduce((acc, word, index) => index === 0 ? word : acc + word.charAt(0).toUpperCase() + word.slice(1), "");
|
|
198
|
+
}
|
|
199
|
+
var ABBREVIATION = /(?:^|[\s(])(?:e\.g|i\.e|etc|vs|approx|incl|min|max)$/i;
|
|
200
|
+
function shortDescription(text, max = 80) {
|
|
201
|
+
if (!text) return "";
|
|
202
|
+
const clean = text.replace(/\s+/g, " ").trim();
|
|
203
|
+
let end = clean.length;
|
|
204
|
+
for (const match of clean.matchAll(/[.!?](?=\s+[A-Z(]|$)/g)) {
|
|
205
|
+
const index = match.index ?? 0;
|
|
206
|
+
if (match[0] === "." && ABBREVIATION.test(clean.slice(0, index))) continue;
|
|
207
|
+
end = index + 1;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
const first = clean.slice(0, end).replace(/\.$/, "");
|
|
211
|
+
if (first.length <= max) return first;
|
|
212
|
+
return `${first.slice(0, max - 1).trimEnd()}${theme.symbols.ellipsis}`;
|
|
213
|
+
}
|
|
214
|
+
function isRecord(value) {
|
|
215
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
216
|
+
}
|
|
217
|
+
function isNullSchema(schema) {
|
|
218
|
+
return schema.type === "null" || Array.isArray(schema.enum) && schema.enum.length === 1 && schema.enum[0] === null;
|
|
219
|
+
}
|
|
220
|
+
function unwrapNullable(schema) {
|
|
221
|
+
let nullable = schema.nullable === true;
|
|
222
|
+
const union = schema.anyOf ?? schema.oneOf;
|
|
223
|
+
if (Array.isArray(union)) {
|
|
224
|
+
const nonNull = union.filter((member) => !isNullSchema(member));
|
|
225
|
+
if (nonNull.length < union.length) nullable = true;
|
|
226
|
+
if (nonNull.length === 1) {
|
|
227
|
+
const only = nonNull[0];
|
|
228
|
+
return { schema: { ...only, description: only.description ?? schema.description }, nullable };
|
|
229
|
+
}
|
|
230
|
+
return { schema: { ...schema, anyOf: nonNull, oneOf: void 0 }, nullable };
|
|
231
|
+
}
|
|
232
|
+
if (Array.isArray(schema.allOf) && schema.allOf.length === 1) {
|
|
233
|
+
return unwrapNullable({ ...schema.allOf[0], description: schema.description });
|
|
234
|
+
}
|
|
235
|
+
if (Array.isArray(schema.type)) {
|
|
236
|
+
const types = schema.type.filter((type) => type !== "null");
|
|
237
|
+
if (types.length < schema.type.length) nullable = true;
|
|
238
|
+
if (types.length === 1) return { schema: { ...schema, type: types[0] }, nullable };
|
|
239
|
+
}
|
|
240
|
+
return { schema, nullable };
|
|
241
|
+
}
|
|
242
|
+
function scalarKind(schema) {
|
|
243
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0 && schema.enum.every((v) => typeof v === "string")) {
|
|
244
|
+
return "enum";
|
|
245
|
+
}
|
|
246
|
+
if (typeof schema.const === "string") return "enum";
|
|
247
|
+
switch (schema.type) {
|
|
248
|
+
case "string":
|
|
249
|
+
return "string";
|
|
250
|
+
case "number":
|
|
251
|
+
return "number";
|
|
252
|
+
case "integer":
|
|
253
|
+
return "integer";
|
|
254
|
+
case "boolean":
|
|
255
|
+
return "boolean";
|
|
256
|
+
default:
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function enumValuesOf(schema) {
|
|
261
|
+
if (typeof schema.const === "string") return [schema.const];
|
|
262
|
+
return Array.isArray(schema.enum) ? schema.enum.filter((v) => typeof v === "string") : void 0;
|
|
263
|
+
}
|
|
264
|
+
function classify(schema) {
|
|
265
|
+
const scalar = scalarKind(schema);
|
|
266
|
+
if (scalar) return { kind: scalar, ...scalar === "enum" ? { enumValues: enumValuesOf(schema) } : {} };
|
|
267
|
+
if (schema.type === "array") {
|
|
268
|
+
const items = Array.isArray(schema.items) ? void 0 : schema.items;
|
|
269
|
+
const item = items ? unwrapNullable(items).schema : void 0;
|
|
270
|
+
const itemKind = item ? scalarKind(item) : null;
|
|
271
|
+
if (item && itemKind) {
|
|
272
|
+
return { kind: "array", itemKind, ...itemKind === "enum" ? { enumValues: enumValuesOf(item) } : {} };
|
|
273
|
+
}
|
|
274
|
+
return { kind: "json", jsonShape: "array" };
|
|
275
|
+
}
|
|
276
|
+
if (schema.type === "object" || schema.properties || schema.additionalProperties) {
|
|
277
|
+
return { kind: "json", jsonShape: "object" };
|
|
278
|
+
}
|
|
279
|
+
return { kind: "json", jsonShape: "any" };
|
|
280
|
+
}
|
|
281
|
+
function safeFlagName(property) {
|
|
282
|
+
const name = kebabCase(property).replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
283
|
+
return name || "value";
|
|
284
|
+
}
|
|
285
|
+
function schemaToFlags(schema) {
|
|
286
|
+
const properties = isRecord(schema?.properties) ? schema?.properties : {};
|
|
287
|
+
const required = new Set(Array.isArray(schema?.required) ? schema.required : []);
|
|
288
|
+
const usedFlags = /* @__PURE__ */ new Set();
|
|
289
|
+
const usedAttrs = /* @__PURE__ */ new Set();
|
|
290
|
+
const specs = [];
|
|
291
|
+
for (const [property, rawProp] of Object.entries(properties)) {
|
|
292
|
+
const { schema: prop, nullable } = unwrapNullable(isRecord(rawProp) ? rawProp : {});
|
|
293
|
+
let flag = safeFlagName(property);
|
|
294
|
+
const renamed = RESERVED.has(flag) || flag.startsWith("no-");
|
|
295
|
+
if (renamed) flag = `in-${flag}`;
|
|
296
|
+
for (let n = 2; usedFlags.has(flag); n += 1) flag = `${flag.replace(/-\d+$/, "")}-${n}`;
|
|
297
|
+
usedFlags.add(flag);
|
|
298
|
+
const attr = commanderAttr(flag);
|
|
299
|
+
usedAttrs.add(attr);
|
|
300
|
+
const spec = {
|
|
301
|
+
property,
|
|
302
|
+
flag,
|
|
303
|
+
attr,
|
|
304
|
+
...classify(prop),
|
|
305
|
+
nullable,
|
|
306
|
+
required: required.has(property),
|
|
307
|
+
description: shortDescription(prop.description ?? rawProp?.description ?? prop.title),
|
|
308
|
+
renamed,
|
|
309
|
+
...prop.default !== void 0 ? { defaultValue: prop.default } : {}
|
|
310
|
+
};
|
|
311
|
+
specs.push(spec);
|
|
312
|
+
}
|
|
313
|
+
for (const spec of specs) {
|
|
314
|
+
const alias = spec.property;
|
|
315
|
+
if (spec.renamed || alias === spec.flag || !/^[A-Za-z][A-Za-z0-9_]*$/.test(alias)) continue;
|
|
316
|
+
const aliasAttr = commanderAttr(alias);
|
|
317
|
+
if (usedFlags.has(alias) || aliasAttr !== spec.attr && usedAttrs.has(aliasAttr)) continue;
|
|
318
|
+
spec.alias = alias;
|
|
319
|
+
spec.aliasAttr = aliasAttr;
|
|
320
|
+
usedAttrs.add(aliasAttr);
|
|
321
|
+
}
|
|
322
|
+
return specs;
|
|
323
|
+
}
|
|
324
|
+
function placeholderFor(spec) {
|
|
325
|
+
switch (spec.kind) {
|
|
326
|
+
case "boolean":
|
|
327
|
+
return "[boolean]";
|
|
328
|
+
case "number":
|
|
329
|
+
return "<number>";
|
|
330
|
+
case "integer":
|
|
331
|
+
return "<int>";
|
|
332
|
+
case "json":
|
|
333
|
+
return "<json>";
|
|
334
|
+
case "enum": {
|
|
335
|
+
const joined = (spec.enumValues ?? []).join("|");
|
|
336
|
+
return joined && joined.length <= 32 ? `<${joined}>` : "<choice>";
|
|
337
|
+
}
|
|
338
|
+
default:
|
|
339
|
+
return "<value>";
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function flagNotes(spec) {
|
|
343
|
+
const notes = [];
|
|
344
|
+
if (spec.required) notes.push("required");
|
|
345
|
+
if (spec.kind === "enum" && placeholderFor(spec) === "<choice>") notes.push(`one of: ${spec.enumValues?.join(", ")}`);
|
|
346
|
+
if (spec.kind === "array") {
|
|
347
|
+
notes.push("repeatable or comma-separated");
|
|
348
|
+
if (spec.enumValues) notes.push(`each one of: ${spec.enumValues.join(", ")}`);
|
|
349
|
+
}
|
|
350
|
+
if (spec.kind === "json") notes.push(spec.jsonShape === "array" ? "JSON array" : spec.jsonShape === "object" ? "JSON object" : "JSON");
|
|
351
|
+
if (spec.renamed) notes.push(`schema field "${spec.property}"`);
|
|
352
|
+
if (spec.defaultValue !== void 0) notes.push(`default: ${JSON.stringify(spec.defaultValue)}`);
|
|
353
|
+
return notes;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/core/schema-flags.ts
|
|
357
|
+
function isRecord2(value) {
|
|
358
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
359
|
+
}
|
|
360
|
+
function collect(value, previous) {
|
|
361
|
+
return [...Array.isArray(previous) ? previous : [], value];
|
|
362
|
+
}
|
|
363
|
+
function schemaToOptions(schema, specs = schemaToFlags(schema)) {
|
|
364
|
+
const options = [];
|
|
365
|
+
for (const spec of specs) {
|
|
366
|
+
const notes = flagNotes(spec);
|
|
367
|
+
const help = [spec.description || spec.property, notes.length ? `(${notes.join("; ")})` : ""].filter(Boolean).join(" ");
|
|
368
|
+
const make = (flag) => {
|
|
369
|
+
const option = new Option(`--${flag} ${placeholderFor(spec)}`, help);
|
|
370
|
+
if (spec.kind === "array") option.argParser(collect);
|
|
371
|
+
return option;
|
|
372
|
+
};
|
|
373
|
+
options.push(make(spec.flag).helpGroup(TOOL_FLAGS_HELP_GROUP));
|
|
374
|
+
if (spec.alias) options.push(make(spec.alias).hideHelp());
|
|
375
|
+
}
|
|
376
|
+
return options;
|
|
377
|
+
}
|
|
378
|
+
function levenshtein(a, b) {
|
|
379
|
+
const row = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
380
|
+
for (let i = 1; i <= a.length; i += 1) {
|
|
381
|
+
let prev = row[0];
|
|
382
|
+
row[0] = i;
|
|
383
|
+
for (let j = 1; j <= b.length; j += 1) {
|
|
384
|
+
const temp = row[j];
|
|
385
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
386
|
+
prev = temp;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return row[b.length];
|
|
390
|
+
}
|
|
391
|
+
function didYouMean(value, candidates) {
|
|
392
|
+
const needle = value.toLowerCase();
|
|
393
|
+
let best;
|
|
394
|
+
for (const candidate of candidates) {
|
|
395
|
+
const lower = candidate.toLowerCase();
|
|
396
|
+
const distance = lower.startsWith(needle) || needle.startsWith(lower) ? 1 : levenshtein(needle, lower);
|
|
397
|
+
if (!best || distance < best.distance) best = { candidate, distance };
|
|
398
|
+
}
|
|
399
|
+
return best && best.distance <= Math.max(2, Math.floor(needle.length / 3)) ? best.candidate : void 0;
|
|
400
|
+
}
|
|
401
|
+
function flagLabel(spec) {
|
|
402
|
+
return `--${spec.flag}`;
|
|
403
|
+
}
|
|
404
|
+
function coerceScalar(spec, kind, raw) {
|
|
405
|
+
const label = flagLabel(spec);
|
|
406
|
+
if (kind === "boolean") {
|
|
407
|
+
if (raw === true || raw === null || raw === void 0) return true;
|
|
408
|
+
if (raw === false) return false;
|
|
409
|
+
const text2 = String(raw).trim().toLowerCase();
|
|
410
|
+
if (["true", "yes", "1", "on"].includes(text2)) return true;
|
|
411
|
+
if (["false", "no", "0", "off"].includes(text2)) return false;
|
|
412
|
+
throw usageError(`${label} expects true or false, got "${String(raw)}"`);
|
|
413
|
+
}
|
|
414
|
+
const text = String(raw);
|
|
415
|
+
if (kind === "number" || kind === "integer") {
|
|
416
|
+
const value = Number(text.trim());
|
|
417
|
+
if (text.trim() === "" || !Number.isFinite(value)) throw usageError(`${label} expects a number, got "${text}"`);
|
|
418
|
+
if (kind === "integer" && !Number.isInteger(value)) throw usageError(`${label} expects a whole number, got "${text}"`);
|
|
419
|
+
return value;
|
|
420
|
+
}
|
|
421
|
+
if (kind === "enum") {
|
|
422
|
+
const allowed = spec.enumValues ?? [];
|
|
423
|
+
const exact = allowed.find((v) => v === text) ?? allowed.find((v) => v.toLowerCase() === text.toLowerCase());
|
|
424
|
+
if (exact !== void 0) return exact;
|
|
425
|
+
const guess = didYouMean(text, allowed);
|
|
426
|
+
throw usageError(
|
|
427
|
+
`${label} must be one of ${allowed.join(", ")}; got "${text}"`,
|
|
428
|
+
guess ? `Did you mean "${guess}"?` : void 0
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
return text;
|
|
432
|
+
}
|
|
433
|
+
function parseJsonFlag(spec, raw) {
|
|
434
|
+
try {
|
|
435
|
+
return JSON.parse(raw);
|
|
436
|
+
} catch {
|
|
437
|
+
if (spec.kind === "json" && spec.jsonShape === "any") return raw;
|
|
438
|
+
throw usageError(
|
|
439
|
+
`${flagLabel(spec)} expects JSON, got ${JSON.stringify(raw.length > 40 ? `${raw.slice(0, 40)}\u2026` : raw)}`,
|
|
440
|
+
`Quote it for the shell, e.g. ${flagLabel(spec)} '${spec.jsonShape === "array" ? "[\u2026]" : '{"key":"value"}'}'`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function coerce(spec, raw) {
|
|
445
|
+
if (spec.nullable && raw === "null") return null;
|
|
446
|
+
if (spec.kind === "array") {
|
|
447
|
+
const values = Array.isArray(raw) ? raw : [raw];
|
|
448
|
+
const items = [];
|
|
449
|
+
for (const value of values) {
|
|
450
|
+
const text = String(value);
|
|
451
|
+
if (text.trim().startsWith("[")) {
|
|
452
|
+
const parsed = parseJsonFlag(spec, text);
|
|
453
|
+
if (!Array.isArray(parsed)) throw usageError(`${flagLabel(spec)} expects a JSON array`);
|
|
454
|
+
items.push(...parsed);
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
for (const part of text.split(",")) {
|
|
458
|
+
const trimmed = part.trim();
|
|
459
|
+
if (trimmed) items.push(coerceScalar(spec, spec.itemKind ?? "string", trimmed));
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return items;
|
|
463
|
+
}
|
|
464
|
+
if (spec.kind === "json") {
|
|
465
|
+
const parsed = typeof raw === "string" ? parseJsonFlag(spec, raw) : raw;
|
|
466
|
+
if (spec.jsonShape === "object" && !isRecord2(parsed)) throw usageError(`${flagLabel(spec)} expects a JSON object`);
|
|
467
|
+
if (spec.jsonShape === "array" && !Array.isArray(parsed)) throw usageError(`${flagLabel(spec)} expects a JSON array`);
|
|
468
|
+
return parsed;
|
|
469
|
+
}
|
|
470
|
+
return coerceScalar(spec, spec.kind, raw);
|
|
471
|
+
}
|
|
472
|
+
function parseToolInput(schema, opts, extras = {}) {
|
|
473
|
+
const specs = schemaToFlags(schema);
|
|
474
|
+
const input = { ...extras.base ?? {} };
|
|
475
|
+
for (const spec of specs) {
|
|
476
|
+
const primary = opts[spec.attr];
|
|
477
|
+
const alias = spec.aliasAttr && spec.aliasAttr !== spec.attr ? opts[spec.aliasAttr] : void 0;
|
|
478
|
+
let raw = primary !== void 0 ? primary : alias;
|
|
479
|
+
if (spec.kind === "array" && Array.isArray(primary) && Array.isArray(alias)) raw = [...alias, ...primary];
|
|
480
|
+
if (raw === void 0) continue;
|
|
481
|
+
input[spec.property] = coerce(spec, raw);
|
|
482
|
+
}
|
|
483
|
+
for (const [key, value] of Object.entries(extras.defaults ?? {})) {
|
|
484
|
+
if (input[key] === void 0 && value !== void 0) input[key] = value;
|
|
485
|
+
}
|
|
486
|
+
const required = Array.isArray(schema?.required) ? schema.required : [];
|
|
487
|
+
const missingRequired = required.filter((key) => input[key] === void 0);
|
|
488
|
+
return { input, missingRequired };
|
|
489
|
+
}
|
|
490
|
+
function flagsForProperties(schema, properties) {
|
|
491
|
+
const specs = schemaToFlags(schema);
|
|
492
|
+
return properties.map((property) => {
|
|
493
|
+
const spec = specs.find((s) => s.property === property);
|
|
494
|
+
return spec ? `--${spec.flag}` : property;
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
function typeLabel(spec) {
|
|
498
|
+
if (spec.kind === "enum") return (spec.enumValues ?? []).join("|");
|
|
499
|
+
if (spec.kind === "array") return `${spec.itemKind === "enum" ? "enum" : spec.itemKind ?? "string"}[]`;
|
|
500
|
+
if (spec.kind === "json") return spec.jsonShape === "any" ? "json" : spec.jsonShape ?? "json";
|
|
501
|
+
return spec.kind;
|
|
502
|
+
}
|
|
503
|
+
function describeSchema(schema) {
|
|
504
|
+
const rows = schemaToFlags(schema).map((spec) => ({
|
|
505
|
+
flag: `--${spec.flag}`,
|
|
506
|
+
...spec.alias ? { alias: `--${spec.alias}` } : {},
|
|
507
|
+
property: spec.property,
|
|
508
|
+
type: typeLabel(spec),
|
|
509
|
+
required: spec.required,
|
|
510
|
+
nullable: spec.nullable,
|
|
511
|
+
description: spec.description,
|
|
512
|
+
notes: [...flagNotes(spec).filter((n) => n !== "required"), ...spec.nullable ? ['"null" clears it'] : []]
|
|
513
|
+
}));
|
|
514
|
+
return [...rows.filter((r) => r.required), ...rows.filter((r) => !r.required)];
|
|
515
|
+
}
|
|
516
|
+
function exampleValue(spec) {
|
|
517
|
+
switch (spec.kind) {
|
|
518
|
+
case "boolean":
|
|
519
|
+
return null;
|
|
520
|
+
case "enum":
|
|
521
|
+
return spec.enumValues?.[0] ?? "<value>";
|
|
522
|
+
case "number":
|
|
523
|
+
case "integer":
|
|
524
|
+
return "<n>";
|
|
525
|
+
case "array":
|
|
526
|
+
return spec.itemKind === "enum" ? spec.enumValues?.[0] ?? "<value>" : `<${spec.flag}>`;
|
|
527
|
+
case "json":
|
|
528
|
+
return spec.jsonShape === "array" ? `'[\u2026]'` : `'{\u2026}'`;
|
|
529
|
+
default:
|
|
530
|
+
return `<${spec.flag}>`;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
function autoFilledProperties(tool, schema) {
|
|
534
|
+
return tool.kind === "workflow" && isRecord2(schema?.properties) && "userId" in (schema?.properties ?? {}) ? ["userId"] : [];
|
|
535
|
+
}
|
|
536
|
+
function exampleInvocation(tool, schema, commandPath = tool.commandPath) {
|
|
537
|
+
if (tool.kind === "agent") return `tabbio ${commandPath.join(" ")} "What jobs fit my CV?"`;
|
|
538
|
+
const skip = new Set(autoFilledProperties(tool, schema));
|
|
539
|
+
const parts = ["tabbio", ...commandPath];
|
|
540
|
+
for (const spec of schemaToFlags(schema)) {
|
|
541
|
+
if (!spec.required || skip.has(spec.property)) continue;
|
|
542
|
+
const value = exampleValue(spec);
|
|
543
|
+
parts.push(value === null ? `--${spec.flag}` : `--${spec.flag} ${value}`);
|
|
544
|
+
}
|
|
545
|
+
return parts.join(" ");
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// src/ui/render.ts
|
|
549
|
+
function isInteractiveTerminal(stdin = process.stdin, stdout = process.stdout) {
|
|
550
|
+
return Boolean(stdin.isTTY && stdout.isTTY);
|
|
551
|
+
}
|
|
552
|
+
function assertInteractive(what = "This screen", stdin = process.stdin, stdout = process.stdout) {
|
|
553
|
+
if (isInteractiveTerminal(stdin, stdout)) return;
|
|
554
|
+
throw new CliError({
|
|
555
|
+
code: "NOT_INTERACTIVE",
|
|
556
|
+
message: `${what} needs an interactive terminal`,
|
|
557
|
+
hint: "Run it in a terminal, or use the non-interactive form (e.g. --json, or pass the values as flags).",
|
|
558
|
+
exitCode: ExitCode.Usage
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
async function renderScreen(what, build, opts = {}) {
|
|
562
|
+
assertInteractive(what);
|
|
563
|
+
const { mountScreen } = await import("./mount-NGUARMCL.js");
|
|
564
|
+
return mountScreen(build, opts);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/ui/hooks.ts
|
|
568
|
+
var registered = null;
|
|
569
|
+
function setInteractiveHooks(hooks) {
|
|
570
|
+
registered = hooks;
|
|
571
|
+
}
|
|
572
|
+
function getInteractiveHooks() {
|
|
573
|
+
return registered;
|
|
574
|
+
}
|
|
575
|
+
function canUseInteractiveUi(ctx) {
|
|
576
|
+
return Boolean(
|
|
577
|
+
process.stdout.isTTY && process.stdin.isTTY && !ctx.globals.json && !process.env.CI && registered
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/core/table.ts
|
|
582
|
+
function isRecord3(value) {
|
|
583
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
584
|
+
}
|
|
585
|
+
function getPath(value, path) {
|
|
586
|
+
let current = value;
|
|
587
|
+
for (const key of path.split(".")) {
|
|
588
|
+
if (!isRecord3(current)) return void 0;
|
|
589
|
+
current = current[key];
|
|
590
|
+
}
|
|
591
|
+
return current;
|
|
592
|
+
}
|
|
593
|
+
var ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
|
|
594
|
+
function isRowList(value) {
|
|
595
|
+
return Array.isArray(value) && value.length > 0 && value.every(isRecord3);
|
|
596
|
+
}
|
|
597
|
+
function displayWidth(text) {
|
|
598
|
+
let width = 0;
|
|
599
|
+
for (const char of text) {
|
|
600
|
+
const code = char.codePointAt(0) ?? 0;
|
|
601
|
+
if (code < 32 || code >= 127 && code < 160) continue;
|
|
602
|
+
if (code >= 768 && code <= 879 || code >= 8203 && code <= 8207 || code === 65039) continue;
|
|
603
|
+
const wide = code >= 4352 && code <= 4447 || code >= 11904 && code <= 42191 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65103 || code >= 65280 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 127744 && code <= 129791 || code >= 131072 && code <= 262141;
|
|
604
|
+
width += wide ? 2 : 1;
|
|
605
|
+
}
|
|
606
|
+
return width;
|
|
607
|
+
}
|
|
608
|
+
function truncate(text, max) {
|
|
609
|
+
if (displayWidth(text) <= max) return text;
|
|
610
|
+
const ellipsis = theme.symbols.ellipsis;
|
|
611
|
+
const budget = Math.max(0, max - displayWidth(ellipsis));
|
|
612
|
+
let out = "";
|
|
613
|
+
let width = 0;
|
|
614
|
+
for (const char of text) {
|
|
615
|
+
const w = displayWidth(char);
|
|
616
|
+
if (width + w > budget) break;
|
|
617
|
+
out += char;
|
|
618
|
+
width += w;
|
|
619
|
+
}
|
|
620
|
+
return `${out}${ellipsis}`;
|
|
621
|
+
}
|
|
622
|
+
function pad(text, width, align) {
|
|
623
|
+
const fill = " ".repeat(Math.max(0, width - displayWidth(text)));
|
|
624
|
+
return align === "right" ? `${fill}${text}` : `${text}${fill}`;
|
|
625
|
+
}
|
|
626
|
+
function compactJson(value) {
|
|
627
|
+
try {
|
|
628
|
+
return JSON.stringify(value) ?? "";
|
|
629
|
+
} catch {
|
|
630
|
+
return String(value);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function humanCell(value, now = Date.now()) {
|
|
634
|
+
if (value === null || value === void 0) return "";
|
|
635
|
+
if (typeof value === "boolean") return value ? "yes" : "no";
|
|
636
|
+
if (typeof value === "string") {
|
|
637
|
+
if (ISO_DATE.test(value)) {
|
|
638
|
+
const date = new Date(value);
|
|
639
|
+
if (!Number.isNaN(date.getTime())) return relativeTime(date, now);
|
|
640
|
+
}
|
|
641
|
+
return value.replace(/\s+/g, " ").trim();
|
|
642
|
+
}
|
|
643
|
+
if (Array.isArray(value) && value.every((v) => v === null || typeof v !== "object")) {
|
|
644
|
+
return value.map((v) => humanCell(v, now)).join(", ");
|
|
645
|
+
}
|
|
646
|
+
if (typeof value === "object") return compactJson(value);
|
|
647
|
+
return String(value);
|
|
648
|
+
}
|
|
649
|
+
function tsvCell(value) {
|
|
650
|
+
if (value === null || value === void 0) return "";
|
|
651
|
+
const text = typeof value === "object" ? compactJson(value) : String(value);
|
|
652
|
+
return text.replace(/[\t\r\n]+/g, " ");
|
|
653
|
+
}
|
|
654
|
+
var MAX_COLUMNS = 6;
|
|
655
|
+
var PRIORITY = [
|
|
656
|
+
["id"],
|
|
657
|
+
["title", "name"],
|
|
658
|
+
["status", "state", "stage"],
|
|
659
|
+
["company", "companyName", "employer"],
|
|
660
|
+
["location"],
|
|
661
|
+
["isPublic", "published", "visibility"],
|
|
662
|
+
["updatedAt", "createdAt"]
|
|
663
|
+
];
|
|
664
|
+
var LOW_VALUE = /^(?:.+Id|.+_id|.+Ids|default[A-Z_].*|(?:.*[a-z])?[Ll]ocale)$/;
|
|
665
|
+
var CONSTANT_MIN_ROWS = 3;
|
|
666
|
+
function isScalar(value) {
|
|
667
|
+
return value === null || value === void 0 || typeof value !== "object";
|
|
668
|
+
}
|
|
669
|
+
function headerLabel(key) {
|
|
670
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toUpperCase();
|
|
671
|
+
}
|
|
672
|
+
function column(key, rows) {
|
|
673
|
+
const values = rows.map((r) => getPath(r, key)).filter((v) => v !== null && v !== void 0);
|
|
674
|
+
const numeric = values.length > 0 && values.every((v) => typeof v === "number");
|
|
675
|
+
return { key, label: headerLabel(key), align: numeric ? "right" : "left" };
|
|
676
|
+
}
|
|
677
|
+
var isEmpty = (value) => value === null || value === void 0 || value === "";
|
|
678
|
+
function pickColumns(rows, fields) {
|
|
679
|
+
if (fields?.length) return fields.map((f) => column(f, rows));
|
|
680
|
+
const sample = rows.slice(0, 50);
|
|
681
|
+
const keys = [];
|
|
682
|
+
for (const row of sample) for (const key of Object.keys(row)) if (!keys.includes(key)) keys.push(key);
|
|
683
|
+
const usable = keys.filter(
|
|
684
|
+
(key) => sample.every((r) => isScalar(r[key]) || Array.isArray(r[key]) && r[key].every(isScalar)) && sample.some((r) => !isEmpty(r[key]))
|
|
685
|
+
);
|
|
686
|
+
const constant = (key) => new Set(rows.map((r) => JSON.stringify(r[key] ?? null))).size === 1;
|
|
687
|
+
const varying = rows.length >= CONSTANT_MIN_ROWS ? usable.filter((key) => !constant(key)) : usable;
|
|
688
|
+
const pool = varying.length ? varying : usable;
|
|
689
|
+
const chosen = [];
|
|
690
|
+
for (const group of PRIORITY) {
|
|
691
|
+
const key = group.find((k) => pool.includes(k));
|
|
692
|
+
if (key) chosen.push(key);
|
|
693
|
+
}
|
|
694
|
+
const avgLength = (key) => sample.reduce((sum, r) => sum + humanCell(r[key]).length, 0) / Math.max(1, sample.length);
|
|
695
|
+
const tier = (key) => {
|
|
696
|
+
if (LOW_VALUE.test(key)) return 3;
|
|
697
|
+
if (sample.filter((r) => isEmpty(r[key])).length * 2 > sample.length) return 2;
|
|
698
|
+
return avgLength(key) > 40 ? 1 : 0;
|
|
699
|
+
};
|
|
700
|
+
const rest = pool.filter((k) => !chosen.includes(k)).map((key, index) => ({ key, index, tier: tier(key) })).sort((a, b) => a.tier - b.tier || a.index - b.index);
|
|
701
|
+
for (const { key } of rest) {
|
|
702
|
+
if (chosen.length >= MAX_COLUMNS) break;
|
|
703
|
+
chosen.push(key);
|
|
704
|
+
}
|
|
705
|
+
return chosen.slice(0, MAX_COLUMNS).map((key) => column(key, rows));
|
|
706
|
+
}
|
|
707
|
+
function fitWidths(widths, max, gap = 2) {
|
|
708
|
+
const out = [...widths];
|
|
709
|
+
if (!max || max <= 0) return out;
|
|
710
|
+
const total = () => out.reduce((a, b) => a + b, 0) + gap * Math.max(0, out.length - 1);
|
|
711
|
+
const floor = 4;
|
|
712
|
+
while (total() > max) {
|
|
713
|
+
let widest = -1;
|
|
714
|
+
for (let i = 0; i < out.length; i += 1) {
|
|
715
|
+
if (out[i] > floor && (widest < 0 || out[i] > out[widest])) widest = i;
|
|
716
|
+
}
|
|
717
|
+
if (widest < 0) break;
|
|
718
|
+
out[widest] = out[widest] - 1;
|
|
719
|
+
}
|
|
720
|
+
return out;
|
|
721
|
+
}
|
|
722
|
+
function formatTable(rows, opts = {}) {
|
|
723
|
+
if (rows.length === 0) return [theme.dim("No results")];
|
|
724
|
+
const columns = pickColumns(rows, opts.fields);
|
|
725
|
+
if (columns.length === 0) return rows.map((r) => compactJson(r));
|
|
726
|
+
const cells = rows.map((row) => columns.map((c) => humanCell(getPath(row, c.key), opts.now)));
|
|
727
|
+
const natural = columns.map((c, i) => Math.max(displayWidth(c.label), ...cells.map((r) => displayWidth(r[i]))));
|
|
728
|
+
const widths = fitWidths(natural, opts.width);
|
|
729
|
+
const line = (values, style) => values.map((v, i) => {
|
|
730
|
+
const text = pad(truncate(v, widths[i]), widths[i], columns[i].align);
|
|
731
|
+
return style ? style(text) : text;
|
|
732
|
+
}).join(" ").trimEnd();
|
|
733
|
+
return [line(columns.map((c) => c.label), theme.dim), ...cells.map((r) => line(r))];
|
|
734
|
+
}
|
|
735
|
+
function formatKv(value, opts = {}) {
|
|
736
|
+
const keys = opts.fields?.length ? [...opts.fields] : Object.keys(value);
|
|
737
|
+
const rows = keys.map((key) => [key, getPath(value, key)]).filter(([, v]) => v !== void 0);
|
|
738
|
+
if (rows.length === 0) return [theme.dim("(empty)")];
|
|
739
|
+
const width = Math.max(...rows.map(([k]) => displayWidth(k)));
|
|
740
|
+
const lines = [];
|
|
741
|
+
for (const [key, raw] of rows) {
|
|
742
|
+
const label = theme.dim(pad(key, width, "left"));
|
|
743
|
+
if (typeof raw === "string" && !ISO_DATE.test(raw)) {
|
|
744
|
+
const [first = "", ...more] = raw.split(/\r?\n/);
|
|
745
|
+
lines.push(`${label} ${first}`);
|
|
746
|
+
for (const next of more) lines.push(`${" ".repeat(width)} ${next}`);
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
const text = isScalar(raw) || Array.isArray(raw) && raw.every(isScalar) ? humanCell(raw, opts.now) : compactJson(raw);
|
|
750
|
+
lines.push(`${label} ${truncate(text, 100)}`);
|
|
751
|
+
}
|
|
752
|
+
return lines;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/core/output.ts
|
|
756
|
+
var MODE_ALIASES = {
|
|
757
|
+
json: "json",
|
|
758
|
+
table: "table",
|
|
759
|
+
human: "table",
|
|
760
|
+
plain: "tsv",
|
|
761
|
+
tsv: "tsv"
|
|
762
|
+
};
|
|
763
|
+
var JSON_SCHEMA_VERSION = 1;
|
|
764
|
+
function parseOutputMode(value, source = "--output") {
|
|
765
|
+
const mode = MODE_ALIASES[value.trim().toLowerCase()];
|
|
766
|
+
if (!mode) throw usageError(`${source} must be one of json, table, plain, tsv; got "${value}"`);
|
|
767
|
+
return mode;
|
|
768
|
+
}
|
|
769
|
+
function resolveOutputMode(opts, env = process.env, isTTY = Boolean(process.stdout.isTTY)) {
|
|
770
|
+
if (opts.json) return "json";
|
|
771
|
+
if (opts.output) return parseOutputMode(opts.output);
|
|
772
|
+
if (env.TABBIO_OUTPUT?.trim()) return parseOutputMode(env.TABBIO_OUTPUT, "TABBIO_OUTPUT");
|
|
773
|
+
return isTTY ? "table" : "tsv";
|
|
774
|
+
}
|
|
775
|
+
function parseFields(value) {
|
|
776
|
+
if (!value) return void 0;
|
|
777
|
+
const fields = value.split(",").map((f) => f.trim()).filter(Boolean);
|
|
778
|
+
return fields.length ? fields : void 0;
|
|
779
|
+
}
|
|
780
|
+
var COUNT_KEYS = ["total", "totalCount", "count"];
|
|
781
|
+
function findListWrapper(value) {
|
|
782
|
+
if (!isRecord3(value) || "id" in value) return null;
|
|
783
|
+
const arrays = Object.entries(value).filter(([, v]) => Array.isArray(v));
|
|
784
|
+
if (arrays.length !== 1) return null;
|
|
785
|
+
const [key, list] = arrays[0];
|
|
786
|
+
if (list.length === 0) {
|
|
787
|
+
const counted = COUNT_KEYS.some((k) => typeof value[k] === "number");
|
|
788
|
+
return counted || Object.keys(value).length === 1 ? { key, rows: [] } : null;
|
|
789
|
+
}
|
|
790
|
+
return list.every(isRecord3) ? { key, rows: list } : null;
|
|
791
|
+
}
|
|
792
|
+
function countFooter(wrapper, key, shown) {
|
|
793
|
+
const parts = [`${shown} ${shown === 1 ? "item" : "items"}`];
|
|
794
|
+
const countKey = COUNT_KEYS.find((k) => typeof wrapper[k] === "number");
|
|
795
|
+
if (countKey) parts.push(`total ${wrapper[countKey]}`);
|
|
796
|
+
const others = Object.keys(wrapper).filter((k) => k !== key && k !== countKey);
|
|
797
|
+
if (others.length) parts.push("--json for all fields");
|
|
798
|
+
return parts.join(" \xB7 ");
|
|
799
|
+
}
|
|
800
|
+
function formatHuman(result, opts = {}) {
|
|
801
|
+
if (result === null || result === void 0) return [theme.dim("(no output)")];
|
|
802
|
+
if (typeof result === "string") return result.split(/\r?\n/);
|
|
803
|
+
if (!isRecord3(result) && !Array.isArray(result)) return [String(result)];
|
|
804
|
+
if (Array.isArray(result)) {
|
|
805
|
+
if (result.length === 0 || isRowList(result)) return formatTable(result, opts);
|
|
806
|
+
return result.map((item) => isScalar(item) ? humanCell(item, opts.now) : compactJson(item));
|
|
807
|
+
}
|
|
808
|
+
const wrapper = findListWrapper(result);
|
|
809
|
+
if (wrapper) {
|
|
810
|
+
return [...formatTable(wrapper.rows, opts), theme.dim(countFooter(result, wrapper.key, wrapper.rows.length))];
|
|
811
|
+
}
|
|
812
|
+
return formatKv(result, opts);
|
|
813
|
+
}
|
|
814
|
+
function toTsv(result, fields) {
|
|
815
|
+
const rowsToTsv = (rows) => {
|
|
816
|
+
const columns = pickColumns(rows, fields);
|
|
817
|
+
return rows.map((row) => columns.map((c) => tsvCell(getPath(row, c.key))).join(" "));
|
|
818
|
+
};
|
|
819
|
+
let lines;
|
|
820
|
+
if (result === null || result === void 0) lines = [];
|
|
821
|
+
else if (Array.isArray(result)) lines = isRowList(result) ? rowsToTsv(result) : result.map(tsvCell);
|
|
822
|
+
else if (isRecord3(result)) {
|
|
823
|
+
const wrapper = findListWrapper(result);
|
|
824
|
+
if (wrapper) lines = rowsToTsv(wrapper.rows);
|
|
825
|
+
else {
|
|
826
|
+
const keys = fields?.length ? fields : Object.keys(result);
|
|
827
|
+
lines = keys.map((key) => `${key} ${tsvCell(getPath(result, key))}`);
|
|
828
|
+
}
|
|
829
|
+
} else lines = [String(result)];
|
|
830
|
+
return lines.length ? `${lines.join("\n")}
|
|
831
|
+
` : "";
|
|
832
|
+
}
|
|
833
|
+
function projectFields(result, fields) {
|
|
834
|
+
if (!fields?.length) return result;
|
|
835
|
+
const pick = (row) => Object.fromEntries(fields.map((f) => [f, getPath(row, f) ?? null]));
|
|
836
|
+
if (Array.isArray(result)) return result.map((item) => isRecord3(item) ? pick(item) : item);
|
|
837
|
+
if (isRecord3(result)) {
|
|
838
|
+
const wrapper = findListWrapper(result);
|
|
839
|
+
if (wrapper) return { ...result, [wrapper.key]: wrapper.rows.map(pick) };
|
|
840
|
+
return pick(result);
|
|
841
|
+
}
|
|
842
|
+
return result;
|
|
843
|
+
}
|
|
844
|
+
function jsonEnvelope(tool, data, meta) {
|
|
845
|
+
return {
|
|
846
|
+
data: data === void 0 ? null : data,
|
|
847
|
+
meta: {
|
|
848
|
+
schemaVersion: JSON_SCHEMA_VERSION,
|
|
849
|
+
tool: tool.id,
|
|
850
|
+
durationMs: Math.round(meta.durationMs),
|
|
851
|
+
...meta.requestId ? { requestId: meta.requestId } : {},
|
|
852
|
+
...meta.extra ?? {}
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function writeJson(value) {
|
|
857
|
+
process.stdout.write(`${JSON.stringify(value, null, process.stdout.isTTY ? 2 : 0)}
|
|
858
|
+
`);
|
|
859
|
+
}
|
|
860
|
+
function formatDuration(ms) {
|
|
861
|
+
if (ms < 6e4) return `${(Math.max(0, ms) / 1e3).toFixed(2)}s`;
|
|
862
|
+
const minutes = Math.floor(ms / 6e4);
|
|
863
|
+
return `${minutes}m ${Math.round(ms % 6e4 / 1e3)}s`;
|
|
864
|
+
}
|
|
865
|
+
function shortId(id) {
|
|
866
|
+
return id.length > 12 ? `${id.slice(0, 4)}${theme.symbols.ellipsis}${id.slice(-3)}` : id;
|
|
867
|
+
}
|
|
868
|
+
function formatResultHeader(tool, meta) {
|
|
869
|
+
const name = (tool.commandPath?.length ? tool.commandPath.join(" ") : tool.id).trim();
|
|
870
|
+
const right = [formatDuration(meta.durationMs), meta.requestId ? `req ${shortId(meta.requestId)}` : null].filter(Boolean).join(" \xB7 ");
|
|
871
|
+
const width = Math.min(meta.width ?? 80, 100);
|
|
872
|
+
const used = displayWidth(theme.symbols.success) + 1 + displayWidth(name) + 2 + displayWidth(right) + 1;
|
|
873
|
+
const rule = theme.symbols.line.repeat(Math.max(3, width - used));
|
|
874
|
+
return `${theme.success(theme.symbols.success)} ${theme.bold(name)} ${theme.accent(rule)} ${theme.dim(right)}`;
|
|
875
|
+
}
|
|
876
|
+
function printEmptyNotice(mode, message, opts = {}) {
|
|
877
|
+
if (mode === "json") return;
|
|
878
|
+
if (mode === "table") process.stdout.write(`${theme.dim(message)}
|
|
879
|
+
`);
|
|
880
|
+
else if (!opts.quiet) process.stderr.write(`${theme.dim(message)}
|
|
881
|
+
`);
|
|
882
|
+
}
|
|
883
|
+
async function renderResult(ctx, tool, result, meta) {
|
|
884
|
+
const mode = meta.mode ?? resolveOutputMode({ json: ctx.globals.json });
|
|
885
|
+
if (mode === "json") {
|
|
886
|
+
writeJson(jsonEnvelope(tool, projectFields(result, meta.fields), meta));
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (mode === "tsv") {
|
|
890
|
+
process.stdout.write(toTsv(result, meta.fields));
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const hooks = getInteractiveHooks();
|
|
894
|
+
if (!meta.fields?.length && hooks?.showResult && canUseInteractiveUi(ctx) && "mcpName" in tool) {
|
|
895
|
+
await hooks.showResult(ctx, tool, result, { durationMs: meta.durationMs, requestId: meta.requestId });
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const width = meta.width ?? process.stdout.columns;
|
|
899
|
+
if (!ctx.globals.quiet) process.stdout.write(`${formatResultHeader(tool, { ...meta, width })}
|
|
900
|
+
`);
|
|
901
|
+
for (const line of formatHuman(result, { fields: meta.fields, width })) process.stdout.write(`${line}
|
|
902
|
+
`);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// src/core/chat-artifacts.ts
|
|
906
|
+
var RESEARCH_TOOL_NAMES = /* @__PURE__ */ new Set(["webSearch", "readUrl", "readPdf", "readGithubRepo", "readSkill"]);
|
|
907
|
+
var ARTIFACT_CARD_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
908
|
+
"generateImage",
|
|
909
|
+
"createDocument",
|
|
910
|
+
"createPage",
|
|
911
|
+
"updatePage",
|
|
912
|
+
"updateDocument",
|
|
913
|
+
"editImage",
|
|
914
|
+
"artifactGet"
|
|
915
|
+
]);
|
|
916
|
+
var ARTIFACT_PRODUCING_TOOL_IDS = /* @__PURE__ */ new Set([
|
|
917
|
+
"artifact.generateImage",
|
|
918
|
+
"artifact.createDocument",
|
|
919
|
+
"artifact.createPage",
|
|
920
|
+
"artifact.updatePage",
|
|
921
|
+
"artifact.updateDocument",
|
|
922
|
+
"artifact.editImage"
|
|
923
|
+
]);
|
|
924
|
+
function isRecord4(value) {
|
|
925
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
926
|
+
}
|
|
927
|
+
function str(value) {
|
|
928
|
+
return typeof value === "string" && value ? value : void 0;
|
|
929
|
+
}
|
|
930
|
+
function isResearchTool(payload) {
|
|
931
|
+
return payload.cardType === "research" || RESEARCH_TOOL_NAMES.has(payload.name);
|
|
932
|
+
}
|
|
933
|
+
function isArtifactTool(payload) {
|
|
934
|
+
return payload.cardType === "artifact" || ARTIFACT_CARD_TOOL_NAMES.has(payload.name);
|
|
935
|
+
}
|
|
936
|
+
var STATUSES = /* @__PURE__ */ new Set(["pending", "ready", "failed"]);
|
|
937
|
+
function artifactFromResult(result) {
|
|
938
|
+
if (!isRecord4(result)) return null;
|
|
939
|
+
const id = str(result.artifactId);
|
|
940
|
+
const status = str(result.status);
|
|
941
|
+
if (!id || !status || !STATUSES.has(status) || result.mode === "suggest") return null;
|
|
942
|
+
const failure = isRecord4(result.failure) ? { code: str(result.failure.code) ?? "FAILED", message: str(result.failure.message) ?? "" } : void 0;
|
|
943
|
+
return {
|
|
944
|
+
id,
|
|
945
|
+
kind: str(result.kind) ?? "file",
|
|
946
|
+
title: str(result.title) ?? "Untitled",
|
|
947
|
+
status,
|
|
948
|
+
...typeof result.versionNumber === "number" ? { versionNumber: result.versionNumber } : {},
|
|
949
|
+
...typeof result.previewAvailable === "boolean" ? { previewAvailable: result.previewAvailable } : {},
|
|
950
|
+
...failure ? { failure } : {}
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
function sourcesFromResult(result) {
|
|
954
|
+
if (!isRecord4(result)) return [];
|
|
955
|
+
const raw = Array.isArray(result.sources) ? result.sources : isRecord4(result.source) ? [result.source] : [];
|
|
956
|
+
return raw.filter(isRecord4).flatMap((source) => {
|
|
957
|
+
const url = str(source.url);
|
|
958
|
+
if (!url) return [];
|
|
959
|
+
return [{ url, title: str(source.title) ?? url, ...str(source.domain) ? { domain: str(source.domain) } : {} }];
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
function mergeSources(existing, incoming) {
|
|
963
|
+
const seen = new Set(existing.map((s) => s.url));
|
|
964
|
+
const merged = [...existing];
|
|
965
|
+
for (const source of incoming) {
|
|
966
|
+
if (seen.has(source.url)) continue;
|
|
967
|
+
seen.add(source.url);
|
|
968
|
+
merged.push(source);
|
|
969
|
+
}
|
|
970
|
+
return merged;
|
|
971
|
+
}
|
|
972
|
+
var CAPABILITY_DENIAL_CODES = /* @__PURE__ */ new Set([
|
|
973
|
+
"SUBSCRIPTION_ENTITLEMENT_DENIED",
|
|
974
|
+
"RESEARCH_DISABLED",
|
|
975
|
+
"RESEARCH_NOT_CONFIGURED",
|
|
976
|
+
"RESEARCH_RATE_LIMITED",
|
|
977
|
+
"IMAGE_DISABLED",
|
|
978
|
+
"ARTIFACTS_DISABLED",
|
|
979
|
+
"ARTIFACT_EDITS_DISABLED",
|
|
980
|
+
"ARTIFACT_LIMIT_REACHED",
|
|
981
|
+
"SITES_DISABLED"
|
|
982
|
+
]);
|
|
983
|
+
var DENIAL_REASONS = {
|
|
984
|
+
capability_quota_reached: "daily quota",
|
|
985
|
+
artifact_edit_quota_reached: "daily edit quota",
|
|
986
|
+
wrong_tier: "plan tier",
|
|
987
|
+
ai_daily_limit_reached: "daily credits",
|
|
988
|
+
RESEARCH_DISABLED: "flag off",
|
|
989
|
+
IMAGE_DISABLED: "flag off",
|
|
990
|
+
ARTIFACTS_DISABLED: "flag off",
|
|
991
|
+
ARTIFACT_EDITS_DISABLED: "flag off",
|
|
992
|
+
SITES_DISABLED: "flag off",
|
|
993
|
+
RESEARCH_NOT_CONFIGURED: "not configured",
|
|
994
|
+
ARTIFACT_LIMIT_REACHED: "daily file cap",
|
|
995
|
+
RESEARCH_RATE_LIMITED: "hourly fetch cap"
|
|
996
|
+
};
|
|
997
|
+
function capabilityDenial(result) {
|
|
998
|
+
if (!isRecord4(result) || result.ok !== false || !isRecord4(result.error)) return null;
|
|
999
|
+
const code = str(result.error.code);
|
|
1000
|
+
if (!code || !CAPABILITY_DENIAL_CODES.has(code)) return null;
|
|
1001
|
+
const decision = isRecord4(result.error.decision) ? result.error.decision : void 0;
|
|
1002
|
+
const reasonCode = str(decision?.reasonCode) ?? (Array.isArray(result.error.reasonCodes) ? str(result.error.reasonCodes[0]) : void 0) ?? code;
|
|
1003
|
+
const reason = DENIAL_REASONS[reasonCode] ?? reasonCode.toLowerCase().replace(/_/g, " ");
|
|
1004
|
+
return { code, message: str(result.error.message) ?? "This is not available right now.", reason };
|
|
1005
|
+
}
|
|
1006
|
+
var RESEARCH_VERBS = {
|
|
1007
|
+
webSearch: ["Searching the web", "Searched the web"],
|
|
1008
|
+
readUrl: ["Reading pages", "Read pages"],
|
|
1009
|
+
readPdf: ["Reading a PDF", "Read a PDF"],
|
|
1010
|
+
readGithubRepo: ["Reading a repository", "Read a repository"],
|
|
1011
|
+
readSkill: ["Reading a skill", "Read a skill"]
|
|
1012
|
+
};
|
|
1013
|
+
function researchTitle(name, running) {
|
|
1014
|
+
const verbs = RESEARCH_VERBS[name];
|
|
1015
|
+
return verbs ? verbs[running ? 0 : 1] : null;
|
|
1016
|
+
}
|
|
1017
|
+
function applyDraft(previous, event) {
|
|
1018
|
+
const base = previous ?? {
|
|
1019
|
+
phase: event.phase,
|
|
1020
|
+
kind: event.kind,
|
|
1021
|
+
artifactId: event.artifactId,
|
|
1022
|
+
chars: 0,
|
|
1023
|
+
lines: 0,
|
|
1024
|
+
sections: 0,
|
|
1025
|
+
headings: 0
|
|
1026
|
+
};
|
|
1027
|
+
return {
|
|
1028
|
+
...base,
|
|
1029
|
+
phase: event.phase,
|
|
1030
|
+
kind: event.kind || base.kind,
|
|
1031
|
+
artifactId: event.artifactId,
|
|
1032
|
+
chars: event.stats?.chars ?? base.chars + (event.textChars ?? 0),
|
|
1033
|
+
lines: event.stats?.lines ?? base.lines + (event.textLines ?? 0),
|
|
1034
|
+
sections: event.stats?.sections ?? base.sections,
|
|
1035
|
+
headings: event.outline ? event.outline.length : base.headings,
|
|
1036
|
+
...event.versionNumber !== void 0 ? { versionNumber: event.versionNumber } : {},
|
|
1037
|
+
...event.error ? { error: event.error.message || event.error.code } : {}
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function draftSummary(draft) {
|
|
1041
|
+
switch (draft.phase) {
|
|
1042
|
+
case "start":
|
|
1043
|
+
return "starting";
|
|
1044
|
+
case "code":
|
|
1045
|
+
return `writing \xB7 ${draft.lines} ${draft.lines === 1 ? "line" : "lines"}`;
|
|
1046
|
+
case "outline":
|
|
1047
|
+
return `writing \xB7 ${draft.headings} ${draft.headings === 1 ? "section" : "sections"}`;
|
|
1048
|
+
case "snapshot":
|
|
1049
|
+
return "previewing";
|
|
1050
|
+
case "checking":
|
|
1051
|
+
return "checking";
|
|
1052
|
+
case "end":
|
|
1053
|
+
return draft.versionNumber ? `version ${draft.versionNumber}` : "written";
|
|
1054
|
+
case "error":
|
|
1055
|
+
return `failed${draft.error ? `: ${draft.error}` : ""}`;
|
|
1056
|
+
default:
|
|
1057
|
+
return "";
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function previewVariantFor(kind) {
|
|
1061
|
+
return kind === "image" ? "image" : "html";
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// src/core/artifacts.ts
|
|
1065
|
+
import { createWriteStream, existsSync, renameSync, rmSync, statSync } from "node:fs";
|
|
1066
|
+
import { join } from "node:path";
|
|
1067
|
+
import { Readable } from "node:stream";
|
|
1068
|
+
import { pipeline } from "node:stream/promises";
|
|
1069
|
+
var ARTIFACT_KINDS = ["document", "slides", "image", "page", "file"];
|
|
1070
|
+
var PREVIEW_VARIANTS = ["html", "pdf", "image", "thumbnail"];
|
|
1071
|
+
var BASE = "/api/artifacts";
|
|
1072
|
+
var enc = encodeURIComponent;
|
|
1073
|
+
function listArtifacts(api, query = {}) {
|
|
1074
|
+
const params = new URLSearchParams();
|
|
1075
|
+
for (const [key, value] of Object.entries(query)) if (value !== void 0 && value !== "") params.set(key, String(value));
|
|
1076
|
+
const qs = params.toString();
|
|
1077
|
+
return api.json(`${BASE}${qs ? `?${qs}` : ""}`);
|
|
1078
|
+
}
|
|
1079
|
+
function getArtifact(api, id) {
|
|
1080
|
+
return api.json(`${BASE}/${enc(id)}`);
|
|
1081
|
+
}
|
|
1082
|
+
function previewUrl(api, id, opts) {
|
|
1083
|
+
const params = new URLSearchParams({ variant: opts.variant, ...opts.theme ? { theme: opts.theme } : {}, ...opts.versionId ? { versionId: opts.versionId } : {} });
|
|
1084
|
+
return api.json(`${BASE}/${enc(id)}/preview-url?${params.toString()}`);
|
|
1085
|
+
}
|
|
1086
|
+
function downloadUrl(api, id, opts) {
|
|
1087
|
+
const params = new URLSearchParams({ variant: opts.variant, ...opts.versionId ? { versionId: opts.versionId } : {} });
|
|
1088
|
+
return api.json(`${BASE}/${enc(id)}/download-url?${params.toString()}`);
|
|
1089
|
+
}
|
|
1090
|
+
function listVersions(api, id, opts = {}) {
|
|
1091
|
+
const params = new URLSearchParams({ ...opts.limit ? { limit: String(opts.limit) } : {}, ...opts.cursor ? { cursor: opts.cursor } : {} });
|
|
1092
|
+
const qs = params.toString();
|
|
1093
|
+
return api.json(`${BASE}/${enc(id)}/versions${qs ? `?${qs}` : ""}`);
|
|
1094
|
+
}
|
|
1095
|
+
function restoreVersion(api, id, versionId) {
|
|
1096
|
+
return api.json(
|
|
1097
|
+
`${BASE}/${enc(id)}/versions/${enc(versionId)}/restore`,
|
|
1098
|
+
{ method: "POST", body: {} }
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
function deleteArtifact(api, id) {
|
|
1102
|
+
return api.json(`${BASE}/${enc(id)}`, { method: "DELETE" });
|
|
1103
|
+
}
|
|
1104
|
+
function formatToVariant(format) {
|
|
1105
|
+
const f = format.trim().toLowerCase().replace(/^\./, "");
|
|
1106
|
+
if (f === "pdf" || f === "pptx" || f === "html") return f;
|
|
1107
|
+
if (["image", "png", "jpg", "jpeg", "webp", "gif"].includes(f)) return "image";
|
|
1108
|
+
throw usageError(`--format must be pdf, pptx, html or an image format (png, jpg, webp); got "${format}"`);
|
|
1109
|
+
}
|
|
1110
|
+
function defaultVariantFor(artifact) {
|
|
1111
|
+
const v = artifact.variants;
|
|
1112
|
+
const preferred = artifact.kind === "image" ? ["image"] : artifact.kind === "slides" ? ["pptx", "pdf", "html"] : artifact.kind === "page" ? ["html"] : ["pdf", "html", "pptx"];
|
|
1113
|
+
return preferred.find((variant) => v?.[variant]) ?? preferred[0];
|
|
1114
|
+
}
|
|
1115
|
+
var IMAGE_EXT = { "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp", "image/gif": "gif" };
|
|
1116
|
+
function extensionFor(variant, contentType) {
|
|
1117
|
+
if (variant === "image") return IMAGE_EXT[(contentType ?? "").split(";")[0]?.trim().toLowerCase() ?? ""] ?? "png";
|
|
1118
|
+
return variant;
|
|
1119
|
+
}
|
|
1120
|
+
function safeFileName(title, ext) {
|
|
1121
|
+
const base = title.normalize("NFKC").replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/, "") || "artifact";
|
|
1122
|
+
return `${base}.${ext}`;
|
|
1123
|
+
}
|
|
1124
|
+
function resolveOutputPath(out, fileName, cwd = process.cwd()) {
|
|
1125
|
+
if (!out) return join(cwd, fileName);
|
|
1126
|
+
try {
|
|
1127
|
+
if (statSync(out).isDirectory()) return join(out, fileName);
|
|
1128
|
+
} catch {
|
|
1129
|
+
}
|
|
1130
|
+
return out;
|
|
1131
|
+
}
|
|
1132
|
+
var WAIT_POLL_START_MS = 2e3;
|
|
1133
|
+
var WAIT_POLL_MAX_MS = 5e3;
|
|
1134
|
+
var DEFAULT_ARTIFACT_WAIT_MS = 5 * 6e4;
|
|
1135
|
+
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
1136
|
+
async function waitForArtifact(fetchStatus, opts = {}) {
|
|
1137
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
1138
|
+
const now = opts.now ?? Date.now;
|
|
1139
|
+
const deadline = now() + (opts.timeoutMs ?? DEFAULT_ARTIFACT_WAIT_MS);
|
|
1140
|
+
let interval = WAIT_POLL_START_MS;
|
|
1141
|
+
let last = null;
|
|
1142
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
1143
|
+
try {
|
|
1144
|
+
last = await fetchStatus();
|
|
1145
|
+
opts.onPoll?.({ status: last.status, attempt });
|
|
1146
|
+
if (last.status !== "pending") return { artifact: last, timedOut: false };
|
|
1147
|
+
} catch (error) {
|
|
1148
|
+
if (!(error instanceof CliError) || error.exitCode !== ExitCode.Network) throw error;
|
|
1149
|
+
}
|
|
1150
|
+
const remaining = deadline - now();
|
|
1151
|
+
if (remaining <= 0) return { artifact: last, timedOut: true };
|
|
1152
|
+
await sleep(Math.min(interval, remaining));
|
|
1153
|
+
interval = Math.min(WAIT_POLL_MAX_MS, Math.round(interval * 1.5));
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
async function downloadToFile(url, dest, opts = {}) {
|
|
1157
|
+
if (existsSync(dest) && !opts.force) {
|
|
1158
|
+
throw new CliError({
|
|
1159
|
+
code: "FILE_EXISTS",
|
|
1160
|
+
message: `${dest} already exists`,
|
|
1161
|
+
hint: "Pass --force to overwrite it, or --out <path> for another name.",
|
|
1162
|
+
exitCode: ExitCode.Usage
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
const fetchImpl = opts.fetch ?? ((...args) => fetch(...args));
|
|
1166
|
+
let response;
|
|
1167
|
+
try {
|
|
1168
|
+
response = await fetchImpl(url, { signal: opts.signal });
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
throw networkError(new URL(url).origin, error);
|
|
1171
|
+
}
|
|
1172
|
+
if (!response.ok || !response.body) {
|
|
1173
|
+
throw new CliError({
|
|
1174
|
+
code: "DOWNLOAD_FAILED",
|
|
1175
|
+
message: `The download failed (HTTP ${response.status})`,
|
|
1176
|
+
hint: "Signed links last 15 minutes; run the command again for a fresh one.",
|
|
1177
|
+
exitCode: response.status === 404 ? ExitCode.NotFound : response.status >= 500 ? ExitCode.Server : ExitCode.Error,
|
|
1178
|
+
status: response.status
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
const totalHeader = Number(response.headers.get("content-length"));
|
|
1182
|
+
const total = Number.isFinite(totalHeader) && totalHeader > 0 ? totalHeader : null;
|
|
1183
|
+
const temp = `${dest}.part-${process.pid}`;
|
|
1184
|
+
let received = 0;
|
|
1185
|
+
const source = Readable.fromWeb(response.body);
|
|
1186
|
+
source.on("data", (chunk) => {
|
|
1187
|
+
received += chunk.length;
|
|
1188
|
+
opts.onProgress?.({ received, total });
|
|
1189
|
+
});
|
|
1190
|
+
try {
|
|
1191
|
+
await pipeline(source, createWriteStream(temp, { mode: 420 }), { signal: opts.signal });
|
|
1192
|
+
if (existsSync(dest) && !opts.force) throw new CliError({ code: "FILE_EXISTS", message: `${dest} already exists`, exitCode: ExitCode.Usage });
|
|
1193
|
+
renameSync(temp, dest);
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
rmSync(temp, { force: true });
|
|
1196
|
+
throw error;
|
|
1197
|
+
}
|
|
1198
|
+
return { path: dest, bytes: received, contentType: response.headers.get("content-type") };
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// src/ui/hyperlink.ts
|
|
1202
|
+
function supportsHyperlinks(env = process.env, isTTY = Boolean(process.stdout.isTTY)) {
|
|
1203
|
+
const forced = env.FORCE_HYPERLINK?.trim();
|
|
1204
|
+
if (forced !== void 0 && forced !== "") return forced !== "0" && forced.toLowerCase() !== "false";
|
|
1205
|
+
if (!isTTY || env.CI || env.TERM === "dumb" || env.NO_COLOR !== void 0 && env.NO_COLOR !== "") return false;
|
|
1206
|
+
if (env.WT_SESSION || env.KITTY_WINDOW_ID || env.WEZTERM_PANE || env.GHOSTTY_RESOURCES_DIR) return true;
|
|
1207
|
+
if (env.TERM === "xterm-kitty" || env.TERM === "xterm-ghostty" || env.TERM === "alacritty") return true;
|
|
1208
|
+
const vte = Number.parseInt(env.VTE_VERSION ?? "", 10);
|
|
1209
|
+
if (Number.isFinite(vte) && vte >= 5e3) return true;
|
|
1210
|
+
switch (env.TERM_PROGRAM) {
|
|
1211
|
+
case "iTerm.app": {
|
|
1212
|
+
const [major = 0, minor = 0] = (env.TERM_PROGRAM_VERSION ?? "").split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
1213
|
+
return major > 3 || major === 3 && minor >= 1;
|
|
1214
|
+
}
|
|
1215
|
+
case "WezTerm":
|
|
1216
|
+
case "vscode":
|
|
1217
|
+
case "ghostty":
|
|
1218
|
+
case "Hyper":
|
|
1219
|
+
case "Tabby":
|
|
1220
|
+
return true;
|
|
1221
|
+
default:
|
|
1222
|
+
return false;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
var OSC = "\x1B]8;;";
|
|
1226
|
+
var BEL = "\x07";
|
|
1227
|
+
function hyperlink(text, url) {
|
|
1228
|
+
const safe = [...url].filter((ch) => ch.charCodeAt(0) > 31 && ch.charCodeAt(0) !== 127).join("");
|
|
1229
|
+
return `${OSC}${safe}${BEL}${text}${OSC}${BEL}`;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// src/core/chat.ts
|
|
1233
|
+
import { createParser } from "eventsource-parser";
|
|
1234
|
+
|
|
1235
|
+
// src/core/chat-events.ts
|
|
1236
|
+
function isRecord5(value) {
|
|
1237
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1238
|
+
}
|
|
1239
|
+
function asString(value) {
|
|
1240
|
+
return typeof value === "string" ? value : void 0;
|
|
1241
|
+
}
|
|
1242
|
+
function asNumber(value) {
|
|
1243
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
1244
|
+
}
|
|
1245
|
+
function toolPayload(raw) {
|
|
1246
|
+
const id = asString(raw.id);
|
|
1247
|
+
const name = asString(raw.name);
|
|
1248
|
+
if (!id || !name) return null;
|
|
1249
|
+
return {
|
|
1250
|
+
...raw,
|
|
1251
|
+
id,
|
|
1252
|
+
name,
|
|
1253
|
+
args: isRecord5(raw.args) ? raw.args : {},
|
|
1254
|
+
display: isRecord5(raw.display) ? raw.display : void 0
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
var DRAFT_PHASES = /* @__PURE__ */ new Set(["start", "code", "outline", "snapshot", "checking", "end", "error"]);
|
|
1258
|
+
function draftPayload(raw) {
|
|
1259
|
+
const toolCallId = asString(raw.toolCallId);
|
|
1260
|
+
const artifactId = asString(raw.artifactId);
|
|
1261
|
+
const phase = asString(raw.phase);
|
|
1262
|
+
if (!toolCallId || !artifactId || !phase || !DRAFT_PHASES.has(phase)) return null;
|
|
1263
|
+
const text = asString(raw.text);
|
|
1264
|
+
const stats = isRecord5(raw.stats) ? raw.stats : void 0;
|
|
1265
|
+
const error = isRecord5(raw.error) ? raw.error : void 0;
|
|
1266
|
+
return {
|
|
1267
|
+
toolCallId,
|
|
1268
|
+
artifactId,
|
|
1269
|
+
kind: asString(raw.kind) ?? "document",
|
|
1270
|
+
...raw.mode === "create" || raw.mode === "edit" ? { mode: raw.mode } : {},
|
|
1271
|
+
title: asString(raw.title) ?? "",
|
|
1272
|
+
seq: asNumber(raw.seq) ?? 0,
|
|
1273
|
+
phase,
|
|
1274
|
+
...text ? { textChars: text.length, textLines: text.split("\n").length - 1 } : {},
|
|
1275
|
+
...Array.isArray(raw.outline) ? {
|
|
1276
|
+
outline: raw.outline.filter(isRecord5).map((entry) => ({ heading: asString(entry.heading) ?? "", done: entry.done === true }))
|
|
1277
|
+
} : {},
|
|
1278
|
+
...stats ? { stats: { chars: asNumber(stats.chars) ?? 0, lines: asNumber(stats.lines) ?? 0, sections: asNumber(stats.sections) ?? 0 } } : {},
|
|
1279
|
+
...asNumber(raw.versionNumber) !== void 0 ? { versionNumber: asNumber(raw.versionNumber) } : {},
|
|
1280
|
+
...error ? { error: { code: asString(error.code) ?? "ERROR", message: asString(error.message) ?? "" } } : {}
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
function parseStreamEvent(data) {
|
|
1284
|
+
let parsed;
|
|
1285
|
+
try {
|
|
1286
|
+
parsed = JSON.parse(data);
|
|
1287
|
+
} catch {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
if (!isRecord5(parsed) || typeof parsed.type !== "string") return null;
|
|
1291
|
+
const payload = isRecord5(parsed.payload) ? parsed.payload : {};
|
|
1292
|
+
switch (parsed.type) {
|
|
1293
|
+
case "start":
|
|
1294
|
+
return {
|
|
1295
|
+
type: "start",
|
|
1296
|
+
payload: {
|
|
1297
|
+
threadId: asString(payload.threadId),
|
|
1298
|
+
mastraThreadId: asString(payload.mastraThreadId),
|
|
1299
|
+
mode: payload.mode === "employer" ? "employer" : payload.mode === "seeker" ? "seeker" : void 0,
|
|
1300
|
+
modelId: asString(payload.modelId)
|
|
1301
|
+
}
|
|
1302
|
+
};
|
|
1303
|
+
case "text-delta":
|
|
1304
|
+
return { type: "text-delta", payload: { text: asString(payload.text) ?? "" } };
|
|
1305
|
+
case "tool-call":
|
|
1306
|
+
case "tool-result": {
|
|
1307
|
+
const tool = toolPayload(payload);
|
|
1308
|
+
return tool ? { type: parsed.type, payload: tool } : null;
|
|
1309
|
+
}
|
|
1310
|
+
case "tool-call-approval": {
|
|
1311
|
+
const tool = toolPayload(payload);
|
|
1312
|
+
const approvalId = asString(payload.approvalId);
|
|
1313
|
+
return tool && approvalId ? { type: "tool-call-approval", payload: { ...tool, approvalId } } : null;
|
|
1314
|
+
}
|
|
1315
|
+
case "artifact-draft": {
|
|
1316
|
+
const draft = draftPayload(payload);
|
|
1317
|
+
return draft ? { type: "artifact-draft", payload: draft } : null;
|
|
1318
|
+
}
|
|
1319
|
+
case "error":
|
|
1320
|
+
return { type: "error", payload: { message: asString(payload.message) ?? "Tabbio hit an error" } };
|
|
1321
|
+
case "finish":
|
|
1322
|
+
return {
|
|
1323
|
+
type: "finish",
|
|
1324
|
+
payload: {
|
|
1325
|
+
threadId: asString(payload.threadId),
|
|
1326
|
+
runId: asString(payload.runId),
|
|
1327
|
+
finishReason: asString(payload.finishReason),
|
|
1328
|
+
text: asString(payload.text),
|
|
1329
|
+
messageId: asString(payload.messageId)
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
default:
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// src/core/chat-tool-extras.ts
|
|
1338
|
+
function holdsArtifact(call) {
|
|
1339
|
+
const artifact = call.artifact;
|
|
1340
|
+
return Boolean(artifact && !artifact.released && (artifact.status === "pending" || artifact.linkPending));
|
|
1341
|
+
}
|
|
1342
|
+
function withLinkState(artifact, links) {
|
|
1343
|
+
const linkPending = links && artifact.status === "ready" && !artifact.previewUrl;
|
|
1344
|
+
return { ...artifact, linkPending };
|
|
1345
|
+
}
|
|
1346
|
+
function toolExtras(payload, stage, opts) {
|
|
1347
|
+
const extras = {};
|
|
1348
|
+
if (isResearchTool(payload)) {
|
|
1349
|
+
extras.research = true;
|
|
1350
|
+
const title = researchTitle(payload.name, stage === "running");
|
|
1351
|
+
if (title) extras.title = title;
|
|
1352
|
+
if (payload.result !== void 0) {
|
|
1353
|
+
const sources = sourcesFromResult(payload.result);
|
|
1354
|
+
if (sources.length) extras.sources = sources;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
if (payload.result !== void 0) {
|
|
1358
|
+
const denial = capabilityDenial(payload.result);
|
|
1359
|
+
if (denial) extras.denial = denial;
|
|
1360
|
+
if (isArtifactTool(payload) || opts.previous?.draft) {
|
|
1361
|
+
const artifact = artifactFromResult(payload.result);
|
|
1362
|
+
if (artifact) {
|
|
1363
|
+
const previous = opts.previous?.artifact;
|
|
1364
|
+
const merged = previous?.id === artifact.id && previous.previewUrl ? { ...artifact, previewUrl: previous.previewUrl } : artifact;
|
|
1365
|
+
extras.artifact = withLinkState(merged, opts.links);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return extras;
|
|
1370
|
+
}
|
|
1371
|
+
function patchArtifact(artifact, action, links) {
|
|
1372
|
+
const next = {
|
|
1373
|
+
...artifact,
|
|
1374
|
+
...action.status ? { status: action.status } : {},
|
|
1375
|
+
...action.title ? { title: action.title } : {},
|
|
1376
|
+
...action.previewUrl ? { previewUrl: action.previewUrl } : {},
|
|
1377
|
+
...action.failure ? { failure: action.failure } : {},
|
|
1378
|
+
...action.release ? { released: true } : {}
|
|
1379
|
+
};
|
|
1380
|
+
const linked = withLinkState(next, links && !action.release);
|
|
1381
|
+
return action.previewUrl === void 0 && action.status === void 0 && artifact.status === "ready" ? { ...linked, linkPending: false } : linked;
|
|
1382
|
+
}
|
|
1383
|
+
function lastShownArtifact(entries, artifactId) {
|
|
1384
|
+
for (let i = entries.length - 1; i >= 0; i -= 1) {
|
|
1385
|
+
const entry = entries[i];
|
|
1386
|
+
if (entry.kind === "tool" && entry.call.artifact?.id === artifactId) return entry.call.artifact;
|
|
1387
|
+
if (entry.kind === "artifact" && entry.artifact.id === artifactId) return entry.artifact;
|
|
1388
|
+
}
|
|
1389
|
+
return void 0;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// src/core/chat-transcript.ts
|
|
1393
|
+
function nextId(d, prefix) {
|
|
1394
|
+
d.state = { ...d.state, seq: d.state.seq + 1 };
|
|
1395
|
+
return `${prefix}-${d.state.seq}`;
|
|
1396
|
+
}
|
|
1397
|
+
function push(d, entry) {
|
|
1398
|
+
d.state = { ...d.state, entries: [...d.state.entries, entry] };
|
|
1399
|
+
}
|
|
1400
|
+
function setTurn(d, turn) {
|
|
1401
|
+
d.state = { ...d.state, turn };
|
|
1402
|
+
}
|
|
1403
|
+
function commitMarkdown(d, turn, text) {
|
|
1404
|
+
if (!text.trim()) return turn;
|
|
1405
|
+
push(d, { kind: "markdown", id: nextId(d, "md"), text, gap: turn.lastKind !== "label" });
|
|
1406
|
+
return { ...turn, committedText: true, lastKind: "markdown" };
|
|
1407
|
+
}
|
|
1408
|
+
function promote(d, final, force = false) {
|
|
1409
|
+
let turn = d.state.turn;
|
|
1410
|
+
if (!turn) return;
|
|
1411
|
+
const items = [...turn.items];
|
|
1412
|
+
while (items.length > 0) {
|
|
1413
|
+
const item = items[0];
|
|
1414
|
+
if (item.kind === "tool") {
|
|
1415
|
+
if (!force && (!FINAL_STAGES.has(item.call.stage) && !item.call.left || holdsArtifact(item.call))) break;
|
|
1416
|
+
const call = item.call.artifact ? { ...item.call, artifact: { ...item.call.artifact, linkPending: false } } : item.call;
|
|
1417
|
+
push(d, { kind: "tool", id: nextId(d, "tool"), call, gap: turn.lastKind === "markdown" });
|
|
1418
|
+
turn = { ...turn, lastKind: "tool" };
|
|
1419
|
+
items.shift();
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
const pending = item.text.slice(item.committed);
|
|
1423
|
+
if (item.closed || final) {
|
|
1424
|
+
turn = commitMarkdown(d, turn, pending);
|
|
1425
|
+
items.shift();
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1428
|
+
const boundary = stableBoundary(pending);
|
|
1429
|
+
if (boundary > 0) {
|
|
1430
|
+
turn = commitMarkdown(d, turn, pending.slice(0, boundary));
|
|
1431
|
+
items[0] = { ...item, committed: item.committed + boundary };
|
|
1432
|
+
}
|
|
1433
|
+
break;
|
|
1434
|
+
}
|
|
1435
|
+
turn = { ...turn, items };
|
|
1436
|
+
const settled = (turn.finished || force) && items.length === 0;
|
|
1437
|
+
if (settled && turn.sources.length) push(d, { kind: "sources", id: nextId(d, "sources"), sources: turn.sources });
|
|
1438
|
+
setTurn(d, settled ? null : turn);
|
|
1439
|
+
}
|
|
1440
|
+
function closeOpenText(items) {
|
|
1441
|
+
return items.map((item) => item.kind === "text" && !item.closed ? { ...item, closed: true } : item);
|
|
1442
|
+
}
|
|
1443
|
+
function upsertTool(d, payload, stage, extra = {}) {
|
|
1444
|
+
const turn = d.state.turn;
|
|
1445
|
+
if (!turn) return;
|
|
1446
|
+
const index = turn.items.findIndex((item) => item.kind === "tool" && item.call.id === payload.id);
|
|
1447
|
+
const final = FINAL_STAGES.has(stage);
|
|
1448
|
+
const previous = index >= 0 ? turn.items[index].call : void 0;
|
|
1449
|
+
const enriched = toolExtras(payload, stage, { links: d.state.links, previous });
|
|
1450
|
+
extra = { ...enriched, ...extra };
|
|
1451
|
+
if (enriched.sources?.length) {
|
|
1452
|
+
setTurn(d, { ...turn, sources: mergeSources(turn.sources, enriched.sources) });
|
|
1453
|
+
}
|
|
1454
|
+
if (index >= 0) {
|
|
1455
|
+
const item = turn.items[index];
|
|
1456
|
+
const finishing = final && item.call.endedAt === void 0;
|
|
1457
|
+
const call2 = {
|
|
1458
|
+
...item.call,
|
|
1459
|
+
stage,
|
|
1460
|
+
args: Object.keys(payload.args).length ? payload.args : item.call.args,
|
|
1461
|
+
...payload.result !== void 0 ? { result: payload.result } : {},
|
|
1462
|
+
preview: payload.display?.preview ?? item.call.preview,
|
|
1463
|
+
...finishing ? { endedAt: d.now, durationMs: Math.max(0, d.now - item.call.startedAt) } : {},
|
|
1464
|
+
...extra
|
|
1465
|
+
};
|
|
1466
|
+
const current2 = d.state.turn;
|
|
1467
|
+
const items = [...current2.items];
|
|
1468
|
+
items[index] = { ...item, call: call2 };
|
|
1469
|
+
setTurn(d, { ...current2, items });
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
const call = {
|
|
1473
|
+
id: payload.id,
|
|
1474
|
+
name: payload.name,
|
|
1475
|
+
title: toolTitle(payload),
|
|
1476
|
+
args: payload.args,
|
|
1477
|
+
stage,
|
|
1478
|
+
...payload.result !== void 0 ? { result: payload.result } : {},
|
|
1479
|
+
preview: payload.display?.preview,
|
|
1480
|
+
startedAt: d.now,
|
|
1481
|
+
...final ? { endedAt: d.now } : {},
|
|
1482
|
+
...extra
|
|
1483
|
+
};
|
|
1484
|
+
const current = d.state.turn;
|
|
1485
|
+
setTurn(d, { ...current, items: [...closeOpenText(current.items), { kind: "tool", id: payload.id, call }] });
|
|
1486
|
+
}
|
|
1487
|
+
function applyArtifactDraft(d, event) {
|
|
1488
|
+
const turn = d.state.turn;
|
|
1489
|
+
const index = turn?.items.findIndex((item2) => item2.kind === "tool" && item2.call.id === event.toolCallId) ?? -1;
|
|
1490
|
+
if (!turn || index < 0) return;
|
|
1491
|
+
const item = turn.items[index];
|
|
1492
|
+
const items = [...turn.items];
|
|
1493
|
+
items[index] = { ...item, call: { ...item.call, draft: applyDraft(item.call.draft, event) } };
|
|
1494
|
+
setTurn(d, { ...turn, items });
|
|
1495
|
+
}
|
|
1496
|
+
function applyArtifactStatus(d, action) {
|
|
1497
|
+
const turn = d.state.turn;
|
|
1498
|
+
const index = turn?.items.findIndex((item) => item.kind === "tool" && item.call.artifact?.id === action.artifactId) ?? -1;
|
|
1499
|
+
if (turn && index >= 0) {
|
|
1500
|
+
const item = turn.items[index];
|
|
1501
|
+
const artifact = patchArtifact(item.call.artifact, action, d.state.links);
|
|
1502
|
+
const items = [...turn.items];
|
|
1503
|
+
items[index] = { ...item, call: { ...item.call, artifact } };
|
|
1504
|
+
setTurn(d, { ...turn, items });
|
|
1505
|
+
promote(d, false);
|
|
1506
|
+
return;
|
|
1507
|
+
}
|
|
1508
|
+
const shown = lastShownArtifact(d.state.entries, action.artifactId);
|
|
1509
|
+
if (!shown || action.release) return;
|
|
1510
|
+
const next = { ...patchArtifact(shown, action, false), linkPending: false };
|
|
1511
|
+
if (next.status === shown.status && next.previewUrl === shown.previewUrl) return;
|
|
1512
|
+
push(d, { kind: "artifact", id: nextId(d, "artifact"), artifact: next });
|
|
1513
|
+
}
|
|
1514
|
+
function finishTurn(d, payload) {
|
|
1515
|
+
let turn = d.state.turn;
|
|
1516
|
+
if (!turn) return;
|
|
1517
|
+
const finalText = payload.text?.trim() ?? "";
|
|
1518
|
+
let items = closeOpenText(turn.items);
|
|
1519
|
+
let revision = null;
|
|
1520
|
+
let extraNote = null;
|
|
1521
|
+
if (payload.finishReason === "error") {
|
|
1522
|
+
if (!turn.error && finalText) turn = { ...turn, error: finalText };
|
|
1523
|
+
else if (finalText && normalizeText(finalText) !== normalizeText(turn.error ?? "")) extraNote = finalText;
|
|
1524
|
+
} else if (finalText && normalizeText(finalText) !== normalizeText(turn.text)) {
|
|
1525
|
+
if (!normalizeText(turn.text) || !turn.committedText) {
|
|
1526
|
+
items = [...items.filter((item) => item.kind !== "text"), { kind: "text", id: "final", text: finalText, committed: 0, closed: true }];
|
|
1527
|
+
} else {
|
|
1528
|
+
revision = finalText;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
turn = { ...turn, items, finished: true, finishReason: payload.finishReason };
|
|
1532
|
+
if (payload.threadId) d.state = { ...d.state, threadId: payload.threadId };
|
|
1533
|
+
setTurn(d, turn);
|
|
1534
|
+
promote(d, true);
|
|
1535
|
+
if (revision) {
|
|
1536
|
+
push(d, { kind: "notice", id: nextId(d, "note"), tone: "info", text: "Tabbio revised this answer:" });
|
|
1537
|
+
push(d, { kind: "markdown", id: nextId(d, "md"), text: revision, gap: false });
|
|
1538
|
+
}
|
|
1539
|
+
if (turn.error) push(d, { kind: "notice", id: nextId(d, "note"), tone: "error", text: turn.error });
|
|
1540
|
+
if (extraNote) push(d, { kind: "notice", id: nextId(d, "note"), tone: "info", text: extraNote });
|
|
1541
|
+
d.state = { ...d.state, streaming: false };
|
|
1542
|
+
}
|
|
1543
|
+
function applyStream(d, event) {
|
|
1544
|
+
const turn = d.state.turn;
|
|
1545
|
+
if (event.type === "start") {
|
|
1546
|
+
d.state = {
|
|
1547
|
+
...d.state,
|
|
1548
|
+
threadId: event.payload.threadId ?? d.state.threadId,
|
|
1549
|
+
modelId: event.payload.modelId ?? d.state.modelId
|
|
1550
|
+
};
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
if (!turn || turn.finished) return;
|
|
1554
|
+
setTurn(d, { ...turn, lastEventAt: d.now });
|
|
1555
|
+
switch (event.type) {
|
|
1556
|
+
case "text-delta": {
|
|
1557
|
+
if (!event.payload.text) return;
|
|
1558
|
+
const current = d.state.turn;
|
|
1559
|
+
const items = [...current.items];
|
|
1560
|
+
const last = items[items.length - 1];
|
|
1561
|
+
if (last?.kind === "text" && !last.closed) items[items.length - 1] = { ...last, text: last.text + event.payload.text };
|
|
1562
|
+
else items.push({ kind: "text", id: nextId(d, "text"), text: event.payload.text, committed: 0, closed: false });
|
|
1563
|
+
setTurn(d, { ...current, items, text: current.text + event.payload.text });
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
case "tool-call":
|
|
1567
|
+
upsertTool(d, event.payload, stageOf(event.payload, "running"));
|
|
1568
|
+
promote(d, false);
|
|
1569
|
+
return;
|
|
1570
|
+
case "tool-result":
|
|
1571
|
+
upsertTool(d, event.payload, stageOf(event.payload, "completed"));
|
|
1572
|
+
promote(d, false);
|
|
1573
|
+
return;
|
|
1574
|
+
case "artifact-draft":
|
|
1575
|
+
applyArtifactDraft(d, event.payload);
|
|
1576
|
+
return;
|
|
1577
|
+
case "tool-call-approval": {
|
|
1578
|
+
upsertTool(d, event.payload, "pending_approval", { approvalId: event.payload.approvalId });
|
|
1579
|
+
const approval = {
|
|
1580
|
+
approvalId: event.payload.approvalId,
|
|
1581
|
+
toolCallId: event.payload.id,
|
|
1582
|
+
name: event.payload.name,
|
|
1583
|
+
title: toolTitle(event.payload),
|
|
1584
|
+
args: event.payload.args,
|
|
1585
|
+
requestedAt: d.now
|
|
1586
|
+
};
|
|
1587
|
+
const others = d.state.approvals.filter((a) => a.approvalId !== approval.approvalId);
|
|
1588
|
+
d.state = { ...d.state, approvals: [...others, approval] };
|
|
1589
|
+
promote(d, false);
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
case "error":
|
|
1593
|
+
setTurn(d, { ...d.state.turn, error: event.payload.message });
|
|
1594
|
+
return;
|
|
1595
|
+
case "finish":
|
|
1596
|
+
finishTurn(d, event.payload);
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
function resolveApproval(d, action) {
|
|
1601
|
+
const approval = d.state.approvals.find((a) => a.approvalId === action.approvalId);
|
|
1602
|
+
d.state = { ...d.state, approvals: d.state.approvals.filter((a) => a.approvalId !== action.approvalId) };
|
|
1603
|
+
if (!approval) return;
|
|
1604
|
+
const decision = action.error ? "leave" : action.decision;
|
|
1605
|
+
const stage = decision === "approve" ? "completed" : decision === "reject" ? "declined" : "pending_approval";
|
|
1606
|
+
const decided = { stage, decision, left: decision === "leave", endedAt: d.now };
|
|
1607
|
+
const waited = { durationMs: Math.max(0, d.now - approval.requestedAt) };
|
|
1608
|
+
const turn = d.state.turn;
|
|
1609
|
+
const live = turn?.items.some((item) => item.kind === "tool" && item.call.id === approval.toolCallId);
|
|
1610
|
+
if (turn && live) {
|
|
1611
|
+
const items = turn.items.map(
|
|
1612
|
+
(item) => item.kind === "tool" && item.call.id === approval.toolCallId ? { ...item, call: { ...item.call, ...decided, ...waited } } : item
|
|
1613
|
+
);
|
|
1614
|
+
setTurn(d, { ...turn, items });
|
|
1615
|
+
promote(d, false);
|
|
1616
|
+
} else {
|
|
1617
|
+
const call = {
|
|
1618
|
+
id: approval.toolCallId,
|
|
1619
|
+
name: approval.name,
|
|
1620
|
+
title: approval.title,
|
|
1621
|
+
args: approval.args,
|
|
1622
|
+
approvalId: approval.approvalId,
|
|
1623
|
+
startedAt: approval.requestedAt,
|
|
1624
|
+
...decided,
|
|
1625
|
+
...waited
|
|
1626
|
+
};
|
|
1627
|
+
push(d, { kind: "tool", id: nextId(d, "tool"), call, gap: false });
|
|
1628
|
+
}
|
|
1629
|
+
if (action.error) push(d, { kind: "notice", id: nextId(d, "note"), tone: "error", text: action.error });
|
|
1630
|
+
if (action.text?.trim()) push(d, { kind: "markdown", id: nextId(d, "md"), text: action.text.trim(), gap: true });
|
|
1631
|
+
}
|
|
1632
|
+
function chatReducer(state, action) {
|
|
1633
|
+
return reduceChat(state, action, Date.now);
|
|
1634
|
+
}
|
|
1635
|
+
function reduceChat(state, action, now) {
|
|
1636
|
+
const d = { state, now: "at" in action && typeof action.at === "number" ? action.at : now() };
|
|
1637
|
+
switch (action.type) {
|
|
1638
|
+
case "submit": {
|
|
1639
|
+
if (d.state.turn) promote(d, true, true);
|
|
1640
|
+
push(d, { kind: "user", id: nextId(d, "user"), text: action.text });
|
|
1641
|
+
push(d, { kind: "assistant", id: nextId(d, "label") });
|
|
1642
|
+
const turn = {
|
|
1643
|
+
id: `turn-${d.state.seq}`,
|
|
1644
|
+
startedAt: d.now,
|
|
1645
|
+
lastEventAt: d.now,
|
|
1646
|
+
items: [],
|
|
1647
|
+
text: "",
|
|
1648
|
+
committedText: false,
|
|
1649
|
+
lastKind: "label",
|
|
1650
|
+
finished: false,
|
|
1651
|
+
sources: []
|
|
1652
|
+
};
|
|
1653
|
+
d.state = { ...d.state, turn, streaming: true };
|
|
1654
|
+
return d.state;
|
|
1655
|
+
}
|
|
1656
|
+
case "tick":
|
|
1657
|
+
promote(d, false);
|
|
1658
|
+
return d.state;
|
|
1659
|
+
case "stream-failed": {
|
|
1660
|
+
const turn = d.state.turn;
|
|
1661
|
+
if (turn && !turn.finished) {
|
|
1662
|
+
setTurn(d, { ...turn, items: closeOpenText(turn.items), finished: true });
|
|
1663
|
+
promote(d, true);
|
|
1664
|
+
}
|
|
1665
|
+
push(d, {
|
|
1666
|
+
kind: "notice",
|
|
1667
|
+
id: nextId(d, "note"),
|
|
1668
|
+
tone: action.cancelled ? "attention" : "error",
|
|
1669
|
+
text: action.message,
|
|
1670
|
+
...action.hint ? { hint: action.hint } : {}
|
|
1671
|
+
});
|
|
1672
|
+
d.state = { ...d.state, streaming: false };
|
|
1673
|
+
return d.state;
|
|
1674
|
+
}
|
|
1675
|
+
case "approval-decided":
|
|
1676
|
+
resolveApproval(d, action);
|
|
1677
|
+
return d.state;
|
|
1678
|
+
case "notice":
|
|
1679
|
+
push(d, { kind: "notice", id: nextId(d, "note"), tone: action.tone, text: action.text, ...action.hint ? { hint: action.hint } : {} });
|
|
1680
|
+
return d.state;
|
|
1681
|
+
case "new-thread":
|
|
1682
|
+
d.state = { ...d.state, threadId: void 0 };
|
|
1683
|
+
return d.state;
|
|
1684
|
+
case "set-thread":
|
|
1685
|
+
d.state = { ...d.state, threadId: action.threadId };
|
|
1686
|
+
return d.state;
|
|
1687
|
+
case "history":
|
|
1688
|
+
d.state = { ...d.state, threadId: action.threadId };
|
|
1689
|
+
for (const message of action.messages) {
|
|
1690
|
+
if (!message.text.trim()) continue;
|
|
1691
|
+
if (message.role === "user") push(d, { kind: "user", id: nextId(d, "user"), text: message.text });
|
|
1692
|
+
else {
|
|
1693
|
+
push(d, { kind: "assistant", id: nextId(d, "label") });
|
|
1694
|
+
push(d, { kind: "markdown", id: nextId(d, "md"), text: message.text, gap: false });
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
return d.state;
|
|
1698
|
+
case "set-mode":
|
|
1699
|
+
d.state = { ...d.state, mode: action.mode, threadId: void 0 };
|
|
1700
|
+
return d.state;
|
|
1701
|
+
case "set-model":
|
|
1702
|
+
d.state = { ...d.state, modelId: action.modelId };
|
|
1703
|
+
return d.state;
|
|
1704
|
+
case "set-capability":
|
|
1705
|
+
d.state = { ...d.state, capabilities: { ...d.state.capabilities, [action.key]: action.value } };
|
|
1706
|
+
return d.state;
|
|
1707
|
+
case "artifact-status":
|
|
1708
|
+
applyArtifactStatus(d, action);
|
|
1709
|
+
return d.state;
|
|
1710
|
+
case "expand-last-tool": {
|
|
1711
|
+
const last = [...d.state.entries].reverse().find((e) => e.kind === "tool");
|
|
1712
|
+
if (last) push(d, { kind: "tool-detail", id: nextId(d, "detail"), call: last.call });
|
|
1713
|
+
return d.state;
|
|
1714
|
+
}
|
|
1715
|
+
default:
|
|
1716
|
+
applyStream(d, action);
|
|
1717
|
+
return d.state;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// src/core/chat.ts
|
|
1722
|
+
var CHAT_PATH = "/api/agent/chat";
|
|
1723
|
+
var DEFAULT_IDLE_MS = 9e4;
|
|
1724
|
+
var DEFAULT_TURN_MS = 30 * 6e4;
|
|
1725
|
+
function agentHeaders(mode, companyId) {
|
|
1726
|
+
return { "x-agent-mode": mode, ...companyId ? { "x-company-id": companyId } : {} };
|
|
1727
|
+
}
|
|
1728
|
+
function isRecord6(value) {
|
|
1729
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1730
|
+
}
|
|
1731
|
+
function isEventStream(response) {
|
|
1732
|
+
return (response.headers.get("content-type") ?? "").toLowerCase().includes("text/event-stream");
|
|
1733
|
+
}
|
|
1734
|
+
async function* streamChat(ctx, request, opts = {}) {
|
|
1735
|
+
for await (const frame of streamChatFrames(ctx, request, opts)) {
|
|
1736
|
+
if (frame.event) yield frame.event;
|
|
1737
|
+
else debug(`chat: skipped stream event ${frame.data.slice(0, 80)}`);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
async function* streamChatFrames(ctx, request, opts = {}) {
|
|
1741
|
+
const { companyId, ...body } = request;
|
|
1742
|
+
const controller = new AbortController();
|
|
1743
|
+
const userSignal = opts.signal;
|
|
1744
|
+
const onUserAbort = () => controller.abort(userSignal?.reason);
|
|
1745
|
+
if (userSignal?.aborted) throw interruptedError("Chat turn cancelled");
|
|
1746
|
+
userSignal?.addEventListener("abort", onUserAbort, { once: true });
|
|
1747
|
+
let stalled = false;
|
|
1748
|
+
let idleTimer;
|
|
1749
|
+
const armIdle = () => {
|
|
1750
|
+
clearTimeout(idleTimer);
|
|
1751
|
+
idleTimer = setTimeout(() => {
|
|
1752
|
+
stalled = true;
|
|
1753
|
+
controller.abort(new Error("stream stalled"));
|
|
1754
|
+
}, opts.idleTimeoutMs ?? DEFAULT_IDLE_MS);
|
|
1755
|
+
idleTimer.unref?.();
|
|
1756
|
+
};
|
|
1757
|
+
const mapError = (error) => {
|
|
1758
|
+
if (userSignal?.aborted) return interruptedError("Chat turn cancelled");
|
|
1759
|
+
if (stalled) {
|
|
1760
|
+
return new CliError({
|
|
1761
|
+
code: "TIMEOUT",
|
|
1762
|
+
message: "Tabbio stopped responding",
|
|
1763
|
+
hint: "The stream went quiet. Try again; your thread keeps its history.",
|
|
1764
|
+
exitCode: ExitCode.Network,
|
|
1765
|
+
cause: error
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
if (error instanceof CliError) return error;
|
|
1769
|
+
return networkError(`${ctx.api.profile.apiUrl}${CHAT_PATH}`, error);
|
|
1770
|
+
};
|
|
1771
|
+
let reader;
|
|
1772
|
+
let done = false;
|
|
1773
|
+
try {
|
|
1774
|
+
armIdle();
|
|
1775
|
+
let response;
|
|
1776
|
+
try {
|
|
1777
|
+
response = await ctx.api.raw(CHAT_PATH, {
|
|
1778
|
+
method: "POST",
|
|
1779
|
+
headers: {
|
|
1780
|
+
"content-type": "application/json",
|
|
1781
|
+
accept: "text/event-stream",
|
|
1782
|
+
...agentHeaders(body.mode, companyId)
|
|
1783
|
+
},
|
|
1784
|
+
body: JSON.stringify(companyId ? { ...body, companyId } : body),
|
|
1785
|
+
signal: controller.signal,
|
|
1786
|
+
timeoutMs: opts.timeoutMs ?? DEFAULT_TURN_MS
|
|
1787
|
+
});
|
|
1788
|
+
} catch (error) {
|
|
1789
|
+
throw mapError(error);
|
|
1790
|
+
}
|
|
1791
|
+
if (!response.ok || !isEventStream(response)) {
|
|
1792
|
+
await parseJsonResponse(response);
|
|
1793
|
+
throw new CliError({
|
|
1794
|
+
code: "BAD_RESPONSE",
|
|
1795
|
+
message: "Tabbio answered the chat request without a stream",
|
|
1796
|
+
exitCode: ExitCode.Server,
|
|
1797
|
+
status: response.status
|
|
1798
|
+
});
|
|
1799
|
+
}
|
|
1800
|
+
if (!response.body) throw new CliError({ code: "BAD_RESPONSE", message: "Empty chat stream", exitCode: ExitCode.Server });
|
|
1801
|
+
const queue = [];
|
|
1802
|
+
const parser = createParser({
|
|
1803
|
+
onEvent(message) {
|
|
1804
|
+
if (message.data.trim()) queue.push({ data: message.data, event: parseStreamEvent(message.data) });
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1807
|
+
const decoder = new TextDecoder();
|
|
1808
|
+
const activeReader = response.body.getReader();
|
|
1809
|
+
reader = activeReader;
|
|
1810
|
+
let sawFinish = false;
|
|
1811
|
+
for (; ; ) {
|
|
1812
|
+
let chunk;
|
|
1813
|
+
try {
|
|
1814
|
+
chunk = await activeReader.read();
|
|
1815
|
+
} catch (error) {
|
|
1816
|
+
throw mapError(error);
|
|
1817
|
+
}
|
|
1818
|
+
if (chunk.done) break;
|
|
1819
|
+
armIdle();
|
|
1820
|
+
parser.feed(decoder.decode(chunk.value, { stream: true }));
|
|
1821
|
+
while (queue.length > 0) {
|
|
1822
|
+
const frame = queue.shift();
|
|
1823
|
+
if (frame.event?.type === "finish") sawFinish = true;
|
|
1824
|
+
yield frame;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
parser.feed(`${decoder.decode()}
|
|
1828
|
+
|
|
1829
|
+
`);
|
|
1830
|
+
while (queue.length > 0) {
|
|
1831
|
+
const frame = queue.shift();
|
|
1832
|
+
if (frame.event?.type === "finish") sawFinish = true;
|
|
1833
|
+
yield frame;
|
|
1834
|
+
}
|
|
1835
|
+
done = true;
|
|
1836
|
+
if (!sawFinish) {
|
|
1837
|
+
if (userSignal?.aborted) throw interruptedError("Chat turn cancelled");
|
|
1838
|
+
throw new CliError({
|
|
1839
|
+
code: "STREAM_ENDED",
|
|
1840
|
+
message: "The chat stream ended before Tabbio finished",
|
|
1841
|
+
hint: "Check your connection and try again.",
|
|
1842
|
+
exitCode: ExitCode.Network,
|
|
1843
|
+
retry: true
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
} finally {
|
|
1847
|
+
clearTimeout(idleTimer);
|
|
1848
|
+
userSignal?.removeEventListener("abort", onUserAbort);
|
|
1849
|
+
if (reader && !done) {
|
|
1850
|
+
controller.abort();
|
|
1851
|
+
await reader.cancel().catch(() => void 0);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
function listThreads(ctx, scope) {
|
|
1856
|
+
const query = new URLSearchParams({ mode: scope.mode, ...scope.companyId ? { companyId: scope.companyId } : {} });
|
|
1857
|
+
return ctx.api.json(`/api/agent/threads?${query.toString()}`, {
|
|
1858
|
+
headers: agentHeaders(scope.mode, scope.companyId)
|
|
1859
|
+
});
|
|
1860
|
+
}
|
|
1861
|
+
async function getThreadMessages(ctx, threadId, scope) {
|
|
1862
|
+
const data = await ctx.api.json(
|
|
1863
|
+
`/api/agent/threads/${encodeURIComponent(threadId)}/messages`,
|
|
1864
|
+
{ headers: agentHeaders(scope.mode, scope.companyId) }
|
|
1865
|
+
);
|
|
1866
|
+
return { messages: data?.messages ?? [], toolCalls: data?.toolCalls ?? [] };
|
|
1867
|
+
}
|
|
1868
|
+
function decide(ctx, approvalId, action, scope) {
|
|
1869
|
+
return ctx.api.json(`/api/agent/approvals/${encodeURIComponent(approvalId)}/${action}`, {
|
|
1870
|
+
method: "POST",
|
|
1871
|
+
body: scope?.reason ? { reason: scope.reason } : {},
|
|
1872
|
+
headers: scope?.mode ? agentHeaders(scope.mode, scope.companyId) : void 0,
|
|
1873
|
+
// Confirming resumes the agent run server-side; it can take a while.
|
|
1874
|
+
timeoutMs: 5 * 6e4
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
function confirmApproval(ctx, approvalId, scope) {
|
|
1878
|
+
return decide(ctx, approvalId, "confirm", scope);
|
|
1879
|
+
}
|
|
1880
|
+
function rejectApproval(ctx, approvalId, scope) {
|
|
1881
|
+
return decide(ctx, approvalId, "reject", scope);
|
|
1882
|
+
}
|
|
1883
|
+
function listModels(ctx, scope) {
|
|
1884
|
+
return ctx.api.json(`/api/agent/models?mode=${encodeURIComponent(scope.mode)}`, {
|
|
1885
|
+
headers: agentHeaders(scope.mode, scope.companyId)
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
function messageText(content) {
|
|
1889
|
+
if (typeof content === "string") return content;
|
|
1890
|
+
if (Array.isArray(content)) {
|
|
1891
|
+
return content.map((part) => isRecord6(part) && typeof part.text === "string" ? part.text : "").filter(Boolean).join("\n");
|
|
1892
|
+
}
|
|
1893
|
+
if (isRecord6(content) && typeof content.text === "string") return content.text;
|
|
1894
|
+
return "";
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
export {
|
|
1898
|
+
setInteractiveHooks,
|
|
1899
|
+
getInteractiveHooks,
|
|
1900
|
+
canUseInteractiveUi,
|
|
1901
|
+
getPath,
|
|
1902
|
+
displayWidth,
|
|
1903
|
+
truncate,
|
|
1904
|
+
humanCell,
|
|
1905
|
+
pickColumns,
|
|
1906
|
+
formatTable,
|
|
1907
|
+
formatKv,
|
|
1908
|
+
resolveOutputMode,
|
|
1909
|
+
parseFields,
|
|
1910
|
+
findListWrapper,
|
|
1911
|
+
toTsv,
|
|
1912
|
+
jsonEnvelope,
|
|
1913
|
+
writeJson,
|
|
1914
|
+
formatDuration,
|
|
1915
|
+
printEmptyNotice,
|
|
1916
|
+
renderResult,
|
|
1917
|
+
ARTIFACT_PRODUCING_TOOL_IDS,
|
|
1918
|
+
artifactFromResult,
|
|
1919
|
+
sourcesFromResult,
|
|
1920
|
+
mergeSources,
|
|
1921
|
+
capabilityDenial,
|
|
1922
|
+
draftSummary,
|
|
1923
|
+
previewVariantFor,
|
|
1924
|
+
ARTIFACT_KINDS,
|
|
1925
|
+
PREVIEW_VARIANTS,
|
|
1926
|
+
listArtifacts,
|
|
1927
|
+
getArtifact,
|
|
1928
|
+
previewUrl,
|
|
1929
|
+
downloadUrl,
|
|
1930
|
+
listVersions,
|
|
1931
|
+
restoreVersion,
|
|
1932
|
+
deleteArtifact,
|
|
1933
|
+
formatToVariant,
|
|
1934
|
+
defaultVariantFor,
|
|
1935
|
+
extensionFor,
|
|
1936
|
+
safeFileName,
|
|
1937
|
+
resolveOutputPath,
|
|
1938
|
+
waitForArtifact,
|
|
1939
|
+
downloadToFile,
|
|
1940
|
+
supportsHyperlinks,
|
|
1941
|
+
hyperlink,
|
|
1942
|
+
DEFAULT_CAPABILITIES,
|
|
1943
|
+
CAPABILITY_CHOICES,
|
|
1944
|
+
CAPABILITY_CHOICE_HELP,
|
|
1945
|
+
parseCapabilityMode,
|
|
1946
|
+
loadCapabilityPrefs,
|
|
1947
|
+
saveCapabilityPrefs,
|
|
1948
|
+
resolveCapabilities,
|
|
1949
|
+
capabilityRequestFields,
|
|
1950
|
+
unavailableNotices,
|
|
1951
|
+
initialChatState,
|
|
1952
|
+
isDestructiveTool,
|
|
1953
|
+
chatReducer,
|
|
1954
|
+
streamChat,
|
|
1955
|
+
streamChatFrames,
|
|
1956
|
+
listThreads,
|
|
1957
|
+
getThreadMessages,
|
|
1958
|
+
confirmApproval,
|
|
1959
|
+
rejectApproval,
|
|
1960
|
+
listModels,
|
|
1961
|
+
messageText,
|
|
1962
|
+
RESERVED_GLOBAL_FLAGS,
|
|
1963
|
+
shortDescription,
|
|
1964
|
+
schemaToFlags,
|
|
1965
|
+
schemaToOptions,
|
|
1966
|
+
didYouMean,
|
|
1967
|
+
parseToolInput,
|
|
1968
|
+
flagsForProperties,
|
|
1969
|
+
describeSchema,
|
|
1970
|
+
autoFilledProperties,
|
|
1971
|
+
exampleInvocation,
|
|
1972
|
+
isInteractiveTerminal,
|
|
1973
|
+
assertInteractive,
|
|
1974
|
+
renderScreen
|
|
1975
|
+
};
|
|
1976
|
+
//# sourceMappingURL=chunk-QVR5KIEQ.js.map
|