@ag-ui/encoder 0.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +23 -0
- package/dist/index.d.mts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +250 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +212 -0
- package/dist/index.mjs.map +1 -0
- package/jest.config.js +6 -0
- package/package.json +32 -0
- package/src/__tests__/encoder.test.ts +101 -0
- package/src/encoder.ts +69 -0
- package/src/index.ts +2 -0
- package/src/media-type.ts +305 -0
- package/tsconfig.json +23 -0
- package/tsup.config.ts +10 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
> @ag-ui/encoder@0.0.27 build /Users/mme/Code/ag-ui-protocol/typescript-sdk/packages/encoder
|
|
4
|
+
> tsup
|
|
5
|
+
|
|
6
|
+
[34mCLI[39m Building entry: src/index.ts
|
|
7
|
+
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
8
|
+
[34mCLI[39m tsup v8.4.0
|
|
9
|
+
[34mCLI[39m Using tsup config: /Users/mme/Code/ag-ui-protocol/typescript-sdk/packages/encoder/tsup.config.ts
|
|
10
|
+
[34mCLI[39m Target: es2017
|
|
11
|
+
[34mCLI[39m Cleaning output folder
|
|
12
|
+
[34mCJS[39m Build start
|
|
13
|
+
[34mESM[39m Build start
|
|
14
|
+
[32mCJS[39m [1mdist/index.js [22m[32m7.09 KB[39m
|
|
15
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m13.37 KB[39m
|
|
16
|
+
[32mCJS[39m ⚡️ Build success in 8ms
|
|
17
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m5.49 KB[39m
|
|
18
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m13.34 KB[39m
|
|
19
|
+
[32mESM[39m ⚡️ Build success in 8ms
|
|
20
|
+
DTS Build start
|
|
21
|
+
DTS ⚡️ Build success in 652ms
|
|
22
|
+
DTS dist/index.d.ts 540.00 B
|
|
23
|
+
DTS dist/index.d.mts 540.00 B
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseEvent } from '@ag-ui/core';
|
|
2
|
+
export { AGUI_MEDIA_TYPE } from '@ag-ui/proto';
|
|
3
|
+
|
|
4
|
+
interface EventEncoderParams {
|
|
5
|
+
accept?: string;
|
|
6
|
+
}
|
|
7
|
+
declare class EventEncoder {
|
|
8
|
+
private acceptsProtobuf;
|
|
9
|
+
constructor(params?: EventEncoderParams);
|
|
10
|
+
getContentType(): string;
|
|
11
|
+
encode(event: BaseEvent): string;
|
|
12
|
+
encodeSSE(event: BaseEvent): string;
|
|
13
|
+
encodeBinary(event: BaseEvent): Uint8Array;
|
|
14
|
+
encodeProtobuf(event: BaseEvent): Uint8Array;
|
|
15
|
+
private isProtobufAccepted;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { EventEncoder, type EventEncoderParams };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseEvent } from '@ag-ui/core';
|
|
2
|
+
export { AGUI_MEDIA_TYPE } from '@ag-ui/proto';
|
|
3
|
+
|
|
4
|
+
interface EventEncoderParams {
|
|
5
|
+
accept?: string;
|
|
6
|
+
}
|
|
7
|
+
declare class EventEncoder {
|
|
8
|
+
private acceptsProtobuf;
|
|
9
|
+
constructor(params?: EventEncoderParams);
|
|
10
|
+
getContentType(): string;
|
|
11
|
+
encode(event: BaseEvent): string;
|
|
12
|
+
encodeSSE(event: BaseEvent): string;
|
|
13
|
+
encodeBinary(event: BaseEvent): Uint8Array;
|
|
14
|
+
encodeProtobuf(event: BaseEvent): Uint8Array;
|
|
15
|
+
private isProtobufAccepted;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { EventEncoder, type EventEncoderParams };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
AGUI_MEDIA_TYPE: () => import_proto.AGUI_MEDIA_TYPE,
|
|
34
|
+
EventEncoder: () => EventEncoder
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/encoder.ts
|
|
39
|
+
var proto = __toESM(require("@ag-ui/proto"));
|
|
40
|
+
|
|
41
|
+
// src/media-type.ts
|
|
42
|
+
function preferredMediaTypes(accept, provided) {
|
|
43
|
+
const accepts = parseAccept(accept === void 0 ? "*/*" : accept || "");
|
|
44
|
+
if (!provided) {
|
|
45
|
+
return accepts.filter((spec) => spec.q > 0).sort((a, b) => {
|
|
46
|
+
return b.q - a.q || b.i - a.i || 0;
|
|
47
|
+
}).map(getFullType);
|
|
48
|
+
}
|
|
49
|
+
const priorities = provided.map(function getPriority(type, index) {
|
|
50
|
+
return getMediaTypePriority(type, accepts, index);
|
|
51
|
+
});
|
|
52
|
+
return priorities.filter((spec) => spec.q > 0).sort(compareSpecs).map(function getType(priority) {
|
|
53
|
+
return provided[priorities.indexOf(priority)];
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
var simpleMediaTypeRegExp = /^\s*([^\s\/;]+)\/([^;\s]+)\s*(?:;(.*))?$/;
|
|
57
|
+
function parseAccept(accept) {
|
|
58
|
+
const accepts = splitMediaTypes(accept);
|
|
59
|
+
const result = [];
|
|
60
|
+
for (let i = 0, j = 0; i < accepts.length; i++) {
|
|
61
|
+
const mediaType = parseMediaType(accepts[i].trim(), i);
|
|
62
|
+
if (mediaType) {
|
|
63
|
+
result[j++] = mediaType;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
function parseMediaType(str, i) {
|
|
69
|
+
const match = simpleMediaTypeRegExp.exec(str);
|
|
70
|
+
if (!match) return null;
|
|
71
|
+
const params = /* @__PURE__ */ Object.create(null);
|
|
72
|
+
let q = 1;
|
|
73
|
+
const subtype = match[2];
|
|
74
|
+
const type = match[1];
|
|
75
|
+
if (match[3]) {
|
|
76
|
+
const kvps = splitParameters(match[3]).map(splitKeyValuePair);
|
|
77
|
+
for (let j = 0; j < kvps.length; j++) {
|
|
78
|
+
const pair = kvps[j];
|
|
79
|
+
const key = pair[0].toLowerCase();
|
|
80
|
+
const val = pair[1];
|
|
81
|
+
const value = val && val[0] === '"' && val[val.length - 1] === '"' ? val.slice(1, -1) : val;
|
|
82
|
+
if (key === "q") {
|
|
83
|
+
q = parseFloat(value);
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
params[key] = value;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
type,
|
|
91
|
+
subtype,
|
|
92
|
+
params,
|
|
93
|
+
q,
|
|
94
|
+
i
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function getMediaTypePriority(type, accepted, index) {
|
|
98
|
+
const priority = { o: -1, q: 0, s: 0 };
|
|
99
|
+
for (let i = 0; i < accepted.length; i++) {
|
|
100
|
+
const spec = specify(type, accepted[i], index);
|
|
101
|
+
if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {
|
|
102
|
+
priority.o = spec.o;
|
|
103
|
+
priority.q = spec.q;
|
|
104
|
+
priority.s = spec.s;
|
|
105
|
+
priority.i = spec.i;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return priority;
|
|
109
|
+
}
|
|
110
|
+
function specify(type, spec, index) {
|
|
111
|
+
const p = parseMediaType(type, 0);
|
|
112
|
+
let s = 0;
|
|
113
|
+
if (!p) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (spec.type.toLowerCase() == p.type.toLowerCase()) {
|
|
117
|
+
s |= 4;
|
|
118
|
+
} else if (spec.type != "*") {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
if (spec.subtype.toLowerCase() == p.subtype.toLowerCase()) {
|
|
122
|
+
s |= 2;
|
|
123
|
+
} else if (spec.subtype != "*") {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const keys = Object.keys(spec.params);
|
|
127
|
+
if (keys.length > 0) {
|
|
128
|
+
if (keys.every(function(k) {
|
|
129
|
+
return spec.params[k] == "*" || (spec.params[k] || "").toLowerCase() == (p.params[k] || "").toLowerCase();
|
|
130
|
+
})) {
|
|
131
|
+
s |= 1;
|
|
132
|
+
} else {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
i: index,
|
|
138
|
+
o: spec.i,
|
|
139
|
+
q: spec.q,
|
|
140
|
+
s
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function compareSpecs(a, b) {
|
|
144
|
+
return b.q - a.q || b.s - a.s || (a.o || 0) - (b.o || 0) || (a.i || 0) - (b.i || 0) || 0;
|
|
145
|
+
}
|
|
146
|
+
function getFullType(spec) {
|
|
147
|
+
return spec.type + "/" + spec.subtype;
|
|
148
|
+
}
|
|
149
|
+
function quoteCount(string) {
|
|
150
|
+
let count = 0;
|
|
151
|
+
let index = 0;
|
|
152
|
+
while ((index = string.indexOf('"', index)) !== -1) {
|
|
153
|
+
count++;
|
|
154
|
+
index++;
|
|
155
|
+
}
|
|
156
|
+
return count;
|
|
157
|
+
}
|
|
158
|
+
function splitKeyValuePair(str) {
|
|
159
|
+
const index = str.indexOf("=");
|
|
160
|
+
let key;
|
|
161
|
+
let val = "";
|
|
162
|
+
if (index === -1) {
|
|
163
|
+
key = str;
|
|
164
|
+
} else {
|
|
165
|
+
key = str.slice(0, index);
|
|
166
|
+
val = str.slice(index + 1);
|
|
167
|
+
}
|
|
168
|
+
return [key, val];
|
|
169
|
+
}
|
|
170
|
+
function splitMediaTypes(accept) {
|
|
171
|
+
const accepts = accept.split(",");
|
|
172
|
+
const result = [accepts[0]];
|
|
173
|
+
for (let i = 1, j = 0; i < accepts.length; i++) {
|
|
174
|
+
if (quoteCount(result[j]) % 2 == 0) {
|
|
175
|
+
result[++j] = accepts[i];
|
|
176
|
+
} else {
|
|
177
|
+
result[j] += "," + accepts[i];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
function splitParameters(str) {
|
|
183
|
+
const parameters = str.split(";");
|
|
184
|
+
const result = [parameters[0]];
|
|
185
|
+
for (let i = 1, j = 0; i < parameters.length; i++) {
|
|
186
|
+
if (quoteCount(result[j]) % 2 == 0) {
|
|
187
|
+
result[++j] = parameters[i];
|
|
188
|
+
} else {
|
|
189
|
+
result[j] += ";" + parameters[i];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for (let i = 0; i < result.length; i++) {
|
|
193
|
+
result[i] = result[i].trim();
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/encoder.ts
|
|
199
|
+
var EventEncoder = class {
|
|
200
|
+
constructor(params) {
|
|
201
|
+
this.acceptsProtobuf = (params == null ? void 0 : params.accept) ? this.isProtobufAccepted(params.accept) : false;
|
|
202
|
+
}
|
|
203
|
+
getContentType() {
|
|
204
|
+
if (this.acceptsProtobuf) {
|
|
205
|
+
return proto.AGUI_MEDIA_TYPE;
|
|
206
|
+
} else {
|
|
207
|
+
return "text/event-stream";
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
encode(event) {
|
|
211
|
+
return this.encodeSSE(event);
|
|
212
|
+
}
|
|
213
|
+
encodeSSE(event) {
|
|
214
|
+
return `data: ${JSON.stringify(event)}
|
|
215
|
+
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
218
|
+
encodeBinary(event) {
|
|
219
|
+
if (this.acceptsProtobuf) {
|
|
220
|
+
return this.encodeProtobuf(event);
|
|
221
|
+
} else {
|
|
222
|
+
const sseString = this.encodeSSE(event);
|
|
223
|
+
const encoder = new TextEncoder();
|
|
224
|
+
return encoder.encode(sseString);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
encodeProtobuf(event) {
|
|
228
|
+
const messageBytes = proto.encode(event);
|
|
229
|
+
const length = messageBytes.length;
|
|
230
|
+
const buffer = new ArrayBuffer(4 + length);
|
|
231
|
+
const dataView = new DataView(buffer);
|
|
232
|
+
dataView.setUint32(0, length, false);
|
|
233
|
+
const result = new Uint8Array(buffer);
|
|
234
|
+
result.set(messageBytes, 4);
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
isProtobufAccepted(acceptHeader) {
|
|
238
|
+
const preferred = preferredMediaTypes(acceptHeader, [proto.AGUI_MEDIA_TYPE]);
|
|
239
|
+
return preferred.includes(proto.AGUI_MEDIA_TYPE);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// src/index.ts
|
|
244
|
+
var import_proto = require("@ag-ui/proto");
|
|
245
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
246
|
+
0 && (module.exports = {
|
|
247
|
+
AGUI_MEDIA_TYPE,
|
|
248
|
+
EventEncoder
|
|
249
|
+
});
|
|
250
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/encoder.ts","../src/media-type.ts"],"sourcesContent":["export * from \"./encoder\";\nexport { AGUI_MEDIA_TYPE } from \"@ag-ui/proto\";\n","import { BaseEvent } from \"@ag-ui/core\";\nimport * as proto from \"@ag-ui/proto\";\nimport { preferredMediaTypes } from \"./media-type\";\n\nexport interface EventEncoderParams {\n accept?: string;\n}\n\nexport class EventEncoder {\n private acceptsProtobuf: boolean;\n\n constructor(params?: EventEncoderParams) {\n this.acceptsProtobuf = params?.accept ? this.isProtobufAccepted(params.accept) : false;\n }\n\n getContentType(): string {\n if (this.acceptsProtobuf) {\n return proto.AGUI_MEDIA_TYPE;\n } else {\n return \"text/event-stream\";\n }\n }\n\n encode(event: BaseEvent): string {\n return this.encodeSSE(event);\n }\n\n encodeSSE(event: BaseEvent): string {\n return `data: ${JSON.stringify(event)}\\n\\n`;\n }\n\n encodeBinary(event: BaseEvent): Uint8Array {\n if (this.acceptsProtobuf) {\n return this.encodeProtobuf(event);\n } else {\n const sseString = this.encodeSSE(event);\n // Convert string to Uint8Array using TextEncoder\n const encoder = new TextEncoder();\n return encoder.encode(sseString);\n }\n }\n\n encodeProtobuf(event: BaseEvent): Uint8Array {\n const messageBytes = proto.encode(event);\n const length = messageBytes.length;\n\n // Create a buffer for 4 bytes (for the uint32 length) plus the message bytes\n const buffer = new ArrayBuffer(4 + length);\n const dataView = new DataView(buffer);\n\n // Write the length as a uint32\n // Set the third parameter to `false` for big-endian or `true` for little-endian\n dataView.setUint32(0, length, false);\n\n // Create a Uint8Array view and copy in the message bytes after the 4-byte header\n const result = new Uint8Array(buffer);\n result.set(messageBytes, 4);\n\n return result;\n }\n\n private isProtobufAccepted(acceptHeader: string): boolean {\n // Pass the Accept header and an array with your media type\n const preferred = preferredMediaTypes(acceptHeader, [proto.AGUI_MEDIA_TYPE]);\n\n // If the returned array includes your media type, it's acceptable\n return preferred.includes(proto.AGUI_MEDIA_TYPE);\n }\n}\n","/**\n * negotiator\n * Copyright(c) 2012 Isaac Z. Schlueter\n * Copyright(c) 2014 Federico Romero\n * Copyright(c) 2014-2015 Douglas Christopher Wilson\n * MIT Licensed\n */\n\n// modified from https://github.com/jshttp/negotiator/blob/master/lib/mediaType.js\n\n/**\n * Module exports.\n * @public\n */\n\nexport function preferredMediaTypes(accept?: string, provided?: string[]): string[] {\n // RFC 2616 sec 14.2: no header = */*\n const accepts = parseAccept(accept === undefined ? \"*/*\" : accept || \"\");\n\n if (!provided) {\n // sorted list of all types\n return accepts\n .filter((spec): spec is MediaType => spec.q > 0)\n .sort((a, b) => {\n return b.q - a.q || b.i - a.i || 0;\n })\n .map(getFullType);\n }\n\n const priorities = provided.map(function getPriority(type: string, index: number) {\n return getMediaTypePriority(type, accepts, index);\n });\n\n // sorted list of accepted types\n return priorities\n .filter((spec): spec is Priority => spec.q > 0)\n .sort(compareSpecs)\n .map(function getType(priority: Priority) {\n return provided[priorities.indexOf(priority)];\n });\n}\n\n/**\n * Module variables.\n * @private\n */\n\nconst simpleMediaTypeRegExp = /^\\s*([^\\s\\/;]+)\\/([^;\\s]+)\\s*(?:;(.*))?$/;\n\n/**\n * Media type interface\n * @private\n */\ninterface MediaType {\n type: string;\n subtype: string;\n params: Record<string, string>;\n q: number;\n i: number;\n}\n\n/**\n * Priority interface\n * @private\n */\ninterface Priority {\n o: number;\n q: number;\n s: number;\n i?: number;\n}\n\n/**\n * Parse the Accept header.\n * @private\n */\nfunction parseAccept(accept: string): MediaType[] {\n const accepts = splitMediaTypes(accept);\n const result: MediaType[] = [];\n\n for (let i = 0, j = 0; i < accepts.length; i++) {\n const mediaType = parseMediaType(accepts[i].trim(), i);\n\n if (mediaType) {\n result[j++] = mediaType;\n }\n }\n\n return result;\n}\n\n/**\n * Parse a media type from the Accept header.\n * @private\n */\nfunction parseMediaType(str: string, i: number): MediaType | null {\n const match = simpleMediaTypeRegExp.exec(str);\n if (!match) return null;\n\n const params: Record<string, string> = Object.create(null);\n let q = 1;\n const subtype = match[2];\n const type = match[1];\n\n if (match[3]) {\n const kvps = splitParameters(match[3]).map(splitKeyValuePair);\n\n for (let j = 0; j < kvps.length; j++) {\n const pair = kvps[j];\n const key = pair[0].toLowerCase();\n const val = pair[1];\n\n // get the value, unwrapping quotes\n const value = val && val[0] === '\"' && val[val.length - 1] === '\"' ? val.slice(1, -1) : val;\n\n if (key === \"q\") {\n q = parseFloat(value);\n break;\n }\n\n // store parameter\n params[key] = value;\n }\n }\n\n return {\n type: type,\n subtype: subtype,\n params: params,\n q: q,\n i: i,\n };\n}\n\n/**\n * Get the priority of a media type.\n * @private\n */\nfunction getMediaTypePriority(type: string, accepted: MediaType[], index: number): Priority {\n const priority: Priority = { o: -1, q: 0, s: 0 };\n\n for (let i = 0; i < accepted.length; i++) {\n const spec = specify(type, accepted[i], index);\n\n if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {\n priority.o = spec.o;\n priority.q = spec.q;\n priority.s = spec.s;\n priority.i = spec.i;\n }\n }\n\n return priority;\n}\n\n/**\n * Get the specificity of the media type.\n * @private\n */\nfunction specify(type: string, spec: MediaType, index: number): Priority | null {\n const p = parseMediaType(type, 0);\n let s = 0;\n\n if (!p) {\n return null;\n }\n\n if (spec.type.toLowerCase() == p.type.toLowerCase()) {\n s |= 4;\n } else if (spec.type != \"*\") {\n return null;\n }\n\n if (spec.subtype.toLowerCase() == p.subtype.toLowerCase()) {\n s |= 2;\n } else if (spec.subtype != \"*\") {\n return null;\n }\n\n const keys = Object.keys(spec.params);\n if (keys.length > 0) {\n if (\n keys.every(function (k) {\n return (\n spec.params[k] == \"*\" ||\n (spec.params[k] || \"\").toLowerCase() == (p.params[k] || \"\").toLowerCase()\n );\n })\n ) {\n s |= 1;\n } else {\n return null;\n }\n }\n\n return {\n i: index,\n o: spec.i,\n q: spec.q,\n s: s,\n };\n}\n\n/**\n * Compare two specs.\n * @private\n */\nfunction compareSpecs(a: Priority, b: Priority): number {\n return b.q - a.q || b.s - a.s || (a.o || 0) - (b.o || 0) || (a.i || 0) - (b.i || 0) || 0;\n}\n\n/**\n * Get full type string.\n * @private\n */\nfunction getFullType(spec: MediaType): string {\n return spec.type + \"/\" + spec.subtype;\n}\n\n/**\n * Check if a spec has any quality.\n * @private\n */\nfunction isQuality(spec: Priority | MediaType): boolean {\n return spec.q > 0;\n}\n\n/**\n * Count the number of quotes in a string.\n * @private\n */\nfunction quoteCount(string: string): number {\n let count = 0;\n let index = 0;\n\n while ((index = string.indexOf('\"', index)) !== -1) {\n count++;\n index++;\n }\n\n return count;\n}\n\n/**\n * Split a key value pair.\n * @private\n */\nfunction splitKeyValuePair(str: string): [string, string] {\n const index = str.indexOf(\"=\");\n let key: string;\n let val: string = \"\";\n\n if (index === -1) {\n key = str;\n } else {\n key = str.slice(0, index);\n val = str.slice(index + 1);\n }\n\n return [key, val];\n}\n\n/**\n * Split an Accept header into media types.\n * @private\n */\nfunction splitMediaTypes(accept: string): string[] {\n const accepts = accept.split(\",\");\n const result: string[] = [accepts[0]];\n\n for (let i = 1, j = 0; i < accepts.length; i++) {\n if (quoteCount(result[j]) % 2 == 0) {\n result[++j] = accepts[i];\n } else {\n result[j] += \",\" + accepts[i];\n }\n }\n\n // trim result\n return result;\n}\n\n/**\n * Split a string of parameters.\n * @private\n */\nfunction splitParameters(str: string): string[] {\n const parameters = str.split(\";\");\n const result: string[] = [parameters[0]];\n\n for (let i = 1, j = 0; i < parameters.length; i++) {\n if (quoteCount(result[j]) % 2 == 0) {\n result[++j] = parameters[i];\n } else {\n result[j] += \";\" + parameters[i];\n }\n }\n\n // trim parameters\n for (let i = 0; i < result.length; i++) {\n result[i] = result[i].trim();\n }\n\n return result;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// src/encoder.ts
|
|
2
|
+
import * as proto from "@ag-ui/proto";
|
|
3
|
+
|
|
4
|
+
// src/media-type.ts
|
|
5
|
+
function preferredMediaTypes(accept, provided) {
|
|
6
|
+
const accepts = parseAccept(accept === void 0 ? "*/*" : accept || "");
|
|
7
|
+
if (!provided) {
|
|
8
|
+
return accepts.filter((spec) => spec.q > 0).sort((a, b) => {
|
|
9
|
+
return b.q - a.q || b.i - a.i || 0;
|
|
10
|
+
}).map(getFullType);
|
|
11
|
+
}
|
|
12
|
+
const priorities = provided.map(function getPriority(type, index) {
|
|
13
|
+
return getMediaTypePriority(type, accepts, index);
|
|
14
|
+
});
|
|
15
|
+
return priorities.filter((spec) => spec.q > 0).sort(compareSpecs).map(function getType(priority) {
|
|
16
|
+
return provided[priorities.indexOf(priority)];
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
var simpleMediaTypeRegExp = /^\s*([^\s\/;]+)\/([^;\s]+)\s*(?:;(.*))?$/;
|
|
20
|
+
function parseAccept(accept) {
|
|
21
|
+
const accepts = splitMediaTypes(accept);
|
|
22
|
+
const result = [];
|
|
23
|
+
for (let i = 0, j = 0; i < accepts.length; i++) {
|
|
24
|
+
const mediaType = parseMediaType(accepts[i].trim(), i);
|
|
25
|
+
if (mediaType) {
|
|
26
|
+
result[j++] = mediaType;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
function parseMediaType(str, i) {
|
|
32
|
+
const match = simpleMediaTypeRegExp.exec(str);
|
|
33
|
+
if (!match) return null;
|
|
34
|
+
const params = /* @__PURE__ */ Object.create(null);
|
|
35
|
+
let q = 1;
|
|
36
|
+
const subtype = match[2];
|
|
37
|
+
const type = match[1];
|
|
38
|
+
if (match[3]) {
|
|
39
|
+
const kvps = splitParameters(match[3]).map(splitKeyValuePair);
|
|
40
|
+
for (let j = 0; j < kvps.length; j++) {
|
|
41
|
+
const pair = kvps[j];
|
|
42
|
+
const key = pair[0].toLowerCase();
|
|
43
|
+
const val = pair[1];
|
|
44
|
+
const value = val && val[0] === '"' && val[val.length - 1] === '"' ? val.slice(1, -1) : val;
|
|
45
|
+
if (key === "q") {
|
|
46
|
+
q = parseFloat(value);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
params[key] = value;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
type,
|
|
54
|
+
subtype,
|
|
55
|
+
params,
|
|
56
|
+
q,
|
|
57
|
+
i
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function getMediaTypePriority(type, accepted, index) {
|
|
61
|
+
const priority = { o: -1, q: 0, s: 0 };
|
|
62
|
+
for (let i = 0; i < accepted.length; i++) {
|
|
63
|
+
const spec = specify(type, accepted[i], index);
|
|
64
|
+
if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {
|
|
65
|
+
priority.o = spec.o;
|
|
66
|
+
priority.q = spec.q;
|
|
67
|
+
priority.s = spec.s;
|
|
68
|
+
priority.i = spec.i;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return priority;
|
|
72
|
+
}
|
|
73
|
+
function specify(type, spec, index) {
|
|
74
|
+
const p = parseMediaType(type, 0);
|
|
75
|
+
let s = 0;
|
|
76
|
+
if (!p) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (spec.type.toLowerCase() == p.type.toLowerCase()) {
|
|
80
|
+
s |= 4;
|
|
81
|
+
} else if (spec.type != "*") {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
if (spec.subtype.toLowerCase() == p.subtype.toLowerCase()) {
|
|
85
|
+
s |= 2;
|
|
86
|
+
} else if (spec.subtype != "*") {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const keys = Object.keys(spec.params);
|
|
90
|
+
if (keys.length > 0) {
|
|
91
|
+
if (keys.every(function(k) {
|
|
92
|
+
return spec.params[k] == "*" || (spec.params[k] || "").toLowerCase() == (p.params[k] || "").toLowerCase();
|
|
93
|
+
})) {
|
|
94
|
+
s |= 1;
|
|
95
|
+
} else {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
i: index,
|
|
101
|
+
o: spec.i,
|
|
102
|
+
q: spec.q,
|
|
103
|
+
s
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function compareSpecs(a, b) {
|
|
107
|
+
return b.q - a.q || b.s - a.s || (a.o || 0) - (b.o || 0) || (a.i || 0) - (b.i || 0) || 0;
|
|
108
|
+
}
|
|
109
|
+
function getFullType(spec) {
|
|
110
|
+
return spec.type + "/" + spec.subtype;
|
|
111
|
+
}
|
|
112
|
+
function quoteCount(string) {
|
|
113
|
+
let count = 0;
|
|
114
|
+
let index = 0;
|
|
115
|
+
while ((index = string.indexOf('"', index)) !== -1) {
|
|
116
|
+
count++;
|
|
117
|
+
index++;
|
|
118
|
+
}
|
|
119
|
+
return count;
|
|
120
|
+
}
|
|
121
|
+
function splitKeyValuePair(str) {
|
|
122
|
+
const index = str.indexOf("=");
|
|
123
|
+
let key;
|
|
124
|
+
let val = "";
|
|
125
|
+
if (index === -1) {
|
|
126
|
+
key = str;
|
|
127
|
+
} else {
|
|
128
|
+
key = str.slice(0, index);
|
|
129
|
+
val = str.slice(index + 1);
|
|
130
|
+
}
|
|
131
|
+
return [key, val];
|
|
132
|
+
}
|
|
133
|
+
function splitMediaTypes(accept) {
|
|
134
|
+
const accepts = accept.split(",");
|
|
135
|
+
const result = [accepts[0]];
|
|
136
|
+
for (let i = 1, j = 0; i < accepts.length; i++) {
|
|
137
|
+
if (quoteCount(result[j]) % 2 == 0) {
|
|
138
|
+
result[++j] = accepts[i];
|
|
139
|
+
} else {
|
|
140
|
+
result[j] += "," + accepts[i];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
function splitParameters(str) {
|
|
146
|
+
const parameters = str.split(";");
|
|
147
|
+
const result = [parameters[0]];
|
|
148
|
+
for (let i = 1, j = 0; i < parameters.length; i++) {
|
|
149
|
+
if (quoteCount(result[j]) % 2 == 0) {
|
|
150
|
+
result[++j] = parameters[i];
|
|
151
|
+
} else {
|
|
152
|
+
result[j] += ";" + parameters[i];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for (let i = 0; i < result.length; i++) {
|
|
156
|
+
result[i] = result[i].trim();
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/encoder.ts
|
|
162
|
+
var EventEncoder = class {
|
|
163
|
+
constructor(params) {
|
|
164
|
+
this.acceptsProtobuf = (params == null ? void 0 : params.accept) ? this.isProtobufAccepted(params.accept) : false;
|
|
165
|
+
}
|
|
166
|
+
getContentType() {
|
|
167
|
+
if (this.acceptsProtobuf) {
|
|
168
|
+
return proto.AGUI_MEDIA_TYPE;
|
|
169
|
+
} else {
|
|
170
|
+
return "text/event-stream";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
encode(event) {
|
|
174
|
+
return this.encodeSSE(event);
|
|
175
|
+
}
|
|
176
|
+
encodeSSE(event) {
|
|
177
|
+
return `data: ${JSON.stringify(event)}
|
|
178
|
+
|
|
179
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
encodeBinary(event) {
|
|
182
|
+
if (this.acceptsProtobuf) {
|
|
183
|
+
return this.encodeProtobuf(event);
|
|
184
|
+
} else {
|
|
185
|
+
const sseString = this.encodeSSE(event);
|
|
186
|
+
const encoder = new TextEncoder();
|
|
187
|
+
return encoder.encode(sseString);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
encodeProtobuf(event) {
|
|
191
|
+
const messageBytes = proto.encode(event);
|
|
192
|
+
const length = messageBytes.length;
|
|
193
|
+
const buffer = new ArrayBuffer(4 + length);
|
|
194
|
+
const dataView = new DataView(buffer);
|
|
195
|
+
dataView.setUint32(0, length, false);
|
|
196
|
+
const result = new Uint8Array(buffer);
|
|
197
|
+
result.set(messageBytes, 4);
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
isProtobufAccepted(acceptHeader) {
|
|
201
|
+
const preferred = preferredMediaTypes(acceptHeader, [proto.AGUI_MEDIA_TYPE]);
|
|
202
|
+
return preferred.includes(proto.AGUI_MEDIA_TYPE);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// src/index.ts
|
|
207
|
+
import { AGUI_MEDIA_TYPE as AGUI_MEDIA_TYPE2 } from "@ag-ui/proto";
|
|
208
|
+
export {
|
|
209
|
+
AGUI_MEDIA_TYPE2 as AGUI_MEDIA_TYPE,
|
|
210
|
+
EventEncoder
|
|
211
|
+
};
|
|
212
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/encoder.ts","../src/media-type.ts","../src/index.ts"],"sourcesContent":["import { BaseEvent } from \"@ag-ui/core\";\nimport * as proto from \"@ag-ui/proto\";\nimport { preferredMediaTypes } from \"./media-type\";\n\nexport interface EventEncoderParams {\n accept?: string;\n}\n\nexport class EventEncoder {\n private acceptsProtobuf: boolean;\n\n constructor(params?: EventEncoderParams) {\n this.acceptsProtobuf = params?.accept ? this.isProtobufAccepted(params.accept) : false;\n }\n\n getContentType(): string {\n if (this.acceptsProtobuf) {\n return proto.AGUI_MEDIA_TYPE;\n } else {\n return \"text/event-stream\";\n }\n }\n\n encode(event: BaseEvent): string {\n return this.encodeSSE(event);\n }\n\n encodeSSE(event: BaseEvent): string {\n return `data: ${JSON.stringify(event)}\\n\\n`;\n }\n\n encodeBinary(event: BaseEvent): Uint8Array {\n if (this.acceptsProtobuf) {\n return this.encodeProtobuf(event);\n } else {\n const sseString = this.encodeSSE(event);\n // Convert string to Uint8Array using TextEncoder\n const encoder = new TextEncoder();\n return encoder.encode(sseString);\n }\n }\n\n encodeProtobuf(event: BaseEvent): Uint8Array {\n const messageBytes = proto.encode(event);\n const length = messageBytes.length;\n\n // Create a buffer for 4 bytes (for the uint32 length) plus the message bytes\n const buffer = new ArrayBuffer(4 + length);\n const dataView = new DataView(buffer);\n\n // Write the length as a uint32\n // Set the third parameter to `false` for big-endian or `true` for little-endian\n dataView.setUint32(0, length, false);\n\n // Create a Uint8Array view and copy in the message bytes after the 4-byte header\n const result = new Uint8Array(buffer);\n result.set(messageBytes, 4);\n\n return result;\n }\n\n private isProtobufAccepted(acceptHeader: string): boolean {\n // Pass the Accept header and an array with your media type\n const preferred = preferredMediaTypes(acceptHeader, [proto.AGUI_MEDIA_TYPE]);\n\n // If the returned array includes your media type, it's acceptable\n return preferred.includes(proto.AGUI_MEDIA_TYPE);\n }\n}\n","/**\n * negotiator\n * Copyright(c) 2012 Isaac Z. Schlueter\n * Copyright(c) 2014 Federico Romero\n * Copyright(c) 2014-2015 Douglas Christopher Wilson\n * MIT Licensed\n */\n\n// modified from https://github.com/jshttp/negotiator/blob/master/lib/mediaType.js\n\n/**\n * Module exports.\n * @public\n */\n\nexport function preferredMediaTypes(accept?: string, provided?: string[]): string[] {\n // RFC 2616 sec 14.2: no header = */*\n const accepts = parseAccept(accept === undefined ? \"*/*\" : accept || \"\");\n\n if (!provided) {\n // sorted list of all types\n return accepts\n .filter((spec): spec is MediaType => spec.q > 0)\n .sort((a, b) => {\n return b.q - a.q || b.i - a.i || 0;\n })\n .map(getFullType);\n }\n\n const priorities = provided.map(function getPriority(type: string, index: number) {\n return getMediaTypePriority(type, accepts, index);\n });\n\n // sorted list of accepted types\n return priorities\n .filter((spec): spec is Priority => spec.q > 0)\n .sort(compareSpecs)\n .map(function getType(priority: Priority) {\n return provided[priorities.indexOf(priority)];\n });\n}\n\n/**\n * Module variables.\n * @private\n */\n\nconst simpleMediaTypeRegExp = /^\\s*([^\\s\\/;]+)\\/([^;\\s]+)\\s*(?:;(.*))?$/;\n\n/**\n * Media type interface\n * @private\n */\ninterface MediaType {\n type: string;\n subtype: string;\n params: Record<string, string>;\n q: number;\n i: number;\n}\n\n/**\n * Priority interface\n * @private\n */\ninterface Priority {\n o: number;\n q: number;\n s: number;\n i?: number;\n}\n\n/**\n * Parse the Accept header.\n * @private\n */\nfunction parseAccept(accept: string): MediaType[] {\n const accepts = splitMediaTypes(accept);\n const result: MediaType[] = [];\n\n for (let i = 0, j = 0; i < accepts.length; i++) {\n const mediaType = parseMediaType(accepts[i].trim(), i);\n\n if (mediaType) {\n result[j++] = mediaType;\n }\n }\n\n return result;\n}\n\n/**\n * Parse a media type from the Accept header.\n * @private\n */\nfunction parseMediaType(str: string, i: number): MediaType | null {\n const match = simpleMediaTypeRegExp.exec(str);\n if (!match) return null;\n\n const params: Record<string, string> = Object.create(null);\n let q = 1;\n const subtype = match[2];\n const type = match[1];\n\n if (match[3]) {\n const kvps = splitParameters(match[3]).map(splitKeyValuePair);\n\n for (let j = 0; j < kvps.length; j++) {\n const pair = kvps[j];\n const key = pair[0].toLowerCase();\n const val = pair[1];\n\n // get the value, unwrapping quotes\n const value = val && val[0] === '\"' && val[val.length - 1] === '\"' ? val.slice(1, -1) : val;\n\n if (key === \"q\") {\n q = parseFloat(value);\n break;\n }\n\n // store parameter\n params[key] = value;\n }\n }\n\n return {\n type: type,\n subtype: subtype,\n params: params,\n q: q,\n i: i,\n };\n}\n\n/**\n * Get the priority of a media type.\n * @private\n */\nfunction getMediaTypePriority(type: string, accepted: MediaType[], index: number): Priority {\n const priority: Priority = { o: -1, q: 0, s: 0 };\n\n for (let i = 0; i < accepted.length; i++) {\n const spec = specify(type, accepted[i], index);\n\n if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {\n priority.o = spec.o;\n priority.q = spec.q;\n priority.s = spec.s;\n priority.i = spec.i;\n }\n }\n\n return priority;\n}\n\n/**\n * Get the specificity of the media type.\n * @private\n */\nfunction specify(type: string, spec: MediaType, index: number): Priority | null {\n const p = parseMediaType(type, 0);\n let s = 0;\n\n if (!p) {\n return null;\n }\n\n if (spec.type.toLowerCase() == p.type.toLowerCase()) {\n s |= 4;\n } else if (spec.type != \"*\") {\n return null;\n }\n\n if (spec.subtype.toLowerCase() == p.subtype.toLowerCase()) {\n s |= 2;\n } else if (spec.subtype != \"*\") {\n return null;\n }\n\n const keys = Object.keys(spec.params);\n if (keys.length > 0) {\n if (\n keys.every(function (k) {\n return (\n spec.params[k] == \"*\" ||\n (spec.params[k] || \"\").toLowerCase() == (p.params[k] || \"\").toLowerCase()\n );\n })\n ) {\n s |= 1;\n } else {\n return null;\n }\n }\n\n return {\n i: index,\n o: spec.i,\n q: spec.q,\n s: s,\n };\n}\n\n/**\n * Compare two specs.\n * @private\n */\nfunction compareSpecs(a: Priority, b: Priority): number {\n return b.q - a.q || b.s - a.s || (a.o || 0) - (b.o || 0) || (a.i || 0) - (b.i || 0) || 0;\n}\n\n/**\n * Get full type string.\n * @private\n */\nfunction getFullType(spec: MediaType): string {\n return spec.type + \"/\" + spec.subtype;\n}\n\n/**\n * Check if a spec has any quality.\n * @private\n */\nfunction isQuality(spec: Priority | MediaType): boolean {\n return spec.q > 0;\n}\n\n/**\n * Count the number of quotes in a string.\n * @private\n */\nfunction quoteCount(string: string): number {\n let count = 0;\n let index = 0;\n\n while ((index = string.indexOf('\"', index)) !== -1) {\n count++;\n index++;\n }\n\n return count;\n}\n\n/**\n * Split a key value pair.\n * @private\n */\nfunction splitKeyValuePair(str: string): [string, string] {\n const index = str.indexOf(\"=\");\n let key: string;\n let val: string = \"\";\n\n if (index === -1) {\n key = str;\n } else {\n key = str.slice(0, index);\n val = str.slice(index + 1);\n }\n\n return [key, val];\n}\n\n/**\n * Split an Accept header into media types.\n * @private\n */\nfunction splitMediaTypes(accept: string): string[] {\n const accepts = accept.split(\",\");\n const result: string[] = [accepts[0]];\n\n for (let i = 1, j = 0; i < accepts.length; i++) {\n if (quoteCount(result[j]) % 2 == 0) {\n result[++j] = accepts[i];\n } else {\n result[j] += \",\" + accepts[i];\n }\n }\n\n // trim result\n return result;\n}\n\n/**\n * Split a string of parameters.\n * @private\n */\nfunction splitParameters(str: string): string[] {\n const parameters = str.split(\";\");\n const result: string[] = [parameters[0]];\n\n for (let i = 1, j = 0; i < parameters.length; i++) {\n if (quoteCount(result[j]) % 2 == 0) {\n result[++j] = parameters[i];\n } else {\n result[j] += \";\" + parameters[i];\n }\n }\n\n // trim parameters\n for (let i = 0; i < result.length; i++) {\n result[i] = result[i].trim();\n }\n\n return result;\n}\n","export * from \"./encoder\";\nexport { AGUI_MEDIA_TYPE } from \"@ag-ui/proto\";\n"],"mappings":"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","names":["AGUI_MEDIA_TYPE"]}
|
package/jest.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ag-ui/encoder",
|
|
3
|
+
"author": "Markus Ecker <markus.ecker@gmail.com>",
|
|
4
|
+
"version": "0.0.27",
|
|
5
|
+
"private": false,
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"module": "./dist/index.mjs",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@ag-ui/core": "0.0.27",
|
|
14
|
+
"@ag-ui/proto": "0.0.27"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/jest": "^29.5.12",
|
|
18
|
+
"jest": "^29.7.0",
|
|
19
|
+
"ts-jest": "^29.1.2",
|
|
20
|
+
"tsup": "^8.0.2",
|
|
21
|
+
"typescript": "^5.8.2"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"dev": "tsup --watch",
|
|
26
|
+
"lint": "eslint \"src/**/*.ts*\"",
|
|
27
|
+
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
|
|
28
|
+
"test": "jest",
|
|
29
|
+
"link:global": "pnpm link --global",
|
|
30
|
+
"unlink:global": "pnpm unlink --global"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { EventEncoder } from "../encoder";
|
|
2
|
+
import { BaseEvent, EventType, TextMessageStartEvent } from "@ag-ui/core";
|
|
3
|
+
import * as proto from "@ag-ui/proto";
|
|
4
|
+
|
|
5
|
+
describe("Encoder Tests", () => {
|
|
6
|
+
// Create a valid TextMessageStartEvent event
|
|
7
|
+
const testEvent: TextMessageStartEvent = {
|
|
8
|
+
type: EventType.TEXT_MESSAGE_START,
|
|
9
|
+
timestamp: 123456789,
|
|
10
|
+
messageId: "msg123",
|
|
11
|
+
role: "assistant",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe("encodeBinary method", () => {
|
|
15
|
+
it("should return protobuf encoded data when accept header includes protobuf media type", () => {
|
|
16
|
+
// Setup an encoder with protobuf accepted
|
|
17
|
+
const encoder = new EventEncoder({
|
|
18
|
+
accept: `text/event-stream, ${proto.AGUI_MEDIA_TYPE}`,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Get the binary encoding
|
|
22
|
+
const result = encoder.encodeBinary(testEvent);
|
|
23
|
+
|
|
24
|
+
// Verify it's a Uint8Array
|
|
25
|
+
expect(result).toBeInstanceOf(Uint8Array);
|
|
26
|
+
|
|
27
|
+
// A protobuf message should start with 4 bytes for length followed by the message
|
|
28
|
+
// So the length should be greater than 4 at minimum
|
|
29
|
+
expect(result.length).toBeGreaterThan(4);
|
|
30
|
+
|
|
31
|
+
// The first 4 bytes should be a uint32 representing the message length
|
|
32
|
+
const dataView = new DataView(result.buffer);
|
|
33
|
+
const messageLength = dataView.getUint32(0, false); // false for big-endian
|
|
34
|
+
|
|
35
|
+
// The actual message should match the length specified in the header
|
|
36
|
+
expect(result.length - 4).toBe(messageLength);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should return SSE encoded data when accept header doesn't include protobuf media type", () => {
|
|
40
|
+
// Setup an encoder without protobuf accepted
|
|
41
|
+
const encoder = new EventEncoder({
|
|
42
|
+
accept: "text/event-stream",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Get the binary encoding
|
|
46
|
+
const result = encoder.encodeBinary(testEvent);
|
|
47
|
+
|
|
48
|
+
// Verify it's a Uint8Array
|
|
49
|
+
expect(result).toBeInstanceOf(Uint8Array);
|
|
50
|
+
|
|
51
|
+
// Convert back to string to verify it's SSE format
|
|
52
|
+
const decoder = new TextDecoder();
|
|
53
|
+
const resultString = decoder.decode(result);
|
|
54
|
+
|
|
55
|
+
// Should match the SSE format with the expected JSON
|
|
56
|
+
expect(resultString).toBe(`data: ${JSON.stringify(testEvent)}\n\n`);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should return SSE encoded data when no accept header is provided", () => {
|
|
60
|
+
// Setup an encoder without any accept header
|
|
61
|
+
const encoder = new EventEncoder();
|
|
62
|
+
|
|
63
|
+
// Get the binary encoding
|
|
64
|
+
const result = encoder.encodeBinary(testEvent);
|
|
65
|
+
|
|
66
|
+
// Verify it's a Uint8Array
|
|
67
|
+
expect(result).toBeInstanceOf(Uint8Array);
|
|
68
|
+
|
|
69
|
+
// Convert back to string to verify it's SSE format
|
|
70
|
+
const decoder = new TextDecoder();
|
|
71
|
+
const resultString = decoder.decode(result);
|
|
72
|
+
|
|
73
|
+
// Should match the SSE format with the expected JSON
|
|
74
|
+
expect(resultString).toBe(`data: ${JSON.stringify(testEvent)}\n\n`);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("encodeProtobuf method", () => {
|
|
79
|
+
it("should encode event as protobuf with length prefix", () => {
|
|
80
|
+
const encoder = new EventEncoder();
|
|
81
|
+
|
|
82
|
+
const result = encoder.encodeProtobuf(testEvent);
|
|
83
|
+
|
|
84
|
+
// Verify it's a Uint8Array
|
|
85
|
+
expect(result).toBeInstanceOf(Uint8Array);
|
|
86
|
+
|
|
87
|
+
// A protobuf message should start with 4 bytes for length followed by the message
|
|
88
|
+
expect(result.length).toBeGreaterThan(4);
|
|
89
|
+
|
|
90
|
+
// The first 4 bytes should be a uint32 representing the message length
|
|
91
|
+
const dataView = new DataView(result.buffer);
|
|
92
|
+
const messageLength = dataView.getUint32(0, false); // false for big-endian
|
|
93
|
+
|
|
94
|
+
// The actual message should match the length specified in the header
|
|
95
|
+
expect(result.length - 4).toBe(messageLength);
|
|
96
|
+
|
|
97
|
+
// The message length should be greater than zero
|
|
98
|
+
expect(messageLength).toBeGreaterThan(0);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
package/src/encoder.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { BaseEvent } from "@ag-ui/core";
|
|
2
|
+
import * as proto from "@ag-ui/proto";
|
|
3
|
+
import { preferredMediaTypes } from "./media-type";
|
|
4
|
+
|
|
5
|
+
export interface EventEncoderParams {
|
|
6
|
+
accept?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class EventEncoder {
|
|
10
|
+
private acceptsProtobuf: boolean;
|
|
11
|
+
|
|
12
|
+
constructor(params?: EventEncoderParams) {
|
|
13
|
+
this.acceptsProtobuf = params?.accept ? this.isProtobufAccepted(params.accept) : false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getContentType(): string {
|
|
17
|
+
if (this.acceptsProtobuf) {
|
|
18
|
+
return proto.AGUI_MEDIA_TYPE;
|
|
19
|
+
} else {
|
|
20
|
+
return "text/event-stream";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
encode(event: BaseEvent): string {
|
|
25
|
+
return this.encodeSSE(event);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
encodeSSE(event: BaseEvent): string {
|
|
29
|
+
return `data: ${JSON.stringify(event)}\n\n`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
encodeBinary(event: BaseEvent): Uint8Array {
|
|
33
|
+
if (this.acceptsProtobuf) {
|
|
34
|
+
return this.encodeProtobuf(event);
|
|
35
|
+
} else {
|
|
36
|
+
const sseString = this.encodeSSE(event);
|
|
37
|
+
// Convert string to Uint8Array using TextEncoder
|
|
38
|
+
const encoder = new TextEncoder();
|
|
39
|
+
return encoder.encode(sseString);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
encodeProtobuf(event: BaseEvent): Uint8Array {
|
|
44
|
+
const messageBytes = proto.encode(event);
|
|
45
|
+
const length = messageBytes.length;
|
|
46
|
+
|
|
47
|
+
// Create a buffer for 4 bytes (for the uint32 length) plus the message bytes
|
|
48
|
+
const buffer = new ArrayBuffer(4 + length);
|
|
49
|
+
const dataView = new DataView(buffer);
|
|
50
|
+
|
|
51
|
+
// Write the length as a uint32
|
|
52
|
+
// Set the third parameter to `false` for big-endian or `true` for little-endian
|
|
53
|
+
dataView.setUint32(0, length, false);
|
|
54
|
+
|
|
55
|
+
// Create a Uint8Array view and copy in the message bytes after the 4-byte header
|
|
56
|
+
const result = new Uint8Array(buffer);
|
|
57
|
+
result.set(messageBytes, 4);
|
|
58
|
+
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private isProtobufAccepted(acceptHeader: string): boolean {
|
|
63
|
+
// Pass the Accept header and an array with your media type
|
|
64
|
+
const preferred = preferredMediaTypes(acceptHeader, [proto.AGUI_MEDIA_TYPE]);
|
|
65
|
+
|
|
66
|
+
// If the returned array includes your media type, it's acceptable
|
|
67
|
+
return preferred.includes(proto.AGUI_MEDIA_TYPE);
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* negotiator
|
|
3
|
+
* Copyright(c) 2012 Isaac Z. Schlueter
|
|
4
|
+
* Copyright(c) 2014 Federico Romero
|
|
5
|
+
* Copyright(c) 2014-2015 Douglas Christopher Wilson
|
|
6
|
+
* MIT Licensed
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// modified from https://github.com/jshttp/negotiator/blob/master/lib/mediaType.js
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Module exports.
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export function preferredMediaTypes(accept?: string, provided?: string[]): string[] {
|
|
17
|
+
// RFC 2616 sec 14.2: no header = */*
|
|
18
|
+
const accepts = parseAccept(accept === undefined ? "*/*" : accept || "");
|
|
19
|
+
|
|
20
|
+
if (!provided) {
|
|
21
|
+
// sorted list of all types
|
|
22
|
+
return accepts
|
|
23
|
+
.filter((spec): spec is MediaType => spec.q > 0)
|
|
24
|
+
.sort((a, b) => {
|
|
25
|
+
return b.q - a.q || b.i - a.i || 0;
|
|
26
|
+
})
|
|
27
|
+
.map(getFullType);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const priorities = provided.map(function getPriority(type: string, index: number) {
|
|
31
|
+
return getMediaTypePriority(type, accepts, index);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// sorted list of accepted types
|
|
35
|
+
return priorities
|
|
36
|
+
.filter((spec): spec is Priority => spec.q > 0)
|
|
37
|
+
.sort(compareSpecs)
|
|
38
|
+
.map(function getType(priority: Priority) {
|
|
39
|
+
return provided[priorities.indexOf(priority)];
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Module variables.
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
const simpleMediaTypeRegExp = /^\s*([^\s\/;]+)\/([^;\s]+)\s*(?:;(.*))?$/;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Media type interface
|
|
52
|
+
* @private
|
|
53
|
+
*/
|
|
54
|
+
interface MediaType {
|
|
55
|
+
type: string;
|
|
56
|
+
subtype: string;
|
|
57
|
+
params: Record<string, string>;
|
|
58
|
+
q: number;
|
|
59
|
+
i: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Priority interface
|
|
64
|
+
* @private
|
|
65
|
+
*/
|
|
66
|
+
interface Priority {
|
|
67
|
+
o: number;
|
|
68
|
+
q: number;
|
|
69
|
+
s: number;
|
|
70
|
+
i?: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parse the Accept header.
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
function parseAccept(accept: string): MediaType[] {
|
|
78
|
+
const accepts = splitMediaTypes(accept);
|
|
79
|
+
const result: MediaType[] = [];
|
|
80
|
+
|
|
81
|
+
for (let i = 0, j = 0; i < accepts.length; i++) {
|
|
82
|
+
const mediaType = parseMediaType(accepts[i].trim(), i);
|
|
83
|
+
|
|
84
|
+
if (mediaType) {
|
|
85
|
+
result[j++] = mediaType;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Parse a media type from the Accept header.
|
|
94
|
+
* @private
|
|
95
|
+
*/
|
|
96
|
+
function parseMediaType(str: string, i: number): MediaType | null {
|
|
97
|
+
const match = simpleMediaTypeRegExp.exec(str);
|
|
98
|
+
if (!match) return null;
|
|
99
|
+
|
|
100
|
+
const params: Record<string, string> = Object.create(null);
|
|
101
|
+
let q = 1;
|
|
102
|
+
const subtype = match[2];
|
|
103
|
+
const type = match[1];
|
|
104
|
+
|
|
105
|
+
if (match[3]) {
|
|
106
|
+
const kvps = splitParameters(match[3]).map(splitKeyValuePair);
|
|
107
|
+
|
|
108
|
+
for (let j = 0; j < kvps.length; j++) {
|
|
109
|
+
const pair = kvps[j];
|
|
110
|
+
const key = pair[0].toLowerCase();
|
|
111
|
+
const val = pair[1];
|
|
112
|
+
|
|
113
|
+
// get the value, unwrapping quotes
|
|
114
|
+
const value = val && val[0] === '"' && val[val.length - 1] === '"' ? val.slice(1, -1) : val;
|
|
115
|
+
|
|
116
|
+
if (key === "q") {
|
|
117
|
+
q = parseFloat(value);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// store parameter
|
|
122
|
+
params[key] = value;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
type: type,
|
|
128
|
+
subtype: subtype,
|
|
129
|
+
params: params,
|
|
130
|
+
q: q,
|
|
131
|
+
i: i,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get the priority of a media type.
|
|
137
|
+
* @private
|
|
138
|
+
*/
|
|
139
|
+
function getMediaTypePriority(type: string, accepted: MediaType[], index: number): Priority {
|
|
140
|
+
const priority: Priority = { o: -1, q: 0, s: 0 };
|
|
141
|
+
|
|
142
|
+
for (let i = 0; i < accepted.length; i++) {
|
|
143
|
+
const spec = specify(type, accepted[i], index);
|
|
144
|
+
|
|
145
|
+
if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {
|
|
146
|
+
priority.o = spec.o;
|
|
147
|
+
priority.q = spec.q;
|
|
148
|
+
priority.s = spec.s;
|
|
149
|
+
priority.i = spec.i;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return priority;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get the specificity of the media type.
|
|
158
|
+
* @private
|
|
159
|
+
*/
|
|
160
|
+
function specify(type: string, spec: MediaType, index: number): Priority | null {
|
|
161
|
+
const p = parseMediaType(type, 0);
|
|
162
|
+
let s = 0;
|
|
163
|
+
|
|
164
|
+
if (!p) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (spec.type.toLowerCase() == p.type.toLowerCase()) {
|
|
169
|
+
s |= 4;
|
|
170
|
+
} else if (spec.type != "*") {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (spec.subtype.toLowerCase() == p.subtype.toLowerCase()) {
|
|
175
|
+
s |= 2;
|
|
176
|
+
} else if (spec.subtype != "*") {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const keys = Object.keys(spec.params);
|
|
181
|
+
if (keys.length > 0) {
|
|
182
|
+
if (
|
|
183
|
+
keys.every(function (k) {
|
|
184
|
+
return (
|
|
185
|
+
spec.params[k] == "*" ||
|
|
186
|
+
(spec.params[k] || "").toLowerCase() == (p.params[k] || "").toLowerCase()
|
|
187
|
+
);
|
|
188
|
+
})
|
|
189
|
+
) {
|
|
190
|
+
s |= 1;
|
|
191
|
+
} else {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
i: index,
|
|
198
|
+
o: spec.i,
|
|
199
|
+
q: spec.q,
|
|
200
|
+
s: s,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Compare two specs.
|
|
206
|
+
* @private
|
|
207
|
+
*/
|
|
208
|
+
function compareSpecs(a: Priority, b: Priority): number {
|
|
209
|
+
return b.q - a.q || b.s - a.s || (a.o || 0) - (b.o || 0) || (a.i || 0) - (b.i || 0) || 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get full type string.
|
|
214
|
+
* @private
|
|
215
|
+
*/
|
|
216
|
+
function getFullType(spec: MediaType): string {
|
|
217
|
+
return spec.type + "/" + spec.subtype;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Check if a spec has any quality.
|
|
222
|
+
* @private
|
|
223
|
+
*/
|
|
224
|
+
function isQuality(spec: Priority | MediaType): boolean {
|
|
225
|
+
return spec.q > 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Count the number of quotes in a string.
|
|
230
|
+
* @private
|
|
231
|
+
*/
|
|
232
|
+
function quoteCount(string: string): number {
|
|
233
|
+
let count = 0;
|
|
234
|
+
let index = 0;
|
|
235
|
+
|
|
236
|
+
while ((index = string.indexOf('"', index)) !== -1) {
|
|
237
|
+
count++;
|
|
238
|
+
index++;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return count;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Split a key value pair.
|
|
246
|
+
* @private
|
|
247
|
+
*/
|
|
248
|
+
function splitKeyValuePair(str: string): [string, string] {
|
|
249
|
+
const index = str.indexOf("=");
|
|
250
|
+
let key: string;
|
|
251
|
+
let val: string = "";
|
|
252
|
+
|
|
253
|
+
if (index === -1) {
|
|
254
|
+
key = str;
|
|
255
|
+
} else {
|
|
256
|
+
key = str.slice(0, index);
|
|
257
|
+
val = str.slice(index + 1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return [key, val];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Split an Accept header into media types.
|
|
265
|
+
* @private
|
|
266
|
+
*/
|
|
267
|
+
function splitMediaTypes(accept: string): string[] {
|
|
268
|
+
const accepts = accept.split(",");
|
|
269
|
+
const result: string[] = [accepts[0]];
|
|
270
|
+
|
|
271
|
+
for (let i = 1, j = 0; i < accepts.length; i++) {
|
|
272
|
+
if (quoteCount(result[j]) % 2 == 0) {
|
|
273
|
+
result[++j] = accepts[i];
|
|
274
|
+
} else {
|
|
275
|
+
result[j] += "," + accepts[i];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// trim result
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Split a string of parameters.
|
|
285
|
+
* @private
|
|
286
|
+
*/
|
|
287
|
+
function splitParameters(str: string): string[] {
|
|
288
|
+
const parameters = str.split(";");
|
|
289
|
+
const result: string[] = [parameters[0]];
|
|
290
|
+
|
|
291
|
+
for (let i = 1, j = 0; i < parameters.length; i++) {
|
|
292
|
+
if (quoteCount(result[j]) % 2 == 0) {
|
|
293
|
+
result[++j] = parameters[i];
|
|
294
|
+
} else {
|
|
295
|
+
result[j] += ";" + parameters[i];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// trim parameters
|
|
300
|
+
for (let i = 0; i < result.length; i++) {
|
|
301
|
+
result[i] = result[i].trim();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return result;
|
|
305
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2017",
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"sourceMap": true,
|
|
9
|
+
"moduleResolution": "node",
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"jsx": "react-jsx",
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"baseUrl": ".",
|
|
17
|
+
"paths": {
|
|
18
|
+
"@/*": ["./src/*"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": ["src"],
|
|
22
|
+
"exclude": ["node_modules", "dist"]
|
|
23
|
+
}
|