@flow-like/widget-sdk 0.1.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/README.md +258 -0
- package/package.json +42 -0
- package/src/contract.ts +121 -0
- package/src/define.ts +91 -0
- package/src/geometry.ts +277 -0
- package/src/global.d.ts +10 -0
- package/src/index.ts +44 -0
- package/src/llm-schemas.ts +1385 -0
- package/src/llm.ts +283 -0
- package/src/media.ts +107 -0
- package/src/microphone.ts +133 -0
- package/src/mount.ts +427 -0
- package/src/protocol.ts +178 -0
- package/src/query.ts +58 -0
- package/src/react.ts +34 -0
- package/src/standalone.ts +54 -0
- package/src/theme.ts +74 -0
- package/src/validate.ts +336 -0
- package/src/vanilla.ts +1 -0
package/src/geometry.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import type { JsonSchema } from "./contract";
|
|
2
|
+
|
|
3
|
+
/** A WGS 84 position: longitude [-180, 180], then latitude [-90, 90]. */
|
|
4
|
+
export type GeoPosition = [longitude: number, latitude: number];
|
|
5
|
+
|
|
6
|
+
interface GeoMembers {
|
|
7
|
+
bbox?: [west: number, south: number, east: number, north: number];
|
|
8
|
+
[member: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** @geometry Point */
|
|
12
|
+
export interface GeoPoint extends GeoMembers {
|
|
13
|
+
type: "Point";
|
|
14
|
+
coordinates: GeoPosition;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @geometry LineString */
|
|
18
|
+
export interface GeoLineString extends GeoMembers {
|
|
19
|
+
type: "LineString";
|
|
20
|
+
coordinates: GeoPosition[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** @geometry Polygon */
|
|
24
|
+
export interface GeoPolygon extends GeoMembers {
|
|
25
|
+
type: "Polygon";
|
|
26
|
+
coordinates: GeoPosition[][];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @geometry MultiPoint */
|
|
30
|
+
export interface GeoMultiPoint extends GeoMembers {
|
|
31
|
+
type: "MultiPoint";
|
|
32
|
+
coordinates: GeoPosition[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** @geometry MultiLineString */
|
|
36
|
+
export interface GeoMultiLineString extends GeoMembers {
|
|
37
|
+
type: "MultiLineString";
|
|
38
|
+
coordinates: GeoPosition[][];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @geometry MultiPolygon */
|
|
42
|
+
export interface GeoMultiPolygon extends GeoMembers {
|
|
43
|
+
type: "MultiPolygon";
|
|
44
|
+
coordinates: GeoPosition[][][];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @geometry GeometryCollection */
|
|
48
|
+
export interface GeoGeometryCollection extends GeoMembers {
|
|
49
|
+
type: "GeometryCollection";
|
|
50
|
+
geometries: GeoGeometry[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** @geometry Any */
|
|
54
|
+
export type GeoGeometry =
|
|
55
|
+
| GeoPoint
|
|
56
|
+
| GeoLineString
|
|
57
|
+
| GeoPolygon
|
|
58
|
+
| GeoMultiPoint
|
|
59
|
+
| GeoMultiLineString
|
|
60
|
+
| GeoMultiPolygon
|
|
61
|
+
| GeoGeometryCollection;
|
|
62
|
+
|
|
63
|
+
type GeometryKind = GeoGeometry["type"];
|
|
64
|
+
const GEOMETRY_KINDS: readonly GeometryKind[] = [
|
|
65
|
+
"Point",
|
|
66
|
+
"LineString",
|
|
67
|
+
"Polygon",
|
|
68
|
+
"MultiPoint",
|
|
69
|
+
"MultiLineString",
|
|
70
|
+
"MultiPolygon",
|
|
71
|
+
"GeometryCollection",
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
// Keep these limits aligned with flow_like_types_contracts::geometry.
|
|
75
|
+
const MAX_GEOMETRY_BYTES = 1_048_576;
|
|
76
|
+
const MAX_GEOMETRY_DEPTH = 32;
|
|
77
|
+
const MAX_GEOMETRY_POSITIONS = 100_000;
|
|
78
|
+
const MAX_GEOMETRY_MEMBERS = 1_000_000;
|
|
79
|
+
const encoder = new TextEncoder();
|
|
80
|
+
|
|
81
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
82
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isGeometryKind(value: unknown): value is GeometryKind {
|
|
86
|
+
return GEOMETRY_KINDS.includes(value as GeometryKind);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function checkJsonProfile(value: unknown): void {
|
|
90
|
+
let members = 0;
|
|
91
|
+
let bytes = 0;
|
|
92
|
+
const visit = (item: unknown, depth: number): void => {
|
|
93
|
+
if (depth > MAX_GEOMETRY_DEPTH || ++members > MAX_GEOMETRY_MEMBERS) {
|
|
94
|
+
throw new Error("Geometry exceeds the depth or member limit");
|
|
95
|
+
}
|
|
96
|
+
if (Array.isArray(item)) {
|
|
97
|
+
for (const child of item) visit(child, depth + 1);
|
|
98
|
+
} else if (isObject(item)) {
|
|
99
|
+
const prototype = Object.getPrototypeOf(item);
|
|
100
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
101
|
+
throw new Error("Geometry must contain JSON objects");
|
|
102
|
+
}
|
|
103
|
+
for (const [key, child] of Object.entries(item)) {
|
|
104
|
+
bytes += encoder.encode(key).length;
|
|
105
|
+
visit(child, depth + 1);
|
|
106
|
+
}
|
|
107
|
+
} else if (typeof item === "string") {
|
|
108
|
+
bytes += encoder.encode(item).length;
|
|
109
|
+
} else if (typeof item === "number") {
|
|
110
|
+
if (!Number.isFinite(item)) {
|
|
111
|
+
throw new Error("Geometry numbers must be finite");
|
|
112
|
+
}
|
|
113
|
+
bytes++;
|
|
114
|
+
} else if (item === null || typeof item === "boolean") {
|
|
115
|
+
bytes++;
|
|
116
|
+
} else {
|
|
117
|
+
throw new Error("Geometry must contain JSON values");
|
|
118
|
+
}
|
|
119
|
+
if (bytes > MAX_GEOMETRY_BYTES) {
|
|
120
|
+
throw new Error("Geometry exceeds the byte limit");
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
visit(value, 0);
|
|
124
|
+
if (encoder.encode(JSON.stringify(value)).length > MAX_GEOMETRY_BYTES) {
|
|
125
|
+
throw new Error("Geometry exceeds the byte limit");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function checkGeometry(value: unknown, expected?: GeometryKind): void {
|
|
130
|
+
checkJsonProfile(value);
|
|
131
|
+
let positions = 0;
|
|
132
|
+
const array = (item: unknown, path: string): unknown[] => {
|
|
133
|
+
if (!Array.isArray(item)) throw new Error(`${path} must be an array`);
|
|
134
|
+
return item;
|
|
135
|
+
};
|
|
136
|
+
const position = (item: unknown, path: string, count = true): GeoPosition => {
|
|
137
|
+
const coordinates = array(item, path);
|
|
138
|
+
const [longitude, latitude] = coordinates;
|
|
139
|
+
if (
|
|
140
|
+
coordinates.length !== 2 ||
|
|
141
|
+
typeof longitude !== "number" ||
|
|
142
|
+
typeof latitude !== "number" ||
|
|
143
|
+
!Number.isFinite(longitude) ||
|
|
144
|
+
!Number.isFinite(latitude)
|
|
145
|
+
) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`${path} requires two finite numbers: longitude, latitude`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
if (
|
|
151
|
+
longitude < -180 ||
|
|
152
|
+
longitude > 180 ||
|
|
153
|
+
latitude < -90 ||
|
|
154
|
+
latitude > 90
|
|
155
|
+
) {
|
|
156
|
+
throw new Error(`${path} is outside WGS 84 longitude/latitude bounds`);
|
|
157
|
+
}
|
|
158
|
+
if (count && ++positions > MAX_GEOMETRY_POSITIONS) {
|
|
159
|
+
throw new Error("Geometry exceeds the position limit");
|
|
160
|
+
}
|
|
161
|
+
return [longitude, latitude];
|
|
162
|
+
};
|
|
163
|
+
const line = (item: unknown, path: string, ring = false): void => {
|
|
164
|
+
const coordinates = array(item, path);
|
|
165
|
+
const minimum = ring ? 4 : 2;
|
|
166
|
+
if (coordinates.length < minimum) {
|
|
167
|
+
throw new Error(`${path} needs at least ${minimum} positions`);
|
|
168
|
+
}
|
|
169
|
+
let first: GeoPosition | undefined;
|
|
170
|
+
let last: GeoPosition | undefined;
|
|
171
|
+
coordinates.forEach((item, index) => {
|
|
172
|
+
last = position(item, `${path}[${index}]`);
|
|
173
|
+
first ??= last;
|
|
174
|
+
});
|
|
175
|
+
if (
|
|
176
|
+
ring &&
|
|
177
|
+
first &&
|
|
178
|
+
last &&
|
|
179
|
+
(first[0] !== last[0] || first[1] !== last[1])
|
|
180
|
+
) {
|
|
181
|
+
throw new Error(`${path} must be closed`);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
const polygon = (item: unknown, path: string): void => {
|
|
185
|
+
const rings = array(item, path);
|
|
186
|
+
if (rings.length === 0) throw new Error(`${path} needs an exterior ring`);
|
|
187
|
+
rings.forEach((ring, index) => line(ring, `${path}[${index}]`, true));
|
|
188
|
+
};
|
|
189
|
+
const geometry = (item: unknown, path: string, kind?: GeometryKind): void => {
|
|
190
|
+
if (!isObject(item) || !isGeometryKind(item.type)) {
|
|
191
|
+
throw new Error(`${path} must be a GeoJSON geometry object`);
|
|
192
|
+
}
|
|
193
|
+
if (kind && item.type !== kind) {
|
|
194
|
+
throw new Error(`${path} must be ${kind}, received ${item.type}`);
|
|
195
|
+
}
|
|
196
|
+
if ("crs" in item) {
|
|
197
|
+
throw new Error(`${path}: alternate CRS declarations are unsupported`);
|
|
198
|
+
}
|
|
199
|
+
if ("bbox" in item) {
|
|
200
|
+
const bbox = array(item.bbox, `${path}.bbox`);
|
|
201
|
+
if (bbox.length !== 4) {
|
|
202
|
+
throw new Error(`${path}.bbox requires four coordinates`);
|
|
203
|
+
}
|
|
204
|
+
const southwest = position(
|
|
205
|
+
bbox.slice(0, 2),
|
|
206
|
+
`${path}.bbox southwest`,
|
|
207
|
+
false,
|
|
208
|
+
);
|
|
209
|
+
const northeast = position(
|
|
210
|
+
bbox.slice(2),
|
|
211
|
+
`${path}.bbox northeast`,
|
|
212
|
+
false,
|
|
213
|
+
);
|
|
214
|
+
if (southwest[1] > northeast[1]) {
|
|
215
|
+
throw new Error(`${path}.bbox south must not exceed north`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (item.type === "GeometryCollection") {
|
|
219
|
+
if ("coordinates" in item) {
|
|
220
|
+
throw new Error(`${path}: GeometryCollection uses geometries`);
|
|
221
|
+
}
|
|
222
|
+
array(item.geometries, `${path}.geometries`).forEach((child, index) =>
|
|
223
|
+
geometry(child, `${path}.geometries[${index}]`),
|
|
224
|
+
);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if ("geometries" in item) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`${path}: only GeometryCollection may contain geometries`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
const pathCoordinates = `${path}.coordinates`;
|
|
233
|
+
switch (item.type) {
|
|
234
|
+
case "Point":
|
|
235
|
+
position(item.coordinates, pathCoordinates);
|
|
236
|
+
break;
|
|
237
|
+
case "LineString":
|
|
238
|
+
line(item.coordinates, pathCoordinates);
|
|
239
|
+
break;
|
|
240
|
+
case "Polygon":
|
|
241
|
+
polygon(item.coordinates, pathCoordinates);
|
|
242
|
+
break;
|
|
243
|
+
case "MultiPoint":
|
|
244
|
+
array(item.coordinates, pathCoordinates).forEach((point, index) =>
|
|
245
|
+
position(point, `${pathCoordinates}[${index}]`),
|
|
246
|
+
);
|
|
247
|
+
break;
|
|
248
|
+
case "MultiLineString":
|
|
249
|
+
array(item.coordinates, pathCoordinates).forEach((item, index) =>
|
|
250
|
+
line(item, `${pathCoordinates}[${index}]`),
|
|
251
|
+
);
|
|
252
|
+
break;
|
|
253
|
+
case "MultiPolygon":
|
|
254
|
+
array(item.coordinates, pathCoordinates).forEach((item, index) =>
|
|
255
|
+
polygon(item, `${pathCoordinates}[${index}]`),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
geometry(value, "Geometry", expected);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Apply the geometry profile when a contract schema carries a geometry marker. */
|
|
263
|
+
export function checkGeometrySchema(schema: JsonSchema, value: unknown): void {
|
|
264
|
+
const compact = schema.$id === "flow:geometry";
|
|
265
|
+
const full = schema["x-flow-like-type"] === "geometry";
|
|
266
|
+
if (!compact && !full && !("x-geometry" in schema)) return;
|
|
267
|
+
const subtype = schema["x-geometry"];
|
|
268
|
+
if (
|
|
269
|
+
(compact &&
|
|
270
|
+
(Object.keys(schema).length !== 2 || !isGeometryKind(subtype))) ||
|
|
271
|
+
(!compact && !full) ||
|
|
272
|
+
("x-geometry" in schema && !isGeometryKind(subtype))
|
|
273
|
+
) {
|
|
274
|
+
throw new Error("Invalid geometry subtype marker");
|
|
275
|
+
}
|
|
276
|
+
checkGeometry(value, isGeometryKind(subtype) ? subtype : undefined);
|
|
277
|
+
}
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WidgetContract } from "./contract";
|
|
2
|
+
import type { FlwStandaloneGlobal } from "./mount";
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
// Injected into the built HTML by @flow-like/widget-bundler; undefined in
|
|
6
|
+
// unbundled dev.
|
|
7
|
+
var __FLW_CONTRACT__: WidgetContract | undefined;
|
|
8
|
+
// Devtools escape hatch registered by the SDK in standalone mode.
|
|
9
|
+
var __flw: FlwStandaloneGlobal | undefined;
|
|
10
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/// <reference path="./global.d.ts" />
|
|
2
|
+
|
|
3
|
+
export * from "./contract";
|
|
4
|
+
export * from "./define";
|
|
5
|
+
export type {
|
|
6
|
+
GeoPosition,
|
|
7
|
+
GeoPoint,
|
|
8
|
+
GeoLineString,
|
|
9
|
+
GeoPolygon,
|
|
10
|
+
GeoMultiPoint,
|
|
11
|
+
GeoMultiLineString,
|
|
12
|
+
GeoMultiPolygon,
|
|
13
|
+
GeoGeometryCollection,
|
|
14
|
+
GeoGeometry,
|
|
15
|
+
} from "./geometry";
|
|
16
|
+
export type {
|
|
17
|
+
LlmAnnotation,
|
|
18
|
+
LlmContent,
|
|
19
|
+
LlmDelta,
|
|
20
|
+
LlmDeltaFunctionCall,
|
|
21
|
+
LlmFunctionCall,
|
|
22
|
+
LlmHistory,
|
|
23
|
+
LlmHistoryMessage,
|
|
24
|
+
LlmImageUrl,
|
|
25
|
+
LlmKind,
|
|
26
|
+
LlmLogProbs,
|
|
27
|
+
LlmResponse,
|
|
28
|
+
LlmResponseChunk,
|
|
29
|
+
LlmResponseMessage,
|
|
30
|
+
LlmRole,
|
|
31
|
+
LlmTokenLogProbs,
|
|
32
|
+
LlmToolCall,
|
|
33
|
+
LlmTopLogProbs,
|
|
34
|
+
LlmUrlCitation,
|
|
35
|
+
LlmUsage,
|
|
36
|
+
} from "./llm";
|
|
37
|
+
export { isLlmKind, llmSchema } from "./llm";
|
|
38
|
+
export * from "./mount";
|
|
39
|
+
export * from "./protocol";
|
|
40
|
+
export * from "./microphone";
|
|
41
|
+
export * from "./theme";
|
|
42
|
+
export * from "./validate";
|
|
43
|
+
|
|
44
|
+
export * from "./media";
|