@agentage/extension 0.1.1 → 0.1.2
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/dist/agcid/agcid.d.ts +1 -0
- package/dist/agcid/agcid.js +2 -2
- package/dist/context/context.js +7 -3
- package/dist/discovery/discovery.js +8 -23
- package/dist/discovery/discovery.schema.d.ts +0 -26
- package/dist/discovery/discovery.schema.js +3 -3
- package/dist/envelope/envelope.js +1 -0
- package/dist/envelope/envelope.schema.d.ts +0 -4
- package/dist/envelope/envelope.schema.js +3 -2
- package/dist/health/health.d.ts +5 -2
- package/dist/health/health.js +23 -8
- package/dist/health/health.schema.d.ts +2 -9
- package/dist/health/health.schema.js +2 -2
- package/dist/index.d.ts +5 -11
- package/dist/index.js +5 -11
- package/dist/log/log.d.ts +1 -0
- package/dist/log/log.js +6 -7
- package/dist/manifest/manifest.schema.d.ts +1 -89
- package/dist/manifest/manifest.schema.js +7 -4
- package/dist/manifest/primitives.schema.d.ts +1 -0
- package/dist/manifest/primitives.schema.js +3 -1
- package/dist/manifest/read-manifest.d.ts +1 -0
- package/dist/manifest/read-manifest.js +26 -3
- package/dist/runtime/build-info.d.ts +1 -0
- package/dist/runtime/build-info.js +4 -4
- package/dist/runtime/main.js +5 -2
- package/dist/runtime/run-part.js +10 -19
- package/dist/server/app.d.ts +0 -2
- package/dist/server/app.js +2 -3
- package/dist/server/errors.d.ts +0 -2
- package/dist/server/errors.js +1 -1
- package/dist/server/serve.d.ts +0 -1
- package/dist/server/serve.js +2 -1
- package/dist/tool/build-info.js +2 -3
- package/dist/tool/build.d.ts +0 -3
- package/dist/tool/build.js +7 -8
- package/dist/tool/docker.d.ts +0 -1
- package/dist/tool/docker.js +2 -1
- package/dist/tool/exec.d.ts +0 -1
- package/dist/tool/exec.js +2 -6
- package/dist/tool/index.d.ts +1 -10
- package/dist/tool/index.js +1 -10
- package/dist/tool/main.d.ts +0 -1
- package/dist/tool/main.js +18 -16
- package/dist/tool/recipe.d.ts +0 -1
- package/dist/tool/recipe.js +7 -3
- package/dist/tool/stage.js +15 -2
- package/package.json +1 -1
package/dist/agcid/agcid.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const AGCID_HEADER = "agcid";
|
|
2
|
+
export declare const AGCID_PATTERN: RegExp;
|
|
2
3
|
export declare const isAgcid: (value: unknown) => value is string;
|
|
3
4
|
export declare const uuidv7: (now?: number, random?: Buffer) => string;
|
|
4
5
|
export declare const agcidOf: (incoming: unknown) => string;
|
package/dist/agcid/agcid.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
export const AGCID_HEADER = 'agcid';
|
|
3
|
-
const
|
|
4
|
-
export const isAgcid = (value) => typeof value === 'string' &&
|
|
3
|
+
export const AGCID_PATTERN = /^[A-Za-z0-9._:-]{8,128}$/;
|
|
4
|
+
export const isAgcid = (value) => typeof value === 'string' && AGCID_PATTERN.test(value);
|
|
5
5
|
const hyphenate = (hex) => [hex.slice(0, 8), hex.slice(8, 12), hex.slice(12, 16), hex.slice(16, 20), hex.slice(20)].join('-');
|
|
6
6
|
// RFC 9562 UUIDv7: 48-bit unix ms, version 7, variant 10, the rest random.
|
|
7
7
|
export const uuidv7 = (now = Date.now(), random = randomBytes(10)) => {
|
package/dist/context/context.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {} from '../manifest/manifest.schema.js';
|
|
2
|
-
import { createLog } from '../log/log.js';
|
|
2
|
+
import { createLog, messageOf } from '../log/log.js';
|
|
3
3
|
export const settingDefaults = (pkg) => Object.fromEntries(Object.entries(pkg.agentage.settings ?? {}).map(([key, setting]) => [key, setting.default]));
|
|
4
4
|
export const createContext = ({ pkg, part, origin, log }) => Object.freeze({
|
|
5
5
|
extension: Object.freeze({ id: pkg.agentage.id, version: pkg.version, part, origin }),
|
|
@@ -9,9 +9,13 @@ export const createContext = ({ pkg, part, origin, log }) => Object.freeze({
|
|
|
9
9
|
const recordingLog = (logs) => ({
|
|
10
10
|
info: (message, fields = {}) => void logs.push({ level: 'info', message, fields }),
|
|
11
11
|
warn: (message, fields = {}) => void logs.push({ level: 'warn', message, fields }),
|
|
12
|
-
error: (error, fields = {}) => void logs.push({
|
|
12
|
+
error: (error, fields = {}) => void logs.push({
|
|
13
|
+
level: 'error',
|
|
14
|
+
message: messageOf(error),
|
|
15
|
+
fields: { ...fields, err: error },
|
|
16
|
+
}),
|
|
13
17
|
});
|
|
14
|
-
// A ctx for unit tests: fixed identity, settings as given, a log that records
|
|
18
|
+
// A ctx for unit tests: fixed identity, settings as given, a log that records what it would print.
|
|
15
19
|
export const createTestContext = (options = {}) => {
|
|
16
20
|
const logs = [];
|
|
17
21
|
const extension = {
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { formatIssues } from '../manifest/read-manifest.js';
|
|
1
|
+
import { formatIssues, readJsonIfExists } from '../manifest/read-manifest.js';
|
|
3
2
|
import {} from '../manifest/manifest.schema.js';
|
|
4
3
|
import { normalizeParts } from '../manifest/parts.js';
|
|
5
4
|
import {} from '../manifest/primitives.schema.js';
|
|
6
5
|
import { ExtensionRecordSchema } from './discovery.schema.js';
|
|
7
6
|
export const DEFAULT_RECORD_FILE = '/run/agentage/agentage-extension.json';
|
|
8
|
-
const commandNames = (pkg) => {
|
|
9
|
-
const names = (pkg.agentage.commands ?? []).map((command) => command.name);
|
|
10
|
-
return names.length > 0 ? names : undefined;
|
|
11
|
-
};
|
|
12
7
|
const partsOf = (pkg, images) => {
|
|
13
8
|
const parts = {};
|
|
14
9
|
for (const [kind, image] of Object.entries(images))
|
|
@@ -20,15 +15,10 @@ const partsOf = (pkg, images) => {
|
|
|
20
15
|
};
|
|
21
16
|
// From the manifest, the build and the pushed images by digest; OpenAPI, tools, widgets come later.
|
|
22
17
|
export const recordOf = (pkg, build, images) => {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
buildTime: build.buildTime,
|
|
28
|
-
parts: partsOf(pkg, images),
|
|
29
|
-
commands: commandNames(pkg),
|
|
30
|
-
};
|
|
31
|
-
return JSON.parse(JSON.stringify(record));
|
|
18
|
+
const { id, commands } = pkg.agentage;
|
|
19
|
+
const { commit, buildTime } = build;
|
|
20
|
+
const record = { id, version: pkg.version, commit, buildTime, parts: partsOf(pkg, images) };
|
|
21
|
+
return commands ? { ...record, commands: commands.map((command) => command.name) } : record;
|
|
32
22
|
};
|
|
33
23
|
export const validateRecord = (value) => {
|
|
34
24
|
const result = ExtensionRecordSchema.safeParse(value);
|
|
@@ -36,15 +26,10 @@ export const validateRecord = (value) => {
|
|
|
36
26
|
return result.data;
|
|
37
27
|
throw new Error(`invalid extension record: ${formatIssues(result.error.issues)}`);
|
|
38
28
|
};
|
|
29
|
+
// No file means no record is mounted; any other read error stops the part from starting.
|
|
39
30
|
export const loadRecord = async (file) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
text = await readFile(file, 'utf8');
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return null; // no record mounted: discovery lists the part's own image by tag
|
|
46
|
-
}
|
|
47
|
-
return validateRecord(JSON.parse(text));
|
|
31
|
+
const record = await readJsonIfExists(file);
|
|
32
|
+
return record === undefined ? null : validateRecord(record);
|
|
48
33
|
};
|
|
49
34
|
export const discoveryDocument = (record, host) => {
|
|
50
35
|
const { id, version, commit, buildTime, ...rest } = record;
|
|
@@ -1,30 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export declare const ImageDigestRefSchema: z.ZodString;
|
|
3
|
-
export declare const PartsRecordSchema: z.ZodObject<{
|
|
4
|
-
pages: z.ZodOptional<z.ZodObject<{
|
|
5
|
-
image: z.ZodString;
|
|
6
|
-
}, z.core.$strict>>;
|
|
7
|
-
ui: z.ZodOptional<z.ZodObject<{
|
|
8
|
-
image: z.ZodString;
|
|
9
|
-
}, z.core.$strict>>;
|
|
10
|
-
api: z.ZodOptional<z.ZodObject<{
|
|
11
|
-
image: z.ZodString;
|
|
12
|
-
}, z.core.$strict>>;
|
|
13
|
-
mcp: z.ZodOptional<z.ZodObject<{
|
|
14
|
-
image: z.ZodString;
|
|
15
|
-
}, z.core.$strict>>;
|
|
16
|
-
worker: z.ZodOptional<z.ZodObject<{
|
|
17
|
-
image: z.ZodString;
|
|
18
|
-
}, z.core.$strict>>;
|
|
19
|
-
store: z.ZodOptional<z.ZodObject<{
|
|
20
|
-
kind: z.ZodEnum<{
|
|
21
|
-
postgres: "postgres";
|
|
22
|
-
git: "git";
|
|
23
|
-
signoz: "signoz";
|
|
24
|
-
}>;
|
|
25
|
-
}, z.core.$strict>>;
|
|
26
|
-
}, z.core.$strict>;
|
|
27
|
-
export declare const OriginSchema: z.ZodString;
|
|
28
2
|
export declare const ExtensionRecordSchema: z.ZodObject<{
|
|
29
3
|
id: z.ZodString;
|
|
30
4
|
version: z.ZodString;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { CommandNameSchema, CommitSchema, ExtensionIdSchema, IMAGE_PART_KINDS, PrefixedToolNameSchema, StoreKindSchema, VersionSchema, } from '../manifest/primitives.schema.js';
|
|
3
3
|
// registry host[:port]/owner/{id}-{part}@sha256: deployed by digest, a version's images never change.
|
|
4
|
-
|
|
4
|
+
const ImageDigestRefSchema = z
|
|
5
5
|
.string()
|
|
6
6
|
.max(256)
|
|
7
7
|
.regex(/^[a-z0-9][a-z0-9.-]*(:[0-9]{1,5})?\/[a-z0-9][a-z0-9-]*\/[a-z][a-z0-9-]*-(pages|ui|api|mcp|worker)@sha256:[0-9a-f]{64}$/);
|
|
8
8
|
const PartImageSchema = z.strictObject({ image: ImageDigestRefSchema });
|
|
9
|
-
|
|
9
|
+
const PartsRecordSchema = z
|
|
10
10
|
.strictObject({
|
|
11
11
|
pages: PartImageSchema.optional(),
|
|
12
12
|
ui: PartImageSchema.optional(),
|
|
@@ -18,7 +18,7 @@ export const PartsRecordSchema = z
|
|
|
18
18
|
.refine((parts) => IMAGE_PART_KINDS.some((kind) => parts[kind] !== undefined), {
|
|
19
19
|
message: 'at least one part with an image',
|
|
20
20
|
});
|
|
21
|
-
|
|
21
|
+
const OriginSchema = z
|
|
22
22
|
.string()
|
|
23
23
|
.max(270)
|
|
24
24
|
.regex(/^(https:\/\/[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+|http:\/\/(127\.0\.0\.1|localhost))(:[0-9]{1,5})?$/);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_RETRY_AFTER_SECONDS, ERROR_STATUS, RETRY_AFTER_CODES, } from './codes.js';
|
|
2
|
+
// A registry symbol, so a failure thrown through a second copy of the kit is still recognised.
|
|
2
3
|
const FAILURE = Symbol.for('agentage.extension.failure');
|
|
3
4
|
export const ok = (data, meta) => meta === undefined ? { success: true, data } : { success: true, data, meta };
|
|
4
5
|
const retryAfterOf = (code, given) => {
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export declare const AgcidSchema: z.ZodString;
|
|
3
|
-
export declare const ErrorCodeSchema: z.ZodEnum<{
|
|
4
|
-
[x: string]: string;
|
|
5
|
-
}>;
|
|
6
2
|
export declare const ErrorEnvelopeSchema: z.ZodObject<{
|
|
7
3
|
success: z.ZodLiteral<false>;
|
|
8
4
|
error: z.ZodObject<{
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { AGCID_PATTERN } from '../agcid/agcid.js';
|
|
2
3
|
import { ERROR_CODES } from './codes.js';
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
const AgcidSchema = z.string().regex(AGCID_PATTERN);
|
|
5
|
+
const ErrorCodeSchema = z.enum(ERROR_CODES);
|
|
5
6
|
export const ErrorEnvelopeSchema = z.strictObject({
|
|
6
7
|
success: z.literal(false),
|
|
7
8
|
error: z.strictObject({
|
package/dist/health/health.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { type RequestHandler } from 'express';
|
|
2
2
|
import { type BuildInfo } from '../discovery/discovery.js';
|
|
3
|
+
import { type ImagePartKind } from '../manifest/primitives.schema.js';
|
|
3
4
|
import { type HealthBody, type HealthStatus } from './health.schema.js';
|
|
4
5
|
export type HealthCheck = () => Promise<HealthStatus>;
|
|
5
6
|
export interface HealthOptions {
|
|
6
7
|
extension: string;
|
|
7
|
-
part:
|
|
8
|
+
part: ImagePartKind;
|
|
8
9
|
version: string;
|
|
9
10
|
build: BuildInfo;
|
|
10
11
|
checks?: Record<string, HealthCheck>;
|
|
@@ -13,6 +14,8 @@ export interface HealthOptions {
|
|
|
13
14
|
now?: () => Date;
|
|
14
15
|
}
|
|
15
16
|
export declare const worstOf: (statuses: HealthStatus[]) => HealthStatus;
|
|
17
|
+
export declare const CHECK_TIMEOUT_MS = 1000;
|
|
16
18
|
export declare const isLoopback: (address: string | undefined) => boolean;
|
|
17
|
-
export
|
|
19
|
+
export type ResolvedHealth = HealthOptions & Required<Pick<HealthOptions, 'instance' | 'startedAt'>>;
|
|
20
|
+
export declare const healthBody: (options: ResolvedHealth) => Promise<HealthBody>;
|
|
18
21
|
export declare const createHealthHandler: (options: HealthOptions) => RequestHandler;
|
package/dist/health/health.js
CHANGED
|
@@ -2,25 +2,40 @@ import { randomBytes } from 'node:crypto';
|
|
|
2
2
|
import {} from 'express';
|
|
3
3
|
import {} from '../discovery/discovery.js';
|
|
4
4
|
import { fail } from '../envelope/envelope.js';
|
|
5
|
+
import {} from '../manifest/primitives.schema.js';
|
|
6
|
+
import { isoSeconds } from '../runtime/build-info.js';
|
|
5
7
|
import {} from './health.schema.js';
|
|
6
8
|
const RANK = { ok: 0, degraded: 1, unavailable: 2 };
|
|
7
9
|
export const worstOf = (statuses) => statuses.reduce((worst, s) => (RANK[s] > RANK[worst] ? s : worst), 'ok');
|
|
10
|
+
// Under the recipe's HEALTHCHECK --timeout=3s: a hung check reads unavailable, never a hung probe.
|
|
11
|
+
export const CHECK_TIMEOUT_MS = 1000;
|
|
12
|
+
const settle = async (check) => {
|
|
13
|
+
let timer;
|
|
14
|
+
const expiry = new Promise((resolve) => {
|
|
15
|
+
timer = setTimeout(() => resolve('unavailable'), CHECK_TIMEOUT_MS).unref();
|
|
16
|
+
});
|
|
17
|
+
try {
|
|
18
|
+
return await Promise.race([check(), expiry]);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return 'unavailable';
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
8
27
|
const runChecks = async (checks) => {
|
|
9
|
-
const entries = await Promise.all(Object.entries(checks).map(async ([name, check]) =>
|
|
10
|
-
const status = await check().catch(() => 'unavailable');
|
|
11
|
-
return [name, status];
|
|
12
|
-
}));
|
|
28
|
+
const entries = await Promise.all(Object.entries(checks).map(async ([name, check]) => [name, await settle(check)]));
|
|
13
29
|
return Object.fromEntries(entries);
|
|
14
30
|
};
|
|
15
|
-
const isoSeconds = (date) => `${date.toISOString().slice(0, 19)}Z`;
|
|
16
31
|
export const isLoopback = (address) => address !== undefined && /^(127\.|::1$|::ffff:127\.)/.test(address);
|
|
17
32
|
export const healthBody = async (options) => {
|
|
18
33
|
const now = (options.now ?? (() => new Date()))();
|
|
19
34
|
const checks = await runChecks(options.checks ?? {});
|
|
20
35
|
const status = worstOf(Object.values(checks));
|
|
21
|
-
const { extension, part, version, build } = options;
|
|
22
|
-
const uptimeSeconds = Math.max(0, Math.floor((now.getTime() -
|
|
23
|
-
const data = { status, extension, part, version, ...build, instance
|
|
36
|
+
const { extension, part, version, build, instance } = options;
|
|
37
|
+
const uptimeSeconds = Math.max(0, Math.floor((now.getTime() - options.startedAt) / 1000));
|
|
38
|
+
const data = { status, extension, part, version, ...build, instance };
|
|
24
39
|
return {
|
|
25
40
|
success: status !== 'unavailable',
|
|
26
41
|
data: { ...data, uptimeSeconds, checkedAt: isoSeconds(now), checks },
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
|
|
2
|
+
declare const HealthStatusSchema: z.ZodEnum<{
|
|
3
3
|
ok: "ok";
|
|
4
4
|
degraded: "degraded";
|
|
5
5
|
unavailable: "unavailable";
|
|
6
6
|
}>;
|
|
7
|
-
export declare const DeployedPartKindSchema: z.ZodEnum<{
|
|
8
|
-
pages: "pages";
|
|
9
|
-
ui: "ui";
|
|
10
|
-
api: "api";
|
|
11
|
-
mcp: "mcp";
|
|
12
|
-
worker: "worker";
|
|
13
|
-
store: "store";
|
|
14
|
-
}>;
|
|
15
7
|
export declare const HealthSchema: z.ZodObject<{
|
|
16
8
|
success: z.ZodBoolean;
|
|
17
9
|
data: z.ZodObject<{
|
|
@@ -44,3 +36,4 @@ export declare const HealthSchema: z.ZodObject<{
|
|
|
44
36
|
}, z.core.$strict>;
|
|
45
37
|
export type HealthStatus = z.infer<typeof HealthStatusSchema>;
|
|
46
38
|
export type HealthBody = z.infer<typeof HealthSchema>;
|
|
39
|
+
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { ExtensionIdSchema, VersionSchema } from '../manifest/primitives.schema.js';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
const HealthStatusSchema = z.enum(['ok', 'degraded', 'unavailable']);
|
|
4
|
+
const DeployedPartKindSchema = z.enum(['pages', 'ui', 'api', 'mcp', 'worker', 'store']);
|
|
5
5
|
// GET /health of every part container (api-standards section 7); never hostnames, IPs or env values.
|
|
6
6
|
export const HealthSchema = z.strictObject({
|
|
7
7
|
success: z.boolean(),
|
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { currentAgcid } from './agcid/scope.js';
|
|
3
|
-
export { createContext, createTestContext, type ExtensionInfo, type LogEntry, type PartContext, type ServerPartKind, type SettingValue, type TestContext, type TestContextOptions, } from './context/context.js';
|
|
4
|
-
export { DEFAULT_RECORD_FILE, discoveryDocument, loadRecord, recordOf, validateRecord, type BuildInfo, type PartImages, } from './discovery/discovery.js';
|
|
1
|
+
export { createTestContext, type ExtensionInfo, type LogEntry, type PartContext, type ServerPartKind, type SettingValue, type TestContext, type TestContextOptions, } from './context/context.js';
|
|
5
2
|
export { DiscoveryDocumentSchema, ExtensionRecordSchema, type DiscoveryDocument, type ExtensionRecord, } from './discovery/discovery.schema.js';
|
|
6
3
|
export { ERROR_CODES, ERROR_STATUS, type ErrorCode } from './envelope/codes.js';
|
|
7
|
-
export {
|
|
4
|
+
export { fail, isFailure, ok, type ErrorDetail, type ErrorEnvelope, type Failure, type FailOptions, type OkEnvelope, } from './envelope/envelope.js';
|
|
8
5
|
export { ErrorEnvelopeSchema, OkEnvelopeSchema } from './envelope/envelope.schema.js';
|
|
9
6
|
export { parse, type ParseSchema } from './envelope/parse.js';
|
|
10
|
-
export { createHealthHandler, type HealthCheck, type HealthOptions } from './health/health.js';
|
|
11
7
|
export { HealthSchema, type HealthBody, type HealthStatus } from './health/health.schema.js';
|
|
12
|
-
export {
|
|
8
|
+
export { type Log, type LogFields } from './log/log.js';
|
|
13
9
|
export { PackageSchema, type ExtensionPackage } from './manifest/manifest.schema.js';
|
|
14
|
-
export {
|
|
10
|
+
export { imageParts } from './manifest/parts.js';
|
|
15
11
|
export { readManifest, validateManifest } from './manifest/read-manifest.js';
|
|
16
|
-
export { createApiApp, PUBLIC_CORS, type ApiAppOptions } from './server/app.js';
|
|
17
|
-
export { serve, type ServeOptions } from './server/serve.js';
|
|
18
|
-
export { startTestServer, type TestServer, type TestServerOptions } from './testing/test-server.js';
|
|
19
12
|
export { type ApiCreate, type ApiHandler, type ApiPart } from './server/part.types.js';
|
|
13
|
+
export { startTestServer, type TestServer, type TestServerOptions } from './testing/test-server.js';
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { currentAgcid } from './agcid/scope.js';
|
|
3
|
-
export { createContext, createTestContext, } from './context/context.js';
|
|
4
|
-
export { DEFAULT_RECORD_FILE, discoveryDocument, loadRecord, recordOf, validateRecord, } from './discovery/discovery.js';
|
|
1
|
+
export { createTestContext, } from './context/context.js';
|
|
5
2
|
export { DiscoveryDocumentSchema, ExtensionRecordSchema, } from './discovery/discovery.schema.js';
|
|
6
3
|
export { ERROR_CODES, ERROR_STATUS } from './envelope/codes.js';
|
|
7
|
-
export {
|
|
4
|
+
export { fail, isFailure, ok, } from './envelope/envelope.js';
|
|
8
5
|
export { ErrorEnvelopeSchema, OkEnvelopeSchema } from './envelope/envelope.schema.js';
|
|
9
6
|
export { parse } from './envelope/parse.js';
|
|
10
|
-
export { createHealthHandler } from './health/health.js';
|
|
11
7
|
export { HealthSchema } from './health/health.schema.js';
|
|
12
|
-
export {
|
|
8
|
+
export {} from './log/log.js';
|
|
13
9
|
export { PackageSchema } from './manifest/manifest.schema.js';
|
|
14
|
-
export {
|
|
10
|
+
export { imageParts } from './manifest/parts.js';
|
|
15
11
|
export { readManifest, validateManifest } from './manifest/read-manifest.js';
|
|
16
|
-
export { createApiApp, PUBLIC_CORS } from './server/app.js';
|
|
17
|
-
export { serve } from './server/serve.js';
|
|
18
|
-
export { startTestServer } from './testing/test-server.js';
|
|
19
12
|
export {} from './server/part.types.js';
|
|
13
|
+
export { startTestServer } from './testing/test-server.js';
|
package/dist/log/log.d.ts
CHANGED
package/dist/log/log.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import { currentAgcid } from '../agcid/scope.js';
|
|
2
|
+
export const messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
2
3
|
const errorFields = (error) => {
|
|
3
4
|
if (!(error instanceof Error))
|
|
4
5
|
return { err: { message: String(error) } };
|
|
5
6
|
return { err: { type: error.name, message: error.message, stack: error.stack } };
|
|
6
7
|
};
|
|
8
|
+
// Spread twice: the key order stays, and a field never overwrites time, level, msg, tags or agcid.
|
|
7
9
|
const line = (level, message, tags, fields) => {
|
|
8
10
|
const agcid = currentAgcid();
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
+
const own = { time: new Date().toISOString(), level, msg: message, ...tags };
|
|
12
|
+
const fixed = agcid ? { ...own, agcid } : own;
|
|
13
|
+
return `${JSON.stringify({ ...fixed, ...fields, ...fixed })}\n`;
|
|
11
14
|
};
|
|
12
15
|
const stderrWrite = (text) => void process.stderr.write(text);
|
|
13
|
-
// JSON lines on stderr, tagged with extension, part and the request's agcid.
|
|
14
16
|
export const createLog = (tags, write = stderrWrite) => ({
|
|
15
17
|
info: (message, fields = {}) => write(line('info', message, tags, fields)),
|
|
16
18
|
warn: (message, fields = {}) => write(line('warn', message, tags, fields)),
|
|
17
|
-
error: (error, fields = {}) => {
|
|
18
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
19
|
-
write(line('error', message, tags, { ...errorFields(error), ...fields }));
|
|
20
|
-
},
|
|
19
|
+
error: (error, fields = {}) => write(line('error', messageOf(error), tags, { ...fields, ...errorFields(error) })),
|
|
21
20
|
});
|
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
declare const CodePartSchema: z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
3
3
|
entry: z.ZodOptional<z.ZodString>;
|
|
4
4
|
}, z.core.$strict>]>;
|
|
5
|
-
|
|
5
|
+
declare const PartsSchema: z.ZodObject<{
|
|
6
6
|
pages: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
7
7
|
entry: z.ZodOptional<z.ZodString>;
|
|
8
8
|
}, z.core.$strict>]>>;
|
|
@@ -33,93 +33,6 @@ export declare const PartsSchema: z.ZodObject<{
|
|
|
33
33
|
}>;
|
|
34
34
|
}, z.core.$strict>]>>;
|
|
35
35
|
}, z.core.$strict>;
|
|
36
|
-
export declare const CommandSchema: z.ZodObject<{
|
|
37
|
-
name: z.ZodString;
|
|
38
|
-
summary: z.ZodString;
|
|
39
|
-
args: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
40
|
-
name: z.ZodString;
|
|
41
|
-
type: z.ZodEnum<{
|
|
42
|
-
string: "string";
|
|
43
|
-
number: "number";
|
|
44
|
-
boolean: "boolean";
|
|
45
|
-
integer: "integer";
|
|
46
|
-
}>;
|
|
47
|
-
required: z.ZodOptional<z.ZodBoolean>;
|
|
48
|
-
description: z.ZodOptional<z.ZodString>;
|
|
49
|
-
}, z.core.$strict>>>;
|
|
50
|
-
}, z.core.$strict>;
|
|
51
|
-
export declare const SettingSchema: z.ZodObject<{
|
|
52
|
-
type: z.ZodEnum<{
|
|
53
|
-
string: "string";
|
|
54
|
-
number: "number";
|
|
55
|
-
boolean: "boolean";
|
|
56
|
-
integer: "integer";
|
|
57
|
-
}>;
|
|
58
|
-
default: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
59
|
-
enum: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
60
|
-
description: z.ZodOptional<z.ZodString>;
|
|
61
|
-
}, z.core.$strict>;
|
|
62
|
-
export declare const AgentageSchema: z.ZodObject<{
|
|
63
|
-
id: z.ZodString;
|
|
64
|
-
parts: z.ZodObject<{
|
|
65
|
-
pages: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
66
|
-
entry: z.ZodOptional<z.ZodString>;
|
|
67
|
-
}, z.core.$strict>]>>;
|
|
68
|
-
ui: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
69
|
-
entry: z.ZodOptional<z.ZodString>;
|
|
70
|
-
}, z.core.$strict>]>>;
|
|
71
|
-
api: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
72
|
-
entry: z.ZodOptional<z.ZodString>;
|
|
73
|
-
}, z.core.$strict>]>>;
|
|
74
|
-
mcp: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
75
|
-
entry: z.ZodOptional<z.ZodString>;
|
|
76
|
-
}, z.core.$strict>]>>;
|
|
77
|
-
worker: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
78
|
-
entry: z.ZodOptional<z.ZodString>;
|
|
79
|
-
}, z.core.$strict>]>>;
|
|
80
|
-
cli: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
81
|
-
entry: z.ZodOptional<z.ZodString>;
|
|
82
|
-
}, z.core.$strict>]>>;
|
|
83
|
-
store: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
84
|
-
postgres: "postgres";
|
|
85
|
-
git: "git";
|
|
86
|
-
signoz: "signoz";
|
|
87
|
-
}>, z.ZodObject<{
|
|
88
|
-
kind: z.ZodEnum<{
|
|
89
|
-
postgres: "postgres";
|
|
90
|
-
git: "git";
|
|
91
|
-
signoz: "signoz";
|
|
92
|
-
}>;
|
|
93
|
-
}, z.core.$strict>]>>;
|
|
94
|
-
}, z.core.$strict>;
|
|
95
|
-
commands: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
96
|
-
name: z.ZodString;
|
|
97
|
-
summary: z.ZodString;
|
|
98
|
-
args: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
99
|
-
name: z.ZodString;
|
|
100
|
-
type: z.ZodEnum<{
|
|
101
|
-
string: "string";
|
|
102
|
-
number: "number";
|
|
103
|
-
boolean: "boolean";
|
|
104
|
-
integer: "integer";
|
|
105
|
-
}>;
|
|
106
|
-
required: z.ZodOptional<z.ZodBoolean>;
|
|
107
|
-
description: z.ZodOptional<z.ZodString>;
|
|
108
|
-
}, z.core.$strict>>>;
|
|
109
|
-
}, z.core.$strict>>>;
|
|
110
|
-
settings: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
111
|
-
type: z.ZodEnum<{
|
|
112
|
-
string: "string";
|
|
113
|
-
number: "number";
|
|
114
|
-
boolean: "boolean";
|
|
115
|
-
integer: "integer";
|
|
116
|
-
}>;
|
|
117
|
-
default: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
118
|
-
enum: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
119
|
-
description: z.ZodOptional<z.ZodString>;
|
|
120
|
-
}, z.core.$strict>>>;
|
|
121
|
-
secrets: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
122
|
-
}, z.core.$strict>;
|
|
123
36
|
export declare const PackageSchema: z.ZodObject<{
|
|
124
37
|
name: z.ZodString;
|
|
125
38
|
version: z.ZodString;
|
|
@@ -190,6 +103,5 @@ export declare const PackageSchema: z.ZodObject<{
|
|
|
190
103
|
}, z.core.$loose>;
|
|
191
104
|
export type CodePart = z.infer<typeof CodePartSchema>;
|
|
192
105
|
export type Parts = z.infer<typeof PartsSchema>;
|
|
193
|
-
export type Agentage = z.infer<typeof AgentageSchema>;
|
|
194
106
|
export type ExtensionPackage = z.infer<typeof PackageSchema>;
|
|
195
107
|
export {};
|
|
@@ -6,7 +6,7 @@ const CodePartSchema = z.union([
|
|
|
6
6
|
]);
|
|
7
7
|
const StorePartSchema = z.union([StoreKindSchema, z.strictObject({ kind: StoreKindSchema })]);
|
|
8
8
|
const ROUTED = ['pages', 'ui', 'api', 'mcp'];
|
|
9
|
-
|
|
9
|
+
const PartsSchema = z
|
|
10
10
|
.strictObject({
|
|
11
11
|
pages: CodePartSchema.optional(),
|
|
12
12
|
ui: CodePartSchema.optional(),
|
|
@@ -20,7 +20,7 @@ export const PartsSchema = z
|
|
|
20
20
|
message: 'at least one routed part (pages, ui, api or mcp)',
|
|
21
21
|
});
|
|
22
22
|
const ArgTypeSchema = z.enum(['string', 'integer', 'number', 'boolean']);
|
|
23
|
-
|
|
23
|
+
const CommandSchema = z.strictObject({
|
|
24
24
|
name: CommandNameSchema,
|
|
25
25
|
summary: z.string().min(1).max(120),
|
|
26
26
|
args: z
|
|
@@ -32,7 +32,7 @@ export const CommandSchema = z.strictObject({
|
|
|
32
32
|
}))
|
|
33
33
|
.optional(),
|
|
34
34
|
});
|
|
35
|
-
|
|
35
|
+
const SettingSchema = z
|
|
36
36
|
.strictObject({
|
|
37
37
|
type: ArgTypeSchema,
|
|
38
38
|
default: z.union([z.string(), z.number(), z.boolean()]),
|
|
@@ -41,6 +41,9 @@ export const SettingSchema = z
|
|
|
41
41
|
})
|
|
42
42
|
.refine((setting) => defaultFits(setting.type, setting.default), {
|
|
43
43
|
message: 'default does not match type',
|
|
44
|
+
})
|
|
45
|
+
.refine((setting) => !setting.enum || setting.enum.includes(String(setting.default)), {
|
|
46
|
+
message: 'default is not one of enum',
|
|
44
47
|
});
|
|
45
48
|
const AgentageBaseSchema = z.strictObject({
|
|
46
49
|
id: ExtensionIdSchema,
|
|
@@ -49,7 +52,7 @@ const AgentageBaseSchema = z.strictObject({
|
|
|
49
52
|
settings: z.record(SettingKeySchema, SettingSchema).optional(),
|
|
50
53
|
secrets: z.array(SecretNameSchema).optional(),
|
|
51
54
|
});
|
|
52
|
-
|
|
55
|
+
const AgentageSchema = AgentageBaseSchema.superRefine(crossFieldRules);
|
|
53
56
|
export const PackageSchema = z
|
|
54
57
|
.looseObject({
|
|
55
58
|
name: PackageNameSchema,
|
|
@@ -13,6 +13,7 @@ export declare const StoreKindSchema: z.ZodEnum<{
|
|
|
13
13
|
git: "git";
|
|
14
14
|
signoz: "signoz";
|
|
15
15
|
}>;
|
|
16
|
+
export declare const FULL_SHA: RegExp;
|
|
16
17
|
export declare const CommitSchema: z.ZodString;
|
|
17
18
|
export declare const PrefixedToolNameSchema: z.ZodString;
|
|
18
19
|
export declare const IMAGE_PART_KINDS: readonly ["pages", "ui", "api", "mcp", "worker"];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
// Platform names and cli verbs, plus Windows device names: an id is a folder name on machines.
|
|
2
3
|
const RESERVED_IDS = 'auth|www|api|sync|mail|mcp|ui|git|setup|status|update|extension|add|remove|help|con|prn|aux|nul|com[0-9]|lpt[0-9]';
|
|
3
4
|
export const ExtensionIdSchema = z
|
|
4
5
|
.string()
|
|
@@ -44,7 +45,8 @@ export const PackagePathSchema = z
|
|
|
44
45
|
.max(256)
|
|
45
46
|
.regex(/^\.\/[A-Za-z0-9._/-]+$/);
|
|
46
47
|
export const StoreKindSchema = z.enum(['postgres', 'git', 'signoz']);
|
|
47
|
-
export const
|
|
48
|
+
export const FULL_SHA = /^[0-9a-f]{40}$/;
|
|
49
|
+
export const CommitSchema = z.string().regex(FULL_SHA, { message: 'full 40-hex git sha' });
|
|
48
50
|
export const PrefixedToolNameSchema = z
|
|
49
51
|
.string()
|
|
50
52
|
.max(64)
|
|
@@ -2,4 +2,5 @@ import { type z } from 'zod';
|
|
|
2
2
|
import { type ExtensionPackage } from './manifest.schema.js';
|
|
3
3
|
export declare const formatIssues: (issues: readonly z.core.$ZodIssue[]) => string;
|
|
4
4
|
export declare const validateManifest: (value: unknown) => ExtensionPackage;
|
|
5
|
+
export declare const readJsonIfExists: (file: string) => Promise<unknown>;
|
|
5
6
|
export declare const readManifest: (dir: string) => Promise<ExtensionPackage>;
|
|
@@ -11,9 +11,32 @@ export const validateManifest = (value) => {
|
|
|
11
11
|
return result.data;
|
|
12
12
|
throw new Error(`invalid agentage manifest in package.json: ${formatIssues(result.error.issues)}`);
|
|
13
13
|
};
|
|
14
|
+
const parseJson = (text, file) => {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(text);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
throw new Error(`${file} is not valid JSON: ${err.message}`);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
// undefined only for a missing file (JSON never parses to it); any other read error is thrown.
|
|
23
|
+
export const readJsonIfExists = async (file) => {
|
|
24
|
+
let text;
|
|
25
|
+
try {
|
|
26
|
+
text = await readFile(file, 'utf8');
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
if (err.code === 'ENOENT')
|
|
30
|
+
return undefined;
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
return parseJson(text, file);
|
|
34
|
+
};
|
|
14
35
|
// The manifest is the `agentage` block of package.json, read as data without running code.
|
|
15
36
|
export const readManifest = async (dir) => {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
37
|
+
const pkg = await readJsonIfExists(join(dir, 'package.json'));
|
|
38
|
+
if (pkg === undefined) {
|
|
39
|
+
throw new Error(`no package.json in ${dir}: run it in an extension package folder`);
|
|
40
|
+
}
|
|
41
|
+
return validateManifest(pkg);
|
|
19
42
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type BuildInfo } from '../discovery/discovery.js';
|
|
2
2
|
export type Env = Readonly<Record<string, string | undefined>>;
|
|
3
|
+
export declare const isoSeconds: (date: Date) => string;
|
|
3
4
|
export declare const buildInfoFromEnv: (env: Env, now?: Date) => BuildInfo;
|
|
4
5
|
export declare const portFromEnv: (env: Env, fallback?: number) => number;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {} from '../discovery/discovery.js';
|
|
2
|
+
import { FULL_SHA } from '../manifest/primitives.schema.js';
|
|
2
3
|
const UNKNOWN_COMMIT = '0'.repeat(40);
|
|
3
|
-
const isoSeconds = (date) => `${date.toISOString().slice(0, 19)}Z`;
|
|
4
|
+
export const isoSeconds = (date) => `${date.toISOString().slice(0, 19)}Z`;
|
|
5
|
+
const commitOf = (sha = '') => (FULL_SHA.test(sha) ? sha : UNKNOWN_COMMIT);
|
|
4
6
|
// COMMIT_SHA and BUILD_TIME are baked into every image by the recipe; a bare dev run shows zeros.
|
|
5
7
|
export const buildInfoFromEnv = (env, now = new Date()) => ({
|
|
6
|
-
commit:
|
|
7
|
-
? env['COMMIT_SHA']
|
|
8
|
-
: UNKNOWN_COMMIT,
|
|
8
|
+
commit: commitOf(env['COMMIT_SHA']),
|
|
9
9
|
buildTime: env['BUILD_TIME'] || isoSeconds(now),
|
|
10
10
|
});
|
|
11
11
|
export const portFromEnv = (env, fallback = 3000) => {
|
package/dist/runtime/main.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createLog } from '../log/log.js';
|
|
2
3
|
import { runPart } from './run-part.js';
|
|
3
4
|
// Image entry: `node node_modules/@agentage/extension/dist/runtime/main.js api` in /app.
|
|
5
|
+
const part = process.argv[2] ?? 'api';
|
|
4
6
|
try {
|
|
5
|
-
await runPart({ dir: process.cwd(), part
|
|
7
|
+
await runPart({ dir: process.cwd(), part, env: process.env });
|
|
6
8
|
}
|
|
7
9
|
catch (err) {
|
|
8
|
-
|
|
10
|
+
// The manifest may be what failed, so the id is not known here; the line keeps the stack.
|
|
11
|
+
createLog({ extension: 'unknown', part }).error(err);
|
|
9
12
|
process.exit(1);
|
|
10
13
|
}
|
package/dist/runtime/run-part.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createContext } from '../context/context.js';
|
|
|
5
5
|
import { DEFAULT_RECORD_FILE, loadRecord } from '../discovery/discovery.js';
|
|
6
6
|
import { createLog } from '../log/log.js';
|
|
7
7
|
import { entryOf } from '../manifest/parts.js';
|
|
8
|
+
import {} from '../manifest/manifest.schema.js';
|
|
8
9
|
import { readManifest } from '../manifest/read-manifest.js';
|
|
9
10
|
import { createApiApp } from '../server/app.js';
|
|
10
11
|
import {} from '../server/part.types.js';
|
|
@@ -24,7 +25,12 @@ const recordFor = async (env, log) => {
|
|
|
24
25
|
log.warn('no release record mounted; discovery lists this image by tag', { file });
|
|
25
26
|
return record;
|
|
26
27
|
};
|
|
27
|
-
|
|
28
|
+
const createRouter = async (options, ctx, pkg) => {
|
|
29
|
+
const file = join(options.dir, entryOf(pkg, 'api'));
|
|
30
|
+
const mod = await (options.importModule ?? importFile)(file);
|
|
31
|
+
return createOf(mod, file)(ctx);
|
|
32
|
+
};
|
|
33
|
+
const contextFor = (pkg, origin, port, log) => createContext({ pkg, part: 'api', origin: origin ?? `http://127.0.0.1:${port}`, log });
|
|
28
34
|
export const runPart = async (options) => {
|
|
29
35
|
if (options.part !== 'api')
|
|
30
36
|
throw new Error(`part ${options.part} is not served by kit 0.1`);
|
|
@@ -33,25 +39,10 @@ export const runPart = async (options) => {
|
|
|
33
39
|
const log = createLog({ extension: pkg.agentage.id, part: 'api' });
|
|
34
40
|
const port = portFromEnv(env);
|
|
35
41
|
const origin = env['AGENTAGE_ORIGIN'] || undefined;
|
|
36
|
-
const
|
|
37
|
-
pkg,
|
|
38
|
-
part: 'api',
|
|
39
|
-
origin: origin ?? `http://127.0.0.1:${port}`,
|
|
40
|
-
log,
|
|
41
|
-
});
|
|
42
|
-
const file = join(options.dir, entryOf(pkg, 'api'));
|
|
43
|
-
const mod = await (options.importModule ?? importFile)(file);
|
|
44
|
-
const router = await createOf(mod, file)(ctx);
|
|
42
|
+
const router = await createRouter(options, contextFor(pkg, origin, port, log), pkg);
|
|
45
43
|
const build = buildInfoFromEnv(env);
|
|
46
44
|
const record = await recordFor(env, log);
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
build,
|
|
50
|
-
router,
|
|
51
|
-
log,
|
|
52
|
-
record,
|
|
53
|
-
origin,
|
|
54
|
-
ownImage: env['AGENTAGE_IMAGE'],
|
|
55
|
-
});
|
|
45
|
+
const ownImage = env['AGENTAGE_IMAGE'];
|
|
46
|
+
const app = createApiApp({ pkg, build, router, log, record, origin, ownImage });
|
|
56
47
|
return serve({ app, port, log, ...options.serve });
|
|
57
48
|
};
|
package/dist/server/app.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type CorsOptions } from 'cors';
|
|
2
1
|
import { type Express } from 'express';
|
|
3
2
|
import { type BuildInfo } from '../discovery/discovery.js';
|
|
4
3
|
import { type ExtensionRecord } from '../discovery/discovery.schema.js';
|
|
@@ -14,5 +13,4 @@ export interface ApiAppOptions {
|
|
|
14
13
|
ownImage?: string;
|
|
15
14
|
origin?: string;
|
|
16
15
|
}
|
|
17
|
-
export declare const PUBLIC_CORS: CorsOptions;
|
|
18
16
|
export declare const createApiApp: (options: ApiAppOptions) => Express;
|
package/dist/server/app.js
CHANGED
|
@@ -10,7 +10,7 @@ import {} from '../manifest/manifest.schema.js';
|
|
|
10
10
|
import { errorHandler, notFound } from './errors.js';
|
|
11
11
|
import {} from './part.types.js';
|
|
12
12
|
// api-standards section 6: CORS * without credentials on /.well-known/ and /api/.
|
|
13
|
-
|
|
13
|
+
const PUBLIC_CORS = {
|
|
14
14
|
origin: '*',
|
|
15
15
|
credentials: false,
|
|
16
16
|
allowedHeaders: ['Authorization', 'Content-Type', 'agcid', 'traceparent', 'If-Match'],
|
|
@@ -31,10 +31,9 @@ const healthHandler = (options) => createHealthHandler({
|
|
|
31
31
|
version: options.pkg.version,
|
|
32
32
|
build: options.build,
|
|
33
33
|
});
|
|
34
|
-
// The kit owns the app: agcid, helmet, CORS, /health, discovery, the body limit, 404 and errors.
|
|
35
34
|
export const createApiApp = (options) => {
|
|
36
35
|
const app = express();
|
|
37
|
-
|
|
36
|
+
// The gateway calls parts over a private network; its X-Forwarded-Proto then sets req.protocol.
|
|
38
37
|
app.set('trust proxy', 'loopback, linklocal, uniquelocal');
|
|
39
38
|
app.use(agcidMiddleware, helmet());
|
|
40
39
|
app.get('/health', healthHandler(options));
|
package/dist/server/errors.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { type ErrorRequestHandler, type RequestHandler } from 'express';
|
|
2
|
-
import { type Failure } from '../envelope/envelope.js';
|
|
3
2
|
import { type Log } from '../log/log.js';
|
|
4
|
-
export declare const toFailure: (err: unknown) => Failure;
|
|
5
3
|
export declare const notFound: RequestHandler;
|
|
6
4
|
export declare const errorHandler: (log: Log) => ErrorRequestHandler;
|
package/dist/server/errors.js
CHANGED
|
@@ -11,7 +11,7 @@ const bodyParserFailure = (err) => {
|
|
|
11
11
|
return null;
|
|
12
12
|
};
|
|
13
13
|
// Anything that is not a kit failure is an INTERNAL 500 with a generic message; the log has it whole.
|
|
14
|
-
|
|
14
|
+
const toFailure = (err) => {
|
|
15
15
|
if (isFailure(err))
|
|
16
16
|
return err;
|
|
17
17
|
return bodyParserFailure(err) ?? fail('INTERNAL', 'Internal error');
|
package/dist/server/serve.d.ts
CHANGED
package/dist/server/serve.js
CHANGED
|
@@ -2,6 +2,7 @@ import {} from 'node:http';
|
|
|
2
2
|
import {} from 'node:net';
|
|
3
3
|
import {} from 'express';
|
|
4
4
|
import {} from '../log/log.js';
|
|
5
|
+
const GRACE_MS = 10_000;
|
|
5
6
|
export const listen = (app, port, host) => new Promise((resolve, reject) => {
|
|
6
7
|
const done = (err) => (err ? reject(err) : resolve(server));
|
|
7
8
|
const server = host === undefined ? app.listen(port, done) : app.listen(port, host, done);
|
|
@@ -13,7 +14,7 @@ export const shutdownOn = (server, options) => {
|
|
|
13
14
|
options.log.info('shutting down');
|
|
14
15
|
server.close(() => exit(0));
|
|
15
16
|
server.closeIdleConnections();
|
|
16
|
-
setTimeout(() => exit(0),
|
|
17
|
+
setTimeout(() => exit(0), GRACE_MS).unref();
|
|
17
18
|
};
|
|
18
19
|
for (const signal of options.signals ?? ['SIGTERM', 'SIGINT'])
|
|
19
20
|
process.once(signal, stop);
|
package/dist/tool/build-info.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {} from '../discovery/discovery.js';
|
|
2
|
-
import {} from '../
|
|
2
|
+
import { FULL_SHA } from '../manifest/primitives.schema.js';
|
|
3
|
+
import { isoSeconds } from '../runtime/build-info.js';
|
|
3
4
|
import { mustRun } from './exec.js';
|
|
4
|
-
const FULL_SHA = /^[0-9a-f]{40}$/;
|
|
5
|
-
const isoSeconds = (date) => `${date.toISOString().slice(0, 19)}Z`;
|
|
6
5
|
const commitOf = async (dir, env, run) => {
|
|
7
6
|
const given = env['COMMIT_SHA'] || env['GITHUB_SHA'];
|
|
8
7
|
if (given)
|
package/dist/tool/build.d.ts
CHANGED
|
@@ -3,14 +3,11 @@ import { type ImagePartKind } from '../manifest/primitives.schema.js';
|
|
|
3
3
|
import { type Env } from '../runtime/build-info.js';
|
|
4
4
|
import { type Docker } from './docker.js';
|
|
5
5
|
import { type Run } from './exec.js';
|
|
6
|
-
export declare const DEFAULT_REGISTRY = "ghcr.io/agentage";
|
|
7
|
-
export declare const SUPPORTED_PARTS: readonly ImagePartKind[];
|
|
8
6
|
export interface BuildOptions {
|
|
9
7
|
dir?: string;
|
|
10
8
|
push?: boolean;
|
|
11
9
|
registry?: string;
|
|
12
10
|
env?: Env;
|
|
13
|
-
now?: Date;
|
|
14
11
|
}
|
|
15
12
|
export interface BuildDeps {
|
|
16
13
|
run: Run;
|
package/dist/tool/build.js
CHANGED
|
@@ -11,8 +11,8 @@ import { createDocker } from './docker.js';
|
|
|
11
11
|
import { mustRun, runCommand } from './exec.js';
|
|
12
12
|
import { labelsOf } from './labels.js';
|
|
13
13
|
import { RECORD_FILE, stageContext } from './stage.js';
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const DEFAULT_REGISTRY = 'ghcr.io/agentage';
|
|
15
|
+
const SUPPORTED_PARTS = ['api'];
|
|
16
16
|
export const imageTag = (registry, pkg, part) => `${registry}/${pkg.agentage.id}-${part}:${pkg.version}`;
|
|
17
17
|
const partsToBuild = (pkg) => {
|
|
18
18
|
const parts = imageParts(pkg);
|
|
@@ -58,25 +58,24 @@ const writeRecord = async (dir, pkg, info, images) => {
|
|
|
58
58
|
await writeFile(file, `${JSON.stringify(record, null, 2)}\n`);
|
|
59
59
|
return file;
|
|
60
60
|
};
|
|
61
|
-
const buildParts = async (base, options, deps) => {
|
|
61
|
+
const buildParts = async (base, parts, options, deps) => {
|
|
62
62
|
const docker = deps.docker ?? createDocker(deps.run);
|
|
63
63
|
const registry = options.registry ?? DEFAULT_REGISTRY;
|
|
64
64
|
const images = [];
|
|
65
|
-
for (const part of
|
|
65
|
+
for (const part of parts) {
|
|
66
66
|
const job = { ...base, part, tag: imageTag(registry, base.pkg, part) };
|
|
67
67
|
await buildImage(job, docker, deps.tmpRoot);
|
|
68
68
|
images.push(options.push ? await pushImage(job, docker) : { part, tag: job.tag });
|
|
69
69
|
}
|
|
70
70
|
return images;
|
|
71
71
|
};
|
|
72
|
-
// One image per server part from the kit recipe; with push, the digests go into the build record.
|
|
73
72
|
export const build = async (options = {}, deps = { run: runCommand }) => {
|
|
74
73
|
const dir = resolve(options.dir ?? '.');
|
|
75
74
|
const pkg = await readManifest(dir);
|
|
76
|
-
partsToBuild(pkg);
|
|
77
|
-
const info = await resolveBuildInfo(dir, options.env ?? process.env, deps.run
|
|
75
|
+
const parts = partsToBuild(pkg);
|
|
76
|
+
const info = await resolveBuildInfo(dir, options.env ?? process.env, deps.run);
|
|
78
77
|
await compile(dir, pkg, deps.run);
|
|
79
|
-
const images = await buildParts({ dir, pkg, info }, options, deps);
|
|
78
|
+
const images = await buildParts({ dir, pkg, info }, parts, options, deps);
|
|
80
79
|
const recordFile = options.push ? await writeRecord(dir, pkg, info, images) : undefined;
|
|
81
80
|
const result = { id: pkg.agentage.id, version: pkg.version, ...info, images };
|
|
82
81
|
return recordFile ? { ...result, recordFile } : result;
|
package/dist/tool/docker.d.ts
CHANGED
|
@@ -9,7 +9,6 @@ export interface Docker {
|
|
|
9
9
|
build(options: DockerBuild): Promise<void>;
|
|
10
10
|
push(tag: string): Promise<string>;
|
|
11
11
|
}
|
|
12
|
-
export declare const PLATFORM = "linux/amd64";
|
|
13
12
|
export declare const buildArgsOf: (options: DockerBuild) => string[];
|
|
14
13
|
export declare const digestOf: (pushOutput: string) => string;
|
|
15
14
|
export declare const createDocker: (run: Run) => Docker;
|
package/dist/tool/docker.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mustRun } from './exec.js';
|
|
2
|
-
|
|
2
|
+
const PLATFORM = 'linux/amd64';
|
|
3
3
|
const flags = (flag, values) => Object.entries(values).flatMap(([key, value]) => [flag, `${key}=${value}`]);
|
|
4
|
+
// No provenance attestation: the pushed digest is then the image itself, as the record expects.
|
|
4
5
|
export const buildArgsOf = (options) => [
|
|
5
6
|
'build',
|
|
6
7
|
'--platform',
|
package/dist/tool/exec.d.ts
CHANGED
package/dist/tool/exec.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
// stdout is captured (and echoed to stderr with `echo`); stderr streams through as progress.
|
|
3
3
|
export const runCommand = (command, args, options = {}) => new Promise((resolve, reject) => {
|
|
4
|
-
const child = spawn(command, args, {
|
|
5
|
-
cwd: options.cwd,
|
|
6
|
-
env: options.env ?? process.env,
|
|
7
|
-
stdio: ['ignore', 'pipe', 'inherit'],
|
|
8
|
-
});
|
|
4
|
+
const child = spawn(command, args, { cwd: options.cwd, stdio: ['ignore', 'pipe', 'inherit'] });
|
|
9
5
|
let stdout = '';
|
|
10
6
|
child.stdout.on('data', (chunk) => {
|
|
11
7
|
stdout += chunk.toString();
|
|
12
8
|
if (options.echo)
|
|
13
9
|
process.stderr.write(chunk);
|
|
14
10
|
});
|
|
15
|
-
child.on('error', reject);
|
|
11
|
+
child.on('error', (err) => reject(err.code === 'ENOENT' ? new Error(`${command} is not installed or not on PATH`) : err));
|
|
16
12
|
child.on('close', (code) => resolve({ code: code ?? 1, stdout }));
|
|
17
13
|
});
|
|
18
14
|
export const mustRun = async (run, command, args, options) => {
|
package/dist/tool/index.d.ts
CHANGED
|
@@ -1,11 +1,2 @@
|
|
|
1
|
-
export { build,
|
|
2
|
-
export { resolveBuildInfo } from './build-info.js';
|
|
3
|
-
export { createDocker, PLATFORM, type Docker, type DockerBuild } from './docker.js';
|
|
4
|
-
export { runCommand, type Run, type RunOptions, type RunResult } from './exec.js';
|
|
1
|
+
export { build, type BuildDeps, type BuildOptions, type BuildResult, type BuiltImage, } from './build.js';
|
|
5
2
|
export { buildLines } from './format.js';
|
|
6
|
-
export { labelsOf } from './labels.js';
|
|
7
|
-
export { main as runTool, USAGE as TOOL_USAGE } from './main.js';
|
|
8
|
-
export { PART_PORT, serverPartRecipe } from './recipe.js';
|
|
9
|
-
export { RECORD_FILE, stageContext } from './stage.js';
|
|
10
|
-
export { readManifest, validateManifest } from '../manifest/read-manifest.js';
|
|
11
|
-
export { type ExtensionPackage } from '../manifest/manifest.schema.js';
|
package/dist/tool/index.js
CHANGED
|
@@ -1,11 +1,2 @@
|
|
|
1
|
-
export { build,
|
|
2
|
-
export { resolveBuildInfo } from './build-info.js';
|
|
3
|
-
export { createDocker, PLATFORM } from './docker.js';
|
|
4
|
-
export { runCommand } from './exec.js';
|
|
1
|
+
export { build, } from './build.js';
|
|
5
2
|
export { buildLines } from './format.js';
|
|
6
|
-
export { labelsOf } from './labels.js';
|
|
7
|
-
export { main as runTool, USAGE as TOOL_USAGE } from './main.js';
|
|
8
|
-
export { PART_PORT, serverPartRecipe } from './recipe.js';
|
|
9
|
-
export { RECORD_FILE, stageContext } from './stage.js';
|
|
10
|
-
export { readManifest, validateManifest } from '../manifest/read-manifest.js';
|
|
11
|
-
export {} from '../manifest/manifest.schema.js';
|
package/dist/tool/main.d.ts
CHANGED
|
@@ -4,5 +4,4 @@ export interface MainDeps {
|
|
|
4
4
|
out: (line: string) => void;
|
|
5
5
|
err: (line: string) => void;
|
|
6
6
|
}
|
|
7
|
-
export declare const USAGE = "usage: agentage-extension build [--push] [--dir <path>] [--registry <host/owner>]\n\n build one image per server part from the kit recipe\n --push push the images and write dist/agentage-extension.json with their digests\n --dir the extension package folder (default: the current folder)\n --registry image registry and owner (default: ghcr.io/agentage)";
|
|
8
7
|
export declare const main: (argv: string[], deps?: MainDeps) => Promise<number>;
|
package/dist/tool/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseArgs } from 'node:util';
|
|
2
2
|
import { build } from './build.js';
|
|
3
3
|
import { buildLines } from './format.js';
|
|
4
|
-
|
|
4
|
+
const USAGE = `usage: agentage-extension build [--push] [--dir <path>] [--registry <host/owner>]
|
|
5
5
|
|
|
6
6
|
build one image per server part from the kit recipe
|
|
7
7
|
--push push the images and write dist/agentage-extension.json with their digests
|
|
@@ -22,6 +22,17 @@ const parse = (argv) => parseArgs({
|
|
|
22
22
|
help: { type: 'boolean', short: 'h' },
|
|
23
23
|
},
|
|
24
24
|
});
|
|
25
|
+
const runBuild = async (options, deps) => {
|
|
26
|
+
try {
|
|
27
|
+
for (const line of buildLines(await deps.build(options)))
|
|
28
|
+
deps.out(line);
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
deps.err(`build failed: ${err.message}`);
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
25
36
|
// Exit codes: 0 ok, 1 the build failed, 2 usage.
|
|
26
37
|
export const main = async (argv, deps = defaultDeps) => {
|
|
27
38
|
let parsed;
|
|
@@ -33,22 +44,13 @@ export const main = async (argv, deps = defaultDeps) => {
|
|
|
33
44
|
return 2;
|
|
34
45
|
}
|
|
35
46
|
const { positionals, values } = parsed;
|
|
36
|
-
if (values.help)
|
|
37
|
-
|
|
38
|
-
if (positionals[0] !== 'build' || positionals.length !== 1)
|
|
39
|
-
return (deps.err(USAGE), 2);
|
|
40
|
-
try {
|
|
41
|
-
const result = await deps.build({
|
|
42
|
-
push: values.push,
|
|
43
|
-
dir: values.dir,
|
|
44
|
-
registry: values.registry,
|
|
45
|
-
});
|
|
46
|
-
for (const line of buildLines(result))
|
|
47
|
-
deps.out(line);
|
|
47
|
+
if (values.help) {
|
|
48
|
+
deps.out(USAGE);
|
|
48
49
|
return 0;
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
-
deps.err(
|
|
52
|
-
return
|
|
51
|
+
if (positionals[0] !== 'build' || positionals.length !== 1) {
|
|
52
|
+
deps.err(USAGE);
|
|
53
|
+
return 2;
|
|
53
54
|
}
|
|
55
|
+
return runBuild({ push: values.push, dir: values.dir, registry: values.registry }, deps);
|
|
54
56
|
};
|
package/dist/tool/recipe.d.ts
CHANGED
package/dist/tool/recipe.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {} from '../manifest/primitives.schema.js';
|
|
2
|
-
|
|
2
|
+
const PART_PORT = 3000;
|
|
3
|
+
// 503 passes: a restart cannot fix a dependency outage, so only a dead or 404 part is unhealthy.
|
|
3
4
|
const HEALTH_PROBE = `fetch('http://127.0.0.1:${PART_PORT}/health')` +
|
|
4
5
|
'.then((r) => process.exit(r.status === 200 || r.status === 503 ? 0 : 1), () => process.exit(1))';
|
|
5
|
-
|
|
6
|
-
export const serverPartRecipe = (part) => [
|
|
6
|
+
const RECIPE = [
|
|
7
7
|
'# syntax=docker/dockerfile:1',
|
|
8
8
|
'# Generated by @agentage/extension build. Extension authors never write a Dockerfile.',
|
|
9
9
|
'FROM node:26-alpine AS deps',
|
|
@@ -27,6 +27,10 @@ export const serverPartRecipe = (part) => [
|
|
|
27
27
|
'USER node',
|
|
28
28
|
`EXPOSE ${PART_PORT}`,
|
|
29
29
|
`HEALTHCHECK --interval=10s --timeout=3s --start-period=15s --retries=3 CMD ["node", "-e", "${HEALTH_PROBE}"]`,
|
|
30
|
+
];
|
|
31
|
+
// The one recipe for a Node server part; the staged context holds deps/ (npm ci) and app/ (code).
|
|
32
|
+
export const serverPartRecipe = (part) => [
|
|
33
|
+
...RECIPE,
|
|
30
34
|
`CMD ["node", "node_modules/@agentage/extension/dist/runtime/main.js", "${part}"]`,
|
|
31
35
|
'',
|
|
32
36
|
].join('\n');
|
package/dist/tool/stage.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { access, cp, mkdir, mkdtemp, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
|
-
import { basename, dirname, isAbsolute, join, normalize } from 'node:path';
|
|
3
|
+
import { basename, dirname, isAbsolute, join, normalize, sep } from 'node:path';
|
|
4
4
|
import {} from '../manifest/manifest.schema.js';
|
|
5
|
+
import { entryOf } from '../manifest/parts.js';
|
|
5
6
|
import {} from '../manifest/primitives.schema.js';
|
|
6
7
|
import { serverPartRecipe } from './recipe.js';
|
|
7
8
|
export const RECORD_FILE = 'agentage-extension.json';
|
|
@@ -36,14 +37,26 @@ const copyDeps = async (dir, root, pkg) => {
|
|
|
36
37
|
};
|
|
37
38
|
const copyApp = async (dir, root) => {
|
|
38
39
|
await copyInto(join(dir, 'package.json'), join(root, 'app', 'package.json'));
|
|
39
|
-
await mkdir(join(root, 'app'), { recursive: true });
|
|
40
40
|
await cp(join(dir, 'dist'), join(root, 'app', 'dist'), {
|
|
41
41
|
recursive: true,
|
|
42
42
|
filter: (source) => basename(source) !== RECORD_FILE,
|
|
43
43
|
});
|
|
44
44
|
};
|
|
45
|
+
// The image carries dist/ only, so an entry outside it, or never built, would not start.
|
|
46
|
+
const assertBuiltEntry = async (dir, pkg, part) => {
|
|
47
|
+
const entry = normalize(entryOf(pkg, part));
|
|
48
|
+
if (!entry.startsWith(`dist${sep}`))
|
|
49
|
+
throw new Error(`the ${part} entry ${entry} is not in dist/`);
|
|
50
|
+
try {
|
|
51
|
+
await access(join(dir, entry));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new Error(`${entry} not found: the package build must write the ${part} entry there`);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
45
57
|
// A clean build context per part: only the manifest, the lockfile, local tarballs and dist/.
|
|
46
58
|
export const stageContext = async (dir, pkg, part, tmpRoot = tmpdir()) => {
|
|
59
|
+
await assertBuiltEntry(dir, pkg, part);
|
|
47
60
|
const root = await mkdtemp(join(tmpRoot, `agentage-${pkg.agentage.id}-${part}-`));
|
|
48
61
|
await copyDeps(dir, root, pkg);
|
|
49
62
|
await copyApp(dir, root);
|
package/package.json
CHANGED