@agentxm/extension-model 0.34.0 → 0.36.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.
Files changed (34) hide show
  1. package/dist/src/unstable/agent-capabilities/derive.js +1 -3
  2. package/dist/src/unstable/agent-capabilities/identity.d.ts +2 -0
  3. package/dist/src/unstable/agent-capabilities/identity.js +3 -0
  4. package/dist/src/unstable/agent-capabilities/index.d.ts +1 -1
  5. package/dist/src/unstable/agent-capabilities/index.js +1 -1
  6. package/dist/src/unstable/agents/registry.d.ts +3 -17
  7. package/dist/src/unstable/agents/registry.js +2 -17
  8. package/dist/src/unstable/agents/types.d.ts +5 -7
  9. package/dist/src/unstable/agents/types.js +2 -4
  10. package/dist/src/unstable/extensions/common.d.ts +20 -8
  11. package/dist/src/unstable/extensions/common.js +29 -32
  12. package/dist/src/unstable/extensions/deprecation.d.ts +24 -3
  13. package/dist/src/unstable/extensions/deprecation.js +22 -3
  14. package/dist/src/unstable/extensions/index.d.ts +1 -1
  15. package/dist/src/unstable/extensions/index.js +1 -1
  16. package/dist/src/unstable/extensions/name-patterns.d.ts +5 -3
  17. package/dist/src/unstable/extensions/name-patterns.js +31 -32
  18. package/dist/src/unstable/extensions/refs/extension-ref.d.ts +9 -0
  19. package/dist/src/unstable/extensions/refs/extension-ref.js +25 -1
  20. package/dist/src/unstable/extensions/registry-source.d.ts +12 -2
  21. package/dist/src/unstable/extensions/registry-source.js +17 -32
  22. package/dist/src/unstable/path-types.d.ts +6 -0
  23. package/dist/src/unstable/path-types.js +20 -0
  24. package/dist/src/unstable/sources/forge-grammar.d.ts +46 -10
  25. package/dist/src/unstable/sources/forge-grammar.js +153 -33
  26. package/dist/src/unstable/sources/parser.d.ts +9 -7
  27. package/dist/src/unstable/sources/parser.js +14 -31
  28. package/dist/src/unstable/sources/printer.js +10 -35
  29. package/dist/src/unstable/sources/source-host-provider.d.ts +10 -6
  30. package/dist/src/unstable/sources/types.d.ts +1 -93
  31. package/dist/src/unstable/sources/types.js +1 -48
  32. package/dist/src/unstable/version-constraints/version-constraints.d.ts +9 -11
  33. package/dist/src/unstable/version-constraints/version-constraints.js +14 -43
  34. package/package.json +3 -3
@@ -1,9 +1,10 @@
1
1
  import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
2
3
  import * as Result from "effect/Result";
3
4
  import * as Schema from "effect/Schema";
4
5
  import * as SchemaIssue from "effect/SchemaIssue";
5
6
  import * as SchemaTransformation from "effect/SchemaTransformation";
6
- import { ExtensionNameSchema, ExtensionTypePluralSchema } from "./common.js";
7
+ import { ExtensionNameSchema, ExtensionTypePluralSchema, splitExtensionReference, } from "./common.js";
7
8
  import { HandleSchema } from "./handle.js";
8
9
  import { VersionRangeSchema } from "../version-constraints/version-constraints.js";
9
10
  const invalidRegistrySourcePattern = (input) => `Expected registry source pattern in @handle, @handle/<plural-type>, or @handle/<plural-type>/<name>@<constraint> form, got: ${input}`;
@@ -44,26 +45,12 @@ export const RegistrySourceRefPartsSchema = Schema.Struct({
44
45
  });
45
46
  const decodeRegistrySourcePatternParts = Schema.decodeUnknownResult(RegistrySourcePatternPartsSchema);
46
47
  const decodeRegistrySourceRefParts = Schema.decodeUnknownResult(RegistrySourceRefPartsSchema);
