@agentage/extension 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/part-auth.d.ts +1 -1
- package/dist/auth/part-auth.js +8 -5
- package/dist/auth/verify-token.d.ts +1 -0
- package/dist/auth/verify-token.js +2 -1
- package/dist/discovery/discovery.js +1 -1
- package/dist/mcp/server.js +1 -2
- package/dist/store/migrate.js +1 -2
- package/dist/testing/test-server.js +1 -1
- package/dist/tool/up/compose.js +12 -19
- package/dist/tool/up/dev-issuer-app.js +1 -2
- package/package.json +1 -1
package/dist/auth/part-auth.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare const partAuth: (env: Env, id: string, log: Log, scopes: string[]
|
|
|
10
10
|
bearer_methods_supported: string[];
|
|
11
11
|
};
|
|
12
12
|
checks: {
|
|
13
|
-
auth:
|
|
13
|
+
auth: () => Promise<"ok" | "degraded" | "unavailable">;
|
|
14
14
|
};
|
|
15
15
|
}>;
|
|
16
16
|
export declare const declaredScopes: (id: string, openapi: Record<string, unknown> | null, tools?: readonly {
|
package/dist/auth/part-auth.js
CHANGED
|
@@ -30,16 +30,19 @@ const settingsOf = (env) => {
|
|
|
30
30
|
const { origin, hostname } = new URL(parsed.data.AGENTAGE_ORIGIN);
|
|
31
31
|
if (!covers(audience, hostname))
|
|
32
32
|
throw new Error(`the host ${hostname} is not under the audience ${audience}`);
|
|
33
|
+
if (origin.startsWith('https:') && !issuer.startsWith('https:'))
|
|
34
|
+
throw new Error(`the issuer ${issuer} is not https under the origin ${origin}`);
|
|
33
35
|
return { issuer, audience, origin };
|
|
34
36
|
};
|
|
35
|
-
//
|
|
37
|
+
// Read before serving, then every 30 s (keys only while none held); no stop on a dead issuer.
|
|
36
38
|
export const partAuth = async (env, id, log, scopes) => {
|
|
37
39
|
const { issuer, audience, origin } = settingsOf(env);
|
|
38
|
-
const { keys, read } = remoteKeys(issuer);
|
|
40
|
+
const { keys, read, held } = remoteKeys(issuer);
|
|
39
41
|
const revoked = createRevokedList(issuer, log);
|
|
40
42
|
const unread = (err) => log.warn('key set not read', { reason: messageOf(err) });
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
const poll = () => Promise.all([held() || read().catch(unread), revoked.refresh()]);
|
|
44
|
+
await poll();
|
|
45
|
+
setInterval(() => void poll(), REVOKED_POLL_MS).unref();
|
|
43
46
|
return {
|
|
44
47
|
tokenCheck: createTokenCheck({ issuer, audience, id, origin, keys, revoked }),
|
|
45
48
|
resource: {
|
|
@@ -48,7 +51,7 @@ export const partAuth = async (env, id, log, scopes) => {
|
|
|
48
51
|
scopes_supported: scopes,
|
|
49
52
|
bearer_methods_supported: ['header'],
|
|
50
53
|
},
|
|
51
|
-
checks: { auth: revoked.check },
|
|
54
|
+
checks: { auth: async () => (held() ? revoked.check() : 'degraded') },
|
|
52
55
|
};
|
|
53
56
|
};
|
|
54
57
|
export const declaredScopes = (id, openapi, tools = []) => {
|
|
@@ -14,5 +14,6 @@ export interface TokenCheckDeps {
|
|
|
14
14
|
export declare const remoteKeys: (issuer: string) => {
|
|
15
15
|
keys: JWTVerifyGetKey<Uint8Array<ArrayBufferLike> | import("node:crypto").webcrypto.CryptoKey>;
|
|
16
16
|
read: () => Promise<void>;
|
|
17
|
+
held: () => boolean;
|
|
17
18
|
};
|
|
18
19
|
export declare const createTokenCheck: (deps: TokenCheckDeps) => TokenCheck;
|
|
@@ -33,7 +33,8 @@ export const remoteKeys = (issuer) => {
|
|
|
33
33
|
timeoutDuration: 3000,
|
|
34
34
|
cacheMaxAge: Infinity,
|
|
35
35
|
});
|
|
36
|
-
|
|
36
|
+
const held = () => set.jwks() !== undefined;
|
|
37
|
+
return { keys: keysOrOutage(set), read: () => retryOnce(set.reload), held };
|
|
37
38
|
};
|
|
38
39
|
const claimsOf = async (authorization, deps) => {
|
|
39
40
|
const [scheme = '', credential = '', ...rest] = authorization.split(' ');
|
|
@@ -13,7 +13,7 @@ const partsOf = (pkg, images, migrations) => {
|
|
|
13
13
|
parts['store'] = migrations === undefined ? { kind: store } : { kind: store, migrations };
|
|
14
14
|
return parts;
|
|
15
15
|
};
|
|
16
|
-
// From the manifest, the build, the images by digest and what the code serves
|
|
16
|
+
// From the manifest, the build, the images by digest and what the code serves.
|
|
17
17
|
export const recordOf = (pkg, build, images, extras = {}) => {
|
|
18
18
|
const { id, commands } = pkg.agentage;
|
|
19
19
|
const { commit, buildTime } = build;
|
package/dist/mcp/server.js
CHANGED
|
@@ -5,8 +5,7 @@ import { fail, isFailure } from '../envelope/envelope.js';
|
|
|
5
5
|
import {} from '../log/log.js';
|
|
6
6
|
import { prefixed } from './tools.js';
|
|
7
7
|
const text = (value) => [{ type: 'text', text: value }];
|
|
8
|
-
// A failed tool
|
|
9
|
-
// Outside a request (no agcid in scope) the error still carries a valid one, minted here.
|
|
8
|
+
// A failed tool: isError, the REST error shape, an agcid (minted outside a request); else INTERNAL.
|
|
10
9
|
const errorResult = (err, log) => {
|
|
11
10
|
const failure = isFailure(err) ? err : fail('INTERNAL', 'Internal error');
|
|
12
11
|
if (failure.status >= 500)
|
package/dist/store/migrate.js
CHANGED
|
@@ -5,8 +5,7 @@ import { migrate } from './migrations.js';
|
|
|
5
5
|
import { createStore } from './store.js';
|
|
6
6
|
// A migration may rewrite a table: minutes, not a request's seconds; the job itself has no retry.
|
|
7
7
|
const MIGRATE_TIMEOUTS = { connectMs: 10_000, statementMs: 300_000 };
|
|
8
|
-
// The api image's migrate
|
|
9
|
-
// Until the manifest is read the id is unknown; every line after it carries the extension.
|
|
8
|
+
// The api image's migrate job (`node dist/store/migrate.js`); lines before the manifest read 'unknown'.
|
|
10
9
|
let log = createLog({ extension: 'unknown', part: 'migrate' });
|
|
11
10
|
try {
|
|
12
11
|
const pkg = await readManifest(process.cwd());
|
|
@@ -12,7 +12,7 @@ const TEST_BUILD = { commit: '0'.repeat(40), buildTime: '2026-01-01T00:00:00Z' }
|
|
|
12
12
|
const testPackage = (ctx, parts) => ({
|
|
13
13
|
name: `@agentage/extension-${ctx.extension.id}`,
|
|
14
14
|
version: ctx.extension.version,
|
|
15
|
-
engines: { agentage: '^0.
|
|
15
|
+
engines: { agentage: '^0.3.0' },
|
|
16
16
|
agentage: { id: ctx.extension.id, parts },
|
|
17
17
|
});
|
|
18
18
|
const startOn = async (app) => {
|
package/dist/tool/up/compose.js
CHANGED
|
@@ -9,11 +9,11 @@ const ISSUER_ENTRY = 'node_modules/@agentage/extension/dist/tool/up/dev-issuer.j
|
|
|
9
9
|
// A local-only credential: the store has no published port, only the parts' network reaches it.
|
|
10
10
|
const STORE_PASSWORD = 'agentage-local';
|
|
11
11
|
const storeUrl = (id) => `postgres://${id}:${STORE_PASSWORD}@store:5432/${id}`;
|
|
12
|
-
const TOKENS = { AGENTAGE_ISSUER: UP_ISSUER, AGENTAGE_AUDIENCE: DEV_AUDIENCE };
|
|
13
12
|
// Only the ui under `up` names the dev issuer: its CSP then lets the harness ask it for a token.
|
|
14
13
|
const partEnv = (pkg, part) => ({
|
|
15
14
|
AGENTAGE_ORIGIN: `http://localhost:${GATEWAY_PORT}`,
|
|
16
|
-
|
|
15
|
+
AGENTAGE_ISSUER: UP_ISSUER,
|
|
16
|
+
AGENTAGE_AUDIENCE: DEV_AUDIENCE,
|
|
17
17
|
AGENTAGE_RECORD_FILE: RECORD_TARGET,
|
|
18
18
|
...(hasPostgres(pkg) && part !== 'ui' ? { AGENTAGE_STORE_URL: storeUrl(pkg.agentage.id) } : {}),
|
|
19
19
|
...(part === 'ui' ? { AGENTAGE_DEV_ISSUER: DEV_ISSUER } : {}),
|
|
@@ -23,10 +23,7 @@ const storeService = (id) => ({
|
|
|
23
23
|
image: STORE_IMAGE,
|
|
24
24
|
environment: { POSTGRES_USER: id, POSTGRES_PASSWORD: STORE_PASSWORD, POSTGRES_DB: id },
|
|
25
25
|
volumes: ['store:/var/lib/postgresql'],
|
|
26
|
-
healthcheck: {
|
|
27
|
-
test: ['CMD-SHELL', `pg_isready -U ${id} -d ${id}`],
|
|
28
|
-
...QUICK_CHECK,
|
|
29
|
-
},
|
|
26
|
+
healthcheck: { test: ['CMD-SHELL', `pg_isready -U ${id} -d ${id}`], ...QUICK_CHECK },
|
|
30
27
|
});
|
|
31
28
|
const HEALTHY = { condition: 'service_healthy' };
|
|
32
29
|
// api and mcp read the issuer's keys and revoked list before they serve.
|
|
@@ -34,30 +31,26 @@ const dependsOn = (pkg, part) => ({
|
|
|
34
31
|
...(hasPostgres(pkg) && part !== 'ui' ? { store: HEALTHY } : {}),
|
|
35
32
|
...(part === 'api' || part === 'mcp' ? { issuer: HEALTHY } : {}),
|
|
36
33
|
});
|
|
37
|
-
const partService = (input, part) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
...(Object.keys(depends_on).length > 0 ? { depends_on } : {}),
|
|
44
|
-
};
|
|
45
|
-
};
|
|
34
|
+
const partService = (input, part) => ({
|
|
35
|
+
image: input.tags[part],
|
|
36
|
+
environment: partEnv(input.pkg, part),
|
|
37
|
+
volumes: [`${input.recordFile}:${RECORD_TARGET}:ro`],
|
|
38
|
+
depends_on: dependsOn(input.pkg, part),
|
|
39
|
+
});
|
|
46
40
|
const fixedServices = (input, anyImage) => ({
|
|
47
41
|
gateway: {
|
|
48
42
|
image: TRAEFIK_IMAGE,
|
|
49
43
|
command: TRAEFIK_COMMAND,
|
|
50
44
|
ports: [`127.0.0.1:${GATEWAY_PORT}:80`],
|
|
51
45
|
volumes: [`${input.gatewayFile}:/etc/traefik/dynamic.yml:ro`],
|
|
52
|
-
healthcheck: {
|
|
53
|
-
test: ['CMD', 'wget', '-qO-', 'http://127.0.0.1:8082/ping'],
|
|
54
|
-
...QUICK_CHECK,
|
|
55
|
-
},
|
|
46
|
+
healthcheck: { test: ['CMD', 'wget', '-qO-', 'http://127.0.0.1:8082/ping'], ...QUICK_CHECK },
|
|
56
47
|
},
|
|
57
48
|
issuer: {
|
|
58
49
|
image: anyImage,
|
|
59
50
|
command: ['node', ISSUER_ENTRY],
|
|
60
51
|
ports: [`127.0.0.1:${ISSUER_PORT}:${PART_PORT}`],
|
|
52
|
+
healthcheck: { start_period: '15s', start_interval: '1s' }, // the image's test, each second
|
|
53
|
+
init: true, // node as PID 1 ignores SIGTERM: without it every stop waits 10 s
|
|
61
54
|
},
|
|
62
55
|
});
|
|
63
56
|
const migrateService = (input) => ({
|
|
@@ -81,9 +81,8 @@ const revokedList = ({ revoked }) => {
|
|
|
81
81
|
const ORIGINS = ['localhost', '127.0.0.1'].map((host) => `http://${host}:${GATEWAY_PORT}`);
|
|
82
82
|
const allowed = ({ settings }, req) => {
|
|
83
83
|
const host = (req.headers.host ?? '').replace(/:[0-9]+$/, '');
|
|
84
|
-
const origin = req.headers.origin;
|
|
85
84
|
const hosts = ['localhost', '127.0.0.1', new URL(settings.issuer).hostname];
|
|
86
|
-
return hosts.includes(host) &&
|
|
85
|
+
return hosts.includes(host) && [undefined, ...ORIGINS].includes(req.headers.origin);
|
|
87
86
|
};
|
|
88
87
|
const route = (issuer, req, res) => {
|
|
89
88
|
const at = `${req.method ?? ''} ${(req.url ?? '/').split('?')[0] ?? ''}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentage/extension",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "The agentage extension kit: create(ctx) per part (pages, ui, api, mcp, worker, cli), the store, /health, discovery, the envelope, and the build tool with up and token",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|