@ericsanchezok/synergy-util 3.0.23 → 3.1.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 (74) hide show
  1. package/LICENSE.codex +201 -0
  2. package/NOTICE +9 -0
  3. package/dist/activity.d.ts +9 -4
  4. package/dist/activity.js +20 -19
  5. package/dist/asset-reference.d.ts +15 -0
  6. package/dist/asset-reference.js +98 -0
  7. package/dist/atomic-file.d.ts +9 -0
  8. package/dist/atomic-file.js +86 -0
  9. package/dist/attachment-presentation.d.ts +26 -0
  10. package/dist/attachment-presentation.js +11 -0
  11. package/dist/capability.d.ts +1 -1
  12. package/dist/capability.js +5 -19
  13. package/dist/cli-command.d.ts +12 -0
  14. package/dist/cli-command.js +3 -0
  15. package/dist/encode.d.ts +3 -0
  16. package/dist/encode.js +7 -0
  17. package/dist/error.d.ts +36 -56
  18. package/dist/feature-config.d.ts +4 -0
  19. package/dist/feature-config.js +23 -0
  20. package/dist/features.d.ts +134 -0
  21. package/dist/features.js +195 -0
  22. package/dist/file-patch.d.ts +31 -0
  23. package/dist/file-patch.js +218 -0
  24. package/dist/filesystem-identity.d.ts +28 -0
  25. package/dist/filesystem-identity.js +71 -0
  26. package/dist/fn.d.ts +1 -1
  27. package/dist/fs-lock.d.ts +5 -1
  28. package/dist/fs-lock.js +14 -6
  29. package/dist/input-assistance.d.ts +51 -0
  30. package/dist/input-assistance.js +61 -0
  31. package/dist/installed-launcher.d.ts +18 -0
  32. package/dist/installed-launcher.js +39 -0
  33. package/dist/io-retry.d.ts +11 -0
  34. package/dist/io-retry.js +36 -0
  35. package/dist/json-value.d.ts +4 -0
  36. package/dist/json-value.js +12 -0
  37. package/dist/markdown-assets.d.ts +6 -0
  38. package/dist/markdown-assets.js +29 -0
  39. package/dist/markdown-link.d.ts +2 -0
  40. package/dist/markdown-link.js +8 -0
  41. package/dist/native-assets.d.ts +7 -0
  42. package/dist/native-assets.js +28 -0
  43. package/dist/network-error.d.ts +7 -0
  44. package/dist/network-error.js +111 -0
  45. package/dist/plugin-policy.d.ts +1 -1
  46. package/dist/process-group.d.ts +45 -0
  47. package/dist/process-group.js +156 -0
  48. package/dist/process-identity.js +5 -2
  49. package/dist/projection-patch.d.ts +9 -0
  50. package/dist/projection-patch.js +74 -0
  51. package/dist/reasoning-item.d.ts +1 -0
  52. package/dist/reasoning-item.js +16 -0
  53. package/dist/render-artifact.d.ts +352 -0
  54. package/dist/render-artifact.js +115 -0
  55. package/dist/render-ui.d.ts +202 -0
  56. package/dist/render-ui.js +208 -0
  57. package/dist/resource-reference.d.ts +73 -0
  58. package/dist/resource-reference.js +227 -0
  59. package/dist/retry.d.ts +4 -0
  60. package/dist/retry.js +56 -23
  61. package/dist/runtime-startup.d.ts +240 -2
  62. package/dist/runtime-startup.js +108 -9
  63. package/dist/terminal.d.ts +217 -0
  64. package/dist/terminal.js +111 -0
  65. package/dist/tool-display.d.ts +10 -0
  66. package/dist/tool-display.js +22 -1
  67. package/dist/tool-timeout.d.ts +21 -0
  68. package/dist/tool-timeout.js +1 -0
  69. package/dist/tool.d.ts +6 -7
  70. package/dist/workbench-view.d.ts +692 -0
  71. package/dist/workbench-view.js +90 -0
  72. package/dist/worker-plan.d.ts +39 -0
  73. package/dist/worker-plan.js +33 -0
  74. package/package.json +28 -4
