@agentage/extension 0.4.1 → 0.4.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.
@@ -2,7 +2,7 @@ import { parseArgs } from 'node:util';
2
2
  import {} from '../manifest/manifest.schema.js';
3
3
  export class UsageError extends Error {
4
4
  }
5
- export const isOptional = (arg) => arg.optional === true || arg.required === false;
5
+ export const isOptional = (arg) => arg.required === false;
6
6
  const coerce = (arg, raw) => {
7
7
  if (arg.type === 'boolean')
8
8
  return raw === true || raw === 'true';
@@ -44,7 +44,6 @@ declare const CommandSchema: z.ZodObject<{
44
44
  boolean: "boolean";
45
45
  integer: "integer";
46
46
  }>;
47
- optional: z.ZodOptional<z.ZodBoolean>;
48
47
  required: z.ZodOptional<z.ZodBoolean>;
49
48
  description: z.ZodOptional<z.ZodString>;
50
49
  }, z.core.$strict>>>;
@@ -99,7 +98,6 @@ export declare const PackageSchema: z.ZodObject<{
99
98
  boolean: "boolean";
100
99
  integer: "integer";
101
100
  }>;
102
- optional: z.ZodOptional<z.ZodBoolean>;
103
101
  required: z.ZodOptional<z.ZodBoolean>;
104
102
  description: z.ZodOptional<z.ZodString>;
105
103
  }, z.core.$strict>>>;
@@ -27,7 +27,6 @@ const CommandSchema = z.strictObject({
27
27
  .array(z.strictObject({
28
28
  name: CommandNameSchema,
29
29
  type: ArgTypeSchema,
30
- optional: z.boolean().optional(),
31
30
  required: z.boolean().optional(),
32
31
  description: z.string().min(1).max(200).optional(),
33
32
  }))
@@ -39,10 +39,11 @@ const guard = async (options, req, body) => {
39
39
  if (!options.tokenCheck) {
40
40
  if (listing.length > 0)
41
41
  throw fail('UNAUTHORIZED', 'tools/list needs a token');
42
- return;
42
+ return false;
43
43
  }
44
- if (needed.length > 0)
45
- await options.tokenCheck(req.get('authorization'), strongest(needed));
44
+ if (needed.length === 0)
45
+ return false;
46
+ return (await options.tokenCheck(req.get('authorization'), strongest(needed))) !== null;
46
47
  };
47
48
  const webRequest = (req, body) => {
48
49
  const headers = new Headers();
@@ -56,9 +57,12 @@ const webRequest = (req, body) => {
56
57
  body,
57
58
  });
58
59
  };
59
- const send = async (response, res) => {
60
+ const send = async (response, res, auth) => {
60
61
  res.status(response.status);
61
62
  response.headers.forEach((value, key) => void res.setHeader(key, value));
63
+ // After the SDK headers: its SSE answer sets no-cache, which a token-checked answer overrides.
64
+ if (auth)
65
+ res.setHeader('Cache-Control', 'no-store');
62
66
  if (!response.body)
63
67
  return void res.end();
64
68
  await pipeline(Readable.fromWeb(response.body), res);
@@ -69,8 +73,8 @@ const handle = (handler, options) => async (req, res) => {
69
73
  // The SDK reads a body again when given none (a BOM, say): it must only run what guard read.
70
74
  if (message === undefined)
71
75
  return void res.status(400).json(PARSE_ERROR);
72
- await guard(options, req, message);
73
- await send(await handler.fetch(webRequest(req, body), { parsedBody: message }), res);
76
+ const auth = await guard(options, req, message);
77
+ await send(await handler.fetch(webRequest(req, body), { parsedBody: message }), res, auth);
74
78
  };
75
79
  const methodNotAllowed = (_req, res) => {
76
80
  res.setHeader('Allow', 'POST');
@@ -57,11 +57,14 @@ export const createPartApp = (options) => {
57
57
  // The committed OpenAPI document, bare JSON and anonymous like discovery (api-standards section 1).
58
58
  const openapiHandler = (doc) => (_req, res) => void res.json(doc);
59
59
  // Before the body is read: each request checked for the access its OpenAPI operation declares.
60
- const routeGuard = (options) => async (req, _res, next) => {
60
+ const routeGuard = (options) => async (req, res, next) => {
61
61
  if (!options.tokenCheck)
62
62
  return next();
63
63
  const access = routeAccess(options.openapi, req.method, `/api/v1${req.path}`);
64
- setCaller(req, await options.tokenCheck(req.get('authorization'), access));
64
+ const caller = await options.tokenCheck(req.get('authorization'), access);
65
+ if (caller)
66
+ res.setHeader('Cache-Control', 'no-store');
67
+ setCaller(req, caller);
65
68
  next();
66
69
  };
67
70
  export const createApiApp = (options) => createPartApp({
@@ -19,7 +19,19 @@ const toFailure = (err) => {
19
19
  export const notFound = () => {
20
20
  throw fail('NOT_FOUND', 'Not found');
21
21
  };
22
+ const GONE_CODES = ['ERR_STREAM_PREMATURE_CLOSE', 'ECONNABORTED'];
23
+ // A client abort leaves the socket destroyed; the same code from an app-side stream does not.
24
+ const clientGone = (err, res) => GONE_CODES.includes(err?.code) && res.socket?.destroyed !== false;
22
25
  export const errorHandler = (log) => (err, _req, res, _next) => {
26
+ // A gone client or a started answer cannot take an envelope: close it, never write twice.
27
+ const gone = clientGone(err, res);
28
+ if (gone || res.headersSent) {
29
+ if (gone)
30
+ log.warn('client closed early');
31
+ else
32
+ log.error(err);
33
+ return void res.destroy();
34
+ }
23
35
  const failure = toFailure(err);
24
36
  if (failure.status >= 500)
25
37
  log.error(err);
package/dist/tool/exec.js CHANGED
@@ -51,7 +51,8 @@ const tail = (text) => text ? `\n${text.trim().split('\n').slice(-15).join('\n')
51
51
  export const mustRun = async (run, command, args, options) => {
52
52
  const result = await run(command, args, options);
53
53
  if (result.code !== 0) {
54
- throw new Error(`${command} ${args[0] ?? ''} failed with exit code ${result.code}${tail(result.stderr)}`);
54
+ // tsc writes its diagnostics to stdout, so an empty stderr falls back to it.
55
+ throw new Error(`${command} ${args[0] ?? ''} failed with exit code ${result.code}${tail(result.stderr?.trim() ? result.stderr : result.stdout)}`);
55
56
  }
56
57
  return result.stdout;
57
58
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentage/extension",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
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",
@@ -213,9 +213,6 @@
213
213
  "boolean"
214
214
  ]
215
215
  },
216
- "optional": {
217
- "type": "boolean"
218
- },
219
216
  "required": {
220
217
  "type": "boolean"
221
218
  },