@agentproto/app-kit 1.4.1 → 1.6.0
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 +2 -2
- package/dist/define-app.d.ts +9 -1
- package/dist/define-app.d.ts.map +1 -1
- package/dist/emit.d.ts +2 -1
- package/dist/emit.d.ts.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +525 -40
- package/dist/index.mjs.map +1 -1
- package/dist/load-app.d.ts.map +1 -1
- package/dist/manifest-fields.d.ts +4 -1
- package/dist/manifest-fields.d.ts.map +1 -1
- package/dist/pack.d.ts +46 -8
- package/dist/pack.d.ts.map +1 -1
- package/dist/types.d.ts +93 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version-range.d.ts +36 -0
- package/dist/version-range.d.ts.map +1 -0
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { join, relative, isAbsolute, dirname, resolve, basename } from 'path';
|
|
1
|
+
import { join, relative, isAbsolute, dirname, resolve, posix, basename } from 'path';
|
|
2
2
|
import { defineWorkspace } from '@agentproto/workspace';
|
|
3
|
-
import { mkdir, writeFile, copyFile, cp, readFile, mkdtemp, readdir, rm
|
|
3
|
+
import { mkdir, writeFile, copyFile, cp, readFile, mkdtemp, stat, readdir, rm } from 'fs/promises';
|
|
4
4
|
import matter2 from 'gray-matter';
|
|
5
5
|
import { parseAgentManifest, agentFromManifest } from '@agentproto/agent/manifest';
|
|
6
6
|
import { loadWorkflowHandle } from '@agentproto/workflow-loader';
|
|
@@ -62,7 +62,8 @@ async function emitApp(app, dir) {
|
|
|
62
62
|
...app.ui.tools !== void 0 ? { tools: app.ui.tools } : {},
|
|
63
63
|
...app.ui.port !== void 0 ? { port: app.ui.port } : {},
|
|
64
64
|
...app.ui.csp !== void 0 ? { csp: app.ui.csp } : {},
|
|
65
|
-
...app.ui.build !== void 0 ? { build: app.ui.build } : {}
|
|
65
|
+
...app.ui.build !== void 0 ? { build: app.ui.build } : {},
|
|
66
|
+
...app.ui.extensions !== void 0 ? { extensions: app.ui.extensions } : {}
|
|
66
67
|
};
|
|
67
68
|
}
|
|
68
69
|
let artifactPath;
|
|
@@ -142,13 +143,28 @@ async function emitApp(app, dir) {
|
|
|
142
143
|
}
|
|
143
144
|
function requiresFrontmatter(app) {
|
|
144
145
|
const r = app.requirements;
|
|
146
|
+
const apps = appsFrontmatter(app);
|
|
147
|
+
const hasAppDetail = (app.appRequirements ?? []).some(
|
|
148
|
+
(e) => e.version !== void 0 || e.workflows !== void 0 && e.workflows.length > 0
|
|
149
|
+
);
|
|
145
150
|
if (r && (r.browser || r.fs || r.gpu || r.secrets.length > 0)) {
|
|
146
151
|
return {
|
|
147
|
-
requires: { browser: r.browser, fs: r.fs, gpu: r.gpu, secrets: r.secrets, apps
|
|
152
|
+
requires: { browser: r.browser, fs: r.fs, gpu: r.gpu, secrets: r.secrets, apps }
|
|
148
153
|
};
|
|
149
154
|
}
|
|
155
|
+
if (hasAppDetail) {
|
|
156
|
+
return { requires: { apps } };
|
|
157
|
+
}
|
|
150
158
|
return app.requires !== void 0 ? { requires: app.requires } : {};
|
|
151
159
|
}
|
|
160
|
+
function appsFrontmatter(app) {
|
|
161
|
+
const entries = app.appRequirements ?? app.requires ?? [];
|
|
162
|
+
return entries.map((e) => {
|
|
163
|
+
if (typeof e === "string") return e;
|
|
164
|
+
if (e.version === void 0 && (e.workflows === void 0 || e.workflows.length === 0)) return e.id;
|
|
165
|
+
return e;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
152
168
|
function toManifest(handle, body) {
|
|
153
169
|
const data = JSON.parse(JSON.stringify(handle));
|
|
154
170
|
return matter2.stringify(`
|
|
@@ -156,6 +172,52 @@ ${body}
|
|
|
156
172
|
`, data);
|
|
157
173
|
}
|
|
158
174
|
|
|
175
|
+
// src/version-range.ts
|
|
176
|
+
var VERSION_RE = /^(\d+)\.(\d+)\.(\d+)(?:[-+][\w.\-]+)?$/;
|
|
177
|
+
function parseVersion(v) {
|
|
178
|
+
const m = VERSION_RE.exec(v);
|
|
179
|
+
if (!m) {
|
|
180
|
+
throw new Error(`invalid version "${v}" \u2014 expected major.minor.patch (e.g. "1.2.3").`);
|
|
181
|
+
}
|
|
182
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
183
|
+
}
|
|
184
|
+
function compare(a, b) {
|
|
185
|
+
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
186
|
+
}
|
|
187
|
+
function parseVersionRange(range) {
|
|
188
|
+
let m = /^\^(\d+)(?:\.(\d+))?(?:\.(\d+))?$/.exec(range);
|
|
189
|
+
if (m !== null) {
|
|
190
|
+
return {
|
|
191
|
+
kind: "caret",
|
|
192
|
+
version: [Number(m[1]), m[2] !== void 0 ? Number(m[2]) : 0, m[3] !== void 0 ? Number(m[3]) : 0]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
m = /^~(\d+)(?:\.(\d+))?(?:\.(\d+))?$/.exec(range);
|
|
196
|
+
if (m !== null) {
|
|
197
|
+
return {
|
|
198
|
+
kind: "tilde",
|
|
199
|
+
version: [Number(m[1]), m[2] !== void 0 ? Number(m[2]) : 0, m[3] !== void 0 ? Number(m[3]) : 0],
|
|
200
|
+
minorGiven: m[2] !== void 0
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (VERSION_RE.test(range)) return { kind: "exact", version: parseVersion(range) };
|
|
204
|
+
throw new Error(
|
|
205
|
+
`invalid version range "${range}" \u2014 expected an exact version ("1.2.3"), a caret ("^1", "^1.2", "^1.2.3"), or a tilde ("~1", "~1.2", "~1.2.3").`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
function versionSatisfies(version, range) {
|
|
209
|
+
const v = parseVersion(version);
|
|
210
|
+
const r = parseVersionRange(range);
|
|
211
|
+
if (r.kind === "exact") return compare(v, r.version) === 0;
|
|
212
|
+
if (compare(v, r.version) < 0) return false;
|
|
213
|
+
if (r.kind === "caret") {
|
|
214
|
+
if (r.version[0] > 0) return v[0] === r.version[0];
|
|
215
|
+
if (r.version[1] > 0) return v[0] === 0 && v[1] === r.version[1];
|
|
216
|
+
return v[0] === 0 && v[1] === 0 && v[2] === r.version[2];
|
|
217
|
+
}
|
|
218
|
+
return v[0] === r.version[0] && (!r.minorGiven || v[1] === r.version[1]);
|
|
219
|
+
}
|
|
220
|
+
|
|
159
221
|
// src/manifest-fields.ts
|
|
160
222
|
var APP_PLACEMENTS = ["local", "box", "any", "split"];
|
|
161
223
|
function isRecord(v) {
|
|
@@ -173,6 +235,52 @@ function bool(v, key, fail) {
|
|
|
173
235
|
if (typeof v !== "boolean") throw fail(`'${key}' must be a boolean.`);
|
|
174
236
|
return v;
|
|
175
237
|
}
|
|
238
|
+
function appRequirementEntry(entry, key, fail) {
|
|
239
|
+
if (typeof entry === "string") {
|
|
240
|
+
if (entry.trim() === "") throw fail(`'${key}' entries must be non-empty strings or objects.`);
|
|
241
|
+
return { id: entry };
|
|
242
|
+
}
|
|
243
|
+
if (!isRecord(entry)) {
|
|
244
|
+
throw fail(`'${key}' entries must be non-empty strings or objects, got ${JSON.stringify(entry)}.`);
|
|
245
|
+
}
|
|
246
|
+
const id = entry.id;
|
|
247
|
+
if (typeof id !== "string" || id.trim() === "") {
|
|
248
|
+
throw fail(`'${key}.id' must be a non-empty string.`);
|
|
249
|
+
}
|
|
250
|
+
const version = entry.version;
|
|
251
|
+
if (version !== void 0) {
|
|
252
|
+
if (typeof version !== "string" || version.trim() === "") {
|
|
253
|
+
throw fail(`'${key}.version' must be a non-empty string.`);
|
|
254
|
+
}
|
|
255
|
+
try {
|
|
256
|
+
parseVersionRange(version);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
throw fail(`'${key}.version' ${err instanceof Error ? err.message : String(err)}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const workflows = entry.workflows;
|
|
262
|
+
if (workflows !== void 0) {
|
|
263
|
+
if (!Array.isArray(workflows) || !workflows.every((w) => typeof w === "string" && w.trim() !== "")) {
|
|
264
|
+
throw fail(`'${key}.workflows' must be an array of non-empty strings.`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const extra = Object.keys(entry).filter((k) => k !== "id" && k !== "version" && k !== "workflows");
|
|
268
|
+
if (extra.length > 0) {
|
|
269
|
+
throw fail(`'${key}.${extra[0]}' is not a supported requires.apps entry field (expected id, version, workflows).`);
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
id,
|
|
273
|
+
...version !== void 0 ? { version } : {},
|
|
274
|
+
...workflows !== void 0 ? { workflows: [...workflows] } : {}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function appRequirementList(v, key, fail) {
|
|
278
|
+
if (v === void 0) return [];
|
|
279
|
+
if (!Array.isArray(v)) {
|
|
280
|
+
throw fail(`'${key}' must be an array of non-empty strings or { id, version?, workflows? } objects.`);
|
|
281
|
+
}
|
|
282
|
+
return v.map((e, i) => appRequirementEntry(e, `${key}[${i}]`, fail));
|
|
283
|
+
}
|
|
176
284
|
function normalizeManifestFields(input, declared, fail) {
|
|
177
285
|
let placement = "any";
|
|
178
286
|
if (input.placement !== void 0) {
|
|
@@ -185,6 +293,7 @@ function normalizeManifestFields(input, declared, fail) {
|
|
|
185
293
|
}
|
|
186
294
|
let requires;
|
|
187
295
|
let requirements = { browser: false, fs: false, gpu: false, secrets: [], apps: [] };
|
|
296
|
+
let appRequirements = [];
|
|
188
297
|
const r = input.requires;
|
|
189
298
|
if (r !== void 0) {
|
|
190
299
|
if (Array.isArray(r)) {
|
|
@@ -193,16 +302,18 @@ function normalizeManifestFields(input, declared, fail) {
|
|
|
193
302
|
}
|
|
194
303
|
requires = [...r];
|
|
195
304
|
requirements = { ...requirements, apps: requires };
|
|
305
|
+
appRequirements = appRequirementList(r, "requires", fail);
|
|
196
306
|
} else if (isRecord(r)) {
|
|
197
|
-
const apps =
|
|
307
|
+
const apps = appRequirementList(r.apps, "requires.apps", fail);
|
|
198
308
|
requirements = {
|
|
199
309
|
browser: bool(r.browser, "requires.browser", fail),
|
|
200
310
|
fs: bool(r.fs, "requires.fs", fail),
|
|
201
311
|
gpu: bool(r.gpu, "requires.gpu", fail),
|
|
202
312
|
secrets: stringList(r.secrets, "requires.secrets", fail),
|
|
203
|
-
apps
|
|
313
|
+
apps: apps.map((e) => e.id)
|
|
204
314
|
};
|
|
205
|
-
|
|
315
|
+
appRequirements = apps;
|
|
316
|
+
if (apps.length > 0) requires = apps.map((e) => e.id);
|
|
206
317
|
} else {
|
|
207
318
|
throw fail("'requires' must be an array of strings or an object { browser, fs, gpu, secrets, apps }.");
|
|
208
319
|
}
|
|
@@ -233,7 +344,7 @@ function normalizeManifestFields(input, declared, fail) {
|
|
|
233
344
|
if (!isRecord(input.accepts)) throw fail("'accepts' must be an object { tasks }.");
|
|
234
345
|
accepts = { tasks: bool(input.accepts.tasks, "accepts.tasks", fail) };
|
|
235
346
|
}
|
|
236
|
-
return { placement, requires, requirements, exposes, accepts };
|
|
347
|
+
return { placement, requires, requirements, appRequirements, exposes, accepts };
|
|
237
348
|
}
|
|
238
349
|
|
|
239
350
|
// src/define-app.ts
|
|
@@ -301,13 +412,28 @@ function defineApp(def) {
|
|
|
301
412
|
secrets: Object.freeze([...fields.requirements.secrets]),
|
|
302
413
|
apps: Object.freeze([...fields.requirements.apps])
|
|
303
414
|
});
|
|
415
|
+
const appRequirements = Object.freeze(
|
|
416
|
+
fields.appRequirements.map((e) => Object.freeze({ ...e, ...e.workflows !== void 0 ? { workflows: Object.freeze([...e.workflows]) } : {} }))
|
|
417
|
+
);
|
|
304
418
|
const placement = fields.placement;
|
|
305
419
|
const exposes = Object.freeze({
|
|
306
420
|
agents: Object.freeze([...fields.exposes.agents]),
|
|
307
421
|
workflows: Object.freeze([...fields.exposes.workflows])
|
|
308
422
|
});
|
|
309
423
|
const accepts = Object.freeze({ ...fields.accepts });
|
|
310
|
-
const
|
|
424
|
+
const uiExtensions = def.ui === void 0 || def.ui.extensions === void 0 ? void 0 : Object.freeze({
|
|
425
|
+
openai: freezeDeep(
|
|
426
|
+
normalizeOpenAIUiExtension(
|
|
427
|
+
validateOpenAIExtensionsNamespace(def.ui.extensions),
|
|
428
|
+
def.ui.tools,
|
|
429
|
+
(msg) => new AppDefinitionError(msg)
|
|
430
|
+
)
|
|
431
|
+
)
|
|
432
|
+
});
|
|
433
|
+
const ui = def.ui ? Object.freeze({
|
|
434
|
+
...def.ui,
|
|
435
|
+
...uiExtensions !== void 0 ? { extensions: uiExtensions } : {}
|
|
436
|
+
}) : void 0;
|
|
311
437
|
const artifact = def.artifact ? Object.freeze({ ...def.artifact }) : void 0;
|
|
312
438
|
const skill = def.skill ? Object.freeze({ ...def.skill }) : void 0;
|
|
313
439
|
const artifacts = def.artifacts ? Object.freeze(def.artifacts.map((a) => Object.freeze({ ...a }))) : void 0;
|
|
@@ -337,6 +463,7 @@ function defineApp(def) {
|
|
|
337
463
|
...description !== void 0 ? { description } : {},
|
|
338
464
|
...requires !== void 0 ? { requires } : {},
|
|
339
465
|
requirements,
|
|
466
|
+
appRequirements,
|
|
340
467
|
placement,
|
|
341
468
|
exposes,
|
|
342
469
|
accepts,
|
|
@@ -380,6 +507,7 @@ function defineApp(def) {
|
|
|
380
507
|
...description !== void 0 ? { description } : {},
|
|
381
508
|
...requires !== void 0 ? { requires } : {},
|
|
382
509
|
requirements,
|
|
510
|
+
appRequirements,
|
|
383
511
|
placement,
|
|
384
512
|
exposes,
|
|
385
513
|
accepts,
|
|
@@ -478,6 +606,240 @@ function validateUniqueIds(items, kind) {
|
|
|
478
606
|
seen.add(item.id);
|
|
479
607
|
}
|
|
480
608
|
}
|
|
609
|
+
function validateOpenAIExtensionsNamespace(raw) {
|
|
610
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
611
|
+
throw new AppDefinitionError("`ui.extensions` must be an object.");
|
|
612
|
+
}
|
|
613
|
+
for (const key of Object.keys(raw)) {
|
|
614
|
+
if (key !== "openai") {
|
|
615
|
+
throw new AppDefinitionError(
|
|
616
|
+
`\`ui.extensions.${key}\` is not supported \u2014 \`extensions\` accepts only \`openai\` (v1).`
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return raw.openai;
|
|
621
|
+
}
|
|
622
|
+
var OPENAI_EXTENSION_KEYS = /* @__PURE__ */ new Set(["entrypoints", "icons", "display", "mentions"]);
|
|
623
|
+
var OPENAI_CUT_KEY_MESSAGE = "is not a supported OpenAI extension field in v1 (cut would be silently half-advertised)";
|
|
624
|
+
var DOT_EXTENSION_RE = /^\.[a-z0-9][a-z0-9._+-]{0,31}$/;
|
|
625
|
+
var OPENAI_MODES = ["inline", "fullscreen"];
|
|
626
|
+
function normalizeOpenAIUiExtension(raw, tools, fail) {
|
|
627
|
+
if (raw === void 0) return void 0;
|
|
628
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
629
|
+
throw fail("`ui.extensions.openai` must be an object.");
|
|
630
|
+
}
|
|
631
|
+
for (const key of Object.keys(raw)) {
|
|
632
|
+
if (!OPENAI_EXTENSION_KEYS.has(key)) {
|
|
633
|
+
throw fail(`\`ui.extensions.openai.${key}\` ${OPENAI_CUT_KEY_MESSAGE}.`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const entrypoints = normalizeOpenAIEntrypoints(raw.entrypoints, fail);
|
|
637
|
+
const icons = normalizeOpenAIIcons(raw.icons, fail);
|
|
638
|
+
const display = normalizeOpenAIDisplay(raw.display, fail);
|
|
639
|
+
const mentions = normalizeOpenAIMentions(raw.mentions, tools, fail);
|
|
640
|
+
return {
|
|
641
|
+
...entrypoints !== void 0 ? { entrypoints } : {},
|
|
642
|
+
...icons !== void 0 ? { icons } : {},
|
|
643
|
+
...display !== void 0 ? { display } : {},
|
|
644
|
+
...mentions !== void 0 ? { mentions } : {}
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function normalizeOpenAIEntrypoints(raw, fail) {
|
|
648
|
+
if (raw === void 0) return void 0;
|
|
649
|
+
if (!Array.isArray(raw)) {
|
|
650
|
+
throw fail("`ui.extensions.openai.entrypoints` must be an array.");
|
|
651
|
+
}
|
|
652
|
+
if (raw.length < 1 || raw.length > 3) {
|
|
653
|
+
throw fail(
|
|
654
|
+
`\`ui.extensions.openai.entrypoints\` must contain 1..3 entries, got ${raw.length}.`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
const seenTypes = /* @__PURE__ */ new Set();
|
|
658
|
+
const out = [];
|
|
659
|
+
for (let i = 0; i < raw.length; i++) {
|
|
660
|
+
const path = `ui.extensions.openai.entrypoints[${i}]`;
|
|
661
|
+
const ep = raw[i];
|
|
662
|
+
if (typeof ep !== "object" || ep === null || Array.isArray(ep)) {
|
|
663
|
+
throw fail(`\`${path}\` must be an object.`);
|
|
664
|
+
}
|
|
665
|
+
const keys = Object.keys(ep);
|
|
666
|
+
const invalidKeys = keys.filter((k) => k !== "type" && k !== "extensions");
|
|
667
|
+
if (invalidKeys.length > 0) {
|
|
668
|
+
throw fail(
|
|
669
|
+
`\`${path}.${invalidKeys[0]}\` is not a supported \`${ep.type}\` entrypoint field.`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
const type = ep.type;
|
|
673
|
+
if (type !== "global" && type !== "thread" && type !== "file") {
|
|
674
|
+
throw fail(`\`${path}.type\` must be "global", "thread" or "file", got ${JSON.stringify(type)}.`);
|
|
675
|
+
}
|
|
676
|
+
if (seenTypes.has(type)) {
|
|
677
|
+
throw fail(`\`ui.extensions.openai.entrypoints\` declares \`${type}\` more than once.`);
|
|
678
|
+
}
|
|
679
|
+
seenTypes.add(type);
|
|
680
|
+
if (type === "file") {
|
|
681
|
+
out.push({ type, extensions: normalizeDotExtensions(ep.extensions, path, fail) });
|
|
682
|
+
} else {
|
|
683
|
+
out.push({ type });
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return out;
|
|
687
|
+
}
|
|
688
|
+
function normalizeDotExtensions(raw, path, fail) {
|
|
689
|
+
if (!Array.isArray(raw)) {
|
|
690
|
+
throw fail(`\`${path}.extensions\` must be an array of dot-prefixed strings.`);
|
|
691
|
+
}
|
|
692
|
+
if (raw.length < 1 || raw.length > 32) {
|
|
693
|
+
throw fail(`\`${path}.extensions\` must contain 1..32 entries, got ${raw.length}.`);
|
|
694
|
+
}
|
|
695
|
+
const seen = /* @__PURE__ */ new Set();
|
|
696
|
+
const out = [];
|
|
697
|
+
for (let i = 0; i < raw.length; i++) {
|
|
698
|
+
const ext = raw[i];
|
|
699
|
+
if (typeof ext !== "string" || ext.trim() === "") {
|
|
700
|
+
throw fail(`\`${path}.extensions[${i}]\` must be a non-empty string.`);
|
|
701
|
+
}
|
|
702
|
+
const normalized = ext.toLowerCase();
|
|
703
|
+
if (seen.has(normalized)) {
|
|
704
|
+
throw fail(`\`${path}.extensions\` declares '${normalized}' more than once.`);
|
|
705
|
+
}
|
|
706
|
+
seen.add(normalized);
|
|
707
|
+
if (!DOT_EXTENSION_RE.test(normalized)) {
|
|
708
|
+
throw fail(
|
|
709
|
+
`\`${path}.extensions[${i}]\` ('${ext}') must be a dot-prefixed lowercase suffix matching /^\\.[a-z0-9][a-z0-9._+-]{0,31}$/, e.g. '.md'.`
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
out.push(normalized);
|
|
713
|
+
}
|
|
714
|
+
return out;
|
|
715
|
+
}
|
|
716
|
+
function normalizeOpenAIIcons(raw, fail) {
|
|
717
|
+
if (raw === void 0) return void 0;
|
|
718
|
+
if (!Array.isArray(raw)) throw fail("`ui.extensions.openai.icons` must be an array.");
|
|
719
|
+
if (raw.length < 1) {
|
|
720
|
+
throw fail("`ui.extensions.openai.icons` must contain at least one icon when present.");
|
|
721
|
+
}
|
|
722
|
+
return raw.map((icon, i) => {
|
|
723
|
+
const path = `ui.extensions.openai.icons[${i}]`;
|
|
724
|
+
if (typeof icon !== "object" || icon === null || Array.isArray(icon)) {
|
|
725
|
+
throw fail(`\`${path}\` must be an object.`);
|
|
726
|
+
}
|
|
727
|
+
const keys = Object.keys(icon);
|
|
728
|
+
for (const key of keys) {
|
|
729
|
+
if (key !== "src" && key !== "mimeType" && key !== "sizes" && key !== "theme") {
|
|
730
|
+
throw fail(`\`${path}.${key}\` is not a supported icon field.`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const src = icon.src;
|
|
734
|
+
if (typeof src !== "string" || src.trim() === "") {
|
|
735
|
+
throw fail(`\`${path}.src\` must be a non-empty string.`);
|
|
736
|
+
}
|
|
737
|
+
if (!(ICON_SRC_HTTPS_RE.test(src) || hasValidIconSrcDataUrl(src))) {
|
|
738
|
+
throw fail(
|
|
739
|
+
`\`${path}.src\` must be an HTTPS URL or a 'data:image/\u2026;base64,\u2026' URL (inline SVG data allowed), got '${src.slice(0, 64)}'.`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
const mimeType = icon.mimeType;
|
|
743
|
+
if (mimeType !== void 0 && (typeof mimeType !== "string" || mimeType.trim() === "")) {
|
|
744
|
+
throw fail(`\`${path}.mimeType\` must be a non-empty string when present.`);
|
|
745
|
+
}
|
|
746
|
+
const sizes = icon.sizes;
|
|
747
|
+
if (sizes !== void 0 && (!Array.isArray(sizes) || sizes.length === 0 || !sizes.every((s) => typeof s === "string" && s.trim() !== ""))) {
|
|
748
|
+
throw fail(`\`${path}.sizes\` must be an array of non-empty strings when present.`);
|
|
749
|
+
}
|
|
750
|
+
const theme = icon.theme;
|
|
751
|
+
if (theme !== void 0 && theme !== "light" && theme !== "dark") {
|
|
752
|
+
throw fail(`\`${path}.theme\` must be "light" or "dark" when present, got ${JSON.stringify(theme)}.`);
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
src,
|
|
756
|
+
...mimeType !== void 0 ? { mimeType } : {},
|
|
757
|
+
...sizes !== void 0 ? { sizes: Object.freeze([...sizes]) } : {},
|
|
758
|
+
...theme !== void 0 ? { theme } : {}
|
|
759
|
+
};
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
var ICON_SRC_HTTPS_RE = /^https:\/\/\S+$/;
|
|
763
|
+
function hasValidIconSrcDataUrl(src) {
|
|
764
|
+
const comma = src.indexOf(",");
|
|
765
|
+
if (comma < 0) return false;
|
|
766
|
+
const header = src.slice(0, comma);
|
|
767
|
+
const payload = src.slice(comma + 1);
|
|
768
|
+
if (!/^data:image\/[a-z0-9.+-]+(?:;charset=[\w-]+)?(?:;base64|;utf8)?$/i.test(header)) return false;
|
|
769
|
+
if (/;base64$/i.test(header)) return /^[A-Za-z0-9+/=\r\n]+$/.test(payload);
|
|
770
|
+
return payload.trim() !== "";
|
|
771
|
+
}
|
|
772
|
+
function normalizeOpenAIDisplay(raw, fail) {
|
|
773
|
+
if (raw === void 0) return void 0;
|
|
774
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
775
|
+
throw fail("`ui.extensions.openai.display` must be an object.");
|
|
776
|
+
}
|
|
777
|
+
const availableModes = raw.availableModes;
|
|
778
|
+
if (availableModes !== void 0) {
|
|
779
|
+
if (!Array.isArray(availableModes) || availableModes.length === 0) {
|
|
780
|
+
throw fail("`ui.extensions.openai.display.availableModes` must be a non-empty array when present.");
|
|
781
|
+
}
|
|
782
|
+
const seen = /* @__PURE__ */ new Set();
|
|
783
|
+
for (const mode of availableModes) {
|
|
784
|
+
if (mode !== "inline" && mode !== "fullscreen") {
|
|
785
|
+
throw fail(
|
|
786
|
+
`\`ui.extensions.openai.display.availableModes\` supports only "inline" and "fullscreen", got ${JSON.stringify(mode)}.`
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
if (seen.has(mode)) {
|
|
790
|
+
throw fail(`\`ui.extensions.openai.display.availableModes\` declares '${mode}' more than once.`);
|
|
791
|
+
}
|
|
792
|
+
seen.add(mode);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
const preferredMode = raw.preferredMode;
|
|
796
|
+
if (preferredMode !== void 0 && !OPENAI_MODES.includes(preferredMode)) {
|
|
797
|
+
throw fail(
|
|
798
|
+
`\`ui.extensions.openai.display.preferredMode\` must be "inline" or "fullscreen", got ${JSON.stringify(preferredMode)}.`
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
if (availableModes !== void 0 && preferredMode !== void 0 && !availableModes.includes(preferredMode)) {
|
|
802
|
+
throw fail(
|
|
803
|
+
`\`ui.extensions.openai.display.preferredMode\` ('${preferredMode}') must occur in \`ui.extensions.openai.display.availableModes\` when both are declared.`
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
...availableModes !== void 0 ? { availableModes: Object.freeze([...availableModes]) } : {},
|
|
808
|
+
...preferredMode !== void 0 ? { preferredMode } : {}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
function normalizeOpenAIMentions(raw, tools, fail) {
|
|
812
|
+
if (raw === void 0) return void 0;
|
|
813
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
814
|
+
throw fail("`ui.extensions.openai.mentions` must be an object.");
|
|
815
|
+
}
|
|
816
|
+
for (const key of Object.keys(raw)) {
|
|
817
|
+
if (key !== "searchTool") {
|
|
818
|
+
throw fail(`\`ui.extensions.openai.mentions.${key}\` is not a supported mentions field.`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
const searchTool = raw.searchTool;
|
|
822
|
+
if (typeof searchTool !== "string" || searchTool.trim() === "") {
|
|
823
|
+
throw fail("`ui.extensions.openai.mentions.searchTool` must be a non-empty string.");
|
|
824
|
+
}
|
|
825
|
+
if (tools === void 0 || !tools.includes(searchTool)) {
|
|
826
|
+
throw fail(
|
|
827
|
+
`\`ui.extensions.openai.mentions.searchTool\` '${searchTool}' must be declared in this same ui block's \`tools\`${tools ? ` (${tools.join(", ")})` : " \u2014 the ui declares none"}.`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
return { searchTool };
|
|
831
|
+
}
|
|
832
|
+
function freezeDeep(value) {
|
|
833
|
+
if (Array.isArray(value)) return Object.freeze(value.map(freezeDeep));
|
|
834
|
+
if (typeof value === "object" && value !== null) {
|
|
835
|
+
const out = {};
|
|
836
|
+
for (const [key, v] of Object.entries(value)) {
|
|
837
|
+
out[key] = freezeDeep(v);
|
|
838
|
+
}
|
|
839
|
+
return Object.freeze(out);
|
|
840
|
+
}
|
|
841
|
+
return value;
|
|
842
|
+
}
|
|
481
843
|
|
|
482
844
|
// src/errors.ts
|
|
483
845
|
var AppLoadError = class extends Error {
|
|
@@ -842,7 +1204,13 @@ async function loadAppHandle(dir) {
|
|
|
842
1204
|
...fm.ui.tools !== void 0 ? { tools: fm.ui.tools } : {},
|
|
843
1205
|
...fm.ui.port !== void 0 ? { port: fm.ui.port } : {},
|
|
844
1206
|
...fm.ui.csp !== void 0 ? { csp: fm.ui.csp } : {},
|
|
845
|
-
...build !== void 0 ? { build } : {}
|
|
1207
|
+
...build !== void 0 ? { build } : {},
|
|
1208
|
+
// Pass through UNFILTERED — `defineApp`'s
|
|
1209
|
+
// `validateOpenAIExtensionsNamespace` rejects any sibling vendor key
|
|
1210
|
+
// (a typo, a future vendor block, hand-authored drift) with the same
|
|
1211
|
+
// diagnostic the TS-authoring path throws. Pre-filtering to `openai`
|
|
1212
|
+
// here would silently drop those instead of failing loudly.
|
|
1213
|
+
...fm.ui.extensions !== void 0 ? { extensions: fm.ui.extensions } : {}
|
|
846
1214
|
};
|
|
847
1215
|
}
|
|
848
1216
|
return defineApp({
|
|
@@ -974,8 +1342,92 @@ function extractMeta(front) {
|
|
|
974
1342
|
...ui !== void 0 ? { ui } : {}
|
|
975
1343
|
};
|
|
976
1344
|
}
|
|
1345
|
+
var RELEASE_DEFAULT_EXCLUDE = [
|
|
1346
|
+
"ui/**",
|
|
1347
|
+
"docs/**",
|
|
1348
|
+
"data/**",
|
|
1349
|
+
"scripts/**",
|
|
1350
|
+
// Root-level only: `**/dev/**` would also drop a shipped agent named
|
|
1351
|
+
// `dev` (`.agentproto/agents/dev/`); `ui/dev/` is covered by `ui/**`.
|
|
1352
|
+
"dev/**",
|
|
1353
|
+
"**/*.log",
|
|
1354
|
+
"**/*.map",
|
|
1355
|
+
"**/.DS_Store",
|
|
1356
|
+
"**/.env",
|
|
1357
|
+
"**/.env.*"
|
|
1358
|
+
];
|
|
1359
|
+
function globToRegExp(glob) {
|
|
1360
|
+
const g = glob.replace(/^\.\//, "");
|
|
1361
|
+
let re = "";
|
|
1362
|
+
for (let i = 0; i < g.length; i++) {
|
|
1363
|
+
const c = g[i];
|
|
1364
|
+
if (c === "*") {
|
|
1365
|
+
if (g[i + 1] === "*") {
|
|
1366
|
+
i++;
|
|
1367
|
+
if (g[i + 1] === "/") {
|
|
1368
|
+
i++;
|
|
1369
|
+
re += "(?:.*/)?";
|
|
1370
|
+
} else {
|
|
1371
|
+
re += ".*";
|
|
1372
|
+
}
|
|
1373
|
+
} else {
|
|
1374
|
+
re += "[^/]*";
|
|
1375
|
+
}
|
|
1376
|
+
} else if ("\\^$+?.()|{}[]".includes(c)) {
|
|
1377
|
+
re += "\\" + c;
|
|
1378
|
+
} else {
|
|
1379
|
+
re += c;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return new RegExp(`^${re}$`);
|
|
1383
|
+
}
|
|
1384
|
+
function toPosix(p) {
|
|
1385
|
+
return p.replace(/\\/g, "/");
|
|
1386
|
+
}
|
|
1387
|
+
function matchesAny(path, globs) {
|
|
1388
|
+
const p = toPosix(path);
|
|
1389
|
+
return globs.some((re) => re.test(p));
|
|
1390
|
+
}
|
|
1391
|
+
function readPackageRules(front) {
|
|
1392
|
+
const raw = front.package;
|
|
1393
|
+
if (raw === void 0 || raw === null) return {};
|
|
1394
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
1395
|
+
throw new AgentAppPackError("invalid-package", "APP.md `package` must be an object.");
|
|
1396
|
+
}
|
|
1397
|
+
const r = raw;
|
|
1398
|
+
const list = (key) => {
|
|
1399
|
+
const v = r[key];
|
|
1400
|
+
if (v === void 0) return void 0;
|
|
1401
|
+
if (!Array.isArray(v) || v.some((s) => typeof s !== "string" || s.trim() === "")) {
|
|
1402
|
+
throw new AgentAppPackError(
|
|
1403
|
+
"invalid-package",
|
|
1404
|
+
`APP.md \`package.${key}\` must be an array of non-empty glob strings.`
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
return v;
|
|
1408
|
+
};
|
|
1409
|
+
if (r.stripBuild !== void 0 && typeof r.stripBuild !== "boolean") {
|
|
1410
|
+
throw new AgentAppPackError("invalid-package", "APP.md `package.stripBuild` must be a boolean.");
|
|
1411
|
+
}
|
|
1412
|
+
const include = list("include");
|
|
1413
|
+
const exclude = list("exclude");
|
|
1414
|
+
return {
|
|
1415
|
+
...include !== void 0 ? { include } : {},
|
|
1416
|
+
...exclude !== void 0 ? { exclude } : {},
|
|
1417
|
+
...typeof r.stripBuild === "boolean" ? { stripBuild: r.stripBuild } : {}
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
function uiRelPath(front) {
|
|
1421
|
+
const ui = front.ui;
|
|
1422
|
+
const raw = typeof ui === "string" ? ui : ui && typeof ui === "object" && !Array.isArray(ui) && typeof ui.path === "string" ? ui.path : void 0;
|
|
1423
|
+
if (raw === void 0 || raw === "" || isAbsolute(raw)) return void 0;
|
|
1424
|
+
const rel = posix.normalize(toPosix(raw)).replace(/^\.\//, "");
|
|
1425
|
+
if (rel.startsWith("../") || rel === "..") return void 0;
|
|
1426
|
+
return rel;
|
|
1427
|
+
}
|
|
977
1428
|
async function packApp(input) {
|
|
978
1429
|
const appDirAbs = resolve(input.appDir);
|
|
1430
|
+
const release = input.release === true;
|
|
979
1431
|
const appMdPath = join(appDirAbs, ".agentproto", "APP.md");
|
|
980
1432
|
if (!await pathExists(appMdPath)) {
|
|
981
1433
|
throw new AgentAppPackError(
|
|
@@ -984,49 +1436,82 @@ async function packApp(input) {
|
|
|
984
1436
|
);
|
|
985
1437
|
}
|
|
986
1438
|
const raw = await readFile(appMdPath, "utf8");
|
|
987
|
-
const
|
|
1439
|
+
const parsedAppMd = matter2(raw);
|
|
1440
|
+
const front = parsedAppMd.data;
|
|
988
1441
|
const meta = extractMeta(front);
|
|
1442
|
+
const rules = readPackageRules(front);
|
|
989
1443
|
const outAbs = input.out !== void 0 ? resolve(input.out) : resolve(process.cwd(), `${safeId(meta.id)}-${meta.version}.agentapp`);
|
|
990
1444
|
await mkdir(dirname(outAbs), { recursive: true });
|
|
991
|
-
const
|
|
992
|
-
const
|
|
993
|
-
const
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
)
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
sha256,
|
|
1011
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1012
|
-
agentprotoVersion: ">=0.1.0"
|
|
1013
|
-
};
|
|
1445
|
+
const appMdRel = ".agentproto/APP.md";
|
|
1446
|
+
const uiRel = uiRelPath(front);
|
|
1447
|
+
const uiAssetsPrefix = uiRel !== void 0 ? `${posix.dirname(uiRel)}/assets/`.replace(/^\.\//, "") : void 0;
|
|
1448
|
+
const includeRes = rules.include?.map(globToRegExp);
|
|
1449
|
+
const excludeRes = [...release ? RELEASE_DEFAULT_EXCLUDE : [], ...rules.exclude ?? []].map(globToRegExp);
|
|
1450
|
+
const alwaysShip = (p) => p === appMdRel || p === uiRel || uiAssetsPrefix !== void 0 && p.startsWith(uiAssetsPrefix);
|
|
1451
|
+
const selected = (await collectFiles(appDirAbs)).filter((f) => f.path !== "manifest.json").filter((f) => join(appDirAbs, f.path) !== outAbs).filter((f) => {
|
|
1452
|
+
const p = toPosix(f.path);
|
|
1453
|
+
if (alwaysShip(p)) return true;
|
|
1454
|
+
if (includeRes !== void 0 && !matchesAny(p, includeRes)) return false;
|
|
1455
|
+
return !matchesAny(p, excludeRes);
|
|
1456
|
+
}).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
1457
|
+
if (release && uiRel !== void 0 && !selected.some((f) => toPosix(f.path) === uiRel)) {
|
|
1458
|
+
throw new AgentAppPackError(
|
|
1459
|
+
"missing-ui",
|
|
1460
|
+
`ui.path "${uiRel}" does not exist in ${appDirAbs} \u2014 build the UI first (\`agentproto app build <appDir>\`, or \`agentproto app pack --release\`, which runs \`ui.build\`).`
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
const stripBuild = rules.stripBuild ?? release;
|
|
1014
1464
|
const staging = await mkdtemp(join(tmpdir(), "agentapp-"));
|
|
1015
1465
|
try {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1466
|
+
for (const f of selected) {
|
|
1467
|
+
const dest = join(staging, f.path);
|
|
1468
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
1469
|
+
await copyFile(join(appDirAbs, f.path), dest);
|
|
1470
|
+
}
|
|
1471
|
+
if (stripBuild) {
|
|
1472
|
+
const ui = front.ui;
|
|
1473
|
+
if (ui && typeof ui === "object" && !Array.isArray(ui) && "build" in ui) {
|
|
1474
|
+
const data = structuredClone(front);
|
|
1475
|
+
delete data.ui.build;
|
|
1476
|
+
await writeFile(join(staging, appMdRel), matter2.stringify(parsedAppMd.content, data), "utf8");
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
const files = [];
|
|
1480
|
+
for (const f of selected) {
|
|
1481
|
+
files.push({ path: f.path, size: (await stat(join(staging, f.path))).size });
|
|
1482
|
+
}
|
|
1483
|
+
const fileCount = files.length;
|
|
1484
|
+
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
|
1485
|
+
const sha256 = await aggregateSha256(
|
|
1486
|
+
staging,
|
|
1487
|
+
files.map((f) => f.path)
|
|
1488
|
+
);
|
|
1489
|
+
const manifest = {
|
|
1490
|
+
format: AGENTAPP_FORMAT,
|
|
1491
|
+
id: meta.id,
|
|
1492
|
+
...meta.name !== void 0 ? { name: meta.name } : {},
|
|
1493
|
+
version: meta.version,
|
|
1494
|
+
...meta.description !== void 0 ? { description: meta.description } : {},
|
|
1495
|
+
agents: meta.agents,
|
|
1496
|
+
workflows: meta.workflows,
|
|
1497
|
+
...meta.ui !== void 0 ? { ui: meta.ui } : {},
|
|
1498
|
+
files: files.map((f) => f.path),
|
|
1499
|
+
fileCount,
|
|
1500
|
+
totalSize,
|
|
1501
|
+
sha256,
|
|
1502
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1503
|
+
agentprotoVersion: ">=0.1.0"
|
|
1504
|
+
};
|
|
1020
1505
|
await writeFile(join(staging, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
1021
1506
|
const entries = (await readdir(staging)).sort();
|
|
1022
1507
|
const packCode = await runTar(["-czf", outAbs, ...entries], { cwd: staging });
|
|
1023
1508
|
if (packCode !== 0) {
|
|
1024
1509
|
throw new AgentAppPackError("tar-failed", `tar failed with exit code ${packCode}`);
|
|
1025
1510
|
}
|
|
1511
|
+
return { file: outAbs, manifest };
|
|
1026
1512
|
} finally {
|
|
1027
1513
|
await rm(staging, { recursive: true, force: true });
|
|
1028
1514
|
}
|
|
1029
|
-
return { file: outAbs, manifest };
|
|
1030
1515
|
}
|
|
1031
1516
|
function isUnsafeBundlePath(p) {
|
|
1032
1517
|
if (p === "" || isAbsolute(p)) return true;
|
|
@@ -1090,6 +1575,6 @@ async function unpackApp(input) {
|
|
|
1090
1575
|
}
|
|
1091
1576
|
}
|
|
1092
1577
|
|
|
1093
|
-
export { AGENTAPP_FORMAT, AgentAppPackError, AppDefinitionError, AppLoadError, aggregateSha256, collectFiles, defineApp, emitApp, isManifest, loadAppBundledTools, loadAppHandle, packApp, peekAppUi, refKey, resolveAppUIRoot, safeId, stripOwner, unpackApp };
|
|
1578
|
+
export { AGENTAPP_FORMAT, AgentAppPackError, AppDefinitionError, AppLoadError, RELEASE_DEFAULT_EXCLUDE, aggregateSha256, collectFiles, defineApp, emitApp, globToRegExp, isManifest, loadAppBundledTools, loadAppHandle, packApp, peekAppUi, readPackageRules, refKey, resolveAppUIRoot, safeId, stripOwner, unpackApp, versionSatisfies };
|
|
1094
1579
|
//# sourceMappingURL=index.mjs.map
|
|
1095
1580
|
//# sourceMappingURL=index.mjs.map
|