@@ -0,0 +1,208 @@
1
+ import { z } from "zod";
2
+ export var RenderUI;
3
+ (function (RenderUI) {
4
+ RenderUI.Key = z
5
+ .string()
6
+ .regex(/^[a-zA-Z][\w-]{0,63}$/)
7
+ .refine((key) => !["constructor", "prototype", "__proto__"].includes(key));
8
+ RenderUI.Value = z.union([z.string().max(2000), z.number().finite(), z.boolean()]);
9
+ RenderUI.Binding = z.union([RenderUI.Value, z.object({ ref: RenderUI.Key }).strict()]);
10
+ const base = { id: RenderUI.Key, parent: RenderUI.Key.optional() };
11
+ const text = z.string().max(4000);
12
+ RenderUI.Node = z.discriminatedUnion("type", [
13
+ z.object({ ...base, type: z.literal("heading"), text, level: z.enum(["2", "3"]).optional() }).strict(),
14
+ z.object({ ...base, type: z.literal("text"), text }).strict(),
15
+ z.object({ ...base, type: z.literal("row"), columns: z.number().int().min(1).max(4).optional() }).strict(),
16
+ z.object({ ...base, type: z.literal("card"), title: text.optional() }).strict(),
17
+ z
18
+ .object({
19
+ ...base,
20
+ type: z.literal("metric"),
21
+ label: text,
22
+ value: RenderUI.Binding,
23
+ prefix: z.string().max(40).optional(),
24
+ suffix: z.string().max(40).optional(),
25
+ })
26
+ .strict(),
27
+ z
28
+ .object({
29
+ ...base,
30
+ type: z.literal("slider"),
31
+ label: text,
32
+ state: RenderUI.Key,
33
+ min: z.number().finite(),
34
+ max: z.number().finite(),
35
+ step: z.number().positive().optional(),
36
+ })
37
+ .strict(),
38
+ z
39
+ .object({
40
+ ...base,
41
+ type: z.literal("select"),
42
+ label: text,
43
+ state: RenderUI.Key,
44
+ options: z.array(z.string().max(200)).min(1).max(24),
45
+ })
46
+ .strict(),
47
+ z.object({ ...base, type: z.literal("checkbox"), label: text, state: RenderUI.Key }).strict(),
48
+ z.object({ ...base, type: z.literal("button"), label: text, text: z.string().min(1).max(4000) }).strict(),
49
+ z
50
+ .object({
51
+ ...base,
52
+ type: z.literal("bar"),
53
+ label: text,
54
+ items: z
55
+ .array(z.object({ label: text, value: RenderUI.Binding }).strict())
56
+ .min(1)
57
+ .max(32),
58
+ })
59
+ .strict(),
60
+ ]);
61
+ RenderUI.Computed = z
62
+ .object({
63
+ id: RenderUI.Key,
64
+ op: z.enum(["add", "subtract", "multiply", "divide", "min", "max"]),
65
+ inputs: z.tuple([RenderUI.Binding, RenderUI.Binding]),
66
+ })
67
+ .strict();
68
+ RenderUI.Spec = z
69
+ .object({
70
+ state: z.record(RenderUI.Key, RenderUI.Value).default({}),
71
+ computed: z.array(RenderUI.Computed).max(64).default([]),
72
+ nodes: z.array(RenderUI.Node).min(1).max(128),
73
+ })
74
+ .strict()
75
+ .superRefine((spec, ctx) => {
76
+ if (new TextEncoder().encode(JSON.stringify(spec)).byteLength > 128 * 1024)
77
+ ctx.addIssue({ code: "custom", message: "UI exceeds 128 KiB" });
78
+ if (new TextEncoder().encode(JSON.stringify(spec.state)).byteLength > 12 * 1024)
79
+ ctx.addIssue({ code: "custom", message: "Initial state exceeds 12 KiB" });
80
+ const values = new Set(Object.keys(spec.state));
81
+ if (values.size > 32)
82
+ ctx.addIssue({ code: "custom", message: "At most 32 state values" });
83
+ const numericValues = new Set(Object.keys(spec.state).filter((key) => typeof spec.state[key] === "number"));
84
+ function binding(value, numeric = false) {
85
+ if (typeof value === "object" && !values.has(value.ref))
86
+ ctx.addIssue({ code: "custom", message: `Unknown value: ${value.ref}` });
87
+ else if (numeric && (typeof value === "object" ? !numericValues.has(value.ref) : typeof value !== "number"))
88
+ ctx.addIssue({ code: "custom", message: "Computations and chart values must be numeric" });
89
+ }
90
+ for (const computed of spec.computed) {
91
+ if (values.has(computed.id))
92
+ ctx.addIssue({ code: "custom", message: `Duplicate value: ${computed.id}` });
93
+ computed.inputs.forEach((value) => binding(value, true));
94
+ values.add(computed.id);
95
+ numericValues.add(computed.id);
96
+ }
97
+ const nodes = new Map();
98
+ for (const node of spec.nodes) {
99
+ if (nodes.has(node.id))
100
+ ctx.addIssue({ code: "custom", message: `Duplicate node: ${node.id}` });
101
+ if (node.parent) {
102
+ const parent = nodes.get(node.parent);
103
+ if (!parent || !["row", "card"].includes(parent.type))
104
+ ctx.addIssue({ code: "custom", message: `Parent must be an earlier row or card: ${node.id}` });
105
+ }
106
+ if ("state" in node) {
107
+ const value = spec.state[node.state];
108
+ if (value === undefined ||
109
+ (node.type === "slider" &&
110
+ (typeof value !== "number" || node.min >= node.max || value < node.min || value > node.max)) ||
111
+ (node.type === "checkbox" && typeof value !== "boolean") ||
112
+ (node.type === "select" && (typeof value !== "string" || !node.options.includes(value))))
113
+ ctx.addIssue({ code: "custom", message: `Invalid control state: ${node.id}` });
114
+ }
115
+ if (node.type === "metric")
116
+ binding(node.value);
117
+ if (node.type === "bar")
118
+ node.items.forEach((item) => binding(item.value, true));
119
+ nodes.set(node.id, node);
120
+ }
121
+ })
122
+ .meta({ ref: "RenderUI" });
123
+ /**
124
+ * Provenance: https://www.openui.com/blog/how-chatgpt-intelligent-ui-works
125
+ * Local adaptation: bounded declarative data, keyed controls and local arithmetic replace authored programs.
126
+ */
127
+ function evaluate(spec, state) {
128
+ const values = Object.assign(Object.create(null), spec.state, state);
129
+ function resolve(value) {
130
+ return typeof value === "object" ? values[value.ref] : value;
131
+ }
132
+ for (const item of spec.computed) {
133
+ const a = resolve(item.inputs[0]), b = resolve(item.inputs[1]);
134
+ if (typeof a !== "number" || typeof b !== "number")
135
+ throw new Error(`Invalid numeric inputs: ${item.id}`);
136
+ const result = item.op === "add"
137
+ ? a + b
138
+ : item.op === "subtract"
139
+ ? a - b
140
+ : item.op === "multiply"
141
+ ? a * b
142
+ : item.op === "divide"
143
+ ? a / b
144
+ : item.op === "min"
145
+ ? Math.min(a, b)
146
+ : Math.max(a, b);
147
+ if (!Number.isFinite(result))
148
+ throw new Error(`Invalid arithmetic result: ${item.id}`);
149
+ values[item.id] = result;
150
+ }
151
+ return values;
152
+ }
153
+ RenderUI.evaluate = evaluate;
154
+ function preview(input) {
155
+ if (!input || typeof input !== "object")
156
+ return;
157
+ const raw = input;
158
+ if (raw.state !== undefined && (!raw.state || typeof raw.state !== "object" || Array.isArray(raw.state)))
159
+ return;
160
+ const state = z.record(RenderUI.Key, RenderUI.Value).safeParse(Object.fromEntries(Object.entries(raw.state ?? {})));
161
+ if (!state.success || Object.keys(state.data).length > 32 || !Array.isArray(raw.nodes) || raw.nodes.length > 128)
162
+ return;
163
+ const computed = [];
164
+ if (Array.isArray(raw.computed)) {
165
+ if (raw.computed.length > 64)
166
+ return;
167
+ for (const value of raw.computed) {
168
+ const parsed = RenderUI.Computed.safeParse(value);
169
+ if (!parsed.success)
170
+ break;
171
+ computed.push(parsed.data);
172
+ }
173
+ }
174
+ const nodes = [];
175
+ for (const value of raw.nodes) {
176
+ const parsed = RenderUI.Node.safeParse(value);
177
+ if (!parsed.success)
178
+ break;
179
+ nodes.push(parsed.data);
180
+ }
181
+ const parsed = RenderUI.Spec.safeParse({ state: state.data, computed, nodes });
182
+ return parsed.success ? parsed.data : undefined;
183
+ }
184
+ RenderUI.preview = preview;
185
+ function fallback(spec) {
186
+ const values = evaluate(spec, spec.state);
187
+ const escape = (text) => String(text).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
188
+ const resolve = (value) => (typeof value === "object" ? values[value.ref] : value);
189
+ return spec.nodes
190
+ .map((node) => {
191
+ if (node.type === "heading")
192
+ return `<h${node.level ?? "2"}>${escape(node.text)}</h${node.level ?? "2"}>`;
193
+ if (node.type === "text")
194
+ return `<p>${escape(node.text)}</p>`;
195
+ if (node.type === "metric")
196
+ return `<p>${escape(node.label)}: ${escape(node.prefix ?? "")}${escape(resolve(node.value))}${escape(node.suffix ?? "")}</p>`;
197
+ if (node.type === "bar")
198
+ return `<p>${escape(node.label)}</p><ul>${node.items.map((item) => `<li>${escape(item.label)}: ${escape(resolve(item.value))}</li>`).join("")}</ul>`;
199
+ if ("state" in node)
200
+ return `<p>${escape(node.label)}: ${escape(spec.state[node.state])}</p>`;
201
+ if (node.type === "card" && node.title)
202
+ return `<h3>${escape(node.title)}</h3>`;
203
+ return "";
204
+ })
205
+ .join("\n");
206
+ }
207
+ RenderUI.fallback = fallback;
208
+ })(RenderUI || (RenderUI = {}));
@@ -0,0 +1,73 @@
1
+ import { z } from "zod";
2
+ export declare namespace ResourceReference {
3
+ const Workspace: z.ZodObject<{
4
+ id: z.ZodString;
5
+ generation: z.ZodNumber;
6
+ root: z.ZodString;
7
+ }, z.core.$strip>;
8
+ type Workspace = z.infer<typeof Workspace>;
9
+ const Context: z.ZodDiscriminatedUnion<[z.ZodObject<{
10
+ state: z.ZodLiteral<"bound">;
11
+ workspace: z.ZodObject<{
12
+ id: z.ZodString;
13
+ generation: z.ZodNumber;
14
+ root: z.ZodString;
15
+ }, z.core.$strip>;
16
+ directory: z.ZodString;
17
+ }, z.core.$strip>, z.ZodObject<{
18
+ state: z.ZodLiteral<"none">;
19
+ }, z.core.$strip>, z.ZodObject<{
20
+ state: z.ZodLiteral<"unresolved">;
21
+ }, z.core.$strip>], "state">;
22
+ type Context = z.infer<typeof Context>;
23
+ const Location: z.ZodDiscriminatedUnion<[z.ZodObject<{
24
+ kind: z.ZodLiteral<"text">;
25
+ line: z.ZodNumber;
26
+ column: z.ZodOptional<z.ZodNumber>;
27
+ endLine: z.ZodOptional<z.ZodNumber>;
28
+ endColumn: z.ZodOptional<z.ZodNumber>;
29
+ }, z.core.$strip>, z.ZodObject<{
30
+ kind: z.ZodLiteral<"heading">;
31
+ id: z.ZodString;
32
+ }, z.core.$strip>, z.ZodObject<{
33
+ kind: z.ZodLiteral<"page">;
34
+ page: z.ZodNumber;
35
+ }, z.core.$strip>], "kind">;
36
+ type Location = z.infer<typeof Location>;
37
+ type Target = {
38
+ kind: "workspace-file";
39
+ path: string;
40
+ location?: Location;
41
+ } | {
42
+ kind: "asset";
43
+ url: string;
44
+ location?: Location;
45
+ } | {
46
+ kind: "note";
47
+ id: string;
48
+ location?: Location;
49
+ } | {
50
+ kind: "url";
51
+ url: string;
52
+ } | {
53
+ kind: "image";
54
+ url: string;
55
+ } | {
56
+ kind: "anchor";
57
+ id: string;
58
+ } | {
59
+ kind: "unavailable";
60
+ value: string;
61
+ reason: "invalid-target" | "invalid-location" | "unsupported-protocol";
62
+ };
63
+ function parse(input: string): Target;
64
+ function normalizePath(value: string): string | undefined;
65
+ function format(reference: Target, location?: Location): string;
66
+ function fileUrl(path: string): string;
67
+ function resolvePath(input: string, context: Context): string | undefined;
68
+ function capture(workspace: {
69
+ id?: string;
70
+ generation?: number;
71
+ path: string;
72
+ } | null | undefined): Context;
73
+ }
@@ -0,0 +1,227 @@
1
+ import { z } from "zod";
2
+ import { AssetReference } from "./asset-reference";
3
+ export var ResourceReference;
4
+ (function (ResourceReference) {
5
+ const position = z.number().int().positive().max(2_147_483_647);
6
+ ResourceReference.Workspace = z.object({ id: z.string().min(1), generation: position, root: z.string() });
7
+ ResourceReference.Context = z.discriminatedUnion("state", [
8
+ z.object({ state: z.literal("bound"), workspace: ResourceReference.Workspace, directory: z.string() }),
9
+ z.object({ state: z.literal("none") }),
10
+ z.object({ state: z.literal("unresolved") }),
11
+ ]);
12
+ ResourceReference.Location = z
13
+ .discriminatedUnion("kind", [
14
+ z.object({
15
+ kind: z.literal("text"),
16
+ line: position,
17
+ column: position.optional(),
18
+ endLine: position.optional(),
19
+ endColumn: position.optional(),
20
+ }),
21
+ z.object({ kind: z.literal("heading"), id: z.string() }),
22
+ z.object({ kind: z.literal("page"), page: position }),
23
+ ])
24
+ .refine((value) => value.kind !== "text" ||
25
+ (value.endLine ?? value.line) > value.line ||
26
+ ((value.endLine ?? value.line) === value.line && (value.endColumn ?? value.column ?? 1) >= (value.column ?? 1)));
27
+ const controls = /[\u0000-\u001f\u007f]/;
28
+ const absolute = (value) => value.startsWith("/") || /^[a-z]:\//i.test(value);
29
+ const slash = (value) => value.replaceAll("\\", "/");
30
+ function textLocation(match) {
31
+ return {
32
+ kind: "text",
33
+ line: Number(match[2]),
34
+ ...(match[3] ? { column: Number(match[3]) } : {}),
35
+ ...(match[4] ? { endLine: Number(match[4]) } : {}),
36
+ ...(match[5] ? { endColumn: Number(match[5]) } : {}),
37
+ };
38
+ }
39
+ function parse(input) {
40
+ const value = input.trim();
41
+ const unavailable = (reason = "invalid-target") => ({
42
+ kind: "unavailable",
43
+ value: input,
44
+ reason,
45
+ });
46
+ if (!value || controls.test(value))
47
+ return unavailable();
48
+ if (/^data:image\/(?:png|jpe?g|gif|webp|avif|bmp|x-icon|vnd\.microsoft\.icon|svg\+xml)(?:;[^,]*)?,/i.test(value))
49
+ return { kind: "image", url: value };
50
+ if (value.startsWith("blob:")) {
51
+ if (/^blob:null\/[^/]+$/.test(value))
52
+ return { kind: "image", url: value };
53
+ try {
54
+ const source = new URL(value.slice(5));
55
+ if (["http:", "https:"].includes(source.protocol) && source.hostname && source.pathname !== "/")
56
+ return { kind: "image", url: value };
57
+ }
58
+ catch { }
59
+ return unavailable("unsupported-protocol");
60
+ }
61
+ if (/^(https?:|mailto:|tel:)/i.test(value)) {
62
+ try {
63
+ const url = new URL(value);
64
+ if ((url.protocol === "http:" || url.protocol === "https:") && !url.hostname)
65
+ return unavailable();
66
+ return { kind: "url", url: value };
67
+ }
68
+ catch {
69
+ return unavailable();
70
+ }
71
+ }
72
+ if (value.startsWith("//"))
73
+ return parse(`https:${value}`);
74
+ try {
75
+ if (/^note:/i.test(value)) {
76
+ const match = value.match(/^note:\/\/([a-zA-Z0-9_-]+)(?:#(.*))?$/i);
77
+ if (!match)
78
+ return unavailable();
79
+ const id = match[1];
80
+ const block = match[2] ? decodeURIComponent(match[2]) : undefined;
81
+ if (block && controls.test(block))
82
+ return unavailable("invalid-location");
83
+ return { kind: "note", id, ...(block ? { location: { kind: "heading", id: block } } : {}) };
84
+ }
85
+ if (value.startsWith("#"))
86
+ return { kind: "anchor", id: decodeURIComponent(value.slice(1)) };
87
+ let path = value;
88
+ let location;
89
+ const hash = path.indexOf("#");
90
+ if (hash >= 0) {
91
+ const fragment = path.slice(hash + 1);
92
+ const lines = fragment.match(/^()L(\d+)(?:C(\d+))?(?:[-–]L?(\d+)(?:C(\d+))?)?$/);
93
+ location = lines
94
+ ? textLocation(lines)
95
+ : /^page=\d+$/.test(fragment)
96
+ ? { kind: "page", page: Number(fragment.slice(5)) }
97
+ : { kind: "heading", id: decodeURIComponent(fragment) };
98
+ if (/^L\d|^page=/.test(fragment) && location.kind === "heading")
99
+ return unavailable("invalid-location");
100
+ path = path.slice(0, hash);
101
+ }
102
+ else {
103
+ const lines = path.match(/^(.*?):(\d+)(?::(\d+))?(?:[-–](\d+)(?::(\d+))?)?$/);
104
+ if (lines) {
105
+ path = lines[1];
106
+ location = textLocation(lines);
107
+ }
108
+ }
109
+ if (location && !ResourceReference.Location.safeParse(location).success)
110
+ return unavailable("invalid-location");
111
+ if (path.startsWith("asset://") || path.startsWith("/asset/")) {
112
+ const asset = AssetReference.parse(path.startsWith("/asset/") ? `asset://${path.slice(7)}` : path);
113
+ return asset ? { kind: "asset", url: asset.url, ...(location ? { location } : {}) } : unavailable();
114
+ }
115
+ if (/^file:/i.test(path)) {
116
+ const url = new URL(path);
117
+ if (url.search || url.username || url.password || url.port)
118
+ return unavailable();
119
+ path = decodeURIComponent(url.pathname);
120
+ if (url.hostname && url.hostname !== "localhost")
121
+ path = `//${url.hostname}${path}`;
122
+ else if (/^\/[a-z]:\//i.test(path))
123
+ path = path.slice(1);
124
+ }
125
+ else {
126
+ if (/^[a-z][a-z\d+.-]*:/i.test(path) && !/^[a-z]:[\\/]/i.test(path))
127
+ return unavailable("unsupported-protocol");
128
+ if (path.includes("?"))
129
+ return unavailable();
130
+ path = decodeURIComponent(path);
131
+ }
132
+ if (!path || controls.test(path))
133
+ return unavailable();
134
+ return { kind: "workspace-file", path: slash(path), ...(location ? { location } : {}) };
135
+ }
136
+ catch {
137
+ return unavailable();
138
+ }
139
+ }
140
+ ResourceReference.parse = parse;
141
+ function normalizePath(value) {
142
+ if (controls.test(value) || absolute(slash(value)))
143
+ return;
144
+ const parts = [];
145
+ for (const part of slash(value).split("/")) {
146
+ if (!part || part === ".")
147
+ continue;
148
+ if (part === "..") {
149
+ if (!parts.length)
150
+ return;
151
+ parts.pop();
152
+ }
153
+ else
154
+ parts.push(part);
155
+ }
156
+ return parts.join("/");
157
+ }
158
+ ResourceReference.normalizePath = normalizePath;
159
+ function format(reference, location) {
160
+ if (reference.kind === "unavailable")
161
+ return reference.value;
162
+ if (reference.kind === "anchor")
163
+ return `#${encodeURIComponent(reference.id)}`;
164
+ if (reference.kind === "url" || reference.kind === "image")
165
+ return reference.url;
166
+ const target = reference.kind === "asset"
167
+ ? reference.url
168
+ : reference.kind === "note"
169
+ ? `note://${reference.id}`
170
+ : absolute(slash(reference.path))
171
+ ? fileUrl(reference.path)
172
+ : reference.path.split("/").map(encodeURIComponent).join("/");
173
+ const position = location ?? reference.location;
174
+ if (!position)
175
+ return target;
176
+ if (position.kind === "heading")
177
+ return `${target}#${encodeURIComponent(position.id)}`;
178
+ if (position.kind === "page")
179
+ return `${target}#page=${position.page}`;
180
+ const start = `L${position.line}${position.column === undefined ? "" : `C${position.column}`}`;
181
+ const end = position.endLine !== undefined || position.endColumn !== undefined
182
+ ? `-L${position.endLine ?? position.line}${position.endColumn === undefined ? "" : `C${position.endColumn}`}`
183
+ : "";
184
+ return `${target}#${start}${end}`;
185
+ }
186
+ ResourceReference.format = format;
187
+ function fileUrl(path) {
188
+ const value = slash(path);
189
+ if (!absolute(value) || controls.test(value))
190
+ throw new Error("A file URL requires an absolute path");
191
+ const encoded = value
192
+ .split("/")
193
+ .map((part, index) => (index === 0 && /^[a-z]:$/i.test(part) ? part : encodeURIComponent(part)))
194
+ .join("/");
195
+ return value.startsWith("//") ? `file:${encoded}` : `file://${value.startsWith("/") ? "" : "/"}${encoded}`;
196
+ }
197
+ ResourceReference.fileUrl = fileUrl;
198
+ function resolvePath(input, context) {
199
+ if (context.state !== "bound" || input.startsWith("~") || controls.test(input))
200
+ return;
201
+ const value = slash(input);
202
+ const root = slash(context.workspace.root).replace(/\/$/, "");
203
+ if (absolute(value)) {
204
+ if (context.workspace.root === "/" && value.startsWith("/"))
205
+ return normalizePath(value.slice(1));
206
+ if (!root)
207
+ return;
208
+ const windows = /^[a-z]:\//i.test(root) || root.startsWith("//");
209
+ const compare = windows ? value.toLowerCase() : value;
210
+ const base = windows ? root.toLowerCase() : root;
211
+ if (compare === base)
212
+ return "";
213
+ if (!compare.startsWith(`${base}/`))
214
+ return;
215
+ return normalizePath(value.slice(root.length + 1));
216
+ }
217
+ return normalizePath(context.directory ? `${context.directory}/${value}` : value);
218
+ }
219
+ ResourceReference.resolvePath = resolvePath;
220
+ function capture(workspace) {
221
+ if (!workspace)
222
+ return { state: "none" };
223
+ const parsed = ResourceReference.Workspace.safeParse({ id: workspace.id, generation: workspace.generation, root: workspace.path });
224
+ return parsed.success ? { state: "bound", workspace: parsed.data, directory: "" } : { state: "unresolved" };
225
+ }
226
+ ResourceReference.capture = capture;
227
+ })(ResourceReference || (ResourceReference = {}));
package/dist/retry.d.ts CHANGED
@@ -4,5 +4,9 @@ export interface RetryOptions {
4
4
  factor?: number;
5
5
  maxDelay?: number;
6
6
  retryIf?: (error: unknown) => boolean;
7
+ retryDelay?: (error: unknown) => number | undefined;
8
+ signal?: AbortSignal;
7
9
  }
10
+ export declare function retrySleep(ms: number, signal?: AbortSignal): Promise<void>;
11
+ export declare function retryAfterMs(headers?: Headers | Record<string, string>, now?: number): number | undefined;
8
12
  export declare function retry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
package/dist/retry.js CHANGED
@@ -1,33 +1,66 @@
1
- const TRANSIENT_MESSAGES = [
2
- "load failed",
3
- "network connection was lost",
4
- "network request failed",
5
- "failed to fetch",
6
- "econnreset",
7
- "econnrefused",
8
- "etimedout",
9
- "socket hang up",
10
- ];
11
- function isTransientError(error) {
12
- if (!error)
13
- return false;
14
- const message = String(error instanceof Error ? error.message : error).toLowerCase();
15
- return TRANSIENT_MESSAGES.some((m) => message.includes(m));
1
+ import { classifyNetworkError } from "./network-error";
2
+ export function retrySleep(ms, signal) {
3
+ return new Promise((resolve, reject) => {
4
+ if (signal?.aborted)
5
+ return reject(signal.reason);
6
+ const abort = () => {
7
+ clearTimeout(timer);
8
+ reject(signal?.reason);
9
+ };
10
+ const timer = setTimeout(() => {
11
+ signal?.removeEventListener("abort", abort);
12
+ resolve();
13
+ }, Math.max(0, Math.min(ms, 2_147_483_647)));
14
+ signal?.addEventListener("abort", abort, { once: true });
15
+ });
16
16
  }
17
+ // Provenance: https://www.rfc-editor.org/rfc/rfc9110.html#section-10.2.3
18
+ // Local adaptation: accept finite retry-after-ms extensions and case-insensitive headers, rejecting partial numeric parses.
19
+ export function retryAfterMs(headers, now = Date.now()) {
20
+ if (!headers)
21
+ return undefined;
22
+ let values;
23
+ try {
24
+ values = headers instanceof Headers ? headers : new Headers(headers);
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ const milliseconds = values.get("retry-after-ms")?.trim();
30
+ if (milliseconds && /^\d+(?:\.\d+)?$/.test(milliseconds) && Number.isFinite(Number(milliseconds)))
31
+ return Number(milliseconds);
32
+ const value = values.get("retry-after")?.trim();
33
+ if (!value)
34
+ return undefined;
35
+ if (/^\d+$/.test(value)) {
36
+ const result = Number(value) * 1000;
37
+ return Number.isFinite(result) ? result : undefined;
38
+ }
39
+ if (!/^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(value))
40
+ return undefined;
41
+ const result = Date.parse(value) - now;
42
+ return Number.isFinite(result) && result >= 0 ? result : undefined;
43
+ }
44
+ // Provenance: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
45
+ // Local adaptation: equal jitter keeps a positive half-delay while preserving server retry hints and caller cancellation.
17
46
  export async function retry(fn, options = {}) {
18
- const { attempts = 3, delay = 500, factor = 2, maxDelay = 10000, retryIf = isTransientError } = options;
19
- let lastError;
20
- for (let attempt = 0; attempt < attempts; attempt++) {
47
+ const { attempts = 3, delay = 500, factor = 2, maxDelay = 10000, retryIf = (error) => classifyNetworkError(error)?.kind === "transient", retryDelay, signal, } = options;
48
+ if (!Number.isInteger(attempts) || attempts < 1)
49
+ throw new RangeError("Retry attempts must be a positive integer");
50
+ for (let attempt = 0;; attempt++) {
51
+ signal?.throwIfAborted();
21
52
  try {
22
53
  return await fn();
23
54
  }
24
55
  catch (error) {
25
- lastError = error;
26
- if (attempt === attempts - 1 || !retryIf(error))
56
+ signal?.throwIfAborted();
57
+ if (attempt >= attempts - 1 || !retryIf(error))
27
58
  throw error;
28
- const wait = Math.min(delay * Math.pow(factor, attempt), maxDelay);
29
- await new Promise((resolve) => setTimeout(resolve, wait));
59
+ const hint = retryDelay?.(error);
60
+ const wait = hint !== undefined && Number.isFinite(hint) && hint >= 0
61
+ ? hint
62
+ : Math.min(delay * Math.pow(factor, attempt), maxDelay) * (0.5 + Math.random() / 2);
63
+ await retrySleep(wait, signal);
30
64
  }
31
65
  }
32
- throw lastError;
33
66
  }