@nylorun/agents 0.18.0-beta → 0.19.1-beta

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/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # @nylorun/agents
2
2
 
3
+ ## 0.19.1-beta
4
+
5
+ ### Patch Changes
6
+
7
+ - Pin core to the tested release.
8
+ - Updated dependencies [212364b]
9
+ - @nylorun/core@0.19.0-beta
10
+
11
+ ## 0.19.0-beta
12
+
13
+ ### Minor Changes
14
+
15
+ - 7780b2f: **One client and connection layer.** The Project link and credentials are read one way, and every Runtime client shares one `/health` probe.
16
+
17
+ - `@nylorun/core`: `@nylorun/core/project` (Node only, the one Core subpath that imports Node modules) reads the Nylorun home, a local Tenant's Host root (`tenantHostRoot`), the Project root (`findProjectRoot`) and the Project link and credentials (`findLinkedProject`, `readProjectLink`, `readCredentialsFile`), validated with `ProjectLinkFileSchema` and `ProjectCredentialsFileSchema`; a broken or newer file throws `ProjectFileError`. `@nylorun/core/transport` (browser-safe) holds the `/health` compatibility probe (`checkHealth`), `parseProtocolRange` (on `ProtocolRangeSchema`, ignoring fields a newer Host adds), `describeIncompatibility`, `requestHeaders` and `readBody`.
18
+ - `@nylorun/agents`: `resolveConnection` and `createClient()` find the Project as every reader does: the nearest directory with `.nylorun/` from `cwd` upwards, never the home directory or above it (they used to walk to the filesystem root, and past a `.nylorun/` without a link). A link without `credentials.json`, or a link or credentials file that does not validate, is now `connection_missing` naming the file and `npx nylorun start`, instead of a raw file-system or parse error. `@nylorun/agents/client` loads the link reader only when `createClient()` resolves a connection, so it imports no Node module. The root entry re-exports `checkHealth`, `describeIncompatibility` and `parseProtocolRange`.
19
+ - `@nylorun/admin`: `createAdmin` reads the Project link with the same reader. A link that does not validate is now refused like one from an older nylorun (when nothing else names the Host root), and a linked Project's `credentials.json` that does not validate is `connection_missing` instead of being skipped. `@nylorun/admin/project` re-exports `@nylorun/core/project` for tools that depend on this package. The `/health` check and request headers come from `@nylorun/core/transport`.
20
+ - `nylorun`: reads the Project link and credentials with `@nylorun/core/project`; a credentials file whose keys are not 64 hex characters is replaced by `start`, as an unreadable one was. No command changes.
21
+ - `@nylorun/cli`: `nylo` reads the Project root, link and credentials through `@nylorun/admin/project` instead of its own copies, with the same messages. No command changes.
22
+ - `@nylorun/studio`: the server's Runtime compatibility probe is `checkHealth` from `@nylorun/agents`.
23
+
24
+ ### Patch Changes
25
+
26
+ - 070674d: **One source of truth for the manifest and Harness API schemas.**
27
+
28
+ - `@nylorun/core`: `Agent.from` (`agentFrom`) checks a manifest with `AgentManifestSchema`, the Runtime's own check, instead of a validator of its own, and refuses with `agent.build-failed` and the schema's messages (each prefixed with its path, such as `capabilities.0.tools.0.name: …`). It now refuses what the Runtime refuses at registration and it let through, such as unknown fields (it dropped them) or a tool or MCP server name that not every model provider accepts. The schema names a top-level `model` or `schemaVersion` and a capability's `model` as before, and the skill whose `files` are wrong (`Skill 'triage' files must include SKILL.md`). The rebuilt manifest is a frozen copy; the object passed in is no longer frozen. `AgentManifestSchema` and `WorkflowManifestSchema` are no longer force-cast to their types: the build checks that each accepts exactly `AgentManifest` and `WorkflowManifest`. `@nylorun/core/contracts` exports `McpServerManifestSchema`, `HEADER_NAME_PATTERN` and `LOOPBACK_HOSTS`. The Harness API's types (`@nylorun/core/harness-api`) are inferred from the schemas that validate its frames, so they match them: `TurnStart.manifest`, `TurnStart.checkpoint` and `RunRouting.rootManifest` are `object`, `WorkspaceCall.tool` is a sandbox tool name, `ABORT_REASONS` is a tuple, and properties are no longer `readonly` (arrays that were stay so).
29
+ - `@nylorun/harness`: `runDurable` checks the manifest once, when it rebuilds the agent, instead of twice. The unpublished, stale `schemas/manifest.schema.json` (manifest `schemaVersion` 2) is gone; `z.toJSONSchema(AgentManifestSchema)` is the manifest's JSON Schema.
30
+ - `@nylorun/agents`: a plugin's MCP servers are checked with core's `McpServerManifestSchema`, header-name pattern and loopback hosts. A server whose key is not a name every model provider accepts is skipped with a `plugin.mcp-server-skipped` warning, since the Runtime would refuse the manifest declaring it.
31
+ - `@nylorun/runtime`: the stack configuration reads its four listeners (`NYLORUN_LISTEN_*`, `NYLORUN_GATES_LISTEN_*`, `NYLORUN_HARNESS_LISTEN_*`, `NYLORUN_EGRESS_LISTEN_*`) with one parser; the variables and their errors are unchanged.
32
+
33
+ - Pin core to the tested release.
34
+ - Updated dependencies [7780b2f]
35
+ - Updated dependencies [070674d]
36
+ - @nylorun/core@0.18.0-beta
37
+
3
38
  ## 0.18.0-beta
