@agentage/extension 0.1.2 → 0.2.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 +6 -5
- package/dist/agcid/current.d.ts +7 -0
- package/dist/agcid/current.js +4 -0
- package/dist/agcid/scope.d.ts +1 -1
- package/dist/agcid/scope.js +3 -4
- package/dist/command/args.d.ts +14 -0
- package/dist/command/args.js +60 -0
- package/dist/command/check-cli.d.ts +1 -0
- package/dist/command/check-cli.js +24 -0
- package/dist/command/command.types.d.ts +14 -0
- package/dist/command/command.types.js +2 -0
- package/dist/command/out.d.ts +19 -0
- package/dist/command/out.js +32 -0
- package/dist/command/run-command.d.ts +15 -0
- package/dist/command/run-command.js +53 -0
- package/dist/context/context.d.ts +8 -14
- package/dist/context/context.js +20 -26
- package/dist/context/settings.d.ts +5 -0
- package/dist/context/settings.js +32 -0
- package/dist/context/test-context.d.ts +44 -0
- package/dist/context/test-context.js +72 -0
- package/dist/discovery/discovery.d.ts +12 -1
- package/dist/discovery/discovery.js +15 -6
- package/dist/discovery/discovery.schema.d.ts +2 -0
- package/dist/discovery/discovery.schema.js +3 -1
- package/dist/discovery/openapi.d.ts +3 -0
- package/dist/discovery/openapi.js +19 -0
- package/dist/health/health.schema.js +1 -1
- package/dist/index.d.ts +16 -3
- package/dist/index.js +16 -3
- package/dist/log/log.js +1 -1
- package/dist/manifest/manifest.schema.d.ts +18 -0
- package/dist/manifest/manifest.schema.js +1 -0
- package/dist/mcp/mcp-app.d.ts +8 -0
- package/dist/mcp/mcp-app.js +74 -0
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +36 -0
- package/dist/mcp/snapshot.d.ts +11 -0
- package/dist/mcp/snapshot.js +69 -0
- package/dist/mcp/tools.d.ts +24 -0
- package/dist/mcp/tools.js +17 -0
- package/dist/pages/pages-app.d.ts +8 -0
- package/dist/pages/pages-app.js +29 -0
- package/dist/pages/pages.d.ts +4 -0
- package/dist/pages/pages.js +16 -0
- package/dist/runtime/part-runners.d.ts +20 -0
- package/dist/runtime/part-runners.js +68 -0
- package/dist/runtime/run-part.d.ts +4 -2
- package/dist/runtime/run-part.js +49 -27
- package/dist/server/app.d.ts +18 -2
- package/dist/server/app.js +28 -11
- package/dist/store/migrate.d.ts +2 -0
- package/dist/store/migrate.js +18 -0
- package/dist/store/migrations.d.ts +10 -0
- package/dist/store/migrations.js +55 -0
- package/dist/store/store.d.ts +10 -0
- package/dist/store/store.js +25 -0
- package/dist/store/store.types.d.ts +7 -0
- package/dist/store/store.types.js +1 -0
- package/dist/store/test-store.d.ts +5 -0
- package/dist/store/test-store.js +39 -0
- package/dist/testing/test-server.d.ts +6 -0
- package/dist/testing/test-server.js +31 -9
- package/dist/tool/build.d.ts +6 -0
- package/dist/tool/build.js +18 -25
- package/dist/tool/exec.d.ts +4 -0
- package/dist/tool/exec.js +15 -3
- package/dist/tool/index.d.ts +8 -0
- package/dist/tool/index.js +8 -0
- package/dist/tool/kit-version.d.ts +1 -0
- package/dist/tool/kit-version.js +3 -0
- package/dist/tool/main.d.ts +6 -0
- package/dist/tool/main.js +48 -22
- package/dist/tool/record-extras.d.ts +4 -0
- package/dist/tool/record-extras.js +60 -0
- package/dist/tool/stage.d.ts +3 -0
- package/dist/tool/stage.js +41 -21
- package/dist/tool/up/compose-run.d.ts +14 -0
- package/dist/tool/up/compose-run.js +24 -0
- package/dist/tool/up/compose.d.ts +18 -0
- package/dist/tool/up/compose.js +77 -0
- package/dist/tool/up/dev-issuer-app.d.ts +22 -0
- package/dist/tool/up/dev-issuer-app.js +80 -0
- package/dist/tool/up/dev-issuer.d.ts +2 -0
- package/dist/tool/up/dev-issuer.js +5 -0
- package/dist/tool/up/down.d.ts +12 -0
- package/dist/tool/up/down.js +38 -0
- package/dist/tool/up/gateway.d.ts +27 -0
- package/dist/tool/up/gateway.js +66 -0
- package/dist/tool/up/lines.d.ts +2 -0
- package/dist/tool/up/lines.js +3 -0
- package/dist/tool/up/local-record.d.ts +44 -0
- package/dist/tool/up/local-record.js +28 -0
- package/dist/tool/up/token.d.ts +6 -0
- package/dist/tool/up/token.js +18 -0
- package/dist/tool/up/up.d.ts +19 -0
- package/dist/tool/up/up.js +81 -0
- package/dist/tool/up/wait.d.ts +8 -0
- package/dist/tool/up/wait.js +24 -0
- package/dist/ui/ui-app.d.ts +6 -0
- package/dist/ui/ui-app.js +26 -0
- package/dist/worker/cron.d.ts +11 -0
- package/dist/worker/cron.js +73 -0
- package/dist/worker/job-options.d.ts +9 -0
- package/dist/worker/job-options.js +17 -0
- package/dist/worker/run-log.d.ts +9 -0
- package/dist/worker/run-log.js +38 -0
- package/dist/worker/runs.d.ts +20 -0
- package/dist/worker/runs.js +59 -0
- package/dist/worker/scheduler.d.ts +20 -0
- package/dist/worker/scheduler.js +97 -0
- package/dist/worker/worker.types.d.ts +5 -0
- package/dist/worker/worker.types.js +2 -0
- package/package.json +11 -2
- package/schemas/extension-discovery.schema.json +5 -0
- package/schemas/extension-package.schema.json +3 -0
- package/schemas/extension-record.schema.json +5 -0
- package/schemas/health.schema.json +1 -1
package/dist/runtime/run-part.js
CHANGED
|
@@ -1,23 +1,18 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
2
|
-
import { pathToFileURL } from 'node:url';
|
|
3
1
|
import {} from 'node:http';
|
|
4
2
|
import { createContext } from '../context/context.js';
|
|
3
|
+
import { settingsFromEnv } from '../context/settings.js';
|
|
5
4
|
import { DEFAULT_RECORD_FILE, loadRecord } from '../discovery/discovery.js';
|
|
5
|
+
import {} from '../health/health.js';
|
|
6
6
|
import { createLog } from '../log/log.js';
|
|
7
|
-
import { entryOf } from '../manifest/parts.js';
|
|
8
7
|
import {} from '../manifest/manifest.schema.js';
|
|
8
|
+
import { imageParts, normalizeParts } from '../manifest/parts.js';
|
|
9
|
+
import {} from '../manifest/primitives.schema.js';
|
|
9
10
|
import { readManifest } from '../manifest/read-manifest.js';
|
|
10
|
-
import { createApiApp } from '../server/app.js';
|
|
11
|
-
import {} from '../server/part.types.js';
|
|
12
11
|
import { serve } from '../server/serve.js';
|
|
12
|
+
import { createStore, storeCheck } from '../store/store.js';
|
|
13
13
|
import { buildInfoFromEnv, portFromEnv } from './build-info.js';
|
|
14
|
-
|
|
15
|
-
export
|
|
16
|
-
const create = mod?.create;
|
|
17
|
-
if (typeof create !== 'function')
|
|
18
|
-
throw new Error(`${file} does not export create(ctx)`);
|
|
19
|
-
return create;
|
|
20
|
-
};
|
|
14
|
+
import { RUNNERS } from './part-runners.js';
|
|
15
|
+
export { createOf } from './part-runners.js';
|
|
21
16
|
const recordFor = async (env, log) => {
|
|
22
17
|
const file = env['AGENTAGE_RECORD_FILE'] || DEFAULT_RECORD_FILE;
|
|
23
18
|
const record = await loadRecord(file);
|
|
@@ -25,24 +20,51 @@ const recordFor = async (env, log) => {
|
|
|
25
20
|
log.warn('no release record mounted; discovery lists this image by tag', { file });
|
|
26
21
|
return record;
|
|
27
22
|
};
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
export const partOf = (pkg, part) => {
|
|
24
|
+
const parts = imageParts(pkg);
|
|
25
|
+
if (!parts.includes(part))
|
|
26
|
+
throw new Error(`part ${part} is not an image part of this manifest`);
|
|
27
|
+
return part;
|
|
32
28
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
// ui serves files and gets no ctx; every other part gets the store when the manifest has postgres.
|
|
30
|
+
const storeFor = (pkg, part, env, log) => part !== 'ui' && normalizeParts(pkg.agentage.parts).store === 'postgres'
|
|
31
|
+
? createStore(env, log)
|
|
32
|
+
: undefined;
|
|
33
|
+
const inputFor = async (options, pkg, part, log) => {
|
|
37
34
|
const { env } = options;
|
|
38
|
-
const pkg = await readManifest(options.dir);
|
|
39
|
-
const log = createLog({ extension: pkg.agentage.id, part: 'api' });
|
|
40
|
-
const port = portFromEnv(env);
|
|
41
35
|
const origin = env['AGENTAGE_ORIGIN'] || undefined;
|
|
42
|
-
const router = await createRouter(options, contextFor(pkg, origin, port, log), pkg);
|
|
43
36
|
const build = buildInfoFromEnv(env);
|
|
37
|
+
const store = storeFor(pkg, part, env, log);
|
|
38
|
+
const settings = settingsFromEnv(pkg, env, log);
|
|
39
|
+
const ctxOrigin = origin ?? `http://127.0.0.1:${portFromEnv(env)}`;
|
|
40
|
+
const ctxPart = (part === 'ui' ? 'pages' : part);
|
|
41
|
+
const ctx = createContext({
|
|
42
|
+
pkg,
|
|
43
|
+
part: ctxPart,
|
|
44
|
+
origin: ctxOrigin,
|
|
45
|
+
commit: build.commit,
|
|
46
|
+
log,
|
|
47
|
+
settings,
|
|
48
|
+
store,
|
|
49
|
+
});
|
|
44
50
|
const record = await recordFor(env, log);
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
return
|
|
51
|
+
const checks = store ? { store: storeCheck(store) } : {};
|
|
52
|
+
const base = { pkg, build, log, record, origin, ownImage: env['AGENTAGE_IMAGE'], checks };
|
|
53
|
+
return {
|
|
54
|
+
dir: options.dir,
|
|
55
|
+
pkg,
|
|
56
|
+
ctx,
|
|
57
|
+
base,
|
|
58
|
+
importModule: options.importModule,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
export const runPart = async (options) => {
|
|
62
|
+
const pkg = await readManifest(options.dir);
|
|
63
|
+
const part = partOf(pkg, options.part);
|
|
64
|
+
const log = createLog({ extension: pkg.agentage.id, part });
|
|
65
|
+
const { app, stop } = await RUNNERS[part](await inputFor(options, pkg, part, log));
|
|
66
|
+
if (stop)
|
|
67
|
+
for (const signal of options.serve?.signals ?? ['SIGTERM', 'SIGINT'])
|
|
68
|
+
process.once(signal, stop);
|
|
69
|
+
return serve({ app, port: portFromEnv(options.env), log, ...options.serve });
|
|
48
70
|
};
|
package/dist/server/app.d.ts
CHANGED
|
@@ -1,16 +1,32 @@
|
|
|
1
|
+
import { type CorsOptions } from 'cors';
|
|
1
2
|
import { type Express } from 'express';
|
|
3
|
+
import { type HelmetOptions } from 'helmet';
|
|
2
4
|
import { type BuildInfo } from '../discovery/discovery.js';
|
|
3
5
|
import { type ExtensionRecord } from '../discovery/discovery.schema.js';
|
|
6
|
+
import { type HealthCheck } from '../health/health.js';
|
|
4
7
|
import { type Log } from '../log/log.js';
|
|
5
8
|
import { type ExtensionPackage } from '../manifest/manifest.schema.js';
|
|
9
|
+
import { type ImagePartKind } from '../manifest/primitives.schema.js';
|
|
6
10
|
import { type ApiHandler } from './part.types.js';
|
|
7
|
-
export interface
|
|
11
|
+
export interface PartAppBase {
|
|
8
12
|
pkg: ExtensionPackage;
|
|
9
13
|
build: BuildInfo;
|
|
10
|
-
router: ApiHandler;
|
|
11
14
|
log: Log;
|
|
12
15
|
record?: ExtensionRecord | null;
|
|
13
16
|
ownImage?: string;
|
|
14
17
|
origin?: string;
|
|
18
|
+
checks?: Record<string, HealthCheck>;
|
|
19
|
+
}
|
|
20
|
+
export interface PartAppOptions extends PartAppBase {
|
|
21
|
+
part: ImagePartKind;
|
|
22
|
+
mount: (app: Express) => void;
|
|
23
|
+
helmet?: HelmetOptions;
|
|
24
|
+
}
|
|
25
|
+
export interface ApiAppOptions extends PartAppBase {
|
|
26
|
+
router: ApiHandler;
|
|
27
|
+
openapi?: Record<string, unknown> | null;
|
|
15
28
|
}
|
|
29
|
+
export declare const PUBLIC_CORS: CorsOptions;
|
|
30
|
+
export declare const BODY_LIMIT = "1mb";
|
|
31
|
+
export declare const createPartApp: (options: PartAppOptions) => Express;
|
|
16
32
|
export declare const createApiApp: (options: ApiAppOptions) => Express;
|
package/dist/server/app.js
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import cors, {} from 'cors';
|
|
2
2
|
import express, {} from 'express';
|
|
3
|
-
import helmet from 'helmet';
|
|
3
|
+
import helmet, {} from 'helmet';
|
|
4
4
|
import { agcidMiddleware } from '../agcid/scope.js';
|
|
5
5
|
import { discoveryDocument, recordOf, } from '../discovery/discovery.js';
|
|
6
6
|
import {} from '../discovery/discovery.schema.js';
|
|
7
7
|
import { createHealthHandler } from '../health/health.js';
|
|
8
8
|
import {} from '../log/log.js';
|
|
9
9
|
import {} from '../manifest/manifest.schema.js';
|
|
10
|
+
import {} from '../manifest/primitives.schema.js';
|
|
10
11
|
import { errorHandler, notFound } from './errors.js';
|
|
11
12
|
import {} from './part.types.js';
|
|
12
|
-
// api-standards section 6: CORS * without credentials on /.well-known/ and /
|
|
13
|
-
const PUBLIC_CORS = {
|
|
13
|
+
// api-standards section 6: CORS * without credentials on /.well-known/, /api/, /ui/ and /mcp.
|
|
14
|
+
export const PUBLIC_CORS = {
|
|
14
15
|
origin: '*',
|
|
15
16
|
credentials: false,
|
|
16
17
|
allowedHeaders: ['Authorization', 'Content-Type', 'agcid', 'traceparent', 'If-Match'],
|
|
17
18
|
exposedHeaders: ['agcid', 'ETag', 'Retry-After', 'RateLimit', 'WWW-Authenticate'],
|
|
18
19
|
};
|
|
19
|
-
const BODY_LIMIT = '1mb';
|
|
20
|
+
export const BODY_LIMIT = '1mb';
|
|
20
21
|
const requestOrigin = (req) => `${req.protocol}://${req.get('host') ?? ''}`;
|
|
21
22
|
const discoveryHandler = (options) => {
|
|
22
|
-
const images = options.ownImage ? {
|
|
23
|
+
const images = options.ownImage ? { [options.part]: options.ownImage } : {};
|
|
23
24
|
const record = options.record ?? recordOf(options.pkg, options.build, images);
|
|
24
25
|
return (req, res) => {
|
|
25
26
|
res.json(discoveryDocument(record, options.origin ?? requestOrigin(req)));
|
|
@@ -27,20 +28,36 @@ const discoveryHandler = (options) => {
|
|
|
27
28
|
};
|
|
28
29
|
const healthHandler = (options) => createHealthHandler({
|
|
29
30
|
extension: options.pkg.agentage.id,
|
|
30
|
-
part:
|
|
31
|
+
part: options.part,
|
|
31
32
|
version: options.pkg.version,
|
|
32
33
|
build: options.build,
|
|
34
|
+
checks: options.checks,
|
|
33
35
|
});
|
|
34
|
-
|
|
36
|
+
// The kit app every part image runs: agcid, headers, /health, discovery, the part, the envelope.
|
|
37
|
+
export const createPartApp = (options) => {
|
|
35
38
|
const app = express();
|
|
36
39
|
// The gateway calls parts over a private network; its X-Forwarded-Proto then sets req.protocol.
|
|
37
40
|
app.set('trust proxy', 'loopback, linklocal, uniquelocal');
|
|
38
|
-
app.use(agcidMiddleware, helmet());
|
|
41
|
+
app.use(agcidMiddleware, helmet(options.helmet));
|
|
39
42
|
app.get('/health', healthHandler(options));
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
if (options.part !== 'worker') {
|
|
44
|
+
app.use('/.well-known', cors(PUBLIC_CORS));
|
|
45
|
+
app.get('/.well-known/agentage-extension.json', discoveryHandler(options));
|
|
46
|
+
}
|
|
47
|
+
options.mount(app);
|
|
43
48
|
app.use(notFound);
|
|
44
49
|
app.use(errorHandler(options.log));
|
|
45
50
|
return app;
|
|
46
51
|
};
|
|
52
|
+
// The committed OpenAPI document, bare JSON and anonymous like discovery (api-standards section 1).
|
|
53
|
+
const openapiHandler = (doc) => (_req, res) => void res.json(doc);
|
|
54
|
+
export const createApiApp = (options) => createPartApp({
|
|
55
|
+
...options,
|
|
56
|
+
part: 'api',
|
|
57
|
+
mount: (app) => {
|
|
58
|
+
app.use('/api/v1', cors(PUBLIC_CORS));
|
|
59
|
+
if (options.openapi)
|
|
60
|
+
app.get('/api/v1/openapi.json', openapiHandler(options.openapi));
|
|
61
|
+
app.use('/api/v1', express.json({ limit: BODY_LIMIT }), options.router);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createLog } from '../log/log.js';
|
|
3
|
+
import { readManifest } from '../manifest/read-manifest.js';
|
|
4
|
+
import { migrate } from './migrations.js';
|
|
5
|
+
import { createStore } from './store.js';
|
|
6
|
+
// The api image's migrate entry (`node dist/store/migrate.js` in /app), run as a job before a rollout.
|
|
7
|
+
const log = createLog({ extension: 'unknown', part: 'migrate' });
|
|
8
|
+
try {
|
|
9
|
+
const pkg = await readManifest(process.cwd());
|
|
10
|
+
const store = createStore(process.env, log);
|
|
11
|
+
const result = await migrate(store, process.cwd(), createLog({ extension: pkg.agentage.id, part: 'migrate' }));
|
|
12
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
13
|
+
await store.end();
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
log.error(err);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Log } from '../log/log.js';
|
|
2
|
+
import { type Store } from './store.types.js';
|
|
3
|
+
export declare const SQL_FILE: RegExp;
|
|
4
|
+
export interface MigrateResult {
|
|
5
|
+
migrations: number;
|
|
6
|
+
applied: number;
|
|
7
|
+
seeds: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const sqlFiles: (dir: string) => Promise<string[]>;
|
|
10
|
+
export declare const migrate: (store: Store, dir: string, log: Log) => Promise<MigrateResult>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import {} from '../log/log.js';
|
|
4
|
+
import {} from './store.types.js';
|
|
5
|
+
export const SQL_FILE = /^[0-9]{3}-[a-z0-9-]+\.sql$/;
|
|
6
|
+
// Filename order; anything else in the folder is refused, so a typo never silently skips a file.
|
|
7
|
+
export const sqlFiles = async (dir) => {
|
|
8
|
+
let names;
|
|
9
|
+
try {
|
|
10
|
+
names = await readdir(dir);
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
if (err.code === 'ENOENT')
|
|
14
|
+
return [];
|
|
15
|
+
throw err;
|
|
16
|
+
}
|
|
17
|
+
const bad = names.filter((name) => !SQL_FILE.test(name));
|
|
18
|
+
if (bad.length > 0)
|
|
19
|
+
throw new Error(`${dir}: not NNN-name.sql: ${bad.join(', ')}`);
|
|
20
|
+
return names.sort();
|
|
21
|
+
};
|
|
22
|
+
const LEDGER = `create table if not exists _migrations (
|
|
23
|
+
name text primary key,
|
|
24
|
+
applied_at timestamptz not null default now()
|
|
25
|
+
)`;
|
|
26
|
+
const appliedNames = async (store) => {
|
|
27
|
+
await store.query(LEDGER);
|
|
28
|
+
const { rows } = await store.query('select name from _migrations');
|
|
29
|
+
return new Set(rows.map((row) => row.name));
|
|
30
|
+
};
|
|
31
|
+
// One string, one round trip: the file and its ledger row commit together or not at all.
|
|
32
|
+
const applyFile = async (store, file, name) => {
|
|
33
|
+
const sql = await readFile(file, 'utf8');
|
|
34
|
+
await store.query(`begin;\n${sql}\n;insert into _migrations (name) values ('${name}');\ncommit;`);
|
|
35
|
+
};
|
|
36
|
+
const applyAll = async (store, dir, prefix, done) => {
|
|
37
|
+
let count = 0;
|
|
38
|
+
for (const file of await sqlFiles(dir)) {
|
|
39
|
+
const name = `${prefix}${file}`;
|
|
40
|
+
if (done.has(name))
|
|
41
|
+
continue;
|
|
42
|
+
await applyFile(store, join(dir, file), name);
|
|
43
|
+
count += 1;
|
|
44
|
+
}
|
|
45
|
+
return count;
|
|
46
|
+
};
|
|
47
|
+
// Migrations in order, each once; seeds once, only when the store had no ledger row before.
|
|
48
|
+
export const migrate = async (store, dir, log) => {
|
|
49
|
+
const done = await appliedNames(store);
|
|
50
|
+
const migrations = (await sqlFiles(join(dir, 'migrations'))).length;
|
|
51
|
+
const applied = await applyAll(store, join(dir, 'migrations'), '', done);
|
|
52
|
+
const seeds = done.size === 0 ? await applyAll(store, join(dir, 'seeds'), 'seed:', done) : 0;
|
|
53
|
+
log.info('migrated', { migrations, applied, seeds });
|
|
54
|
+
return { migrations, applied, seeds };
|
|
55
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type HealthCheck } from '../health/health.js';
|
|
2
|
+
import { type Log } from '../log/log.js';
|
|
3
|
+
import { type Env } from '../runtime/build-info.js';
|
|
4
|
+
import { type Store } from './store.types.js';
|
|
5
|
+
export interface PoolStore extends Store {
|
|
6
|
+
end(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare const storeUrl: (env: Env, read?: (file: string) => string) => string | undefined;
|
|
9
|
+
export declare const createStore: (env: Env, log: Log) => PoolStore;
|
|
10
|
+
export declare const storeCheck: (store: Store) => HealthCheck;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import pg from 'pg';
|
|
3
|
+
import {} from '../health/health.js';
|
|
4
|
+
import {} from '../log/log.js';
|
|
5
|
+
import {} from '../runtime/build-info.js';
|
|
6
|
+
import {} from './store.types.js';
|
|
7
|
+
// The host sets the connection: AGENTAGE_STORE_URL, a secret file named by _FILE, else libpq PG* env.
|
|
8
|
+
export const storeUrl = (env, read = (file) => readFileSync(file, 'utf8')) => {
|
|
9
|
+
const file = env['AGENTAGE_STORE_URL_FILE'];
|
|
10
|
+
if (file)
|
|
11
|
+
return read(file).trim();
|
|
12
|
+
return env['AGENTAGE_STORE_URL'] || undefined;
|
|
13
|
+
};
|
|
14
|
+
export const createStore = (env, log) => {
|
|
15
|
+
const pool = new pg.Pool({ connectionString: storeUrl(env), max: 5, idleTimeoutMillis: 30_000 });
|
|
16
|
+
pool.on('error', (err) => log.error(err, { store: 'pool' }));
|
|
17
|
+
return {
|
|
18
|
+
query: async (text, params) => pool.query(text, params),
|
|
19
|
+
end: () => pool.end(),
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export const storeCheck = (store) => async () => {
|
|
23
|
+
await store.query('select 1');
|
|
24
|
+
return 'ok';
|
|
25
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {} from './store.types.js';
|
|
2
|
+
const open = async () => {
|
|
3
|
+
try {
|
|
4
|
+
const { PGlite } = (await import('@electric-sql/pglite'));
|
|
5
|
+
return new PGlite();
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
throw new Error('a test store needs @electric-sql/pglite: npm install -D @electric-sql/pglite');
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
// Like pg: a parameterless query may hold several statements (migrations), the last one answers.
|
|
12
|
+
const bound = (db) => ({
|
|
13
|
+
query: async (text, params) => {
|
|
14
|
+
if (params && params.length > 0)
|
|
15
|
+
return db.query(text, params);
|
|
16
|
+
const results = await db.exec(text);
|
|
17
|
+
return { rows: (results.at(-1)?.rows ?? []) };
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
// In-process Postgres (WASM): a throwaway store of the same kind, prepared once on first use.
|
|
21
|
+
export const createTestStore = (prepare) => {
|
|
22
|
+
let ready;
|
|
23
|
+
const start = async () => {
|
|
24
|
+
const db = await open();
|
|
25
|
+
const store = bound(db);
|
|
26
|
+
await prepare(store);
|
|
27
|
+
return { ...store, db };
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
query: async (text, params) => {
|
|
31
|
+
ready ??= start();
|
|
32
|
+
return (await ready).query(text, params);
|
|
33
|
+
},
|
|
34
|
+
close: async () => {
|
|
35
|
+
if (ready)
|
|
36
|
+
await (await ready).db.close();
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { type PartContext } from '../context/context.js';
|
|
2
|
+
import { type McpContext } from '../mcp/tools.js';
|
|
2
3
|
import { type ApiCreate } from '../server/part.types.js';
|
|
3
4
|
export interface TestServerOptions<C extends PartContext> {
|
|
4
5
|
create: ApiCreate<C>;
|
|
5
6
|
ctx?: C;
|
|
6
7
|
}
|
|
8
|
+
export interface TestMcpOptions {
|
|
9
|
+
create: (ctx: McpContext) => unknown;
|
|
10
|
+
ctx?: PartContext;
|
|
11
|
+
}
|
|
7
12
|
export interface TestServer {
|
|
8
13
|
url: string;
|
|
9
14
|
fetch(path: string, init?: RequestInit): Promise<Response>;
|
|
10
15
|
close(): Promise<void>;
|
|
11
16
|
}
|
|
12
17
|
export declare const startTestServer: <C extends PartContext = PartContext>(options: TestServerOptions<C>) => Promise<TestServer>;
|
|
18
|
+
export declare const startTestMcp: (options: TestMcpOptions) => Promise<TestServer>;
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import {} from 'node:net';
|
|
2
|
-
import {
|
|
2
|
+
import {} from 'express';
|
|
3
|
+
import {} from '../context/context.js';
|
|
4
|
+
import { createTestContext } from '../context/test-context.js';
|
|
3
5
|
import {} from '../manifest/manifest.schema.js';
|
|
6
|
+
import { createMcpApp } from '../mcp/mcp-app.js';
|
|
7
|
+
import { createToolRegistry } from '../mcp/tools.js';
|
|
4
8
|
import { createApiApp } from '../server/app.js';
|
|
5
9
|
import {} from '../server/part.types.js';
|
|
6
10
|
import { listen } from '../server/serve.js';
|
|
7
11
|
const TEST_BUILD = { commit: '0'.repeat(40), buildTime: '2026-01-01T00:00:00Z' };
|
|
8
|
-
const testPackage = (ctx) => ({
|
|
12
|
+
const testPackage = (ctx, parts) => ({
|
|
9
13
|
name: `@agentage/extension-${ctx.extension.id}`,
|
|
10
14
|
version: ctx.extension.version,
|
|
11
|
-
engines: { agentage: '^0.
|
|
12
|
-
agentage: { id: ctx.extension.id, parts
|
|
15
|
+
engines: { agentage: '^0.2.0' },
|
|
16
|
+
agentage: { id: ctx.extension.id, parts },
|
|
13
17
|
});
|
|
14
|
-
|
|
15
|
-
export const startTestServer = async (options) => {
|
|
16
|
-
const ctx = options.ctx ?? createTestContext();
|
|
17
|
-
const router = await options.create(ctx);
|
|
18
|
-
const app = createApiApp({ pkg: testPackage(ctx), build: TEST_BUILD, router, log: ctx.log });
|
|
18
|
+
const startOn = async (app) => {
|
|
19
19
|
const server = await listen(app, 0, '127.0.0.1');
|
|
20
20
|
const url = `http://127.0.0.1:${server.address().port}`;
|
|
21
21
|
return {
|
|
@@ -24,3 +24,25 @@ export const startTestServer = async (options) => {
|
|
|
24
24
|
close: () => new Promise((resolve) => server.close(() => resolve())),
|
|
25
25
|
};
|
|
26
26
|
};
|
|
27
|
+
// Runs an api part's create(ctx) inside the kit's real app on 127.0.0.1, port chosen by the OS.
|
|
28
|
+
export const startTestServer = async (options) => {
|
|
29
|
+
const ctx = options.ctx ?? createTestContext();
|
|
30
|
+
const router = await options.create(ctx);
|
|
31
|
+
const pkg = testPackage(ctx, { api: true });
|
|
32
|
+
return startOn(createApiApp({ pkg, build: TEST_BUILD, router, log: ctx.log }));
|
|
33
|
+
};
|
|
34
|
+
// Runs an mcp part's create(ctx) inside the kit's real /mcp app: tools named {id}__{name}.
|
|
35
|
+
export const startTestMcp = async (options) => {
|
|
36
|
+
const ctx = options.ctx ?? createTestContext();
|
|
37
|
+
const registry = createToolRegistry();
|
|
38
|
+
await options.create({ ...ctx, tool: registry.tool });
|
|
39
|
+
const pkg = testPackage(ctx, { mcp: true });
|
|
40
|
+
const app = createMcpApp({
|
|
41
|
+
pkg,
|
|
42
|
+
build: TEST_BUILD,
|
|
43
|
+
log: ctx.log,
|
|
44
|
+
tools: registry.tools,
|
|
45
|
+
listing: 'open',
|
|
46
|
+
});
|
|
47
|
+
return startOn(app);
|
|
48
|
+
};
|
package/dist/tool/build.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { type BuildInfo } from '../discovery/discovery.js';
|
|
2
|
+
import { type ExtensionRecord } from '../discovery/discovery.schema.js';
|
|
1
3
|
import { type ExtensionPackage } from '../manifest/manifest.schema.js';
|
|
2
4
|
import { type ImagePartKind } from '../manifest/primitives.schema.js';
|
|
3
5
|
import { type Env } from '../runtime/build-info.js';
|
|
4
6
|
import { type Docker } from './docker.js';
|
|
5
7
|
import { type Run } from './exec.js';
|
|
8
|
+
export declare const DEFAULT_REGISTRY = "ghcr.io/agentage";
|
|
6
9
|
export interface BuildOptions {
|
|
7
10
|
dir?: string;
|
|
8
11
|
push?: boolean;
|
|
@@ -29,4 +32,7 @@ export interface BuildResult {
|
|
|
29
32
|
recordFile?: string;
|
|
30
33
|
}
|
|
31
34
|
export declare const imageTag: (registry: string, pkg: ExtensionPackage, part: ImagePartKind) => string;
|
|
35
|
+
export declare const recordFor: (dir: string, pkg: ExtensionPackage, info: BuildInfo, images: BuiltImage[]) => Promise<ExtensionRecord>;
|
|
36
|
+
declare const writeRecord: (dir: string, record: ExtensionRecord, file?: string) => Promise<string>;
|
|
32
37
|
export declare const build: (options?: BuildOptions, deps?: BuildDeps) => Promise<BuildResult>;
|
|
38
|
+
export { writeRecord };
|
package/dist/tool/build.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { recordOf, validateRecord } from '../discovery/discovery.js';
|
|
4
|
+
import {} from '../discovery/discovery.schema.js';
|
|
4
5
|
import {} from '../manifest/manifest.schema.js';
|
|
5
6
|
import { imageParts } from '../manifest/parts.js';
|
|
6
7
|
import {} from '../manifest/primitives.schema.js';
|
|
@@ -10,18 +11,10 @@ import { resolveBuildInfo } from './build-info.js';
|
|
|
10
11
|
import { createDocker } from './docker.js';
|
|
11
12
|
import { mustRun, runCommand } from './exec.js';
|
|
12
13
|
import { labelsOf } from './labels.js';
|
|
14
|
+
import { recordExtras } from './record-extras.js';
|
|
13
15
|
import { RECORD_FILE, stageContext } from './stage.js';
|
|
14
|
-
const DEFAULT_REGISTRY = 'ghcr.io/agentage';
|
|
15
|
-
const SUPPORTED_PARTS = ['api'];
|
|
16
|
+
export const DEFAULT_REGISTRY = 'ghcr.io/agentage';
|
|
16
17
|
export const imageTag = (registry, pkg, part) => `${registry}/${pkg.agentage.id}-${part}:${pkg.version}`;
|
|
17
|
-
const partsToBuild = (pkg) => {
|
|
18
|
-
const parts = imageParts(pkg);
|
|
19
|
-
const unsupported = parts.filter((part) => !SUPPORTED_PARTS.includes(part));
|
|
20
|
-
if (unsupported.length > 0) {
|
|
21
|
-
throw new Error(`@agentage/extension 0.1 builds the api part only, not: ${unsupported.join(', ')}`);
|
|
22
|
-
}
|
|
23
|
-
return parts;
|
|
24
|
-
};
|
|
25
18
|
const compile = async (dir, pkg, run) => {
|
|
26
19
|
const scripts = pkg.scripts;
|
|
27
20
|
if (typeof scripts?.['build'] === 'string')
|
|
@@ -35,12 +28,8 @@ const buildImage = async (job, docker, tmpRoot) => {
|
|
|
35
28
|
BUILD_TIME: job.info.buildTime,
|
|
36
29
|
IMAGE: job.tag,
|
|
37
30
|
};
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
tag: job.tag,
|
|
41
|
-
buildArgs,
|
|
42
|
-
labels: labelsOf(job.pkg, job.part, job.info),
|
|
43
|
-
});
|
|
31
|
+
const labels = labelsOf(job.pkg, job.part, job.info);
|
|
32
|
+
await docker.build({ context, tag: job.tag, buildArgs, labels });
|
|
44
33
|
}
|
|
45
34
|
finally {
|
|
46
35
|
await rm(context, { recursive: true, force: true });
|
|
@@ -51,32 +40,36 @@ const pushImage = async (job, docker) => {
|
|
|
51
40
|
const repository = job.tag.slice(0, job.tag.lastIndexOf(':'));
|
|
52
41
|
return { part: job.part, tag: job.tag, digest, image: `${repository}@${digest}` };
|
|
53
42
|
};
|
|
54
|
-
|
|
43
|
+
// The record from the code and the images by digest; `up` writes the same shape from local digests.
|
|
44
|
+
export const recordFor = async (dir, pkg, info, images) => {
|
|
55
45
|
const byPart = Object.fromEntries(images.map((built) => [built.part, built.image]));
|
|
56
|
-
|
|
57
|
-
|
|
46
|
+
return validateRecord(recordOf(pkg, info, byPart, await recordExtras(dir, pkg)));
|
|
47
|
+
};
|
|
48
|
+
const writeRecord = async (dir, record, file = join(dir, 'dist', RECORD_FILE)) => {
|
|
58
49
|
await writeFile(file, `${JSON.stringify(record, null, 2)}\n`);
|
|
59
50
|
return file;
|
|
60
51
|
};
|
|
61
|
-
const buildParts = async (base,
|
|
52
|
+
const buildParts = async (base, options, deps) => {
|
|
62
53
|
const docker = deps.docker ?? createDocker(deps.run);
|
|
63
54
|
const registry = options.registry ?? DEFAULT_REGISTRY;
|
|
64
55
|
const images = [];
|
|
65
|
-
for (const part of
|
|
56
|
+
for (const part of imageParts(base.pkg)) {
|
|
66
57
|
const job = { ...base, part, tag: imageTag(registry, base.pkg, part) };
|
|
67
58
|
await buildImage(job, docker, deps.tmpRoot);
|
|
68
59
|
images.push(options.push ? await pushImage(job, docker) : { part, tag: job.tag });
|
|
69
60
|
}
|
|
70
61
|
return images;
|
|
71
62
|
};
|
|
63
|
+
// Validate, compile, one image per part from the kit recipe; --push pushes and writes the record.
|
|
72
64
|
export const build = async (options = {}, deps = { run: runCommand }) => {
|
|
73
65
|
const dir = resolve(options.dir ?? '.');
|
|
74
66
|
const pkg = await readManifest(dir);
|
|
75
|
-
const parts = partsToBuild(pkg);
|
|
76
67
|
const info = await resolveBuildInfo(dir, options.env ?? process.env, deps.run);
|
|
77
68
|
await compile(dir, pkg, deps.run);
|
|
78
|
-
const images = await buildParts({ dir, pkg, info },
|
|
79
|
-
const recordFile = options.push ? await writeRecord(dir, pkg, info, images) : undefined;
|
|
69
|
+
const images = await buildParts({ dir, pkg, info }, options, deps);
|
|
80
70
|
const result = { id: pkg.agentage.id, version: pkg.version, ...info, images };
|
|
81
|
-
|
|
71
|
+
if (!options.push)
|
|
72
|
+
return result;
|
|
73
|
+
return { ...result, recordFile: await writeRecord(dir, await recordFor(dir, pkg, info, images)) };
|
|
82
74
|
};
|
|
75
|
+
export { writeRecord };
|
package/dist/tool/exec.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
export interface RunOptions {
|
|
2
2
|
cwd?: string;
|
|
3
3
|
echo?: boolean;
|
|
4
|
+
quiet?: boolean;
|
|
5
|
+
env?: Record<string, string | undefined>;
|
|
4
6
|
}
|
|
5
7
|
export interface RunResult {
|
|
6
8
|
code: number;
|
|
7
9
|
stdout: string;
|
|
10
|
+
stderr?: string;
|
|
8
11
|
}
|
|
9
12
|
export type Run = (command: string, args: string[], options?: RunOptions) => Promise<RunResult>;
|
|
10
13
|
export declare const runCommand: Run;
|
|
11
14
|
export declare const mustRun: (run: Run, command: string, args: string[], options?: RunOptions) => Promise<string>;
|
|
15
|
+
export declare const quietly: (run: Run) => Run;
|
package/dist/tool/exec.js
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import {} from 'node:stream';
|
|
2
3
|
// stdout is captured (and echoed to stderr with `echo`); stderr streams through as progress.
|
|
3
4
|
export const runCommand = (command, args, options = {}) => new Promise((resolve, reject) => {
|
|
4
|
-
const
|
|
5
|
+
const stdio = ['ignore', 'pipe', options.quiet ? 'pipe' : 'inherit'];
|
|
6
|
+
const env = options.env ? { ...process.env, ...options.env } : process.env;
|
|
7
|
+
const child = spawn(command, args, {
|
|
8
|
+
cwd: options.cwd,
|
|
9
|
+
stdio: [...stdio],
|
|
10
|
+
env,
|
|
11
|
+
});
|
|
5
12
|
let stdout = '';
|
|
13
|
+
let stderr = '';
|
|
6
14
|
child.stdout.on('data', (chunk) => {
|
|
7
15
|
stdout += chunk.toString();
|
|
8
16
|
if (options.echo)
|
|
9
17
|
process.stderr.write(chunk);
|
|
10
18
|
});
|
|
19
|
+
child.stderr?.on('data', (chunk) => void (stderr += chunk.toString()));
|
|
11
20
|
child.on('error', (err) => reject(err.code === 'ENOENT' ? new Error(`${command} is not installed or not on PATH`) : err));
|
|
12
|
-
child.on('close', (code) => resolve({ code: code ?? 1, stdout }));
|
|
21
|
+
child.on('close', (code) => resolve({ code: code ?? 1, stdout, ...(options.quiet ? { stderr } : {}) }));
|
|
13
22
|
});
|
|
23
|
+
const tail = (text) => text ? `\n${text.trim().split('\n').slice(-15).join('\n')}` : '';
|
|
14
24
|
export const mustRun = async (run, command, args, options) => {
|
|
15
25
|
const result = await run(command, args, options);
|
|
16
26
|
if (result.code !== 0) {
|
|
17
|
-
throw new Error(`${command} ${args[0] ?? ''} failed with exit code ${result.code}`);
|
|
27
|
+
throw new Error(`${command} ${args[0] ?? ''} failed with exit code ${result.code}${tail(result.stderr)}`);
|
|
18
28
|
}
|
|
19
29
|
return result.stdout;
|
|
20
30
|
};
|
|
31
|
+
// Every command a runner starts runs quiet: what `up` and `down` print are their own lines.
|
|
32
|
+
export const quietly = (run) => (command, args, options) => run(command, args, { ...options, quiet: true });
|
package/dist/tool/index.d.ts
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
|
+
export { checkCli } from '../command/check-cli.js';
|
|
2
|
+
export { runCommand, type RunCommandOptions } from '../command/run-command.js';
|
|
3
|
+
export { type ExtensionPackage, type CommandSpec } from '../manifest/manifest.schema.js';
|
|
4
|
+
export { readManifest, validateManifest } from '../manifest/read-manifest.js';
|
|
1
5
|
export { build, type BuildDeps, type BuildOptions, type BuildResult, type BuiltImage, } from './build.js';
|
|
2
6
|
export { buildLines } from './format.js';
|
|
7
|
+
export { KIT_VERSION } from './kit-version.js';
|
|
8
|
+
export { down, type DownOptions, type DownResult } from './up/down.js';
|
|
9
|
+
export { token, type TokenOptions } from './up/token.js';
|
|
10
|
+
export { up, type UpDeps, type UpOptions, type UpResult } from './up/up.js';
|