@lotics/app-sdk 0.100.1 → 0.101.1
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/AGENTS.md +32 -47
- package/dist/agent_stream.d.ts +131 -0
- package/dist/ask_ai.d.ts +27 -0
- package/dist/attachments.d.ts +58 -0
- package/dist/chunk-ARV5FAU5.js +1132 -0
- package/dist/comments.d.ts +89 -0
- package/dist/error_report.d.ts +9 -0
- package/dist/folder_pick.d.ts +8 -0
- package/dist/geolocation.d.ts +42 -0
- package/dist/hooks.d.ts +251 -0
- package/dist/{src/index.d.ts → index.d.ts} +13 -22
- package/dist/index.js +31331 -0
- package/dist/index.js.LEGAL.txt +11 -0
- package/dist/members.d.ts +32 -0
- package/dist/mock.d.ts +37 -0
- package/dist/mount.d.ts +19 -0
- package/dist/new_record.d.ts +37 -0
- package/dist/open_app.d.ts +12 -0
- package/dist/open_external.d.ts +10 -0
- package/dist/overlay.d.ts +25 -0
- package/dist/queries.d.ts +231 -0
- package/dist/recording.d.ts +47 -0
- package/dist/recording_state.d.ts +43 -0
- package/dist/rename_file.d.ts +13 -0
- package/dist/router.d.ts +10 -0
- package/dist/router.js +97 -0
- package/dist/row.d.ts +87 -0
- package/dist/rpc.d.ts +114 -0
- package/dist/select.d.ts +24 -0
- package/dist/shared_types.d.ts +8 -0
- package/dist/store.d.ts +43 -0
- package/dist/types.d.ts +36 -0
- package/dist/upload/optimize.d.ts +30 -0
- package/dist/upload/pipeline.d.ts +36 -0
- package/dist/upload/transport.d.ts +19 -0
- package/dist/url_params.d.ts +55 -0
- package/dist/use_recents.d.ts +15 -0
- package/dist/{src/use_url_state.d.ts → use_url_state.d.ts} +0 -2
- package/dist/viewer.d.ts +41 -0
- package/dist/written.d.ts +79 -0
- package/docs/ai.md +74 -133
- package/docs/data_fetching.md +209 -290
- package/docs/files.md +61 -51
- package/docs/members_and_options.md +92 -62
- package/docs/mutations.md +136 -205
- package/docs/navigation_and_state.md +26 -35
- package/docs/queries.md +144 -207
- package/docs/recipes.md +21 -45
- package/docs/runtime.md +74 -137
- package/docs/security.md +8 -11
- package/docs/workflows.md +189 -174
- package/package.json +27 -28
- package/dist/src/agent_stream.d.ts +0 -200
- package/dist/src/agent_stream.js +0 -314
- package/dist/src/ask_ai.d.ts +0 -40
- package/dist/src/ask_ai.js +0 -35
- package/dist/src/attachments.d.ts +0 -68
- package/dist/src/attachments.js +0 -93
- package/dist/src/comments.d.ts +0 -127
- package/dist/src/comments.js +0 -192
- package/dist/src/download.js +0 -54
- package/dist/src/geolocation.d.ts +0 -64
- package/dist/src/geolocation.js +0 -96
- package/dist/src/hooks.d.ts +0 -781
- package/dist/src/hooks.js +0 -860
- package/dist/src/index.js +0 -34
- package/dist/src/members.d.ts +0 -105
- package/dist/src/members.js +0 -62
- package/dist/src/mock.d.ts +0 -118
- package/dist/src/mock.js +0 -124
- package/dist/src/mount.d.ts +0 -47
- package/dist/src/mount.js +0 -34
- package/dist/src/new_record.d.ts +0 -74
- package/dist/src/new_record.js +0 -117
- package/dist/src/open_app.d.ts +0 -15
- package/dist/src/open_app.js +0 -18
- package/dist/src/open_external.d.ts +0 -16
- package/dist/src/open_external.js +0 -19
- package/dist/src/recording.d.ts +0 -59
- package/dist/src/recording.js +0 -30
- package/dist/src/recording_state.d.ts +0 -59
- package/dist/src/recording_state.js +0 -94
- package/dist/src/router.d.ts +0 -17
- package/dist/src/router.js +0 -144
- package/dist/src/row.d.ts +0 -159
- package/dist/src/row.js +0 -254
- package/dist/src/rpc.d.ts +0 -207
- package/dist/src/rpc.js +0 -904
- package/dist/src/select.d.ts +0 -48
- package/dist/src/select.js +0 -40
- package/dist/src/types.d.ts +0 -115
- package/dist/src/types.js +0 -1
- package/dist/src/upload/optimize.d.ts +0 -54
- package/dist/src/upload/optimize.js +0 -207
- package/dist/src/upload/pipeline.d.ts +0 -55
- package/dist/src/upload/pipeline.js +0 -52
- package/dist/src/upload/transport.d.ts +0 -42
- package/dist/src/upload/transport.js +0 -128
- package/dist/src/url_params.d.ts +0 -93
- package/dist/src/url_params.js +0 -215
- package/dist/src/use_optimistic.d.ts +0 -27
- package/dist/src/use_optimistic.js +0 -27
- package/dist/src/use_recents.d.ts +0 -19
- package/dist/src/use_recents.js +0 -71
- package/dist/src/use_url_state.js +0 -73
- package/dist/src/viewer.d.ts +0 -26
- package/dist/src/viewer.js +0 -47
- /package/dist/{src/download.d.ts → download.d.ts} +0 -0
|
@@ -0,0 +1,1132 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/url_params.ts
|
|
8
|
+
function first(raw) {
|
|
9
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
10
|
+
}
|
|
11
|
+
function valueEqual(a, b) {
|
|
12
|
+
if (a === b) return true;
|
|
13
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
14
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
15
|
+
return a.length === b.length && a.every((x, i) => valueEqual(x, b[i]));
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
function optional(base) {
|
|
20
|
+
return {
|
|
21
|
+
decode: base.decode,
|
|
22
|
+
encode: base.encode,
|
|
23
|
+
withDefault(fallback) {
|
|
24
|
+
return {
|
|
25
|
+
decode: (raw) => {
|
|
26
|
+
const v = base.decode(raw);
|
|
27
|
+
return v === void 0 ? fallback : v;
|
|
28
|
+
},
|
|
29
|
+
encode: (value) => valueEqual(value, fallback) ? void 0 : base.encode(value)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
var stringCodec = optional({
|
|
35
|
+
decode: (raw) => raw === void 0 ? void 0 : first(raw),
|
|
36
|
+
encode: (v) => v
|
|
37
|
+
});
|
|
38
|
+
var numberCodec = optional({
|
|
39
|
+
decode: (raw) => {
|
|
40
|
+
const s = first(raw);
|
|
41
|
+
if (s === void 0 || s === "") return void 0;
|
|
42
|
+
const n = Number(s);
|
|
43
|
+
return Number.isFinite(n) ? n : void 0;
|
|
44
|
+
},
|
|
45
|
+
encode: (v) => v === void 0 ? void 0 : String(v)
|
|
46
|
+
});
|
|
47
|
+
var booleanCodec = optional({
|
|
48
|
+
decode: (raw) => {
|
|
49
|
+
const s = first(raw);
|
|
50
|
+
return s === "true" ? true : s === "false" ? false : void 0;
|
|
51
|
+
},
|
|
52
|
+
encode: (v) => v === void 0 ? void 0 : v ? "true" : "false"
|
|
53
|
+
});
|
|
54
|
+
var isoDateCodec = optional({
|
|
55
|
+
decode: (raw) => {
|
|
56
|
+
const s = first(raw);
|
|
57
|
+
if (s === void 0 || !/^\d{4}-\d{2}-\d{2}$/.test(s)) return void 0;
|
|
58
|
+
const d = /* @__PURE__ */ new Date(`${s}T00:00:00.000Z`);
|
|
59
|
+
return Number.isNaN(d.getTime()) ? void 0 : d;
|
|
60
|
+
},
|
|
61
|
+
encode: (v) => v === void 0 ? void 0 : v.toISOString().slice(0, 10)
|
|
62
|
+
});
|
|
63
|
+
function enumCodec(values) {
|
|
64
|
+
return optional({
|
|
65
|
+
decode: (raw) => {
|
|
66
|
+
const s = first(raw);
|
|
67
|
+
return s === void 0 ? void 0 : values.find((v) => v === s);
|
|
68
|
+
},
|
|
69
|
+
encode: (v) => v
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function arrayOf(inner) {
|
|
73
|
+
return optional({
|
|
74
|
+
decode: (raw) => {
|
|
75
|
+
if (raw === void 0) return void 0;
|
|
76
|
+
const items = Array.isArray(raw) ? raw : [raw];
|
|
77
|
+
return items.map((x) => inner.decode(x)).filter((x) => x !== void 0);
|
|
78
|
+
},
|
|
79
|
+
encode: (value) => {
|
|
80
|
+
if (value === void 0 || value.length === 0) return void 0;
|
|
81
|
+
const out = value.map((x) => inner.encode(x)).filter((x) => typeof x === "string");
|
|
82
|
+
return out.length > 0 ? out : void 0;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
var urlParam = {
|
|
87
|
+
string: stringCodec,
|
|
88
|
+
number: numberCodec,
|
|
89
|
+
boolean: booleanCodec,
|
|
90
|
+
isoDate: isoDateCodec,
|
|
91
|
+
enum: enumCodec,
|
|
92
|
+
arrayOf
|
|
93
|
+
};
|
|
94
|
+
function decodeAll(defs, params) {
|
|
95
|
+
const out = {};
|
|
96
|
+
for (const key of Object.keys(defs)) {
|
|
97
|
+
out[key] = defs[key].decode(params[key]);
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
function encodePatch(defs, values) {
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const key of Object.keys(values)) {
|
|
104
|
+
const codec = defs[key];
|
|
105
|
+
if (codec) out[key] = codec.encode(values[key]);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
function parseSearch(search) {
|
|
110
|
+
const sp = new URLSearchParams(search);
|
|
111
|
+
const out = {};
|
|
112
|
+
for (const key of sp.keys()) {
|
|
113
|
+
if (key in out) continue;
|
|
114
|
+
const all = sp.getAll(key);
|
|
115
|
+
out[key] = all.length > 1 ? all : all[0];
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
function serializeMerge(search, patch) {
|
|
120
|
+
const sp = new URLSearchParams(search);
|
|
121
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
122
|
+
sp.delete(key);
|
|
123
|
+
if (value === void 0) continue;
|
|
124
|
+
if (Array.isArray(value)) {
|
|
125
|
+
for (const item of value) sp.append(key, item);
|
|
126
|
+
} else {
|
|
127
|
+
sp.append(key, value);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return sp.toString();
|
|
131
|
+
}
|
|
132
|
+
function applyPatch(params, patch) {
|
|
133
|
+
const next = { ...params };
|
|
134
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
135
|
+
if (value === void 0) delete next[key];
|
|
136
|
+
else next[key] = value;
|
|
137
|
+
}
|
|
138
|
+
return next;
|
|
139
|
+
}
|
|
140
|
+
function paramsEqual(a, b) {
|
|
141
|
+
const ak = Object.keys(a);
|
|
142
|
+
const bk = Object.keys(b);
|
|
143
|
+
if (ak.length !== bk.length) return false;
|
|
144
|
+
for (const key of ak) {
|
|
145
|
+
const av = a[key];
|
|
146
|
+
const bv = b[key];
|
|
147
|
+
if (Array.isArray(av) || Array.isArray(bv)) {
|
|
148
|
+
if (!Array.isArray(av) || !Array.isArray(bv)) return false;
|
|
149
|
+
if (av.length !== bv.length || !av.every((x, i) => x === bv[i])) return false;
|
|
150
|
+
} else if (av !== bv) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ../shared/src/app_host/api_base_meta.ts
|
|
158
|
+
var API_BASE_META_NAME = "lotics-api-base";
|
|
159
|
+
|
|
160
|
+
// ../shared/src/transport_error.ts
|
|
161
|
+
function gatewayErrorMessage(status) {
|
|
162
|
+
if (status === 524) {
|
|
163
|
+
return "The request took too long to finish (gateway timeout). It may still be running \u2014 check back in a moment, or try again.";
|
|
164
|
+
}
|
|
165
|
+
if (status >= 500) {
|
|
166
|
+
return "The service is temporarily unavailable. Please try again shortly.";
|
|
167
|
+
}
|
|
168
|
+
return "The service returned an unexpected response. Please try again.";
|
|
169
|
+
}
|
|
170
|
+
function transportErrorMessage(status, parsed) {
|
|
171
|
+
const jsonMessage = parsed && typeof parsed.message === "string" ? parsed.message : null;
|
|
172
|
+
if (jsonMessage === null) return gatewayErrorMessage(status);
|
|
173
|
+
const authored = typeof parsed.code === "string" && parsed.code.length > 0;
|
|
174
|
+
return status < 500 || authored ? jsonMessage : gatewayErrorMessage(status);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/upload/optimize.ts
|
|
178
|
+
var DEFAULT_IMAGE_FIDELITY = "high";
|
|
179
|
+
var IMAGE_POLICIES = {
|
|
180
|
+
standard: { maxDimensionPx: 1280, jpegQuality: 0.8 },
|
|
181
|
+
high: { maxDimensionPx: 1568, jpegQuality: 0.95 }
|
|
182
|
+
};
|
|
183
|
+
var CONVERTIBLE_EXTENSION_PATTERN = /\.(heic|heif|png|webp)$/i;
|
|
184
|
+
async function optimizeImageForUpload(file, fidelity = DEFAULT_IMAGE_FIDELITY) {
|
|
185
|
+
if (fidelity === "original") return unchanged(file, "skipped_original");
|
|
186
|
+
const policy = IMAGE_POLICIES[fidelity];
|
|
187
|
+
const format = getOptimizableFormat(file);
|
|
188
|
+
const mimeType = format?.outputMimeType;
|
|
189
|
+
const unsupportedReason = getUnsupportedReason(mimeType);
|
|
190
|
+
if (unsupportedReason) return unchanged(file, unsupportedReason);
|
|
191
|
+
if (!mimeType || !format) return unchanged(file, "skipped_unsupported_format");
|
|
192
|
+
const loaded = await loadImage(file);
|
|
193
|
+
if (loaded.status === "decode_unavailable") return unchanged(file, "skipped_decode_unavailable");
|
|
194
|
+
const { source, width, height } = loaded;
|
|
195
|
+
if (width <= 0 || height <= 0) {
|
|
196
|
+
closeSource(source);
|
|
197
|
+
return unchanged(file, "skipped_invalid_dimensions");
|
|
198
|
+
}
|
|
199
|
+
if (Math.max(width, height) <= policy.maxDimensionPx) {
|
|
200
|
+
closeSource(source);
|
|
201
|
+
return unchanged(file, "skipped_small_dimensions", width, height);
|
|
202
|
+
}
|
|
203
|
+
const { width: targetWidth, height: targetHeight } = scale(width, height, policy.maxDimensionPx);
|
|
204
|
+
const canvas = document.createElement("canvas");
|
|
205
|
+
canvas.width = targetWidth;
|
|
206
|
+
canvas.height = targetHeight;
|
|
207
|
+
const ctx = canvas.getContext("2d");
|
|
208
|
+
if (!ctx || typeof canvas.toBlob !== "function") {
|
|
209
|
+
closeSource(source);
|
|
210
|
+
return unchanged(file, "skipped_canvas_unavailable", width, height);
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
ctx.imageSmoothingEnabled = true;
|
|
214
|
+
ctx.imageSmoothingQuality = "high";
|
|
215
|
+
ctx.drawImage(source, 0, 0, targetWidth, targetHeight);
|
|
216
|
+
} finally {
|
|
217
|
+
closeSource(source);
|
|
218
|
+
}
|
|
219
|
+
const blob = await canvasToBlob(canvas, mimeType, policy.jpegQuality);
|
|
220
|
+
if (blob === null) return unchanged(file, "skipped_canvas_no_data", width, height, targetWidth, targetHeight);
|
|
221
|
+
if (blob.type !== mimeType) {
|
|
222
|
+
return unchanged(file, "skipped_canvas_type_mismatch", width, height, targetWidth, targetHeight);
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
file: new File([blob], format.outputFilename, {
|
|
226
|
+
type: mimeType,
|
|
227
|
+
lastModified: file.lastModified
|
|
228
|
+
}),
|
|
229
|
+
optimized: true,
|
|
230
|
+
reason: "optimized",
|
|
231
|
+
originalSizeBytes: file.size,
|
|
232
|
+
optimizedSizeBytes: blob.size,
|
|
233
|
+
width,
|
|
234
|
+
height,
|
|
235
|
+
targetWidth,
|
|
236
|
+
targetHeight
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function getUnsupportedReason(mimeType) {
|
|
240
|
+
if (mimeType === void 0) return "skipped_unsupported_format";
|
|
241
|
+
if (typeof window === "undefined" || typeof document === "undefined" || typeof document.createElement !== "function" || typeof URL.createObjectURL !== "function" || typeof URL.revokeObjectURL !== "function" || typeof Image === "undefined") {
|
|
242
|
+
return "skipped_environment_unsupported";
|
|
243
|
+
}
|
|
244
|
+
return void 0;
|
|
245
|
+
}
|
|
246
|
+
function getOptimizableFormat(file) {
|
|
247
|
+
const mimeType = normalizeMimeType(file.type);
|
|
248
|
+
if (!mimeType) return void 0;
|
|
249
|
+
if (mimeType === "image/jpeg") {
|
|
250
|
+
return { outputMimeType: "image/jpeg", outputFilename: file.name };
|
|
251
|
+
}
|
|
252
|
+
return { outputMimeType: "image/jpeg", outputFilename: replaceExtensionWithJpeg(file.name) };
|
|
253
|
+
}
|
|
254
|
+
function normalizeMimeType(mimeType) {
|
|
255
|
+
const m = mimeType.toLowerCase();
|
|
256
|
+
if (m === "image/jpeg" || m === "image/jpg") return "image/jpeg";
|
|
257
|
+
if (m === "image/heic" || m === "image/heif") return m;
|
|
258
|
+
if (m === "image/png" || m === "image/webp") return m;
|
|
259
|
+
return void 0;
|
|
260
|
+
}
|
|
261
|
+
function replaceExtensionWithJpeg(filename) {
|
|
262
|
+
if (CONVERTIBLE_EXTENSION_PATTERN.test(filename)) {
|
|
263
|
+
return filename.replace(CONVERTIBLE_EXTENSION_PATTERN, ".jpg");
|
|
264
|
+
}
|
|
265
|
+
return `${filename}.jpg`;
|
|
266
|
+
}
|
|
267
|
+
function unchanged(file, reason, width = 0, height = 0, targetWidth = width, targetHeight = height) {
|
|
268
|
+
return {
|
|
269
|
+
file,
|
|
270
|
+
optimized: false,
|
|
271
|
+
reason,
|
|
272
|
+
originalSizeBytes: file.size,
|
|
273
|
+
optimizedSizeBytes: file.size,
|
|
274
|
+
width,
|
|
275
|
+
height,
|
|
276
|
+
targetWidth,
|
|
277
|
+
targetHeight
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function closeSource(source) {
|
|
281
|
+
if ("close" in source && typeof source.close === "function") source.close();
|
|
282
|
+
}
|
|
283
|
+
function scale(width, height, maxDimensionPx) {
|
|
284
|
+
const largest = Math.max(width, height);
|
|
285
|
+
if (largest <= maxDimensionPx) return { width, height };
|
|
286
|
+
const factor = maxDimensionPx / largest;
|
|
287
|
+
return {
|
|
288
|
+
width: Math.max(1, Math.round(width * factor)),
|
|
289
|
+
height: Math.max(1, Math.round(height * factor))
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
async function loadImage(file) {
|
|
293
|
+
if (typeof createImageBitmap === "function") {
|
|
294
|
+
try {
|
|
295
|
+
const bitmap = await createImageBitmap(file);
|
|
296
|
+
return { status: "decoded", source: bitmap, width: bitmap.width, height: bitmap.height };
|
|
297
|
+
} catch {
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const objectUrl = URL.createObjectURL(file);
|
|
301
|
+
return new Promise((resolve) => {
|
|
302
|
+
const image = new Image();
|
|
303
|
+
const cleanup = () => {
|
|
304
|
+
image.onload = null;
|
|
305
|
+
image.onerror = null;
|
|
306
|
+
URL.revokeObjectURL(objectUrl);
|
|
307
|
+
};
|
|
308
|
+
image.onload = () => {
|
|
309
|
+
cleanup();
|
|
310
|
+
resolve({ status: "decoded", source: image, width: image.naturalWidth, height: image.naturalHeight });
|
|
311
|
+
};
|
|
312
|
+
image.onerror = () => {
|
|
313
|
+
cleanup();
|
|
314
|
+
resolve({ status: "decode_unavailable" });
|
|
315
|
+
};
|
|
316
|
+
image.src = objectUrl;
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
async function canvasToBlob(canvas, mimeType, jpegQuality) {
|
|
320
|
+
return new Promise((resolve) => {
|
|
321
|
+
canvas.toBlob(resolve, mimeType, jpegQuality);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/upload/transport.ts
|
|
326
|
+
var UPLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
327
|
+
var UPLOAD_PUT_MAX_ATTEMPTS = 3;
|
|
328
|
+
var UPLOAD_PUT_BACKOFF_BASE_MS = 1e3;
|
|
329
|
+
var UploadTimeoutError = class extends Error {
|
|
330
|
+
constructor(url, timeoutMs) {
|
|
331
|
+
super(`Upload request to ${url} timed out after ${timeoutMs}ms`);
|
|
332
|
+
this.url = url;
|
|
333
|
+
this.timeoutMs = timeoutMs;
|
|
334
|
+
}
|
|
335
|
+
url;
|
|
336
|
+
timeoutMs;
|
|
337
|
+
name = "UploadTimeoutError";
|
|
338
|
+
};
|
|
339
|
+
var UploadAbortedError = class extends Error {
|
|
340
|
+
name = "UploadAbortedError";
|
|
341
|
+
constructor() {
|
|
342
|
+
super("Upload aborted");
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
async function fetchWithTimeout(url, init, timeoutMs) {
|
|
346
|
+
const controller = new AbortController();
|
|
347
|
+
let didTimeout = false;
|
|
348
|
+
const timeoutId = setTimeout(() => {
|
|
349
|
+
didTimeout = true;
|
|
350
|
+
controller.abort();
|
|
351
|
+
}, timeoutMs);
|
|
352
|
+
if (init.signal) {
|
|
353
|
+
if (init.signal.aborted) {
|
|
354
|
+
clearTimeout(timeoutId);
|
|
355
|
+
throw new UploadAbortedError();
|
|
356
|
+
}
|
|
357
|
+
init.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
361
|
+
} catch (err) {
|
|
362
|
+
if (didTimeout) throw new UploadTimeoutError(url, timeoutMs);
|
|
363
|
+
if (init.signal?.aborted) throw new UploadAbortedError();
|
|
364
|
+
throw err;
|
|
365
|
+
} finally {
|
|
366
|
+
clearTimeout(timeoutId);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async function putToStorageWithRetry(uploadUrl, file, signal) {
|
|
370
|
+
let lastError;
|
|
371
|
+
for (let attempt = 1; attempt <= UPLOAD_PUT_MAX_ATTEMPTS; attempt += 1) {
|
|
372
|
+
try {
|
|
373
|
+
const response = await fetchWithTimeout(
|
|
374
|
+
uploadUrl,
|
|
375
|
+
{
|
|
376
|
+
method: "PUT",
|
|
377
|
+
headers: { "Content-Type": file.type },
|
|
378
|
+
body: file,
|
|
379
|
+
signal
|
|
380
|
+
},
|
|
381
|
+
UPLOAD_TIMEOUT_MS
|
|
382
|
+
);
|
|
383
|
+
if (response.ok) return response;
|
|
384
|
+
if (response.status >= 400 && response.status < 500) return response;
|
|
385
|
+
lastError = new Error(`Storage PUT returned ${response.status}`);
|
|
386
|
+
} catch (err) {
|
|
387
|
+
if (err instanceof UploadAbortedError) throw err;
|
|
388
|
+
lastError = err;
|
|
389
|
+
}
|
|
390
|
+
if (attempt < UPLOAD_PUT_MAX_ATTEMPTS) {
|
|
391
|
+
const delayMs = UPLOAD_PUT_BACKOFF_BASE_MS * 2 ** (attempt - 1);
|
|
392
|
+
await sleep(delayMs, signal);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
throw lastError instanceof Error ? lastError : new Error("Storage PUT failed");
|
|
396
|
+
}
|
|
397
|
+
function sleep(ms, signal) {
|
|
398
|
+
return new Promise((resolve, reject) => {
|
|
399
|
+
if (signal?.aborted) {
|
|
400
|
+
reject(new UploadAbortedError());
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const timeoutId = setTimeout(() => {
|
|
404
|
+
cleanup();
|
|
405
|
+
resolve();
|
|
406
|
+
}, ms);
|
|
407
|
+
const cleanup = () => {
|
|
408
|
+
clearTimeout(timeoutId);
|
|
409
|
+
signal?.removeEventListener("abort", onAbort);
|
|
410
|
+
};
|
|
411
|
+
const onAbort = () => {
|
|
412
|
+
cleanup();
|
|
413
|
+
reject(new UploadAbortedError());
|
|
414
|
+
};
|
|
415
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/upload/pipeline.ts
|
|
420
|
+
async function runUploadPipeline(file, rpc2, options = {}) {
|
|
421
|
+
const { signal, fidelity = DEFAULT_IMAGE_FIDELITY } = options;
|
|
422
|
+
const { file: candidate } = await optimizeImageForUpload(file, fidelity);
|
|
423
|
+
if (signal?.aborted) throw new Error("Upload aborted");
|
|
424
|
+
const init = await rpc2.initUpload({
|
|
425
|
+
filename: candidate.name,
|
|
426
|
+
mime_type: candidate.type,
|
|
427
|
+
file_size: candidate.size
|
|
428
|
+
});
|
|
429
|
+
const putResponse = await putToStorageWithRetry(init.upload_url, candidate, signal);
|
|
430
|
+
if (!putResponse.ok) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
`Storage upload failed (${putResponse.status}). Please check your connection and try again.`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
const { file: uploaded } = await rpc2.completeUpload({
|
|
436
|
+
file_id: init.file_id,
|
|
437
|
+
file_storage_key: init.file_storage_key,
|
|
438
|
+
filename: candidate.name
|
|
439
|
+
});
|
|
440
|
+
return uploaded;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/recording_state.ts
|
|
444
|
+
var IDLE_RECORDING = { phase: "idle" };
|
|
445
|
+
var states = /* @__PURE__ */ new Map();
|
|
446
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
447
|
+
function emit() {
|
|
448
|
+
for (const listener of listeners) listener();
|
|
449
|
+
}
|
|
450
|
+
function subscribeRecordings(listener) {
|
|
451
|
+
listeners.add(listener);
|
|
452
|
+
return () => {
|
|
453
|
+
listeners.delete(listener);
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function recordingStateOf(alias) {
|
|
457
|
+
return states.get(alias) ?? IDLE_RECORDING;
|
|
458
|
+
}
|
|
459
|
+
function anyRecordingLive() {
|
|
460
|
+
for (const state of states.values()) if (state.phase === "live") return true;
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
function applyRecordingChange(alias, state) {
|
|
464
|
+
if (state.phase === "idle") states.delete(alias);
|
|
465
|
+
else states.set(alias, state);
|
|
466
|
+
emit();
|
|
467
|
+
}
|
|
468
|
+
function replaceRecordings(snapshot) {
|
|
469
|
+
states.clear();
|
|
470
|
+
for (const [alias, state] of Object.entries(snapshot)) {
|
|
471
|
+
if (state.phase !== "idle") states.set(alias, state);
|
|
472
|
+
}
|
|
473
|
+
emit();
|
|
474
|
+
}
|
|
475
|
+
function isRecord(value) {
|
|
476
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
477
|
+
}
|
|
478
|
+
function readRecordingState(raw) {
|
|
479
|
+
if (!isRecord(raw)) return null;
|
|
480
|
+
if (raw.phase === "idle") return IDLE_RECORDING;
|
|
481
|
+
const inputs = raw.inputs;
|
|
482
|
+
if (!isRecord(inputs)) return null;
|
|
483
|
+
switch (raw.phase) {
|
|
484
|
+
case "live":
|
|
485
|
+
case "interrupted":
|
|
486
|
+
return typeof raw.elapsed_seconds === "number" ? { phase: raw.phase, elapsed_seconds: raw.elapsed_seconds, inputs } : null;
|
|
487
|
+
case "processing":
|
|
488
|
+
return { phase: "processing", inputs };
|
|
489
|
+
case "filed":
|
|
490
|
+
return typeof raw.execution_id === "string" ? { phase: "filed", execution_id: raw.execution_id, inputs } : null;
|
|
491
|
+
case "failed":
|
|
492
|
+
return typeof raw.message === "string" ? { phase: "failed", message: raw.message, inputs } : null;
|
|
493
|
+
default:
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function readRecordingSnapshot(raw) {
|
|
498
|
+
if (!isRecord(raw)) return {};
|
|
499
|
+
const entries = Object.entries(raw).flatMap(([alias, value]) => {
|
|
500
|
+
const state = readRecordingState(value);
|
|
501
|
+
if (state === null) {
|
|
502
|
+
console.error(`The host's context carried a malformed recording for "${alias}"; it reads as idle.`, value);
|
|
503
|
+
return [];
|
|
504
|
+
}
|
|
505
|
+
return [[alias, state]];
|
|
506
|
+
});
|
|
507
|
+
return Object.fromEntries(entries);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/rpc.ts
|
|
511
|
+
var hostOriginCache;
|
|
512
|
+
function getHostOrigin() {
|
|
513
|
+
if (hostOriginCache === void 0) {
|
|
514
|
+
hostOriginCache = new URLSearchParams(window.location.search).get("lotics_host");
|
|
515
|
+
}
|
|
516
|
+
return hostOriginCache;
|
|
517
|
+
}
|
|
518
|
+
function isEmbedded() {
|
|
519
|
+
return Boolean(getHostOrigin());
|
|
520
|
+
}
|
|
521
|
+
function rpc(op, payload) {
|
|
522
|
+
const hostOrigin = getHostOrigin();
|
|
523
|
+
return hostOrigin ? rpcBridged(op, payload, hostOrigin) : rpcStandalone(op, payload);
|
|
524
|
+
}
|
|
525
|
+
function getUrlParams() {
|
|
526
|
+
return rpc("urlState.get", {});
|
|
527
|
+
}
|
|
528
|
+
function setUrlParams(patch) {
|
|
529
|
+
return rpc("urlState.set", { params: patch });
|
|
530
|
+
}
|
|
531
|
+
function peekUrlParams() {
|
|
532
|
+
return isEmbedded() ? {} : parseSearch(window.location.search);
|
|
533
|
+
}
|
|
534
|
+
function subscribeUrlParams(cb) {
|
|
535
|
+
if (isEmbedded()) {
|
|
536
|
+
ensureListener();
|
|
537
|
+
urlStateSubscribers.add(cb);
|
|
538
|
+
return () => {
|
|
539
|
+
urlStateSubscribers.delete(cb);
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
const handler = () => cb(parseSearch(window.location.search));
|
|
543
|
+
window.addEventListener("popstate", handler);
|
|
544
|
+
return () => window.removeEventListener("popstate", handler);
|
|
545
|
+
}
|
|
546
|
+
function postHostNotification(message) {
|
|
547
|
+
const hostOrigin = getHostOrigin();
|
|
548
|
+
if (!hostOrigin) return;
|
|
549
|
+
window.parent.postMessage(message, hostOrigin);
|
|
550
|
+
}
|
|
551
|
+
function subscribeQueriesChanged(cb) {
|
|
552
|
+
ensureListener();
|
|
553
|
+
refetchSubscribers.add(cb);
|
|
554
|
+
return () => {
|
|
555
|
+
refetchSubscribers.delete(cb);
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
var mountedAliases = /* @__PURE__ */ new Map();
|
|
559
|
+
function notifyLocalWrite(reads) {
|
|
560
|
+
const aliases = reads === void 0 ? [...mountedAliases.keys()] : [...reads];
|
|
561
|
+
if (aliases.length === 0) return;
|
|
562
|
+
for (const cb of refetchSubscribers) cb(aliases);
|
|
563
|
+
}
|
|
564
|
+
var pending = /* @__PURE__ */ new Map();
|
|
565
|
+
var streaming = /* @__PURE__ */ new Map();
|
|
566
|
+
var urlStateSubscribers = /* @__PURE__ */ new Set();
|
|
567
|
+
var refetchSubscribers = /* @__PURE__ */ new Set();
|
|
568
|
+
var nextRpcId = 0;
|
|
569
|
+
var listenerInstalled = false;
|
|
570
|
+
function ensureListener() {
|
|
571
|
+
if (listenerInstalled) return;
|
|
572
|
+
listenerInstalled = true;
|
|
573
|
+
window.addEventListener("message", (event) => {
|
|
574
|
+
if (event.source !== window.parent || event.origin !== getHostOrigin()) return;
|
|
575
|
+
const msg = event.data;
|
|
576
|
+
if (!msg) return;
|
|
577
|
+
if (msg.type === "url-state" && msg.params && typeof msg.params === "object") {
|
|
578
|
+
for (const cb of urlStateSubscribers) cb(msg.params);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (msg.type === "queriesChanged" && Array.isArray(msg.aliases)) {
|
|
582
|
+
const aliases = msg.aliases.filter((a) => typeof a === "string");
|
|
583
|
+
for (const cb of refetchSubscribers) cb(aliases);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
if (msg.type === "recordingChanged") {
|
|
587
|
+
const state = readRecordingState(msg.state);
|
|
588
|
+
if (typeof msg.alias !== "string" || state === null) {
|
|
589
|
+
console.error("The host sent a malformed recordingChanged push; it is ignored.", msg);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const before = recordingStateOf(msg.alias);
|
|
593
|
+
applyRecordingChange(msg.alias, state);
|
|
594
|
+
const newlyFiled = state.phase === "filed" && !(before.phase === "filed" && before.execution_id === state.execution_id);
|
|
595
|
+
if (newlyFiled) notifyLocalWrite();
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (typeof msg.id !== "number") return;
|
|
599
|
+
const handler = pending.get(msg.id);
|
|
600
|
+
if (handler) {
|
|
601
|
+
pending.delete(msg.id);
|
|
602
|
+
if (msg.type === "result") handler.resolve(msg.data);
|
|
603
|
+
else handler.reject(new Error(msg.message ?? "RPC failed"));
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const stream = streaming.get(msg.id);
|
|
607
|
+
if (!stream) return;
|
|
608
|
+
if (msg.type === "run-id") {
|
|
609
|
+
if (typeof msg.runId === "string") stream.onRunId?.(msg.runId);
|
|
610
|
+
} else if (msg.type === "stream-chunk") {
|
|
611
|
+
if (typeof msg.chunk === "string") stream.onText(msg.chunk);
|
|
612
|
+
} else if (msg.type === "stream-end") {
|
|
613
|
+
streaming.delete(msg.id);
|
|
614
|
+
stream.resolve();
|
|
615
|
+
} else if (msg.type === "error") {
|
|
616
|
+
streaming.delete(msg.id);
|
|
617
|
+
stream.reject(new Error(msg.message ?? "Run failed"));
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
function rpcBridged(op, payload, host) {
|
|
622
|
+
ensureListener();
|
|
623
|
+
return new Promise((resolve, reject) => {
|
|
624
|
+
const id = nextRpcId++;
|
|
625
|
+
pending.set(id, { resolve, reject });
|
|
626
|
+
window.parent.postMessage({ id, op, payload }, host);
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
function rpcAgentRun(payload, onText, onRunId) {
|
|
630
|
+
const hostOrigin = getHostOrigin();
|
|
631
|
+
return hostOrigin ? agentRunBridged(payload, onText, hostOrigin, onRunId) : agentRunStandalone(payload, onText, onRunId);
|
|
632
|
+
}
|
|
633
|
+
function agentRunBridged(payload, onText, host, onRunId) {
|
|
634
|
+
ensureListener();
|
|
635
|
+
const id = nextRpcId++;
|
|
636
|
+
let settleDone = () => {
|
|
637
|
+
};
|
|
638
|
+
const done = new Promise((resolve, reject) => {
|
|
639
|
+
settleDone = resolve;
|
|
640
|
+
streaming.set(id, { onText, onRunId, resolve, reject });
|
|
641
|
+
window.parent.postMessage({ id, op: "agentRun", payload }, host);
|
|
642
|
+
});
|
|
643
|
+
return {
|
|
644
|
+
done,
|
|
645
|
+
abort: () => {
|
|
646
|
+
if (!streaming.has(id)) return;
|
|
647
|
+
streaming.delete(id);
|
|
648
|
+
window.parent.postMessage({ id, type: "abort" }, host);
|
|
649
|
+
settleDone();
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function rpcAgentRunContinue(payload, onText) {
|
|
654
|
+
const hostOrigin = getHostOrigin();
|
|
655
|
+
if (hostOrigin) {
|
|
656
|
+
ensureListener();
|
|
657
|
+
const id = nextRpcId++;
|
|
658
|
+
let settleDone = () => {
|
|
659
|
+
};
|
|
660
|
+
const done2 = new Promise((resolve, reject) => {
|
|
661
|
+
settleDone = resolve;
|
|
662
|
+
streaming.set(id, { onText, resolve, reject });
|
|
663
|
+
window.parent.postMessage({ id, op: "agentRunContinue", payload }, hostOrigin);
|
|
664
|
+
});
|
|
665
|
+
return {
|
|
666
|
+
done: done2,
|
|
667
|
+
abort: () => {
|
|
668
|
+
if (!streaming.has(id)) return;
|
|
669
|
+
streaming.delete(id);
|
|
670
|
+
window.parent.postMessage({ id, type: "abort" }, hostOrigin);
|
|
671
|
+
settleDone();
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
const controller = new AbortController();
|
|
676
|
+
const done = (async () => {
|
|
677
|
+
const { app_id } = await boot();
|
|
678
|
+
const headers = { "content-type": "application/json" };
|
|
679
|
+
if (sessionToken) headers[APP_PUBLIC_SESSION_HEADER] = sessionToken;
|
|
680
|
+
const continueToken = runTokens.get(payload.run_id);
|
|
681
|
+
if (continueToken) headers[APP_AGENT_RUN_TOKEN_HEADER] = continueToken;
|
|
682
|
+
const res = await fetch(
|
|
683
|
+
`${apiBase()}/v1/apps/${app_id}/agent-runs/${encodeURIComponent(payload.run_id)}/continue`,
|
|
684
|
+
{
|
|
685
|
+
method: "POST",
|
|
686
|
+
headers,
|
|
687
|
+
body: JSON.stringify({ tool_call_id: payload.tool_call_id, output: payload.output }),
|
|
688
|
+
signal: controller.signal
|
|
689
|
+
}
|
|
690
|
+
);
|
|
691
|
+
if (!res.ok || !res.body) {
|
|
692
|
+
throw await streamStartError(res);
|
|
693
|
+
}
|
|
694
|
+
const reader = res.body.getReader();
|
|
695
|
+
const decoder = new TextDecoder();
|
|
696
|
+
try {
|
|
697
|
+
for (; ; ) {
|
|
698
|
+
const { value, done: streamDone } = await reader.read();
|
|
699
|
+
if (streamDone) break;
|
|
700
|
+
onText(decoder.decode(value, { stream: true }));
|
|
701
|
+
}
|
|
702
|
+
const tail = decoder.decode();
|
|
703
|
+
if (tail) onText(tail);
|
|
704
|
+
} catch (err) {
|
|
705
|
+
if (controller.signal.aborted) return;
|
|
706
|
+
throw err;
|
|
707
|
+
}
|
|
708
|
+
})();
|
|
709
|
+
return { done, abort: () => controller.abort() };
|
|
710
|
+
}
|
|
711
|
+
function agentRunStandalone(payload, onText, onRunId) {
|
|
712
|
+
const controller = new AbortController();
|
|
713
|
+
const done = (async () => {
|
|
714
|
+
const { app_id } = await boot();
|
|
715
|
+
const headers = { "content-type": "application/json" };
|
|
716
|
+
if (sessionToken) headers[APP_PUBLIC_SESSION_HEADER] = sessionToken;
|
|
717
|
+
const res = await fetch(`${apiBase()}/v1/apps/${app_id}/agents/${encodeURIComponent(payload.alias)}/runs`, {
|
|
718
|
+
method: "POST",
|
|
719
|
+
headers,
|
|
720
|
+
body: JSON.stringify({ session_id: payload.session_id, input: payload.input }),
|
|
721
|
+
signal: controller.signal
|
|
722
|
+
});
|
|
723
|
+
if (!res.ok || !res.body) {
|
|
724
|
+
throw await streamStartError(res);
|
|
725
|
+
}
|
|
726
|
+
const runId = res.headers.get("x-app-agent-run-id");
|
|
727
|
+
if (runId) {
|
|
728
|
+
rememberRunToken(runId, res.headers.get(APP_AGENT_RUN_TOKEN_HEADER));
|
|
729
|
+
onRunId?.(runId);
|
|
730
|
+
}
|
|
731
|
+
const reader = res.body.getReader();
|
|
732
|
+
const decoder = new TextDecoder();
|
|
733
|
+
try {
|
|
734
|
+
for (; ; ) {
|
|
735
|
+
const { value, done: streamDone } = await reader.read();
|
|
736
|
+
if (streamDone) break;
|
|
737
|
+
onText(decoder.decode(value, { stream: true }));
|
|
738
|
+
}
|
|
739
|
+
const tail = decoder.decode();
|
|
740
|
+
if (tail) onText(tail);
|
|
741
|
+
} catch (err) {
|
|
742
|
+
if (controller.signal.aborted) return;
|
|
743
|
+
throw err;
|
|
744
|
+
}
|
|
745
|
+
})();
|
|
746
|
+
return { done, abort: () => controller.abort() };
|
|
747
|
+
}
|
|
748
|
+
var apiBaseCache = null;
|
|
749
|
+
function apiBase() {
|
|
750
|
+
if (apiBaseCache !== null) return apiBaseCache;
|
|
751
|
+
const content = document.querySelector(`meta[name="${API_BASE_META_NAME}"]`)?.getAttribute("content")?.trim();
|
|
752
|
+
if (!content) {
|
|
753
|
+
throw new Error(
|
|
754
|
+
`This app has no API address. A standalone app reads it from <meta name="${API_BASE_META_NAME}"> in the page the app host serves, and this page carries none.`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
apiBaseCache = content.replace(/\/+$/, "");
|
|
758
|
+
return apiBaseCache;
|
|
759
|
+
}
|
|
760
|
+
var PASSWORD_REQUIRED_CODE = "PASSWORD_REQUIRED";
|
|
761
|
+
var bootPromise = null;
|
|
762
|
+
var appInfoPromise = null;
|
|
763
|
+
var sessionToken = null;
|
|
764
|
+
var APP_PUBLIC_SESSION_HEADER = "x-lotics-app-session";
|
|
765
|
+
var runTokens = /* @__PURE__ */ new Map();
|
|
766
|
+
var MAX_TRACKED_RUN_TOKENS = 8;
|
|
767
|
+
function rememberRunToken(runId, token) {
|
|
768
|
+
if (!token) return;
|
|
769
|
+
runTokens.set(runId, token);
|
|
770
|
+
while (runTokens.size > MAX_TRACKED_RUN_TOKENS) {
|
|
771
|
+
const oldest = runTokens.keys().next();
|
|
772
|
+
if (oldest.done) break;
|
|
773
|
+
runTokens.delete(oldest.value);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
var APP_AGENT_RUN_TOKEN_HEADER = "x-app-agent-run-token";
|
|
777
|
+
var SESSION_COOKIE = "lotics_app_session";
|
|
778
|
+
function readSessionCookie() {
|
|
779
|
+
for (const part of document.cookie.split(";")) {
|
|
780
|
+
const eq = part.indexOf("=");
|
|
781
|
+
if (eq < 0) continue;
|
|
782
|
+
if (part.slice(0, eq).trim() === SESSION_COOKIE) {
|
|
783
|
+
return decodeURIComponent(part.slice(eq + 1).trim()) || null;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return null;
|
|
787
|
+
}
|
|
788
|
+
function resolveAppInfo() {
|
|
789
|
+
if (appInfoPromise) return appInfoPromise;
|
|
790
|
+
const slug = window.location.hostname.split(".")[0];
|
|
791
|
+
const attempt = apiCall(
|
|
792
|
+
"GET",
|
|
793
|
+
`/v1/apps/by-subdomain/${encodeURIComponent(slug)}`
|
|
794
|
+
);
|
|
795
|
+
attempt.catch(() => {
|
|
796
|
+
if (appInfoPromise === attempt) appInfoPromise = null;
|
|
797
|
+
});
|
|
798
|
+
appInfoPromise = attempt;
|
|
799
|
+
return attempt;
|
|
800
|
+
}
|
|
801
|
+
async function boot() {
|
|
802
|
+
if (bootPromise) return bootPromise;
|
|
803
|
+
const attempt = (async () => {
|
|
804
|
+
const info = await resolveAppInfo();
|
|
805
|
+
if (info.requires_password) {
|
|
806
|
+
sessionToken = readSessionCookie();
|
|
807
|
+
}
|
|
808
|
+
return info;
|
|
809
|
+
})();
|
|
810
|
+
attempt.catch(() => {
|
|
811
|
+
if (bootPromise === attempt) bootPromise = null;
|
|
812
|
+
});
|
|
813
|
+
bootPromise = attempt;
|
|
814
|
+
return attempt;
|
|
815
|
+
}
|
|
816
|
+
var ApiRefusal = class extends Error {
|
|
817
|
+
constructor(message, field_errors) {
|
|
818
|
+
super(message);
|
|
819
|
+
this.field_errors = field_errors;
|
|
820
|
+
}
|
|
821
|
+
field_errors;
|
|
822
|
+
};
|
|
823
|
+
function readFieldErrors(parsed) {
|
|
824
|
+
if (parsed === null || typeof parsed !== "object" || !("field_errors" in parsed)) {
|
|
825
|
+
return void 0;
|
|
826
|
+
}
|
|
827
|
+
const raw = parsed.field_errors;
|
|
828
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
829
|
+
const named = Object.entries(raw).filter(
|
|
830
|
+
(entry) => typeof entry[1] === "string"
|
|
831
|
+
);
|
|
832
|
+
return named.length > 0 ? Object.fromEntries(named) : void 0;
|
|
833
|
+
}
|
|
834
|
+
async function streamStartError(res) {
|
|
835
|
+
const text = await res.text().catch(() => "");
|
|
836
|
+
let parsed = null;
|
|
837
|
+
if (text) {
|
|
838
|
+
try {
|
|
839
|
+
parsed = JSON.parse(text);
|
|
840
|
+
} catch {
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
return new Error(transportErrorMessage(res.status, parsed));
|
|
844
|
+
}
|
|
845
|
+
var API_TIMEOUT_MS = 3e4;
|
|
846
|
+
async function apiCall(method, path, body, opts) {
|
|
847
|
+
const headers = {};
|
|
848
|
+
if (body) headers["content-type"] = "application/json";
|
|
849
|
+
if (sessionToken && !opts?.skipAuth) {
|
|
850
|
+
headers[APP_PUBLIC_SESSION_HEADER] = sessionToken;
|
|
851
|
+
}
|
|
852
|
+
if (opts?.runToken) {
|
|
853
|
+
headers[APP_AGENT_RUN_TOKEN_HEADER] = opts.runToken;
|
|
854
|
+
}
|
|
855
|
+
const controller = new AbortController();
|
|
856
|
+
let didTimeout = false;
|
|
857
|
+
const timeoutId = setTimeout(() => {
|
|
858
|
+
didTimeout = true;
|
|
859
|
+
controller.abort();
|
|
860
|
+
}, API_TIMEOUT_MS);
|
|
861
|
+
let res;
|
|
862
|
+
try {
|
|
863
|
+
res = await fetch(`${apiBase()}${path}`, {
|
|
864
|
+
method,
|
|
865
|
+
headers,
|
|
866
|
+
...method === "POST" && body ? { body: JSON.stringify(body) } : {},
|
|
867
|
+
signal: controller.signal
|
|
868
|
+
});
|
|
869
|
+
} catch (err) {
|
|
870
|
+
if (didTimeout && err instanceof DOMException && err.name === "AbortError") {
|
|
871
|
+
throw new Error(gatewayErrorMessage(524));
|
|
872
|
+
}
|
|
873
|
+
throw err;
|
|
874
|
+
} finally {
|
|
875
|
+
clearTimeout(timeoutId);
|
|
876
|
+
}
|
|
877
|
+
const text = await res.text();
|
|
878
|
+
let parsed = null;
|
|
879
|
+
if (text) {
|
|
880
|
+
try {
|
|
881
|
+
parsed = JSON.parse(text);
|
|
882
|
+
} catch {
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
if (!res.ok) {
|
|
886
|
+
const errorBody = parsed;
|
|
887
|
+
if (res.status === 401 && errorBody?.error_code === PASSWORD_REQUIRED_CODE && !opts?.skipAuth) {
|
|
888
|
+
sessionToken = null;
|
|
889
|
+
window.location.assign("/__gate");
|
|
890
|
+
return new Promise(() => {
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
throw new ApiRefusal(transportErrorMessage(res.status, parsed), readFieldErrors(parsed));
|
|
894
|
+
}
|
|
895
|
+
return parsed ?? (text ? text : {});
|
|
896
|
+
}
|
|
897
|
+
function rpcStandalone(op, payload) {
|
|
898
|
+
switch (op) {
|
|
899
|
+
case "query":
|
|
900
|
+
return standaloneQuery(payload);
|
|
901
|
+
case "field_options":
|
|
902
|
+
return standaloneFieldOptions(payload);
|
|
903
|
+
case "workflow":
|
|
904
|
+
return standaloneWorkflow(payload);
|
|
905
|
+
case "agentRuns":
|
|
906
|
+
return standaloneAgentRuns(payload);
|
|
907
|
+
case "agentRun.get":
|
|
908
|
+
return standaloneAgentRunGet(payload);
|
|
909
|
+
case "agentRun.cancel":
|
|
910
|
+
return standaloneAgentRunCancel(payload);
|
|
911
|
+
case "upload":
|
|
912
|
+
return standaloneUpload(
|
|
913
|
+
payload.file,
|
|
914
|
+
payload.fidelity
|
|
915
|
+
);
|
|
916
|
+
case "file.rename":
|
|
917
|
+
return standaloneRename(payload);
|
|
918
|
+
case "members":
|
|
919
|
+
return standaloneMembers(payload);
|
|
920
|
+
case "context":
|
|
921
|
+
return standaloneContext();
|
|
922
|
+
case "write_model":
|
|
923
|
+
return standaloneWriteModel();
|
|
924
|
+
case "prediction_miss":
|
|
925
|
+
return standalonePredictionMiss(payload);
|
|
926
|
+
case "openExternal":
|
|
927
|
+
return standaloneOpenExternal(payload);
|
|
928
|
+
case "openApp":
|
|
929
|
+
return Promise.reject(
|
|
930
|
+
new Error("openApp needs the Lotics host: a standalone app has no sibling apps to open. Gate the control on isEmbedded().")
|
|
931
|
+
);
|
|
932
|
+
case "askAi":
|
|
933
|
+
return Promise.reject(new Error("askAi is only available when the app runs inside Lotics"));
|
|
934
|
+
case "urlState.get":
|
|
935
|
+
return Promise.resolve(parseSearch(window.location.search));
|
|
936
|
+
case "urlState.set":
|
|
937
|
+
return standaloneUrlStateSet(payload);
|
|
938
|
+
case "folder.get":
|
|
939
|
+
return standaloneFolderGet(payload);
|
|
940
|
+
case "folder.set":
|
|
941
|
+
return standaloneFolderSet(payload);
|
|
942
|
+
case "comments.list":
|
|
943
|
+
case "comments.create":
|
|
944
|
+
case "comments.update":
|
|
945
|
+
case "comments.delete":
|
|
946
|
+
case "comments.counts":
|
|
947
|
+
return rejectCommentsStandalone();
|
|
948
|
+
case "recording.start":
|
|
949
|
+
case "recording.stop":
|
|
950
|
+
return Promise.reject(
|
|
951
|
+
new Error("Recording needs the Lotics host and a signed-in member. Gate the control on useRecording's `available`.")
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function folderTable(payload) {
|
|
956
|
+
const table = payload !== null && typeof payload === "object" ? Reflect.get(payload, "table_id") : void 0;
|
|
957
|
+
if (typeof table !== "string" || table === "") throw new Error("A folder op names the folder's table as `table_id`.");
|
|
958
|
+
return table;
|
|
959
|
+
}
|
|
960
|
+
var folderKey = (table) => `lotics.folder.${table}`;
|
|
961
|
+
async function standaloneFolderGet(payload) {
|
|
962
|
+
const key = folderKey(folderTable(payload));
|
|
963
|
+
try {
|
|
964
|
+
return { record_id: localStorage.getItem(key) };
|
|
965
|
+
} catch (cause) {
|
|
966
|
+
console.warn(`The folder kept under ${key} could not be read`, cause);
|
|
967
|
+
return { record_id: null };
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
async function standaloneFolderSet(payload) {
|
|
971
|
+
const key = folderKey(folderTable(payload));
|
|
972
|
+
const id = payload !== null && typeof payload === "object" ? Reflect.get(payload, "record_id") : void 0;
|
|
973
|
+
if (id !== null && typeof id !== "string") throw new Error("`folder.set` keeps a record id, or `null` for none.");
|
|
974
|
+
try {
|
|
975
|
+
if (id === null) localStorage.removeItem(key);
|
|
976
|
+
else localStorage.setItem(key, id);
|
|
977
|
+
} catch (cause) {
|
|
978
|
+
console.warn(`The folder could not be kept under ${key}; it holds for this visit`, cause);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
function rejectCommentsStandalone() {
|
|
982
|
+
return Promise.reject(
|
|
983
|
+
new Error("Comments are available only in embedded apps \u2014 a signed-in member is required.")
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
async function standaloneMembers(p) {
|
|
987
|
+
const { app_id } = await boot();
|
|
988
|
+
const qs = p.group ? `?group_id=${encodeURIComponent(p.group)}` : "";
|
|
989
|
+
const r = await apiCall("GET", `/v1/apps/${app_id}/members${qs}`, void 0, {
|
|
990
|
+
appId: app_id
|
|
991
|
+
});
|
|
992
|
+
return { members: r.members ?? [] };
|
|
993
|
+
}
|
|
994
|
+
function openValidatedUrl(url) {
|
|
995
|
+
if (typeof url !== "string") throw new Error("openExternal requires a url string");
|
|
996
|
+
const parsed = new URL(url);
|
|
997
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
998
|
+
throw new Error(`openExternal: unsupported URL scheme "${parsed.protocol}"`);
|
|
999
|
+
}
|
|
1000
|
+
window.open(parsed.href, "_blank", "noopener,noreferrer");
|
|
1001
|
+
}
|
|
1002
|
+
async function standaloneOpenExternal(p) {
|
|
1003
|
+
openValidatedUrl(p.url);
|
|
1004
|
+
}
|
|
1005
|
+
async function standaloneUrlStateSet(p) {
|
|
1006
|
+
const search = serializeMerge(window.location.search, p.params ?? {});
|
|
1007
|
+
const url = window.location.pathname + (search ? `?${search}` : "") + window.location.hash;
|
|
1008
|
+
window.history.replaceState(null, "", url);
|
|
1009
|
+
}
|
|
1010
|
+
async function standaloneContext() {
|
|
1011
|
+
const info = await resolveAppInfo();
|
|
1012
|
+
return {
|
|
1013
|
+
member_id: null,
|
|
1014
|
+
comments_enabled: info.comments_enabled,
|
|
1015
|
+
timezone: info.timezone,
|
|
1016
|
+
default_currency: info.default_currency,
|
|
1017
|
+
recording_enabled: false
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
async function standalonePredictionMiss(payload) {
|
|
1021
|
+
const { app_id } = await boot();
|
|
1022
|
+
return apiCall("POST", `/v1/apps/${app_id}/prediction_misses`, payload, { appId: app_id });
|
|
1023
|
+
}
|
|
1024
|
+
async function standaloneWriteModel() {
|
|
1025
|
+
const { app_id } = await boot();
|
|
1026
|
+
return apiCall("GET", `/v1/apps/${app_id}/write_model`, void 0, { appId: app_id });
|
|
1027
|
+
}
|
|
1028
|
+
async function standaloneQuery(payload) {
|
|
1029
|
+
const { app_id } = await boot();
|
|
1030
|
+
return apiCall("POST", `/v1/apps/${app_id}/query`, payload, { appId: app_id });
|
|
1031
|
+
}
|
|
1032
|
+
async function standaloneFieldOptions(p) {
|
|
1033
|
+
const { app_id } = await boot();
|
|
1034
|
+
return apiCall("POST", `/v1/apps/${app_id}/field-options`, { alias: p.alias }, { appId: app_id });
|
|
1035
|
+
}
|
|
1036
|
+
async function standaloneWorkflow(p) {
|
|
1037
|
+
const { app_id } = await boot();
|
|
1038
|
+
try {
|
|
1039
|
+
return await apiCall(
|
|
1040
|
+
"POST",
|
|
1041
|
+
`/v1/apps/${app_id}/workflows/${encodeURIComponent(p.alias)}/execute`,
|
|
1042
|
+
{ inputs: p.inputs, ...p.minted_ids === void 0 ? {} : { minted_ids: p.minted_ids } },
|
|
1043
|
+
{ appId: app_id }
|
|
1044
|
+
);
|
|
1045
|
+
} catch (err) {
|
|
1046
|
+
return {
|
|
1047
|
+
status: "error",
|
|
1048
|
+
message: err instanceof Error ? err.message : "The workflow failed to run.",
|
|
1049
|
+
...err instanceof ApiRefusal && err.field_errors ? { field_errors: err.field_errors } : {}
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
async function standaloneAgentRuns(p) {
|
|
1054
|
+
const { app_id } = await boot();
|
|
1055
|
+
const qs = new URLSearchParams({ session_id: p.session_id });
|
|
1056
|
+
if (p.limit != null) qs.set("limit", String(p.limit));
|
|
1057
|
+
if (p.offset != null) qs.set("offset", String(p.offset));
|
|
1058
|
+
const r = await apiCall("GET", `/v1/apps/${app_id}/agent-runs?${qs.toString()}`, void 0, {
|
|
1059
|
+
appId: app_id
|
|
1060
|
+
});
|
|
1061
|
+
return { runs: r.runs ?? [] };
|
|
1062
|
+
}
|
|
1063
|
+
async function standaloneAgentRunGet(p) {
|
|
1064
|
+
const { app_id } = await boot();
|
|
1065
|
+
const r = await apiCall("GET", `/v1/apps/${app_id}/agent-runs/${encodeURIComponent(p.run_id)}`, void 0, {
|
|
1066
|
+
appId: app_id,
|
|
1067
|
+
runToken: runTokens.get(p.run_id)
|
|
1068
|
+
});
|
|
1069
|
+
return { run: r.run };
|
|
1070
|
+
}
|
|
1071
|
+
async function standaloneAgentRunCancel(p) {
|
|
1072
|
+
const { app_id } = await boot();
|
|
1073
|
+
await apiCall("POST", `/v1/apps/${app_id}/agent-runs/${encodeURIComponent(p.run_id)}/cancel`, {}, {
|
|
1074
|
+
appId: app_id,
|
|
1075
|
+
runToken: runTokens.get(p.run_id)
|
|
1076
|
+
});
|
|
1077
|
+
return { ok: true };
|
|
1078
|
+
}
|
|
1079
|
+
async function standaloneRename(payload) {
|
|
1080
|
+
const fileId = payload !== null && typeof payload === "object" ? Reflect.get(payload, "file_id") : void 0;
|
|
1081
|
+
const filename = payload !== null && typeof payload === "object" ? Reflect.get(payload, "filename") : void 0;
|
|
1082
|
+
if (typeof fileId !== "string" || typeof filename !== "string") throw new Error("A file.rename names the file as `file_id` and its new name as `filename`.");
|
|
1083
|
+
const { app_id } = await boot();
|
|
1084
|
+
const { file } = await apiCall("POST", `/v1/apps/${app_id}/files/${encodeURIComponent(fileId)}/rename`, { filename }, { appId: app_id });
|
|
1085
|
+
return file;
|
|
1086
|
+
}
|
|
1087
|
+
async function standaloneUpload(file, fidelity) {
|
|
1088
|
+
if (!(file instanceof File)) {
|
|
1089
|
+
throw new Error("upload payload must include a File");
|
|
1090
|
+
}
|
|
1091
|
+
const { app_id } = await boot();
|
|
1092
|
+
const uploaded = await runUploadPipeline(file, {
|
|
1093
|
+
initUpload: (input) => apiCall(
|
|
1094
|
+
"POST",
|
|
1095
|
+
`/v1/apps/${app_id}/files/upload-url`,
|
|
1096
|
+
input,
|
|
1097
|
+
{ appId: app_id }
|
|
1098
|
+
),
|
|
1099
|
+
completeUpload: (input) => apiCall(
|
|
1100
|
+
"POST",
|
|
1101
|
+
`/v1/apps/${app_id}/files/complete`,
|
|
1102
|
+
input,
|
|
1103
|
+
{ appId: app_id }
|
|
1104
|
+
)
|
|
1105
|
+
}, { fidelity });
|
|
1106
|
+
return uploaded;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
export {
|
|
1110
|
+
__export,
|
|
1111
|
+
DEFAULT_IMAGE_FIDELITY,
|
|
1112
|
+
subscribeRecordings,
|
|
1113
|
+
recordingStateOf,
|
|
1114
|
+
anyRecordingLive,
|
|
1115
|
+
replaceRecordings,
|
|
1116
|
+
readRecordingSnapshot,
|
|
1117
|
+
urlParam,
|
|
1118
|
+
decodeAll,
|
|
1119
|
+
encodePatch,
|
|
1120
|
+
applyPatch,
|
|
1121
|
+
paramsEqual,
|
|
1122
|
+
isEmbedded,
|
|
1123
|
+
rpc,
|
|
1124
|
+
getUrlParams,
|
|
1125
|
+
setUrlParams,
|
|
1126
|
+
peekUrlParams,
|
|
1127
|
+
subscribeUrlParams,
|
|
1128
|
+
postHostNotification,
|
|
1129
|
+
subscribeQueriesChanged,
|
|
1130
|
+
rpcAgentRun,
|
|
1131
|
+
rpcAgentRunContinue
|
|
1132
|
+
};
|