4
39
 
5
40
  ### Major Changes
package/README.md CHANGED
@@ -86,9 +86,13 @@ only on the Runtime (a local `run()` reports `http.runtime-only`).
86
86
  2. Environment — if `NYLORUN_RUNTIME_URL` or `NYLORUN_SERVER_KEY` is set, both
87
87
  must be present
88
88
  3. Project link — `.nylorun/link.json` + `credentials.json` (the application
89
- key), written by `npx nylorun start`. A link from an older nylorun
90
- (format 0 to 2) fails with `connection_missing`: run `npx nylorun start` in
91
- the project again.
89
+ key), written by `npx nylorun start`, in the nearest directory with
90
+ `.nylorun/` from `cwd` upwards (never the home directory or above it, whose
91
+ `.nylorun/` is the Nylorun home). A link from an older nylorun
92
+ (format 0 to 2), a link without credentials, or a file that does not
93
+ validate fails with `connection_missing`: run `npx nylorun start` in the
94
+ project again. The Project files are read by `@nylorun/core/project`, as
95
+ `@nylorun/admin`, `nylorun` and `nylo` read them.
92
96
 
93
97
  Sources never mix. Partial environment fails with `connection_missing`. A
94
98
  Runtime serves one Tenant, so nothing names it: the `tenant` option is gone
package/dist/client.d.ts CHANGED
@@ -5,7 +5,8 @@ import type { Destination } from "./http.js";
5
5
  * Build a Tenant API client.
6
6
  * Explicit connection fields keep today's sync Transport resolution (including
7
7
  * environment fill-ins). With no connection fields, resolves via