47
- const parseNameAndConstraintSegment = (segment) => {
48
- const atIndex = segment.indexOf("@");
49
- if (atIndex === 0) {
50
- return undefined;
51
- }
52
- if (atIndex === -1) {
53
- return { name: segment };
54
- }
55
- const name = segment.slice(0, atIndex);
56
- const versionRange = segment.slice(atIndex + 1);
57
- if (name.length === 0 || versionRange.length === 0) {
58
- return undefined;
59
- }
60
- return { name, versionRange };
61
- };
62
48
  export const parseRegistrySourcePatternParts = (input) => {
63
49
  if (!input.startsWith("@")) {
64
50
  return undefined;
65
51
  }
66
- const segments = input.split("/");
52
+ const { name: reference, constraint } = splitExtensionReference(input);
53
+ const segments = reference.split("/");
67
54
  if (segments.length < 1 || segments.length > 3) {
68
55
  return undefined;
69
56
  }
@@ -83,17 +70,9 @@ export const parseRegistrySourcePatternParts = (input) => {
83
70
  }
84
71
  else {
85
72
  const type = segments[1];
86
- const third = segments[2];
87
- if (type !== undefined && third !== undefined) {
88
- const parsedName = parseNameAndConstraintSegment(third);
89
- if (parsedName !== undefined) {
90
- candidate = {
91
- owner,
92
- type,
93
- name: parsedName.name,
94
- versionRange: parsedName.versionRange,
95
- };
96
- }
73
+ const name = segments[2];
74
+ if (type !== undefined && name !== undefined) {
75
+ candidate = { owner, type, name, versionRange: constraint };
97
76
  }
98
77
  }
99
78
  if (candidate === undefined) {
@@ -102,25 +81,31 @@ export const parseRegistrySourcePatternParts = (input) => {
102
81
  const result = decodeRegistrySourcePatternParts(candidate);
103
82
  return Result.isSuccess(result) ? result.success : undefined;
104
83
  };
105
- /** Parse a Registry locator, assigning unqualified input to the built-in agentxm source. */
84
+ /**
85
+ * Parse a Registry locator, keeping the configured source name it spells
86
+ * (`<source>:@owner/...`) and leaving an unqualified locator unbound.
87
+ */
106
88
  export const parseSourceQualifiedRegistrySourcePatternParts = (input) => {
107
89
  const unqualified = parseRegistrySourcePatternParts(input);
108
90
  if (unqualified !== undefined) {
109
- return { ...unqualified, sourceName: "agentxm" };
91
+ return { ...unqualified, sourceName: Option.none() };
110
92
  }
111
93
  const separator = input.indexOf(":");
112
94
  if (separator <= 0)
113
95
  return undefined;
114
96
  const sourceName = input.slice(0, separator);
115
97
  const qualified = parseRegistrySourcePatternParts(input.slice(separator + 1));
116
- return qualified === undefined ? undefined : { ...qualified, sourceName };
98
+ return qualified === undefined
99
+ ? undefined
100
+ : { ...qualified, sourceName: Option.some(sourceName) };
117
101
  };
118
102
  export const parseRegistrySourceRef = (input) => {
119
103
  const parsed = parseSourceQualifiedRegistrySourcePatternParts(input);
120
104
  if (parsed?.type === undefined || parsed.name === undefined) {
121
105
  return undefined;
122
106
  }
123
- const result = decodeRegistrySourceRefParts(parsed);
107
+ const { sourceName: _sourceName, ...parts } = parsed;
108
+ const result = decodeRegistrySourceRefParts(parts);
124
109
  return Result.isSuccess(result) ? result.success : undefined;
125
110
  };
126
111
  export const formatRegistrySourcePatternParts = (value) => {
@@ -26,4 +26,10 @@ export declare const makeWorkspaceRelativeSourcePath: (path: Path.Path, workspac
26
26
  * prefix is not mistaken for a child.
27
27
  */
28
28
  export declare const isPathSafe: (path: Path.Path, base: string, target: string) => boolean;
29
+ /** Resolve a child against its base and reject paths that escape it. */
30
+ export declare const safeChildPath: (path: Path.Path, base: string, target: string) => Option.Option<AbsolutePath>;
31
+ /** Relative-path containment, including the parent itself. */
32
+ export declare const isWithinOrEqual: (path: Path.Path, parent: string, child: string) => boolean;
33
+ /** Relative-path containment that excludes the parent itself. */
34
+ export declare const isStrictlyWithin: (path: Path.Path, parent: string, child: string) => boolean;
29
35
  //# sourceMappingURL=path-types.d.ts.map
@@ -56,4 +56,24 @@ export const isPathSafe = (path, base, target) => {
56
56
  const resolvedTarget = path.resolve(target);
57
57
  return resolvedTarget === resolvedBase || resolvedTarget.startsWith(resolvedBase + path.sep);
58
58
  };
59
+ /** Resolve a child against its base and reject paths that escape it. */
60
+ export const safeChildPath = (path, base, target) => {
61
+ const resolvedBase = path.resolve(base);
62
+ const resolvedTarget = path.isAbsolute(target)
63
+ ? path.resolve(target)
64
+ : path.resolve(resolvedBase, target);
65
+ return isPathSafe(path, resolvedBase, resolvedTarget)
66
+ ? Option.some(makeAbsolutePath(path, resolvedTarget))
67
+ : Option.none();
68
+ };
69
+ /** Relative-path containment, including the parent itself. */
70
+ export const isWithinOrEqual = (path, parent, child) => {
71
+ const relative = path.relative(path.resolve(parent), path.resolve(child));
72
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
73
+ };
74
+ /** Relative-path containment that excludes the parent itself. */
75
+ export const isStrictlyWithin = (path, parent, child) => {
76
+ const relative = path.relative(path.resolve(parent), path.resolve(child));
77
+ return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
78
+ };
59
79
  //# sourceMappingURL=path-types.js.map
@@ -1,22 +1,58 @@
1
1
  /**
2
- * Pure per-forge source shorthand printing grammar.
2
+ * Forge coordinate, clone URL, and browser URL grammar.
3
3
  *
4
- * Each printer formats source params as the forge's canonical shorthand
5
- * string. Probing and acquisition stay with the source-resolution providers;
6
- * only the string grammar lives here.
4
+ * Probing and acquisition stay with source resolution; the model owns the
5
+ * strings and URLs that identify each forge repository.
7
6
  *
8
7
  * @experimental This API is unstable and may change without notice.
9
8
  * @packageDocumentation
10
9
  */
11
- import type { AzureReposSourceParams, BitbucketSourceParams, GitHubSourceParams, GitLabSourceParams, LocalSourceParams } from "./types.js";
10
+ import * as Option from "effect/Option";
11
+ import * as Result from "effect/Result";
12
12
  /** @experimental */
13
- export declare const printGitHubSource: (source: GitHubSourceParams, sourceName?: string) => string;
13
+ export declare const FORGE_PREFIXES: readonly ["github", "gitlab", "bitbucket", "azurerepos"];
14
14
  /** @experimental */
15
- export declare const printGitLabSource: (source: GitLabSourceParams, sourceName?: string) => string;
15
+ export type ForgePrefix = (typeof FORGE_PREFIXES)[number];
16
16
  /** @experimental */
17
- export declare const printBitbucketSource: (source: BitbucketSourceParams, sourceName?: string) => string;
17
+ export declare const isForgePrefix: (value: string) => value is ForgePrefix;
18
18
  /** @experimental */
19
- export declare const printAzureReposSource: (source: AzureReposSourceParams, sourceName?: string) => string;
19
+ export interface ForgeCoordinate {
20
+ readonly forge: ForgePrefix;
21
+ readonly repository: string;
22
+ readonly ref: Option.Option<string>;
23
+ readonly subPath: Option.Option<string>;
24
+ }
25
+ interface BrowserPath {
26
+ readonly repository: string;
27
+ readonly ref?: string;
28
+ readonly subPath?: string;
29
+ }
20
30
  /** @experimental */
21
- export declare const printLocalSource: (source: LocalSourceParams) => string;
31
+ export interface Forge {
32
+ readonly prefix: ForgePrefix;
33
+ readonly hostname: string;
34
+ readonly repositorySegments: (count: number) => boolean;
35
+ readonly cloneUrl: (repository: string) => URL;
36
+ readonly repositoryFromCloneUrl: (pathname: string) => string | undefined;
37
+ readonly parseBrowserPath: (pathname: string) => BrowserPath | undefined;
38
+ }
39
+ /** @experimental */
40
+ export declare const FORGES: Record<ForgePrefix, Forge>;
41
+ /** @experimental */
42
+ export declare const forgeForHostname: (hostname: string) => Forge | undefined;
43
+ /** @experimental */
44
+ export declare const parseForgeCoordinate: (forge: ForgePrefix, body: string) => Result.Result<ForgeCoordinate, {
45
+ readonly reason: string;
46
+ }>;
47
+ /** @experimental */
48
+ export declare const printForgeCoordinateBody: (coordinate: ForgeCoordinate) => string;
49
+ /** @experimental */
50
+ export declare const printForgeCoordinate: (coordinate: ForgeCoordinate) => string;
51
+ /** @experimental */
52
+ export declare const forgeCloneUrl: (coordinate: ForgeCoordinate) => URL;
53
+ /** @experimental */
54
+ export declare const forgeCoordinateFromGitUrl: (url: URL, ref: Option.Option<string>, subPath: Option.Option<string>) => Option.Option<ForgeCoordinate>;
55
+ /** @experimental */
56
+ export declare const parseForgeBrowserUrl: (url: URL) => Option.Option<ForgeCoordinate>;
57
+ export {};
22
58
  //# sourceMappingURL=forge-grammar.d.ts.map
@@ -1,50 +1,170 @@
1
1
  /**
2
- * Pure per-forge source shorthand printing grammar.
2
+ * Forge coordinate, clone URL, and browser URL grammar.
3
3
  *
4
- * Each printer formats source params as the forge's canonical shorthand
5
- * string. Probing and acquisition stay with the source-resolution providers;
6
- * only the string grammar lives here.
4
+ * Probing and acquisition stay with source resolution; the model owns the
5
+ * strings and URLs that identify each forge repository.
7
6
  *
8
7
  * @experimental This API is unstable and may change without notice.
9
8
  * @packageDocumentation
10
9
  */
11
10
  import * as Option from "effect/Option";
11
+ import * as Result from "effect/Result";
12
+ import * as Schema from "effect/Schema";
13
+ import { SourceNamespaceSchema, SourceRefSchema, SourceSubPathSchema } from "./types.js";
12
14
  /** @experimental */
13
- export const printGitHubSource = (source, sourceName = source.sourceName ?? "github") => {
14
- let s = `${sourceName}:${source.owner}/${source.repo}`;
15
- if (Option.isSome(source.subPath))
16
- s += `//${source.subPath.value}`;
17
- if (Option.isSome(source.ref))
18
- s += `@${source.ref.value}`;
19
- return s;
15
+ export const FORGE_PREFIXES = ["github", "gitlab", "bitbucket", "azurerepos"];
16
+ /** @experimental */
17
+ export const isForgePrefix = (value) => FORGE_PREFIXES.some((prefix) => prefix === value);
18
+ const segments = (pathname) => pathname.split("/").filter((segment) => segment.length > 0);
19
+ const withoutGit = (name) => (name.endsWith(".git") ? name.slice(0, -4) : name);
20
+ const hostedRepositoryFromCloneUrl = (pathname) => {
21
+ const parts = segments(pathname);
22
+ const last = parts.at(-1);
23
+ return last === undefined || parts.length < 2
24
+ ? undefined
25
+ : [...parts.slice(0, -1), withoutGit(last)].join("/");
26
+ };
27
+ const hostedBrowserPath = (pathname, marker) => {
28
+ const pattern = marker === "tree"
29
+ ? /^\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/tree\/([^/]+)(?:\/(.+))?)?$/u
30
+ : /^\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/src\/([^/]+)(?:\/(.+))?)?$/u;
31
+ const match = pattern.exec(pathname);
32
+ if (match?.[1] === undefined || match[2] === undefined)
33
+ return undefined;
34
+ return {
35
+ repository: `${match[1]}/${match[2]}`,
36
+ ...(match[3] === undefined ? {} : { ref: match[3] }),
37
+ ...(match[4] === undefined ? {} : { subPath: match[4] }),
38
+ };
39
+ };
40
+ const gitlabBrowserPath = (pathname) => {
41
+ const marker = "/-/tree/";
42
+ const markerIndex = pathname.indexOf(marker);
43
+ const repositoryParts = segments(markerIndex < 0 ? pathname : pathname.slice(0, markerIndex));
44
+ const last = repositoryParts.at(-1);
45
+ if (last === undefined || repositoryParts.length < 2)
46
+ return undefined;
47
+ const treePath = markerIndex < 0 ? [] : pathname.slice(markerIndex + marker.length).split("/");
48
+ const subPath = treePath.slice(1).join("/");
49
+ return {
50
+ repository: [...repositoryParts.slice(0, -1), withoutGit(last)].join("/"),
51
+ ...(treePath[0] === undefined ? {} : { ref: treePath[0] }),
52
+ ...(subPath.length === 0 ? {} : { subPath }),
53
+ };
54
+ };
55
+ const azureRepositoryFromCloneUrl = (pathname) => {
56
+ const parts = segments(pathname);
57
+ return parts.length === 4 && parts[2] === "_git"
58
+ ? `${parts[0]}/${parts[1]}/${withoutGit(parts[3] ?? "")}`
59
+ : undefined;
60
+ };
61
+ const azureBrowserPath = (pathname) => {
62
+ const match = /^\/([^/]+)\/([^/]+)\/_git\/([^/]+?)(?:\.git)?$/u.exec(pathname);
63
+ return match?.[1] === undefined || match[2] === undefined || match[3] === undefined
64
+ ? undefined
65
+ : { repository: `${match[1]}/${match[2]}/${match[3]}` };
66
+ };
67
+ const hostedForge = (prefix, hostname, parseBrowserPath) => ({
68
+ prefix,
69
+ hostname,
70
+ repositorySegments: (count) => count >= 2,
71
+ cloneUrl: (repository) => new URL(`https://${hostname}/${repository}.git`),
72
+ repositoryFromCloneUrl: hostedRepositoryFromCloneUrl,
73
+ parseBrowserPath,
74
+ });
75
+ /** @experimental */
76
+ export const FORGES = {
77
+ github: hostedForge("github", "github.com", (pathname) => hostedBrowserPath(pathname, "tree")),
78
+ gitlab: hostedForge("gitlab", "gitlab.com", gitlabBrowserPath),
79
+ bitbucket: hostedForge("bitbucket", "bitbucket.org", (pathname) => hostedBrowserPath(pathname, "src")),
80
+ azurerepos: {
81
+ prefix: "azurerepos",
82
+ hostname: "dev.azure.com",
83
+ repositorySegments: (count) => count === 3,
84
+ cloneUrl: (repository) => {
85
+ const [organization, project, repo] = repository.split("/");
86
+ return new URL(`https://dev.azure.com/${organization}/${project}/_git/${repo}`);
87
+ },
88
+ repositoryFromCloneUrl: azureRepositoryFromCloneUrl,
89
+ parseBrowserPath: azureBrowserPath,
90
+ },
91
+ };
92
+ /** @experimental */
93
+ export const forgeForHostname = (hostname) => Object.values(FORGES).find((forge) => forge.hostname === hostname);
94
+ const decodeNamespace = Schema.decodeUnknownResult(SourceNamespaceSchema);
95
+ const decodeSubPath = Schema.decodeUnknownResult(SourceSubPathSchema);
96
+ const decodeRef = Schema.decodeUnknownResult(SourceRefSchema);
97
+ const validCoordinate = (coordinate) => FORGES[coordinate.forge].repositorySegments(coordinate.repository.split("/").length) &&
98
+ Result.isSuccess(decodeNamespace(coordinate.repository)) &&
99
+ (Option.isNone(coordinate.subPath) ||
100
+ Result.isSuccess(decodeSubPath(coordinate.subPath.value))) &&
101
+ (Option.isNone(coordinate.ref) || Result.isSuccess(decodeRef(coordinate.ref.value)));
102
+ const splitRef = (input) => {
103
+ const refIndex = input.lastIndexOf("@");
104
+ if (refIndex <= 0)
105
+ return { coordinate: input };
106
+ const ref = input.slice(refIndex + 1);
107
+ const subPathMarker = input.indexOf("//");
108
+ const isAtPathSegment = subPathMarker !== -1 &&
109
+ refIndex > subPathMarker + 1 &&
110
+ input[refIndex - 1] === "/" &&
111
+ ref.includes("/");
112
+ return isAtPathSegment ? { coordinate: input } : { coordinate: input.slice(0, refIndex), ref };
20
113
  };
21
114
  /** @experimental */
22
- export const printGitLabSource = (source, sourceName = source.sourceName ?? "gitlab") => {
23
- let s = `${sourceName}:${source.owner}/${source.repo}`;
24
- if (Option.isSome(source.subPath))
25
- s += `//${source.subPath.value}`;
26
- if (Option.isSome(source.ref))
27
- s += `@${source.ref.value}`;
28
- return s;
115
+ export const parseForgeCoordinate = (forge, body) => {
116
+ const { coordinate, ref } = splitRef(body);
117
+ const marker = coordinate.indexOf("//");
118
+ const repository = marker < 0 ? coordinate : coordinate.slice(0, marker);
119
+ const subPath = marker < 0 ? undefined : coordinate.slice(marker + 2);
120
+ const parsed = {
121
+ forge,
122
+ repository,
123
+ ref: Option.fromUndefinedOr(ref),
124
+ subPath: Option.fromUndefinedOr(subPath),
125
+ };
126
+ return validCoordinate(parsed)
127
+ ? Result.succeed(parsed)
128
+ : Result.fail({ reason: `Expected valid ${forge} repository[//subpath][@ref]` });
29
129
  };
30
130
  /** @experimental */
31
- export const printBitbucketSource = (source, sourceName = source.sourceName ?? "bitbucket") => {
32
- let s = `${sourceName}:${source.owner}/${source.repo}`;
33
- if (Option.isSome(source.subPath))
34
- s += `//${source.subPath.value}`;
35
- if (Option.isSome(source.ref))
36
- s += `@${source.ref.value}`;
37
- return s;
131
+ export const printForgeCoordinateBody = (coordinate) => {
132
+ let body = coordinate.repository;
133
+ if (Option.isSome(coordinate.subPath))
134
+ body += `//${coordinate.subPath.value}`;
135
+ if (Option.isSome(coordinate.ref))
136
+ body += `@${coordinate.ref.value}`;
137
+ return body;
38
138
  };
39
139
  /** @experimental */
40
- export const printAzureReposSource = (source, sourceName = source.sourceName ?? "azurerepos") => {
41
- let s = `${sourceName}:${source.organization}/${source.project}/${source.repo}`;
42
- if (Option.isSome(source.subPath))
43
- s += `//${source.subPath.value}`;
44
- if (Option.isSome(source.ref))
45
- s += `@${source.ref.value}`;
46
- return s;
140
+ export const printForgeCoordinate = (coordinate) => `${coordinate.forge}:${printForgeCoordinateBody(coordinate)}`;
141
+ /** @experimental */
142
+ export const forgeCloneUrl = (coordinate) => FORGES[coordinate.forge].cloneUrl(coordinate.repository);
143
+ /** @experimental */
144
+ export const forgeCoordinateFromGitUrl = (url, ref, subPath) => {
145
+ const forge = forgeForHostname(url.hostname);
146
+ if (forge === undefined)
147
+ return Option.none();
148
+ const repository = forge.repositoryFromCloneUrl(url.pathname);
149
+ if (repository === undefined)
150
+ return Option.none();
151
+ const coordinate = { forge: forge.prefix, repository, ref, subPath };
152
+ return validCoordinate(coordinate) ? Option.some(coordinate) : Option.none();
47
153
  };
48
154
  /** @experimental */
49
- export const printLocalSource = (source) => source.path.startsWith("/") || source.path.startsWith(".") ? source.path : `./${source.path}`;
155
+ export const parseForgeBrowserUrl = (url) => {
156
+ const forge = forgeForHostname(url.hostname);
157
+ if (forge === undefined)
158
+ return Option.none();
159
+ const parsed = forge.parseBrowserPath(url.pathname);
160
+ if (parsed === undefined)
161
+ return Option.none();
162
+ const coordinate = {
163
+ forge: forge.prefix,
164
+ repository: parsed.repository,
165
+ ref: Option.fromUndefinedOr(parsed.ref),
166
+ subPath: Option.fromUndefinedOr(parsed.subPath),
167
+ };
168
+ return validCoordinate(coordinate) ? Option.some(coordinate) : Option.none();
169
+ };
50
170
  //# sourceMappingURL=forge-grammar.js.map
@@ -9,6 +9,7 @@
9
9
  * @packageDocumentation
10
10
  */
11
11
  import * as Option from "effect/Option";
12
+ import { type ForgeCoordinate, type ForgePrefix } from "./forge-grammar.js";
12
13
  import type { VersionRange } from "../version-constraints/version-constraints.js";
13
14
  import type { ExtensionName, ExtensionType, ExtensionTypePlural } from "../extensions/common.js";
14
15
  import type { Handle } from "../extensions/handle.js";
@@ -17,10 +18,14 @@ type NameInput = {
17
18
  readonly pattern: "name-input";
18
19
  readonly name: string;
19
20
  };
20
- /** A namespaced registry source: `[source-name:]@owner/<plural-type>/name`. */
21
+ /**
22
+ * A namespaced registry source: `[source-name:]@owner/<plural-type>/name`.
23
+ * `sourceName` is the configured source the input spells; an unqualified
24
+ * input leaves it unbound for the workspace to assign.
25
+ */
21
26
  type RegistryPatternInput = {
22
27
  readonly pattern: "registry-pattern-input";
23
- readonly sourceName: string;
28
+ readonly sourceName: Option.Option<string>;
24
29
  readonly type: Option.Option<ExtensionTypePlural>;
25
30
  readonly owner: Handle;
26
31
  readonly name: Option.Option<ExtensionName>;
@@ -41,10 +46,7 @@ type GitScpAddress = {
41
46
  /** An `owner/repo[//path][@ref]` style pattern containing `/` (not a URL or file path). */
42
47
  type SlashPattern = {
43
48
  readonly pattern: "slash-pattern";
44
- readonly first: string;
45
- readonly second: string;
46
- readonly third: Option.Option<string>;
47
- readonly ref: Option.Option<string>;
49
+ readonly coordinate: ForgeCoordinate;
48
50
  };
49
51
  /** A local filesystem path matching `LOCAL_PATH_PATTERN`. */
50
52
  type FilePathPattern = {
@@ -54,7 +56,7 @@ type FilePathPattern = {
54
56
  /** A shorthand prefixed input: `<prefix>:...` where prefix is a known source provider. */
55
57
  export type ShorthandInput = {
56
58
  readonly pattern: "shorthand-input";
57
- readonly prefix: string;
59
+ readonly prefix: ForgePrefix;
58
60
  readonly remainingInput: string;
59
61
  };
60
62
  /** A glob pattern containing `*` wildcards (e.g. `effect-*`). */
@@ -10,14 +10,13 @@
10
10
  */
11
11
  import * as Option from "effect/Option";
12
12
  import * as Result from "effect/Result";
13
+ import { isForgePrefix, parseForgeCoordinate, } from "./forge-grammar.js";
13
14
  import { parseFqn } from "../extensions/fqn.js";
14
15
  import { parseSourceQualifiedRegistrySourcePatternParts } from "../extensions/registry-source.js";
15
16
  /** Matches: ./path, ../path, /path, ~/path, ~\path, or Windows paths like C:\path */
16
17
  const LOCAL_PATH_PATTERN = /^(?:\.\.?\/|\/|~\/|~\\|[A-Za-z]:[\\/])/;
17
18
  /** SCP-style: `user@host:path` — no `://` scheme. */
18
19
  const SCP_PATTERN = /^([^@]+)@([^:]+):(.+)$/;
19
- /** Known shorthand prefixes. */
20
- const SHORTHAND_PREFIXES = new Set(["github", "gitlab", "bitbucket", "azurerepos"]);
21
20
  /** Simple name: alphanumeric with hyphens, no leading/trailing hyphen. */
22
21
  const NAME_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
23
22
  /**
@@ -66,10 +65,11 @@ export const parseInputPattern = (input) => {
66
65
  }
67
66
  // 4. Shorthand prefix (github:..., gitlab:..., etc.) — must check before URL
68
67
  const colonIndex = input.indexOf(":");
69
- if (colonIndex > 0 && SHORTHAND_PREFIXES.has(input.slice(0, colonIndex))) {
68
+ const prefix = input.slice(0, colonIndex);
69
+ if (colonIndex > 0 && isForgePrefix(prefix)) {
70
70
  return Option.some(wrap({
71
71
  pattern: "shorthand-input",
72
- prefix: input.slice(0, colonIndex),
72
+ prefix,
73
73
  remainingInput: input.slice(colonIndex + 1),
74
74
  }));
75
75
  }
@@ -90,34 +90,17 @@ export const parseInputPattern = (input) => {
90
90
  }
91
91
  // 7. Slash pattern (`owner/repo[//path][@ref]`)
92
92
  if (input.includes("/")) {
93
- const refIndex = input.lastIndexOf("@");
94
- const hasRef = refIndex > 0 && refIndex < input.length - 1;
95
- const coordinate = hasRef ? input.slice(0, refIndex) : input;
96
- const ref = hasRef ? input.slice(refIndex + 1) : undefined;
97
- const subPathIndex = coordinate.indexOf("//");
98
- const repositoryCoordinate = subPathIndex < 0 ? coordinate : coordinate.slice(0, subPathIndex);
99
- const subPath = subPathIndex < 0 ? undefined : coordinate.slice(subPathIndex + 2);
100
- const segments = repositoryCoordinate.split("/");
101
- const first = segments.at(0);
102
- const second = segments.at(1);
103
- if (first !== undefined &&
104
- second !== undefined &&
105
- segments.length === 2 &&
106
- NAME_PATTERN.test(first) &&
107
- NAME_PATTERN.test(second) &&
108
- (ref === undefined || (ref.length > 0 && !ref.includes("/"))) &&
109
- (subPath === undefined ||
110
- (subPath.length > 0 &&
111
- subPath.split("/").every((segment) => segment.length > 0 && segment !== "..")))) {
112
- return Option.some(wrap({
113
- pattern: "slash-pattern",
114
- first,
115
- second,
116
- third: Option.fromUndefinedOr(subPath),
117
- ref: Option.fromUndefinedOr(ref),
118
- }));
93
+ const parsed = parseForgeCoordinate("github", input);
94
+ if (Result.isFailure(parsed))
95
+ return Option.none();
96
+ const coordinate = parsed.success;
97
+ const segments = coordinate.repository.split("/");
98
+ if (segments.length !== 2 ||
99
+ !segments.every((segment) => NAME_PATTERN.test(segment)) ||
100
+ Option.exists(coordinate.ref, (ref) => ref.includes("/"))) {
101
+ return Option.none();
119
102
  }
120
- return Option.none();
103
+ return Option.some(wrap({ pattern: "slash-pattern", coordinate }));
121
104
  }
122
105
  // 8. Glob pattern (contains `*` wildcard)
123
106
  if (input.includes("*")) {
@@ -6,7 +6,8 @@
6
6
  */
7
7
  import * as Option from "effect/Option";
8
8
  import { formatFqn } from "../extensions/fqn.js";
9
- import { printLocalSource } from "./forge-grammar.js";
9
+ import { forgeCoordinateFromGitUrl, printForgeCoordinate } from "./forge-grammar.js";
10
+ const printLocalSource = (source) => source.path.startsWith("/") || source.path.startsWith(".") ? source.path : `./${source.path}`;
10
11
  /**
11
12
  * Print source params as their canonical shorthand string.
12
13
  *
@@ -17,40 +18,14 @@ export const printSourceParams = (source) => {
17
18
  case "local":
18
19
  return printLocalSource(source);
19
20
  case "git": {
20
- const repositorySegments = source.url.pathname
21
- .split("/")
22
- .filter((segment) => segment.length > 0);
23
- if (source.url.hostname === "dev.azure.com" &&
24
- repositorySegments.length === 4 &&
25
- repositorySegments[2] === "_git") {
26
- let shorthand = `azurerepos:${repositorySegments[0]}/${repositorySegments[1]}/${repositorySegments[3]}`;
27
- if (Option.isSome(source.subPath))
28
- shorthand += `//${source.subPath.value}`;
29
- if (Option.isSome(source.ref))
30
- shorthand += `@${source.ref.value}`;
31
- return shorthand;
32
- }
33
- const lastSegment = repositorySegments.at(-1);
34
- const repo = lastSegment?.endsWith(".git") ? lastSegment.slice(0, -4) : lastSegment;
35
- const sourcePrefix = source.url.hostname === "github.com"
36
- ? "github"
37
- : source.url.hostname === "gitlab.com"
38
- ? "gitlab"
39
- : source.url.hostname === "bitbucket.org"
40
- ? "bitbucket"
41
- : undefined;
42
- if (sourcePrefix !== undefined && repo !== undefined && repositorySegments.length >= 2) {
43
- const owner = repositorySegments.slice(0, -1).join("/");
44
- let shorthand = `${sourcePrefix}:${owner}/${repo}`;
45
- if (Option.isSome(source.subPath))
46
- shorthand += `//${source.subPath.value}`;
47
- if (Option.isSome(source.ref))
48
- shorthand += `@${source.ref.value}`;
49
- return shorthand;
50
- }
51
- const url = new URL(source.url.href);
52
- url.hash = Option.getOrElse(source.ref, () => "");
53
- return url.href;
21
+ return Option.match(forgeCoordinateFromGitUrl(source.url, source.ref, source.subPath), {
22
+ onSome: printForgeCoordinate,
23
+ onNone: () => {
24
+ const url = new URL(source.url.href);
25
+ url.hash = Option.getOrElse(source.ref, () => "");
26
+ return url.href;
27
+ },
28
+ });
54
29
  }
55
30
  case "registry": {
56
31
  return source.sourceName ?? ("name" in source ? String(source.name) : "agentxm");
@@ -7,7 +7,6 @@
7
7
  * @experimental This API is unstable and may change without notice.
8
8
  * @packageDocumentation
9
9
  */
10
- import type * as Duration from "effect/Duration";
11
10
  import type * as Effect from "effect/Effect";
12
11
  import type * as Option from "effect/Option";
13
12
  import type { ExtensionType, Handle } from "../extensions/index.js";
@@ -29,11 +28,6 @@ export interface FindOptions {
29
28
  /** Registry owner filter (e.g. "@acme"). */
30
29
  readonly owner: Option.Option<Handle>;
31
30
  readonly versionRange: Option.Option<string>;
32
- /**
33
- * Optional minimum age a registry release must have before it is selectable.
34
- * Omitted for explicit attended installs.
35
- */
36
- readonly minimumReleaseAge?: Option.Option<Duration.Duration>;
37
31
  }
38
32
  export interface NamedRegistryFindOptions {
39
33
  readonly name: string;
@@ -54,6 +48,8 @@ export type NamedRegistryResolution = {
54
48
  readonly refType: "registry";
55
49
  }>;
56
50
  readonly newerHeld?: ReleaseAgeEvidence;
51
+ /** The newest release a range could select, whatever range was requested. */
52
+ readonly newestVisible?: string;
57
53
  } | {
58
54
  readonly kind: "exempted";
59
55
  readonly target: string;
@@ -62,6 +58,7 @@ export type NamedRegistryResolution = {
62
58
  }>;
63
59
  readonly bypassed: ReleaseAgeEvidence;
64
60
  readonly exemption: ReleaseAgeExemption;
61
+ readonly newestVisible?: string;
65
62
  } | {
66
63
  readonly kind: "not_found";
67
64
  readonly target: string;
@@ -87,11 +84,18 @@ export type NamedRegistryVersionDecision = {
87
84
  readonly kind: "selected";
88
85
  readonly version: string;
89
86
  readonly newerHeld?: ReleaseAgeEvidence;
87
+ /**
88
+ * The newest release the index lists that a range could select — not
89
+ * yanked, whatever range was requested — so a caller can say what its
90
+ * constraint held the selection below.
91
+ */
92
+ readonly newestVisible?: string;
90
93
  } | {
91
94
  readonly kind: "exempted";
92
95
  readonly version: string;
93
96
  readonly bypassed: ReleaseAgeEvidence;
94
97
  readonly exemption: ReleaseAgeExemption;
98
+ readonly newestVisible?: string;
95
99
  } | {
96
100
  readonly kind: "not_found";
97
101
  } | {