8
- * `resolveConnection` (environment → project link).
8
+ * `resolveConnection` (environment → project link), which reads the Project's files: it is
9
+ * loaded only then, so this entry point imports no Node module (Studio's web bundle uses it).
9
10
  */
10
11
  export declare function createClient(destination: Destination): AgentsClient;
11
12
  export declare function createClient(destination?: undefined): Promise<AgentsClient>;
package/dist/client.js CHANGED
@@ -1,13 +1,15 @@
1
1
  export * from "./session-client.js";
2
2
  import { AgentsClient } from "./session-client.js";
3
- import { resolveConnection } from "./connection.js";
4
3
  export function createClient(destination = {}) {
5
4
  if (destination.url !== undefined || destination.key !== undefined) {
6
5
  return new AgentsClient(destination);
7
6
  }
8
- return resolveConnection().then((connection) => new AgentsClient({
9
- url: connection.url,
10
- key: connection.key,
11
- ...(destination.fetch ? { fetch: destination.fetch } : {}),
12
- }));
7
+ return import("./connection.js").then(async ({ resolveConnection }) => {
8
+ const connection = await resolveConnection();
9
+ return new AgentsClient({
10
+ url: connection.url,
11
+ key: connection.key,
12
+ ...(destination.fetch ? { fetch: destination.fetch } : {}),
13
+ });
14
+ });
13
15
  }
@@ -1,6 +1,4 @@
1
- import { readFile } from "node:fs/promises";
2
- import { dirname, join, resolve } from "node:path";
3
- import { ProjectCredentialsFileSchema, ProjectLinkFileSchema, } from "@nylorun/core/contracts";
1
+ import { ProjectFileError, findLinkedProject } from "@nylorun/core/project";
4
2
  import { env } from "./http.js";
5
3
  export class ConnectionError extends Error {
6
4
  code = "connection_missing";
@@ -17,37 +15,31 @@ function missing(tried) {
17
15
  function stripTrailingSlash(url) {
18
16
  return url.replace(/\/$/, "");
19
17
  }
20
- async function readProjectLink(startDir) {
21
- let dir = resolve(startDir);
22
- for (;;) {
23
- const nylorun = join(dir, ".nylorun");
24
- const linkPath = join(nylorun, "link.json");
25
- let linkRaw;
26
- try {
27
- linkRaw = await readFile(linkPath, "utf8");
28
- }
29
- catch (error) {
30
- if (error.code === "ENOENT") {
31
- const parent = dirname(dir);
32
- if (parent === dir)
33
- return undefined;
34
- dir = parent;
35
- continue;
36
- }
37
- throw error;
38
- }
39
- const link = ProjectLinkFileSchema.parse(JSON.parse(linkRaw));
40
- // Formats 0 to 2 are from older releases.
41
- if (link.format < 3)
42
- throw new ConnectionError(`connection_missing: the Project link at ${linkPath} is from an older nylorun. ` +
43
- `Run "npx nylorun start" in this project to link it again.`);
44
- const credentials = ProjectCredentialsFileSchema.parse(JSON.parse(await readFile(join(nylorun, "credentials.json"), "utf8")));
45
- return {
46
- url: stripTrailingSlash(link.hostUrl),
47
- key: credentials.applicationKey,
48
- source: "project-link",
49
- };
18
+ /** The application key of the linked Project from `cwd` upwards (`@nylorun/core/project`). */
19
+ function readProjectLink(cwd) {
20
+ let project;
21
+ try {
22
+ project = findLinkedProject(cwd);
23
+ }
24
+ catch (error) {
25
+ if (error instanceof ProjectFileError)
26
+ throw new ConnectionError(`connection_missing: ${error.message}`);
27
+ throw error;
50
28
  }
29
+ if (!project)
30
+ return undefined;
31
+ // Formats 0 to 2 are from older releases.
32
+ if (project.link.format < 3)
33
+ throw new ConnectionError(`connection_missing: the Project link at ${project.linkPath} is from an older nylorun. ` +
34
+ `Run "npx nylorun start" in this project to link it again.`);
35
+ if (!project.credentials)
36
+ throw new ConnectionError(`connection_missing: the Project at ${project.root} has a link but no credentials ` +
37
+ `(.nylorun/credentials.json). Run "npx nylorun start" in this project to link it again.`);
38
+ return {
39
+ url: project.link.hostUrl,
40
+ key: project.credentials.applicationKey,
41
+ source: "project-link",
42
+ };
51
43
  }
52
44
  /**
53
45
  * Resolve Tenant API connection: options → environment → project link (D§7.1). A connection is a
@@ -83,7 +75,7 @@ export async function resolveConnection(options) {
83
75
  }
84
76
  tried.push("project-link");
85
77
  const cwd = options?.cwd ?? (typeof process !== "undefined" ? process.cwd() : ".");
86
- const fromLink = await readProjectLink(cwd);
78
+ const fromLink = readProjectLink(cwd);
87
79
  if (fromLink)
88
80
  return fromLink;
89
81
  missing(tried);
package/dist/http.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { type Compatibility, type ProtocolRange } from "@nylorun/core/compatibility";
1
+ import { type ProtocolRange } from "@nylorun/core/compatibility";
2
+ import { type Incompatibility } from "@nylorun/core/transport";
2
3
  /**
3
4
  * Bearer tokens for a client that holds no Tenant key: a trusted issuer's tokens (Host feature
4
5
  * `trusted-issuers`), from the operator's identity provider. `get` returns a current token and
@@ -26,9 +27,7 @@ export declare class RuntimeError extends Error {
26
27
  readonly body: unknown;
27
28
  constructor(status: number, body: unknown);
28
29
  }
29
- export type IncompatibleReason = Extract<Compatibility, {
30
- ok: false;
31
- }>;
30
+ export type IncompatibleReason = Incompatibility;
32
31
  /** Host protocol is outside the client's supported range or missing required features. */
33
32
  export declare class IncompatibleRuntimeError extends Error {
34
33
  readonly compatibility: IncompatibleReason;
package/dist/http.js CHANGED
@@ -1,4 +1,5 @@
1
- import { PROTOCOL_FEATURES, PROTOCOL_HEADER, PROTOCOL_VERSION, checkCompatibility, } from "@nylorun/core/compatibility";
1
+ import { PROTOCOL_FEATURES, } from "@nylorun/core/compatibility";
2
+ import { advertisedProtocol, checkHealth, clientCompatibility, describeIncompatibility, readBody, requestHeaders, } from "@nylorun/core/transport";
2
3
  export function env(name) {
3
4
  return typeof process !== "undefined" ? process.env[name] : undefined;
4
5
  }
@@ -18,10 +19,7 @@ export class IncompatibleRuntimeError extends Error {
18
19
  host;
19
20
  constructor(compatibility) {
20
21
  const remedy = remedyFor(compatibility);
21
- const detail = compatibility.reason === "version"
22
- ? `client protocol ${compatibility.client} is outside Host range ${compatibility.host.min}–${compatibility.host.max}`
23
- : `Host is missing required features: ${compatibility.missing.join(", ")}`;
24
- super(`Incompatible Runtime: ${detail}. ${remedy}`);
22
+ super(`Incompatible Runtime: ${describeIncompatibility(compatibility)}. ${remedy}`);
25
23
  this.name = "IncompatibleRuntimeError";
26
24
  this.compatibility = compatibility;
27
25
  this.remedy = remedy;
@@ -37,21 +35,6 @@ function remedyFor(compatibility) {
37
35
  }
38
36
  return "Upgrade the Runtime Host with nylorun runtime restart from a newer CLI, or install a matching older client (npm i -D @nylorun/cli@<compatible>).";
39
37
  }
40
- function parseProtocolRange(value) {
41
- if (!value || typeof value !== "object" || Array.isArray(value))
42
- return undefined;
43
- const record = value;
44
- if (typeof record.min !== "number" ||
45
- typeof record.max !== "number" ||
46
- !Array.isArray(record.features) ||
47
- !record.features.every((f) => typeof f === "string"))
48
- return undefined;
49
- return {
50
- min: record.min,
51
- max: record.max,
52
- features: record.features,
53
- };
54
- }
55
38
  export class Transport {
56
39
  url;
57
40
  /** The Tenant key; empty for a client that uses issuer tokens. */
@@ -99,36 +82,23 @@ export class Transport {
99
82
  async ensureCompatible(signal) {
100
83
  if (this.check.compatible)
101
84
  return;
102
- const response = await this.fetcher(`${this.url}/health`, {
103
- method: "GET",
104
- redirect: "error",
105
- signal,
85
+ const health = await checkHealth(this.url, {
86
+ fetch: this.fetcher,
87
+ ...(signal ? { signal } : {}),
106
88
  });
107
- const text = await response.text();
108
- let body = text;
109
- try {
110
- body = JSON.parse(text);
111
- }
112
- catch {
113
- /* keep text */
114
- }
115
- if (!response.ok)
116
- throw new RuntimeError(response.status, body);
117
- const protocol = parseProtocolRange(body && typeof body === "object" && !Array.isArray(body)
118
- ? body.protocol
119
- : undefined);
120
- if (!protocol)
89
+ if (health.result === "failed")
90
+ throw new RuntimeError(health.status, health.body);
91
+ if (health.result === "unadvertised")
121
92
  throw new IncompatibleRuntimeError({
122
93
  ok: false,
123
94
  reason: "feature",
124
95
  missing: [...PROTOCOL_FEATURES],
125
96
  host: { min: 0, max: 0, features: [] },
126
97
  });
127
- const result = checkCompatibility({ version: PROTOCOL_VERSION, required: [...PROTOCOL_FEATURES] }, protocol);
128
- if (!result.ok)
129
- throw new IncompatibleRuntimeError(result);
130
- this.check.features = [...protocol.features];
131
- this.check.range = protocol;
98
+ if (health.result === "incompatible")
99
+ throw new IncompatibleRuntimeError(health.compatibility);
100
+ this.check.features = [...health.protocol.features];
101
+ this.check.range = health.protocol;
132
102
  this.check.compatible = true;
133
103
  }
134
104
  /** Throws `IncompatibleRuntimeError` before anything is sent when the Host lacks `feature`. */
@@ -154,8 +124,8 @@ export class Transport {
154
124
  const bearer = this.token
155
125
  ? await this.token.get(init.signal ?? undefined)
156
126
  : this.key;
157
- headers.set("Authorization", `Bearer ${bearer}`);
158
- headers.set(PROTOCOL_HEADER, String(PROTOCOL_VERSION));
127
+ for (const [name, value] of Object.entries(requestHeaders(bearer)))
128
+ headers.set(name, value);
159
129
  // JSON unless the caller says otherwise (an artifact upload sends the file's type).
160
130
  if (init.body && !headers.has("content-type"))
161
131
  headers.set("Content-Type", "application/json");
@@ -183,35 +153,17 @@ export class Transport {
183
153
  await this.ensureCompatible(init.signal === null ? undefined : init.signal);
184
154
  return this.request(path, init, { retried426: true });
185
155
  }
186
- const text = await response.text();
187
- let body = text;
188
- try {
189
- body = JSON.parse(text);
190
- }
191
- catch {
192
- /* keep text */
193
- }
194
- const protocol = parseProtocolRange(body && typeof body === "object" && !Array.isArray(body)
195
- ? body.protocol
196
- : undefined);
156
+ const body = await readBody(response);
157
+ const protocol = advertisedProtocol(body);
197
158
  if (protocol) {
198
- const result = checkCompatibility({ version: PROTOCOL_VERSION, required: [...PROTOCOL_FEATURES] }, protocol);
159
+ const result = clientCompatibility(protocol);
199
160
  if (!result.ok)
200
161
  throw new IncompatibleRuntimeError(result);
201
162
  }
202
163
  throw new RuntimeError(426, body);
203
164
  }
204
- if (!response.ok) {
205
- const text = await response.text();
206
- let body = text;
207
- try {
208
- body = JSON.parse(text);
209
- }
210
- catch {
211
- /* keep text */
212
- }
213
- throw new RuntimeError(response.status, body);
214
- }
165
+ if (!response.ok)
166
+ throw new RuntimeError(response.status, await readBody(response));
215
167
  return response;
216
168
  }
217
169
  /**
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, PROTOCOL_HEADER, SUBJECT_HEADER, SCOPES_HEADER, SESSION_ID_HEADER, TURN_ID_HEADER, AGENT_ID_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
2
2
  export type { ProtocolFeature, ProtocolRange, Compatibility, ErrorCode, } from "@nylorun/core/compatibility";
3
+ export { checkHealth, describeIncompatibility, parseProtocolRange, } from "@nylorun/core/transport";
4
+ export type { HealthCheck, Incompatibility } from "@nylorun/core/transport";
3
5
  export { Agent, AgentBuilder } from "./builder.js";
4
6
  export { AgentBuildError, AgentLifecycleError, VerdictSchema, isVerdict, WorkflowBuildError, isBuiltWorkflow, capability, CapabilityBuilder, flow, tool, http, defineSchema, ToolError, } from "@nylorun/core/define";
5
7
  export type { FlowAgentBuilder, Flow, Named, LoopOptions, StageOptions, CapabilityIdentity, HttpTarget, } from "@nylorun/core/define";
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // Definition authoring and wire contracts are the only harness runtime imports.
2
2
  export { hashManifest, PROTOCOL_VERSION, PROTOCOL_FEATURES, HOST_PROTOCOL, DEFINITION_SCHEMA_VERSION, PROTOCOL_HEADER, SUBJECT_HEADER, SCOPES_HEADER, SESSION_ID_HEADER, TURN_ID_HEADER, AGENT_ID_HEADER, TENANT_ID_PATTERN, isTenantId, newTenantId, newPrincipalId, checkCompatibility, compareVersions, ERROR_CODES, } from "@nylorun/core/compatibility";
3
+ // The `/health` compatibility probe every Runtime client shares (Studio's server uses it).
4
+ export { checkHealth, describeIncompatibility, parseProtocolRange, } from "@nylorun/core/transport";
3
5
  export { Agent, AgentBuilder } from "./builder.js";
4
6
  export { AgentBuildError, AgentLifecycleError, VerdictSchema, isVerdict, WorkflowBuildError, isBuiltWorkflow, capability, CapabilityBuilder, flow, tool, http, defineSchema, ToolError, } from "@nylorun/core/define";
5
7
  export { SUBJECT_SCOPES } from "@nylorun/core/contracts";
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
2
2
  import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
3
  import { stdioMcpRefusal } from "@nylorun/core/define";
4
+ import { HEADER_NAME_PATTERN, LOOPBACK_HOSTS, McpServerManifestSchema, } from "@nylorun/core/contracts";
4
5
  import { loadSkillsFromDirectory } from "../skills/load.js";
5
6
  export const PLUGIN_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
6
7
  export const MCP_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json";
@@ -211,38 +212,34 @@ function validateServer(name, value) {
211
212
  return validateRemote(name, value);
212
213
  return `type '${value.type}' is not streamable-http or sse`;
213
214
  }
215
+ /**
216
+ * A plugin's server as a manifest declares it (`McpServerManifestSchema`), named by its key. A
217
+ * plugin may set only type, url and headers, and the URL and headers must be fit to send.
218
+ */
214
219
  function validateRemote(name, value) {
215
220
  const allowed = new Set(["type", "url", "headers"]);
216
221
  const unknown = Object.keys(value).filter((key) => !allowed.has(key));
217
222
  if (unknown.length > 0)
218
223
  return `${value.type} servers take only type, url and headers, not ${unknown.join(", ")}`;
219
- if (typeof value.url !== "string")
220
- return "url must be a string";
221
- const problem = remoteUrlProblem(value.url);
224
+ const parsed = McpServerManifestSchema.safeParse({ ...value, name });
225
+ if (!parsed.success)
226
+ return parsed.error.issues
227
+ .map((issue) => (issue.path.length ? `${issue.path.join(".")}: ${issue.message}` : issue.message))
228
+ .join("; ");
229
+ const server = parsed.data;
230
+ const problem = remoteUrlProblem(server.url);
222
231
  if (problem)
223
232
  return problem;
224
- let headers;
225
- if (value.headers !== undefined) {
226
- if (!isRecord(value.headers))
227
- return "headers must be an object";
228
- const seen = new Set();
229
- headers = {};
230
- for (const [key, item] of Object.entries(value.headers)) {
231
- if (typeof item !== "string" || !isHeaderName(key) || /[\r\n\0]/.test(item))
232
- return `header '${key}' is not a valid header`;
233
- const folded = key.toLowerCase();
234
- if (seen.has(folded))
235
- return `header '${key}' is repeated`;
236
- seen.add(folded);
237
- headers[key] = item;
238
- }
233
+ const seen = new Set();
234
+ for (const [key, item] of Object.entries(server.headers ?? {})) {
235
+ if (!HEADER_NAME_PATTERN.test(key) || /[\r\n\0]/.test(item))
236
+ return `header '${key}' is not a valid header`;
237
+ const folded = key.toLowerCase();
238
+ if (seen.has(folded))
239
+ return `header '${key}' is repeated`;
240
+ seen.add(folded);
239
241
  }
240
- return {
241
- name,
242
- type: value.type,
243
- url: value.url,
244
- ...(headers === undefined ? {} : { headers }),
245
- };
242
+ return server;
246
243
  }
247
244
  /**
248
245
  * Why `value` is not a remote server URL: https, or plain http to this machine's loopback (a
@@ -264,22 +261,10 @@ function remoteUrlProblem(value) {
264
261
  return undefined;
265
262
  if (url.protocol !== "http:")
266
263
  return `url must be https, not ${url.protocol.slice(0, -1)}`;
267
- if (isLoopback(url.hostname))
264
+ if (LOOPBACK_HOSTS.test(url.hostname))
268
265
  return undefined;
269
266
  return `plain http is allowed only for localhost, 127.0.0.1 or [::1]; use https for ${url.hostname}`;
270
267
  }
271
- function isLoopback(hostname) {
272
- const host = hostname.replace(/^\[|\]$/g, "");
273
- if (host === "localhost" || host === "::1")
274
- return true;
275
- const match = /^127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
276
- if (!match)
277
- return false;
278
- return match.slice(1).every((part) => Number(part) <= 255);
279
- }
280
- function isHeaderName(name) {
281
- return /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(name);
282
- }
283
268
  function isPluginName(value) {
284
269
  if (typeof value !== "string" || value.length < 1 || value.length > 64)
285
270
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/agents",
3
- "version": "0.18.0-beta",
3
+ "version": "0.19.1-beta",
4
4
  "description": "Nylorun agent definitions and session client",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -43,7 +43,7 @@
43
43
  "test": "vitest run --passWithNoTests"
44
44
  },
45
45
  "dependencies": {
46
- "@nylorun/core": "0.17.0-beta",
46
+ "@nylorun/core": "0.19.0-beta",
47
47
  "gray-matter": "^4.0.3"
48
48
  },
49
49
  "peerDependencies": {