@spotpatch/vite 1.3.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -30,492 +30,28 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- DEFAULT_AGENT_LIMITS: () => import_shared12.DEFAULT_AGENT_LIMITS,
34
- DEFAULT_OPTIONS: () => DEFAULT_OPTIONS,
35
- resolveOptions: () => resolveOptions,
33
+ DEFAULT_AGENT_LIMITS: () => import_shared.DEFAULT_AGENT_LIMITS,
34
+ DEFAULT_OPTIONS: () => import_dev_server4.DEFAULT_OPTIONS,
35
+ resolveOptions: () => import_dev_server4.resolveOptions,
36
36
  spotPatch: () => spotPatch
37
37
  });
38
38
  module.exports = __toCommonJS(index_exports);
39
39
 
40
40
  // src/plugin.ts
41
- var import_node_path10 = __toESM(require("path"), 1);
41
+ var import_node_path5 = __toESM(require("path"), 1);
42
+ var import_dev_server3 = require("@spotpatch/dev-server");
42
43
  var import_vite2 = require("vite");
43
44
 
44
- // src/environment-ai.ts
45
- var AI_ENVIRONMENT_NAMES = Object.freeze({
46
- authentication: "SPOTPATCH_AI_AUTHENTICATION",
47
- baseURL: "SPOTPATCH_AI_BASE_URL",
48
- credential: "SPOTPATCH_AI_API_KEY",
49
- model: "SPOTPATCH_AI_MODEL",
50
- protocol: "SPOTPATCH_AI_PROTOCOL"
51
- });
52
- function normalizedValue(environment, name) {
53
- const value = environment[name];
54
- if (value === void 0 || value.trim().length === 0) {
55
- return void 0;
56
- }
57
- return value.trim();
58
- }
59
- function resolveEnvironmentAiConfiguration(environment) {
60
- const baseURL = normalizedValue(environment, AI_ENVIRONMENT_NAMES.baseURL);
61
- const model = normalizedValue(environment, AI_ENVIRONMENT_NAMES.model);
62
- const credential = normalizedValue(environment, AI_ENVIRONMENT_NAMES.credential);
63
- const protocol = normalizedValue(environment, AI_ENVIRONMENT_NAMES.protocol);
64
- const authentication = normalizedValue(
65
- environment,
66
- AI_ENVIRONMENT_NAMES.authentication
67
- );
68
- const configuredValues = [baseURL, model, credential, protocol, authentication];
69
- if (configuredValues.every((value) => value === void 0)) {
70
- return Object.freeze({ ai: false });
71
- }
72
- const missing = [
73
- [AI_ENVIRONMENT_NAMES.baseURL, baseURL],
74
- [AI_ENVIRONMENT_NAMES.model, model],
75
- [AI_ENVIRONMENT_NAMES.credential, credential]
76
- ].filter((entry) => entry[1] === void 0).map(([name]) => name);
77
- if (missing.length > 0) {
78
- throw new RangeError(
79
- `SpotPatch AI environment configuration is incomplete; missing ${missing.join(", ")}.`
80
- );
81
- }
82
- if (baseURL === void 0 || model === void 0 || credential === void 0) {
83
- throw new RangeError("SpotPatch AI environment configuration is incomplete.");
84
- }
85
- if (protocol !== void 0 && protocol !== "responses" && protocol !== "chat-completions") {
86
- throw new RangeError(
87
- "SpotPatch SPOTPATCH_AI_PROTOCOL must be responses or chat-completions."
88
- );
89
- }
90
- if (authentication !== void 0 && authentication !== "bearer" && authentication !== "x-api-key") {
91
- throw new RangeError(
92
- "SpotPatch SPOTPATCH_AI_AUTHENTICATION must be bearer or x-api-key."
93
- );
94
- }
95
- return Object.freeze({
96
- ai: Object.freeze({
97
- baseURL,
98
- model,
99
- ...protocol === void 0 ? {} : { protocol },
100
- ...authentication === void 0 ? {} : { authentication }
101
- })
102
- });
103
- }
104
-
105
- // src/options.ts
106
- var import_shared = require("@spotpatch/shared");
107
- var import_zod = require("zod");
108
- var DEFAULT_EXCLUDE = Object.freeze([
109
- /node_modules/,
110
- /\.test\.[jt]sx$/,
111
- /\.spec\.[jt]sx$/,
112
- /\.stories\.[jt]sx$/,
113
- /(?:^|\/)dist(?:\/|$)/,
114
- /(?:^|\/)coverage(?:\/|$)/
115
- ]);
116
- var DEFAULT_INCLUDE = Object.freeze([/(?:^|[/\\])src[/\\].+\.(?:jsx|tsx)$/]);
117
- var DEFAULT_BUDGET = Object.freeze({
118
- totalCharacters: 16e3,
119
- domCharacters: 3e3,
120
- cssCharacters: 4e3,
121
- codeCharacters: 7e3,
122
- maxCodeLines: 80,
123
- maxComponentDepth: 8
124
- });
125
- var DEFAULT_OPTIONS = Object.freeze({
126
- enabled: true,
127
- include: DEFAULT_INCLUDE,
128
- exclude: DEFAULT_EXCLUDE,
129
- editor: "auto",
130
- redact: true,
131
- budget: DEFAULT_BUDGET,
132
- shortcut: "Mod+Shift+S",
133
- allowLan: false,
134
- debug: false,
135
- locale: "auto",
136
- maxTargets: 8,
137
- ai: false
138
- });
139
- var PROFILE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
140
- var ENV_NAME_PATTERN = /^[A-Z][A-Z0-9_]{1,127}$/;
141
- var agentLimitsSchema = import_zod.z.strictObject({
142
- maxTurns: import_zod.z.number().optional(),
143
- maxToolCalls: import_zod.z.number().optional(),
144
- maxChangedFiles: import_zod.z.number().optional(),
145
- maxDiffBytes: import_zod.z.number().optional(),
146
- maxReadBytesPerFile: import_zod.z.number().optional(),
147
- maxToolOutputCharacters: import_zod.z.number().optional(),
148
- maxProviderResponseBytes: import_zod.z.number().optional(),
149
- providerConnectTimeoutMs: import_zod.z.number().optional(),
150
- providerFirstByteTimeoutMs: import_zod.z.number().optional(),
151
- providerIdleTimeoutMs: import_zod.z.number().optional(),
152
- checkTimeoutMs: import_zod.z.number().optional(),
153
- jobTimeoutMs: import_zod.z.number().optional()
154
- }).optional();
155
- var agentCheckSchema = import_zod.z.strictObject({
156
- label: import_zod.z.string(),
157
- command: import_zod.z.string(),
158
- args: import_zod.z.array(import_zod.z.string()).optional(),
159
- required: import_zod.z.boolean().optional(),
160
- timeoutMs: import_zod.z.number().optional()
161
- });
162
- var aiOptionsSchema = import_zod.z.strictObject({
163
- providers: import_zod.z.record(
164
- import_zod.z.string(),
165
- import_zod.z.strictObject({
166
- type: import_zod.z.literal("openai-compatible"),
167
- label: import_zod.z.string(),
168
- protocol: import_zod.z.enum(["responses", "chat-completions"]),
169
- authentication: import_zod.z.enum(["bearer", "x-api-key"]).optional(),
170
- baseURL: import_zod.z.string(),
171
- apiKeyEnv: import_zod.z.string(),
172
- models: import_zod.z.record(
173
- import_zod.z.string(),
174
- import_zod.z.strictObject({ label: import_zod.z.string(), model: import_zod.z.string() })
175
- ),
176
- defaultModel: import_zod.z.string()
177
- })
178
- ),
179
- defaultProvider: import_zod.z.string(),
180
- execution: import_zod.z.strictObject({
181
- isolation: import_zod.z.literal("git-worktree").optional(),
182
- applyMode: import_zod.z.enum(["review", "auto"]).optional(),
183
- checks: import_zod.z.record(import_zod.z.string(), agentCheckSchema).optional(),
184
- limits: agentLimitsSchema
185
- }).optional()
186
- });
187
- var simpleAiOptionsSchema = import_zod.z.strictObject({
188
- baseURL: import_zod.z.string(),
189
- model: import_zod.z.string(),
190
- apiKeyEnv: import_zod.z.string().optional(),
191
- protocol: import_zod.z.enum(["responses", "chat-completions"]).optional(),
192
- authentication: import_zod.z.enum(["bearer", "x-api-key"]).optional(),
193
- providerLabel: import_zod.z.string().optional(),
194
- modelLabel: import_zod.z.string().optional(),
195
- execution: aiOptionsSchema.shape.execution
196
- });
197
- function assertIdentifier(value, label) {
198
- if (!PROFILE_ID_PATTERN.test(value)) {
199
- throw new RangeError(
200
- `SpotPatch ${label} must contain only letters, numbers, dot, underscore, or hyphen.`
201
- );
202
- }
203
- }
204
- function nonEmpty(value, label, maximum = 256) {
205
- const normalized = value.trim();
206
- if (normalized.length === 0 || normalized.length > maximum || value.includes("\0")) {
207
- throw new RangeError(`SpotPatch ${label} is invalid.`);
208
- }
209
- return normalized;
210
- }
211
- function normalizeProviderBaseURL(value) {
212
- let url;
213
- try {
214
- url = new URL(value);
215
- } catch {
216
- throw new RangeError("SpotPatch AI provider baseURL must be a valid URL.");
217
- }
218
- const loopbackHosts = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
219
- const allowedProtocol = url.protocol === "https:" || url.protocol === "http:" && loopbackHosts.has(url.hostname);
220
- if (!allowedProtocol || url.username.length > 0 || url.password.length > 0 || url.search.length > 0 || url.hash.length > 0) {
221
- throw new RangeError("SpotPatch AI provider baseURL violates URL policy.");
222
- }
223
- url.pathname = url.pathname.replace(/\/{2,}/g, "/").replace(/\/$/, "");
224
- return url.toString().replace(/\/$/, "");
225
- }
226
- function resolveLimits(limits) {
227
- const resolved = Object.freeze({
228
- maxTurns: limits?.maxTurns ?? import_shared.DEFAULT_AGENT_LIMITS.maxTurns,
229
- maxToolCalls: limits?.maxToolCalls ?? import_shared.DEFAULT_AGENT_LIMITS.maxToolCalls,
230
- maxChangedFiles: limits?.maxChangedFiles ?? import_shared.DEFAULT_AGENT_LIMITS.maxChangedFiles,
231
- maxDiffBytes: limits?.maxDiffBytes ?? import_shared.DEFAULT_AGENT_LIMITS.maxDiffBytes,
232
- maxReadBytesPerFile: limits?.maxReadBytesPerFile ?? import_shared.DEFAULT_AGENT_LIMITS.maxReadBytesPerFile,
233
- maxToolOutputCharacters: limits?.maxToolOutputCharacters ?? import_shared.DEFAULT_AGENT_LIMITS.maxToolOutputCharacters,
234
- maxProviderResponseBytes: limits?.maxProviderResponseBytes ?? import_shared.DEFAULT_AGENT_LIMITS.maxProviderResponseBytes,
235
- providerConnectTimeoutMs: limits?.providerConnectTimeoutMs ?? import_shared.DEFAULT_AGENT_LIMITS.providerConnectTimeoutMs,
236
- providerFirstByteTimeoutMs: limits?.providerFirstByteTimeoutMs ?? import_shared.DEFAULT_AGENT_LIMITS.providerFirstByteTimeoutMs,
237
- providerIdleTimeoutMs: limits?.providerIdleTimeoutMs ?? import_shared.DEFAULT_AGENT_LIMITS.providerIdleTimeoutMs,
238
- checkTimeoutMs: limits?.checkTimeoutMs ?? import_shared.DEFAULT_AGENT_LIMITS.checkTimeoutMs,
239
- jobTimeoutMs: limits?.jobTimeoutMs ?? import_shared.DEFAULT_AGENT_LIMITS.jobTimeoutMs
240
- });
241
- for (const [name, value] of Object.entries(resolved)) {
242
- if (!Number.isSafeInteger(value) || value <= 0) {
243
- throw new RangeError(`SpotPatch AI limit ${name} must be a positive integer.`);
244
- }
245
- }
246
- return resolved;
247
- }
248
- function resolveModels(models) {
249
- const entries = Object.entries(models);
250
- if (entries.length === 0) {
251
- throw new RangeError("SpotPatch AI provider must declare at least one model.");
252
- }
253
- return Object.freeze(
254
- Object.fromEntries(
255
- entries.map(([id, model]) => {
256
- assertIdentifier(id, "model profile id");
257
- return [
258
- id,
259
- Object.freeze({
260
- id,
261
- label: nonEmpty(model.label, "model label", 100),
262
- model: nonEmpty(model.model, "provider model name")
263
- })
264
- ];
265
- })
266
- )
267
- );
268
- }
269
- function resolveProviders(providers) {
270
- const entries = Object.entries(providers);
271
- if (entries.length === 0) {
272
- throw new RangeError("SpotPatch AI must declare at least one provider.");
273
- }
274
- return Object.freeze(
275
- Object.fromEntries(
276
- entries.map(([id, provider]) => {
277
- assertIdentifier(id, "provider profile id");
278
- if (!ENV_NAME_PATTERN.test(provider.apiKeyEnv) || provider.apiKeyEnv.startsWith("VITE_")) {
279
- throw new RangeError(
280
- "SpotPatch AI apiKeyEnv must be an uppercase non-VITE environment name."
281
- );
282
- }
283
- const models = resolveModels(provider.models);
284
- if (!(provider.defaultModel in models)) {
285
- throw new RangeError(
286
- "SpotPatch AI provider defaultModel must reference a configured model."
287
- );
288
- }
289
- return [
290
- id,
291
- Object.freeze({
292
- id,
293
- type: provider.type,
294
- label: nonEmpty(provider.label, "provider label", 100),
295
- protocol: provider.protocol,
296
- authentication: provider.authentication ?? "bearer",
297
- baseURL: normalizeProviderBaseURL(provider.baseURL),
298
- apiKeyEnv: provider.apiKeyEnv,
299
- models,
300
- defaultModel: provider.defaultModel
301
- })
302
- ];
303
- })
304
- )
305
- );
306
- }
307
- function resolveChecks(checks, defaultTimeoutMs) {
308
- return Object.freeze(
309
- Object.fromEntries(
310
- Object.entries(checks ?? {}).map(([id, check]) => {
311
- assertIdentifier(id, "check id");
312
- const timeoutMs = check.timeoutMs ?? defaultTimeoutMs;
313
- if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
314
- throw new RangeError("SpotPatch AI check timeout must be positive.");
315
- }
316
- const args = Object.freeze(
317
- [...check.args ?? []].map(
318
- (argument) => nonEmpty(argument, "check argument", 4096)
319
- )
320
- );
321
- return [
322
- id,
323
- Object.freeze({
324
- id,
325
- label: nonEmpty(check.label, "check label", 100),
326
- command: nonEmpty(check.command, "check command", 1024),
327
- args,
328
- required: check.required ?? true,
329
- timeoutMs
330
- })
331
- ];
332
- })
333
- )
334
- );
335
- }
336
- function resolveAiOptions(options) {
337
- if (options === void 0 || options === false) {
338
- return false;
339
- }
340
- const expanded = "providers" in options ? options : (() => {
341
- const simple = simpleAiOptionsSchema.safeParse(options);
342
- if (!simple.success) {
343
- throw new RangeError("SpotPatch AI configuration is invalid.");
344
- }
345
- const providerId = "default";
346
- const modelId = "default";
347
- return {
348
- providers: {
349
- [providerId]: {
350
- type: "openai-compatible",
351
- label: simple.data.providerLabel ?? "AI provider",
352
- protocol: simple.data.protocol ?? "chat-completions",
353
- authentication: simple.data.authentication ?? "bearer",
354
- baseURL: simple.data.baseURL,
355
- apiKeyEnv: simple.data.apiKeyEnv ?? "SPOTPATCH_AI_API_KEY",
356
- models: {
357
- [modelId]: {
358
- label: simple.data.modelLabel ?? "AI model",
359
- model: simple.data.model
360
- }
361
- },
362
- defaultModel: modelId
363
- }
364
- },
365
- defaultProvider: providerId,
366
- ...simple.data.execution === void 0 ? {} : { execution: simple.data.execution }
367
- };
368
- })();
369
- const parsed = aiOptionsSchema.safeParse(expanded);
370
- if (!parsed.success) {
371
- throw new RangeError("SpotPatch AI configuration is invalid.");
372
- }
373
- const validated = parsed.data;
374
- const limits = resolveLimits(validated.execution?.limits);
375
- const checks = resolveChecks(validated.execution?.checks, limits.checkTimeoutMs);
376
- const applyMode = validated.execution?.applyMode ?? "review";
377
- if (applyMode === "auto" && !Object.values(checks).some((check) => check.required)) {
378
- throw new RangeError("SpotPatch AI auto mode requires a required check.");
379
- }
380
- const providers = resolveProviders(validated.providers);
381
- if (!(validated.defaultProvider in providers)) {
382
- throw new RangeError(
383
- "SpotPatch AI defaultProvider must reference a configured provider."
384
- );
385
- }
386
- return Object.freeze({
387
- providers,
388
- defaultProvider: validated.defaultProvider,
389
- execution: Object.freeze({
390
- isolation: "git-worktree",
391
- applyMode,
392
- checks,
393
- limits
394
- })
395
- });
396
- }
397
- function createRuntimeAiConfig(options) {
398
- if (options === false) {
399
- return Object.freeze({ enabled: false });
400
- }
401
- return Object.freeze({
402
- enabled: true,
403
- defaultProvider: options.defaultProvider,
404
- applyMode: options.execution.applyMode,
405
- providers: Object.freeze(
406
- Object.values(options.providers).map(
407
- (provider) => Object.freeze({
408
- id: provider.id,
409
- label: provider.label,
410
- protocol: provider.protocol,
411
- defaultModel: provider.defaultModel,
412
- models: Object.freeze(
413
- Object.values(provider.models).map(
414
- (model) => Object.freeze({ id: model.id, label: model.label })
415
- )
416
- )
417
- })
418
- )
419
- )
420
- });
421
- }
422
- function assertPositiveBudget(budget) {
423
- for (const [name, value] of Object.entries(budget)) {
424
- if (!Number.isSafeInteger(value) || value <= 0) {
425
- throw new RangeError(`SpotPatch budget ${name} must be a positive integer.`);
426
- }
427
- }
428
- }
429
- function resolveOptions(options = {}, environmentAi) {
430
- const budget = Object.freeze({
431
- ...DEFAULT_OPTIONS.budget,
432
- ...options.budget
433
- });
434
- assertPositiveBudget(budget);
435
- const maxTargets = options.maxTargets ?? DEFAULT_OPTIONS.maxTargets;
436
- const locale = options.locale ?? DEFAULT_OPTIONS.locale;
437
- const editor = options.editor ?? DEFAULT_OPTIONS.editor;
438
- if (!import_shared.SPOTPATCH_LOCALE_PREFERENCES.includes(locale)) {
439
- throw new RangeError("SpotPatch locale must be auto, en-US, or zh-CN.");
440
- }
441
- if (!import_shared.SPOTPATCH_EDITOR_PREFERENCES.includes(editor)) {
442
- throw new RangeError("SpotPatch editor must be auto, vscode, or cursor.");
443
- }
444
- if (!Number.isSafeInteger(maxTargets) || maxTargets < 1 || maxTargets > import_shared.MAX_ANNOTATION_TARGETS) {
445
- throw new RangeError(
446
- `SpotPatch maxTargets must be an integer between 1 and ${String(import_shared.MAX_ANNOTATION_TARGETS)}.`
447
- );
448
- }
449
- const resolved = {
450
- enabled: options.enabled ?? DEFAULT_OPTIONS.enabled,
451
- include: Object.freeze([...options.include ?? DEFAULT_OPTIONS.include]),
452
- exclude: Object.freeze([...options.exclude ?? DEFAULT_OPTIONS.exclude]),
453
- editor,
454
- redact: options.redact ?? DEFAULT_OPTIONS.redact,
455
- budget,
456
- shortcut: options.shortcut ?? DEFAULT_OPTIONS.shortcut,
457
- allowLan: options.allowLan ?? DEFAULT_OPTIONS.allowLan,
458
- debug: options.debug ?? DEFAULT_OPTIONS.debug,
459
- locale,
460
- maxTargets,
461
- ai: resolveAiOptions(options.ai ?? environmentAi)
462
- };
463
- if (resolved.shortcut.trim().length === 0) {
464
- throw new RangeError("SpotPatch shortcut cannot be empty.");
465
- }
466
- return Object.freeze(resolved);
467
- }
468
-
469
- // src/registry/source-registry.ts
470
- var import_node_path = __toESM(require("path"), 1);
471
-
472
- // src/registry/source-id.ts
473
- var import_node_crypto = require("crypto");
474
- var SOURCE_ID_BYTES = 8;
475
- var createRandomSourceId = () => (0, import_node_crypto.randomBytes)(SOURCE_ID_BYTES).toString("base64url");
476
-
477
- // src/registry/source-registry.ts
478
- function normalizeAbsolutePath(absolutePath) {
479
- return import_node_path.default.normalize(import_node_path.default.resolve(absolutePath));
480
- }
481
- function createSourceRegistry(options = {}) {
482
- const createId = options.createId ?? createRandomSourceId;
483
- const pathToId = /* @__PURE__ */ new Map();
484
- const idToPath = /* @__PURE__ */ new Map();
485
- return Object.freeze({
486
- register(absolutePath) {
487
- const normalizedPath = normalizeAbsolutePath(absolutePath);
488
- const existingId = pathToId.get(normalizedPath);
489
- if (existingId !== void 0) {
490
- return existingId;
491
- }
492
- let fileId = createId();
493
- while (idToPath.has(fileId)) {
494
- fileId = createId();
495
- }
496
- pathToId.set(normalizedPath, fileId);
497
- idToPath.set(fileId, normalizedPath);
498
- return fileId;
499
- },
500
- resolve(fileId) {
501
- return idToPath.get(fileId);
502
- },
503
- clear() {
504
- pathToId.clear();
505
- idToPath.clear();
506
- }
507
- });
508
- }
509
-
510
45
  // src/runtime/runtime-injection-plugin.ts
511
46
  var import_node_module = require("module");
512
47
  var import_node_fs = require("fs");
513
- var import_node_path2 = __toESM(require("path"), 1);
48
+ var import_node_path = __toESM(require("path"), 1);
49
+ var import_dev_server = require("@spotpatch/dev-server");
514
50
 
515
51
  // package.json
516
52
  var package_default = {
517
53
  name: "@spotpatch/vite",
518
- version: "1.3.1",
54
+ version: "1.4.1",
519
55
  description: "Vite development plugin for SpotPatch.",
520
56
  license: "MIT",
521
57
  repository: {
@@ -563,15 +99,11 @@ var package_default = {
563
99
  typecheck: "tsc --noEmit -p tsconfig.json"
564
100
  },
565
101
  dependencies: {
566
- "@rollup/pluginutils": "5.4.0",
567
- "@spotpatch/agent": "workspace:^",
102
+ "@spotpatch/compiler": "workspace:^",
103
+ "@spotpatch/dev-server": "workspace:^",
568
104
  "@spotpatch/react-adapter": "workspace:^",
569
105
  "@spotpatch/runtime": "workspace:^",
570
- "@spotpatch/shared": "workspace:^",
571
- "launch-editor": "2.14.1",
572
- "magic-string": "1.1.0",
573
- "oxc-parser": "0.143.0",
574
- zod: "4.4.3"
106
+ "@spotpatch/shared": "workspace:^"
575
107
  },
576
108
  peerDependencies: {
577
109
  vite: "^5.0.0 || ^6.0.0 || ^7.0.0"
@@ -589,14 +121,14 @@ var RESOLVED_SPOTPATCH_CLIENT_MODULE_ID = `\0${SPOTPATCH_CLIENT_MODULE_ID}`;
589
121
  var SPOTPATCH_REACT_ADAPTER_MODULE_ID = "virtual:spotpatch/react-adapter";
590
122
  var RESOLVED_SPOTPATCH_REACT_ADAPTER_MODULE_ID = `\0${SPOTPATCH_REACT_ADAPTER_MODULE_ID}`;
591
123
  function readRuntimeBundle(root, fileName) {
592
- const resolveFromProject = (0, import_node_module.createRequire)(import_node_path2.default.join(root, "package.json"));
124
+ const resolveFromProject = (0, import_node_module.createRequire)(import_node_path.default.join(root, "package.json"));
593
125
  const packageEntry = resolveFromProject.resolve("@spotpatch/vite");
594
- const bundlePath = import_node_path2.default.join(import_node_path2.default.dirname(packageEntry), fileName);
126
+ const bundlePath = import_node_path.default.join(import_node_path.default.dirname(packageEntry), fileName);
595
127
  return (0, import_node_fs.readFileSync)(bundlePath, "utf8");
596
128
  }
597
129
  function readConsumerViteVersion(root) {
598
130
  try {
599
- const resolveFromProject = (0, import_node_module.createRequire)(import_node_path2.default.join(root, "package.json"));
131
+ const resolveFromProject = (0, import_node_module.createRequire)(import_node_path.default.join(root, "package.json"));
600
132
  const manifestPath = resolveFromProject.resolve("vite/package.json");
601
133
  const manifest = JSON.parse((0, import_node_fs.readFileSync)(manifestPath, "utf8"));
602
134
  if (typeof manifest === "object" && manifest !== null && "version" in manifest && typeof manifest.version === "string") {
@@ -609,17 +141,18 @@ function readConsumerViteVersion(root) {
609
141
  function createClientModule(input, clientBundle, viteVersion) {
610
142
  const options = input.context.getOptions();
611
143
  const runtimeConfig = {
612
- ai: createRuntimeAiConfig(options.ai),
144
+ ai: (0, import_dev_server.createRuntimeAiConfig)(options.ai),
613
145
  budget: options.budget,
614
146
  debug: options.debug,
615
147
  editor: options.editor,
148
+ framework: "vite",
149
+ frameworkVersion: viteVersion,
616
150
  locale: options.locale,
617
151
  maxTargets: options.maxTargets,
618
152
  redact: options.redact,
619
153
  sessionToken: input.session.token,
620
154
  shortcut: options.shortcut,
621
- spotPatchVersion: package_default.version,
622
- viteVersion
155
+ spotPatchVersion: package_default.version
623
156
  };
624
157
  return [
625
158
  `const __SPOTPATCH_RUNTIME_CONFIG__ = ${JSON.stringify(runtimeConfig)};`,
@@ -635,7 +168,7 @@ function createRuntimeInjectionPlugin(input) {
635
168
  apply: "serve",
636
169
  enforce: "pre",
637
170
  configResolved(config) {
638
- root = import_node_path2.default.resolve(config.root);
171
+ root = import_node_path.default.resolve(config.root);
639
172
  viteVersion = readConsumerViteVersion(root);
640
173
  },
641
174
  resolveId(id, importer) {
@@ -673,1533 +206,8 @@ function createRuntimeInjectionPlugin(input) {
673
206
  }
674
207
 
675
208
  // src/server/server-plugin.ts
676
- var import_node_path6 = __toESM(require("path"), 1);
677
-
678
- // src/agent/job-manager.ts
679
- var import_node_crypto2 = require("crypto");
680
- var import_agent = require("@spotpatch/agent");
681
- var import_shared2 = require("@spotpatch/shared");
682
- var MAX_RETAINED_JOBS = 32;
683
- var MAX_RETAINED_EVENTS = 512;
684
- var JOB_ID_PATTERN = /^[A-Za-z0-9_-]{22,128}$/;
685
- var ACTIVE_JOB_STATUSES = /* @__PURE__ */ new Set([
686
- "queued",
687
- "preparing",
688
- "running",
689
- "validating",
690
- "awaiting-review",
691
- "applying",
692
- "cancelling",
693
- "reverting"
694
- ]);
695
- var CANCELLABLE_JOB_STATUSES = /* @__PURE__ */ new Set([
696
- "queued",
697
- "preparing",
698
- "running",
699
- "validating",
700
- "awaiting-review"
701
- ]);
702
- var PRUNABLE_JOB_STATUSES = /* @__PURE__ */ new Set([
703
- "completed",
704
- "cancelled",
705
- "reverted",
706
- "failed"
707
- ]);
708
- var DEFAULT_DEPENDENCIES = Object.freeze({
709
- applyChange: import_agent.applyPreparedAgentChange,
710
- createJobId: () => (0, import_node_crypto2.randomBytes)(16).toString("base64url"),
711
- executeChange: import_agent.executeAgentChange,
712
- inspectWorkspace: import_agent.inspectAgentWorkspace,
713
- now: () => (/* @__PURE__ */ new Date()).toISOString(),
714
- probeCapability: import_agent.probeProviderCapability,
715
- resolveCredential: import_agent.resolveProviderCredential,
716
- revertChange: import_agent.revertPreparedAgentChange
717
- });
718
- function normalizeError(error) {
719
- return error instanceof import_shared2.SpotPatchError ? error : new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.INTERNAL_ERROR, void 0, { cause: error });
720
- }
721
- function isActive(status) {
722
- return ACTIVE_JOB_STATUSES.has(status);
723
- }
724
- function snapshot(job) {
725
- const base = {
726
- jobId: job.id,
727
- status: job.status,
728
- providerProfileId: job.provider.id,
729
- providerLabel: job.provider.label,
730
- modelProfileId: job.model.id,
731
- modelLabel: job.model.label,
732
- phaseMessage: job.phaseMessage,
733
- createdAt: job.createdAt,
734
- updatedAt: job.updatedAt,
735
- canCancel: CANCELLABLE_JOB_STATUSES.has(job.status),
736
- canApply: job.status === "awaiting-review" && job.preparedChange?.validationPassed === true && job.result !== void 0 && job.result.diff.length > 0,
737
- canRevert: job.status === "applied"
738
- };
739
- return Object.freeze(
740
- job.errorCode === void 0 ? base : { ...base, errorCode: job.errorCode }
741
- );
742
- }
743
- function capabilityCacheKey(provider, model) {
744
- const configurationDigest = (0, import_node_crypto2.createHash)("sha256").update(provider.baseURL).update("\0").update(provider.protocol).update("\0").update(provider.authentication).digest("hex");
745
- return `${provider.id}:${model.id}:${configurationDigest}`;
746
- }
747
- function freezeEvent(event) {
748
- return Object.freeze(event);
749
- }
750
- function createAgentJobManager(options) {
751
- const dependencies = Object.freeze({
752
- ...DEFAULT_DEPENDENCIES,
753
- ...options.dependencies
754
- });
755
- const jobs = /* @__PURE__ */ new Map();
756
- const capabilityCache = /* @__PURE__ */ new Map();
757
- const providerConsents = /* @__PURE__ */ new Set();
758
- let closed = false;
759
- const resolveSelection = (providerProfileId, modelProfileId) => {
760
- const provider = options.ai.providers[providerProfileId];
761
- if (provider === void 0) {
762
- throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.PROVIDER_NOT_CONFIGURED);
763
- }
764
- const model = provider.models[modelProfileId];
765
- if (model === void 0) {
766
- throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.MODEL_NOT_ALLOWED);
767
- }
768
- const credential = dependencies.resolveCredential(
769
- provider.apiKeyEnv,
770
- options.environment
771
- );
772
- return Object.freeze({ credential, model, provider });
773
- };
774
- const requireJob = (jobId) => {
775
- if (!JOB_ID_PATTERN.test(jobId)) {
776
- throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.INVALID_REQUEST);
777
- }
778
- const job = jobs.get(jobId);
779
- if (job === void 0) {
780
- throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.INVALID_REQUEST);
781
- }
782
- return job;
783
- };
784
- const appendEvent = (job, event) => {
785
- job.events.push(event);
786
- if (job.events.length > MAX_RETAINED_EVENTS) {
787
- job.events.splice(0, job.events.length - MAX_RETAINED_EVENTS);
788
- }
789
- for (const listener of job.listeners) {
790
- listener(event);
791
- }
792
- };
793
- const eventBase = (job) => {
794
- job.sequence += 1;
795
- return {
796
- schemaVersion: 1,
797
- sequence: job.sequence,
798
- jobId: job.id,
799
- status: job.status,
800
- timestamp: dependencies.now()
801
- };
802
- };
803
- const emitSnapshot = (job) => {
804
- appendEvent(
805
- job,
806
- freezeEvent({
807
- ...eventBase(job),
808
- type: "snapshot",
809
- data: Object.freeze({ snapshot: snapshot(job) })
810
- })
811
- );
812
- };
813
- const emitPhase = (job, message) => {
814
- appendEvent(
815
- job,
816
- freezeEvent({
817
- ...eventBase(job),
818
- type: "phase",
819
- data: Object.freeze({ message })
820
- })
821
- );
822
- };
823
- const emitError = (job, code) => {
824
- appendEvent(
825
- job,
826
- freezeEvent({
827
- ...eventBase(job),
828
- type: "error",
829
- data: Object.freeze({ code, message: "The Agent job failed." })
830
- })
831
- );
832
- };
833
- const transition = (job, status, phaseMessage, errorCode) => {
834
- job.status = status;
835
- job.phaseMessage = phaseMessage;
836
- job.errorCode = errorCode;
837
- job.updatedAt = dependencies.now();
838
- emitSnapshot(job);
839
- emitPhase(job, phaseMessage);
840
- };
841
- const probeResolved = async (selection, signal) => {
842
- const key = capabilityCacheKey(selection.provider, selection.model);
843
- const cached = capabilityCache.get(key);
844
- if (cached !== void 0) {
845
- return cached;
846
- }
847
- const capability = await dependencies.probeCapability({
848
- provider: selection.provider,
849
- modelProfileId: selection.model.id,
850
- limits: options.ai.execution.limits,
851
- credential: selection.credential,
852
- signal,
853
- ...options.fetch === void 0 ? {} : { fetch: options.fetch }
854
- });
855
- if (capability.state !== "agent-ready") {
856
- throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED);
857
- }
858
- capabilityCache.set(key, capability);
859
- return capability;
860
- };
861
- const finishWithError = (job, error) => {
862
- const normalized = normalizeError(error);
863
- const cancelled = job.controller.signal.aborted || normalized.code === import_shared2.ERROR_CODES.AGENT_CANCELLED;
864
- transition(
865
- job,
866
- cancelled ? "cancelled" : "failed",
867
- cancelled ? "Agent job cancelled." : "Agent job failed.",
868
- cancelled ? import_shared2.ERROR_CODES.AGENT_CANCELLED : normalized.code
869
- );
870
- if (!cancelled) {
871
- emitError(job, normalized.code);
872
- }
873
- };
874
- const applyChange = async (job, preparedChange) => {
875
- transition(job, "applying", "Applying validated changes to the project.");
876
- try {
877
- await dependencies.applyChange(preparedChange);
878
- transition(job, "applied", "Changes were applied to local project files.");
879
- } catch (error) {
880
- const normalized = normalizeError(error);
881
- transition(job, "failed", "Agent change could not be applied.", normalized.code);
882
- emitError(job, normalized.code);
883
- throw normalized;
884
- }
885
- };
886
- const runJob = async (job) => {
887
- try {
888
- transition(job, "preparing", "Verifying provider and model capabilities.");
889
- await probeResolved(
890
- Object.freeze({
891
- credential: job.credential,
892
- model: job.model,
893
- provider: job.provider
894
- }),
895
- job.controller.signal
896
- );
897
- const callbacks = {
898
- onCheck(result) {
899
- appendEvent(
900
- job,
901
- freezeEvent({
902
- ...eventBase(job),
903
- type: "check",
904
- data: Object.freeze({ result })
905
- })
906
- );
907
- },
908
- onPhase(event) {
909
- transition(job, event.phase, event.message);
910
- },
911
- onTool(event) {
912
- appendEvent(
913
- job,
914
- freezeEvent({
915
- ...eventBase(job),
916
- type: "tool",
917
- data: Object.freeze({ ...event })
918
- })
919
- );
920
- }
921
- };
922
- const preparedChange = await dependencies.executeChange({
923
- annotation: job.annotation,
924
- callbacks,
925
- credential: job.credential,
926
- execution: options.ai.execution,
927
- jobId: job.id,
928
- model: job.model,
929
- provider: job.provider,
930
- root: options.root,
931
- signal: job.controller.signal,
932
- workingTreeMode: job.workingTreeMode,
933
- ...options.fetch === void 0 ? {} : { fetch: options.fetch }
934
- });
935
- job.preparedChange = preparedChange;
936
- job.result = preparedChange.result;
937
- appendEvent(
938
- job,
939
- freezeEvent({
940
- ...eventBase(job),
941
- type: "result-ready",
942
- data: Object.freeze({ hasResult: true })
943
- })
944
- );
945
- if (!preparedChange.validationPassed) {
946
- transition(
947
- job,
948
- "failed",
949
- "Required validation checks failed.",
950
- import_shared2.ERROR_CODES.VALIDATION_FAILED
951
- );
952
- emitError(job, import_shared2.ERROR_CODES.VALIDATION_FAILED);
953
- return;
954
- }
955
- if (preparedChange.result.diff.length === 0) {
956
- transition(job, "completed", "No source changes were proposed.");
957
- return;
958
- }
959
- if (options.ai.execution.applyMode === "auto" && preparedChange.autoApplyEligible) {
960
- try {
961
- await applyChange(job, preparedChange);
962
- } catch {
963
- }
964
- return;
965
- }
966
- transition(job, "awaiting-review", "Validated changes are ready for review.");
967
- } catch (error) {
968
- finishWithError(job, error);
969
- }
970
- };
971
- const hasActiveJob = (excludedJobId) => [...jobs.values()].some((job) => job.id !== excludedJobId && isActive(job.status));
972
- const pruneJobs = () => {
973
- if (jobs.size < MAX_RETAINED_JOBS) {
974
- return;
975
- }
976
- for (const [jobId, job] of jobs) {
977
- if (PRUNABLE_JOB_STATUSES.has(job.status)) {
978
- jobs.delete(jobId);
979
- }
980
- if (jobs.size < MAX_RETAINED_JOBS) {
981
- return;
982
- }
983
- }
984
- };
985
- return Object.freeze({
986
- async apply(jobId) {
987
- const job = requireJob(jobId);
988
- if (job.status !== "awaiting-review" || job.preparedChange === void 0 || !job.preparedChange.validationPassed || job.result?.diff.length === 0) {
989
- throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.PATCH_REJECTED);
990
- }
991
- await applyChange(job, job.preparedChange);
992
- return snapshot(job);
993
- },
994
- cancel(jobId) {
995
- const job = requireJob(jobId);
996
- if (!CANCELLABLE_JOB_STATUSES.has(job.status)) {
997
- return snapshot(job);
998
- }
999
- if (job.status === "awaiting-review") {
1000
- job.preparedChange = void 0;
1001
- job.controller.abort("agent-review-cancelled");
1002
- transition(
1003
- job,
1004
- "cancelled",
1005
- "Agent review was closed without applying changes.",
1006
- import_shared2.ERROR_CODES.AGENT_CANCELLED
1007
- );
1008
- return snapshot(job);
1009
- }
1010
- transition(job, "cancelling", "Cancelling Agent job.");
1011
- job.controller.abort("agent-job-cancelled");
1012
- return snapshot(job);
1013
- },
1014
- async close() {
1015
- if (closed) {
1016
- return;
1017
- }
1018
- closed = true;
1019
- for (const job of jobs.values()) {
1020
- if (CANCELLABLE_JOB_STATUSES.has(job.status)) {
1021
- job.controller.abort("vite-server-closed");
1022
- }
1023
- }
1024
- await Promise.allSettled(
1025
- [...jobs.values()].map((job) => job.runPromise).filter((promise) => promise !== void 0)
1026
- );
1027
- capabilityCache.clear();
1028
- providerConsents.clear();
1029
- jobs.clear();
1030
- },
1031
- create(request) {
1032
- if (closed) {
1033
- throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.AI_DISABLED);
1034
- }
1035
- if (hasActiveJob()) {
1036
- throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.AGENT_BUSY);
1037
- }
1038
- pruneJobs();
1039
- if (jobs.size >= MAX_RETAINED_JOBS) {
1040
- throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.AGENT_BUSY);
1041
- }
1042
- const selection = resolveSelection(
1043
- request.providerProfileId,
1044
- request.modelProfileId
1045
- );
1046
- providerConsents.add(selection.provider.id);
1047
- const id = dependencies.createJobId();
1048
- if (!JOB_ID_PATTERN.test(id) || jobs.has(id)) {
1049
- throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.INTERNAL_ERROR);
1050
- }
1051
- const timestamp = dependencies.now();
1052
- const job = {
1053
- annotation: request.annotation,
1054
- controller: new AbortController(),
1055
- createdAt: timestamp,
1056
- credential: selection.credential,
1057
- errorCode: void 0,
1058
- events: [],
1059
- id,
1060
- listeners: /* @__PURE__ */ new Set(),
1061
- model: selection.model,
1062
- phaseMessage: "Agent job queued.",
1063
- preparedChange: void 0,
1064
- provider: selection.provider,
1065
- result: void 0,
1066
- runPromise: void 0,
1067
- sequence: 0,
1068
- status: "queued",
1069
- updatedAt: timestamp,
1070
- workingTreeMode: request.workingTreeMode
1071
- };
1072
- jobs.set(id, job);
1073
- emitSnapshot(job);
1074
- emitPhase(job, job.phaseMessage);
1075
- job.runPromise = Promise.resolve().then(async () => runJob(job));
1076
- return snapshot(job);
1077
- },
1078
- events(jobId) {
1079
- return Object.freeze([...requireJob(jobId).events]);
1080
- },
1081
- async probe(request, signal) {
1082
- if (closed) {
1083
- throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.AI_DISABLED);
1084
- }
1085
- return probeResolved(
1086
- resolveSelection(request.providerProfileId, request.modelProfileId),
1087
- signal
1088
- );
1089
- },
1090
- result(jobId) {
1091
- const job = requireJob(jobId);
1092
- const response = job.result === void 0 ? { snapshot: snapshot(job) } : { snapshot: snapshot(job), result: job.result };
1093
- return Object.freeze(response);
1094
- },
1095
- async revert(jobId) {
1096
- const job = requireJob(jobId);
1097
- if (job.status !== "applied" || job.preparedChange === void 0 || hasActiveJob(job.id)) {
1098
- throw new import_shared2.SpotPatchError(
1099
- hasActiveJob(job.id) ? import_shared2.ERROR_CODES.AGENT_BUSY : import_shared2.ERROR_CODES.APPLY_CONFLICT
1100
- );
1101
- }
1102
- transition(job, "reverting", "Reverting the applied Agent change.");
1103
- try {
1104
- await dependencies.revertChange(job.preparedChange);
1105
- transition(job, "reverted", "The Agent change was safely reverted.");
1106
- } catch (error) {
1107
- const normalized = normalizeError(error);
1108
- transition(
1109
- job,
1110
- "applied",
1111
- "Revert was rejected because project files changed.",
1112
- normalized.code
1113
- );
1114
- emitError(job, normalized.code);
1115
- throw normalized;
1116
- }
1117
- return snapshot(job);
1118
- },
1119
- subscribe(jobId, listener) {
1120
- const job = requireJob(jobId);
1121
- job.listeners.add(listener);
1122
- return () => {
1123
- job.listeners.delete(listener);
1124
- };
1125
- },
1126
- workspaceHealth(signal) {
1127
- if (closed) {
1128
- throw new import_shared2.SpotPatchError(import_shared2.ERROR_CODES.AI_DISABLED);
1129
- }
1130
- return dependencies.inspectWorkspace(options.root, signal);
1131
- }
1132
- });
1133
- }
1134
-
1135
- // src/server/middleware.ts
1136
- var import_shared9 = require("@spotpatch/shared");
1137
-
1138
- // src/server/agent-http.ts
1139
- var import_shared7 = require("@spotpatch/shared");
1140
-
1141
- // src/server/agent-request.ts
1142
- var import_promises3 = require("fs/promises");
1143
- var import_node_path5 = __toESM(require("path"), 1);
1144
- var import_shared5 = require("@spotpatch/shared");
1145
-
1146
- // src/server/source-context.ts
1147
- var import_promises2 = require("fs/promises");
1148
- var import_node_path4 = __toESM(require("path"), 1);
1149
- var import_shared4 = require("@spotpatch/shared");
1150
-
1151
- // src/server/extract-code-context.ts
1152
- var import_oxc_parser = require("oxc-parser");
1153
- function isComponentName(name) {
1154
- return /^[A-Z]/u.test(name);
1155
- }
1156
- function unwrapTypeExpression(expression) {
1157
- switch (expression.type) {
1158
- case "TSAsExpression":
1159
- case "TSSatisfiesExpression":
1160
- case "TSTypeAssertion":
1161
- case "TSNonNullExpression":
1162
- case "TSInstantiationExpression":
1163
- return unwrapTypeExpression(expression.expression);
1164
- default:
1165
- return expression;
1166
- }
1167
- }
1168
- function calleeName(expression) {
1169
- const unwrapped = unwrapTypeExpression(expression);
1170
- if (unwrapped.type === "Identifier") {
1171
- return unwrapped.name;
1172
- }
1173
- if (unwrapped.type === "MemberExpression" && !unwrapped.computed) {
1174
- return unwrapped.property.type === "Identifier" ? unwrapped.property.name : void 0;
1175
- }
1176
- return void 0;
1177
- }
1178
- function isFunctionExpression(expression) {
1179
- const unwrapped = unwrapTypeExpression(expression);
1180
- return unwrapped.type === "ArrowFunctionExpression" || unwrapped.type === "FunctionExpression";
1181
- }
1182
- function isSupportedComponentInitializer(expression) {
1183
- const unwrapped = unwrapTypeExpression(expression);
1184
- if (isFunctionExpression(unwrapped)) {
1185
- return true;
1186
- }
1187
- if (unwrapped.type !== "CallExpression") {
1188
- return false;
1189
- }
1190
- const name = calleeName(unwrapped.callee);
1191
- if (name !== "memo" && name !== "forwardRef") {
1192
- return false;
1193
- }
1194
- const firstArgument = unwrapped.arguments[0];
1195
- return firstArgument !== void 0 && firstArgument.type !== "SpreadElement" && (isFunctionExpression(firstArgument) || isSupportedComponentInitializer(firstArgument));
1196
- }
1197
- function variableComponent(node) {
1198
- if (node.id.type !== "Identifier" || !isComponentName(node.id.name) || node.init === null || !isSupportedComponentInitializer(node.init)) {
1199
- return void 0;
1200
- }
1201
- return Object.freeze({ start: node.start, end: node.end, name: node.id.name });
1202
- }
1203
- function functionComponent(node) {
1204
- return node.id !== null && isComponentName(node.id.name) && node.body !== null ? Object.freeze({ start: node.start, end: node.end, name: node.id.name }) : void 0;
1205
- }
1206
- function isReactComponentSuperclass(expression) {
1207
- if (expression === null) {
1208
- return false;
1209
- }
1210
- const unwrapped = unwrapTypeExpression(expression);
1211
- if (unwrapped.type === "Identifier") {
1212
- return unwrapped.name === "Component" || unwrapped.name === "PureComponent";
1213
- }
1214
- return unwrapped.type === "MemberExpression" && !unwrapped.computed && unwrapped.object.type === "Identifier" && unwrapped.object.name === "React" && (unwrapped.property.name === "Component" || unwrapped.property.name === "PureComponent");
1215
- }
1216
- function classComponent(node) {
1217
- return node.id !== null && isComponentName(node.id.name) && isReactComponentSuperclass(node.superClass) ? Object.freeze({ start: node.start, end: node.end, name: node.id.name }) : void 0;
1218
- }
1219
- function selectedOffset(source, line, column) {
1220
- const lines = source.split(/\r?\n/u);
1221
- if (line < 1 || line > lines.length) {
1222
- return void 0;
1223
- }
1224
- const lineStart = lines.slice(0, line - 1).reduce((total, value) => total + value.length + 1, 0);
1225
- const lineLength = lines[line - 1]?.length ?? 0;
1226
- return lineStart + Math.min(Math.max(0, column - 1), lineLength);
1227
- }
1228
- function findComponentSpan(options) {
1229
- const offset = selectedOffset(options.source, options.line, options.column);
1230
- if (offset === void 0) {
1231
- return void 0;
1232
- }
1233
- let parseResult;
1234
- try {
1235
- parseResult = (0, import_oxc_parser.parseSync)(options.sourcePath, options.source, {
1236
- sourceType: "module"
1237
- });
1238
- } catch {
1239
- return void 0;
1240
- }
1241
- if (parseResult.errors.length > 0) {
1242
- return void 0;
1243
- }
1244
- const jsxNodes = [];
1245
- const components = [];
1246
- const visitor = new import_oxc_parser.Visitor({
1247
- JSXElement(node) {
1248
- jsxNodes.push(node);
1249
- },
1250
- JSXFragment(node) {
1251
- jsxNodes.push(node);
1252
- },
1253
- FunctionDeclaration(node) {
1254
- const candidate = functionComponent(node);
1255
- if (candidate !== void 0) {
1256
- components.push(candidate);
1257
- }
1258
- },
1259
- VariableDeclarator(node) {
1260
- const candidate = variableComponent(node);
1261
- if (candidate !== void 0) {
1262
- components.push(candidate);
1263
- }
1264
- },
1265
- ClassDeclaration(node) {
1266
- const candidate = classComponent(node);
1267
- if (candidate !== void 0) {
1268
- components.push(candidate);
1269
- }
1270
- }
1271
- });
1272
- visitor.visit(parseResult.program);
1273
- const selectedJsx = jsxNodes.filter((node) => node.start <= offset && node.end >= offset).sort((left, right) => left.end - left.start - (right.end - right.start))[0];
1274
- if (selectedJsx === void 0) {
1275
- return void 0;
1276
- }
1277
- return components.filter(
1278
- (component) => component.start <= selectedJsx.start && component.end >= selectedJsx.end
1279
- ).sort((left, right) => left.end - left.start - (right.end - right.start))[0];
1280
- }
1281
- function lineAtOffset(source, offset) {
1282
- let line = 1;
1283
- for (let index = 0; index < offset; index += 1) {
1284
- if (source[index] === "\n") {
1285
- line += 1;
1286
- }
1287
- }
1288
- return line;
1289
- }
1290
- function componentRange(source, component) {
1291
- return Object.freeze({
1292
- startLine: lineAtOffset(source, component.start),
1293
- endLine: lineAtOffset(source, Math.max(component.start, component.end - 1))
1294
- });
1295
- }
1296
- function truncateSelectedLine(line, column, maxCharacters) {
1297
- if (line.length <= maxCharacters) {
1298
- return line;
1299
- }
1300
- if (maxCharacters === 1) {
1301
- return "\u2026";
1302
- }
1303
- const contentCharacters = maxCharacters - 2;
1304
- const desiredStart = Math.max(0, column - 1 - Math.floor(contentCharacters / 2));
1305
- const start = Math.min(desiredStart, line.length - contentCharacters);
1306
- const end = start + contentCharacters;
1307
- return `${start > 0 ? "\u2026" : ""}${line.slice(start, end)}${end < line.length ? "\u2026" : ""}`.slice(
1308
- 0,
1309
- maxCharacters
1310
- );
1311
- }
1312
- function boundedRange(lines, selectedLine, column, initialStart, initialEnd, maxCharacters) {
1313
- let startLine = initialStart;
1314
- let endLine = initialEnd;
1315
- let excerpt = lines.slice(startLine - 1, endLine).join("\n");
1316
- while (excerpt.length > maxCharacters && startLine < endLine) {
1317
- if (endLine - selectedLine >= selectedLine - startLine) {
1318
- endLine -= 1;
1319
- } else {
1320
- startLine += 1;
1321
- }
1322
- excerpt = lines.slice(startLine - 1, endLine).join("\n");
1323
- }
1324
- if (excerpt.length > maxCharacters) {
1325
- startLine = selectedLine;
1326
- endLine = selectedLine;
1327
- excerpt = truncateSelectedLine(
1328
- lines[selectedLine - 1] ?? "",
1329
- column,
1330
- maxCharacters
1331
- );
1332
- }
1333
- return Object.freeze({ startLine, endLine, excerpt });
1334
- }
1335
- function nearbyContext(options) {
1336
- const lines = options.source.split(/\r?\n/u);
1337
- const initialStart = Math.max(1, options.line - Math.floor(options.maxLines / 2));
1338
- const initialEnd = Math.min(lines.length, initialStart + options.maxLines - 1);
1339
- const startLine = Math.max(1, initialEnd - options.maxLines + 1);
1340
- const bounded = boundedRange(
1341
- lines,
1342
- options.line,
1343
- options.column,
1344
- startLine,
1345
- initialEnd,
1346
- options.maxCharacters
1347
- );
1348
- return Object.freeze({
1349
- relativePath: options.relativePath,
1350
- language: options.language,
1351
- startLine: bounded.startLine,
1352
- endLine: bounded.endLine,
1353
- excerpt: bounded.excerpt,
1354
- boundary: "nearby-lines"
1355
- });
1356
- }
1357
- function extractCodeContext(options) {
1358
- const component = findComponentSpan(options);
1359
- if (component !== void 0) {
1360
- const range = componentRange(options.source, component);
1361
- const lineCount = range.endLine - range.startLine + 1;
1362
- const excerpt = options.source.split(/\r?\n/u).slice(range.startLine - 1, range.endLine).join("\n");
1363
- if (lineCount <= options.maxLines && excerpt.length <= options.maxCharacters) {
1364
- return Object.freeze({
1365
- relativePath: options.relativePath,
1366
- language: options.language,
1367
- startLine: range.startLine,
1368
- endLine: range.endLine,
1369
- excerpt,
1370
- boundary: "component"
1371
- });
1372
- }
1373
- }
1374
- return nearbyContext(options);
1375
- }
1376
-
1377
- // src/server/source-file.ts
1378
- var import_promises = require("fs/promises");
1379
- var import_node_path3 = __toESM(require("path"), 1);
1380
- var import_shared3 = require("@spotpatch/shared");
1381
-
1382
- // src/server/constants.ts
1383
- var MAX_REQUEST_BODY_BYTES = 32 * 1024;
1384
- var MAX_AGENT_REQUEST_BODY_BYTES = 256 * 1024;
1385
- var MAX_SOURCE_FILE_BYTES = 1024 * 1024;
1386
-
1387
- // src/server/source-file.ts
1388
- var ALLOWED_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx"]);
1389
- function isMissingFileError(error) {
1390
- return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
1391
- }
1392
- async function assertInsideRoot(root, candidate) {
1393
- let realRoot;
1394
- let realCandidate;
1395
- try {
1396
- [realRoot, realCandidate] = await Promise.all([
1397
- (0, import_promises.realpath)(root),
1398
- (0, import_promises.realpath)(candidate)
1399
- ]);
1400
- } catch (error) {
1401
- if (isMissingFileError(error)) {
1402
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
1403
- cause: error
1404
- });
1405
- }
1406
- throw error;
1407
- }
1408
- const relative = import_node_path3.default.relative(realRoot, realCandidate);
1409
- const outside = relative.startsWith(`..${import_node_path3.default.sep}`) || relative === ".." || import_node_path3.default.isAbsolute(relative);
1410
- if (outside) {
1411
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_OUTSIDE_ROOT);
1412
- }
1413
- return realCandidate;
1414
- }
1415
- async function resolveSourceFile(options) {
1416
- const registeredPath = options.registry.resolve(options.fileId);
1417
- if (registeredPath === void 0) {
1418
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND);
1419
- }
1420
- const sourcePath = await assertInsideRoot(options.root, registeredPath);
1421
- if (!ALLOWED_EXTENSIONS.has(import_node_path3.default.extname(sourcePath).toLowerCase())) {
1422
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND);
1423
- }
1424
- let sourceStat;
1425
- try {
1426
- sourceStat = await (0, import_promises.stat)(sourcePath);
1427
- } catch (error) {
1428
- if (isMissingFileError(error)) {
1429
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
1430
- cause: error
1431
- });
1432
- }
1433
- throw error;
1434
- }
1435
- if (!sourceStat.isFile()) {
1436
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_NOT_FOUND);
1437
- }
1438
- if (sourceStat.size > MAX_SOURCE_FILE_BYTES) {
1439
- throw new import_shared3.SpotPatchError(import_shared3.ERROR_CODES.SOURCE_TOO_LARGE);
1440
- }
1441
- return sourcePath;
1442
- }
1443
-
1444
- // src/server/source-context.ts
1445
- function toDisplayPath(root, sourcePath) {
1446
- return import_node_path4.default.relative(root, sourcePath).split(import_node_path4.default.sep).join("/");
1447
- }
1448
- async function readSourceContext(options) {
1449
- const sourcePath = await resolveSourceFile({
1450
- fileId: options.request.fileId,
1451
- registry: options.registry,
1452
- root: options.root
1453
- });
1454
- let source;
1455
- try {
1456
- source = await (0, import_promises2.readFile)(sourcePath, "utf8");
1457
- } catch (error) {
1458
- if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
1459
- throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.SOURCE_NOT_FOUND, void 0, {
1460
- cause: error
1461
- });
1462
- }
1463
- throw error;
1464
- }
1465
- const lines = source.split(/\r?\n/);
1466
- if (options.request.line > lines.length) {
1467
- throw new import_shared4.SpotPatchError(import_shared4.ERROR_CODES.INVALID_REQUEST);
1468
- }
1469
- const extension = import_node_path4.default.extname(sourcePath).toLowerCase();
1470
- return extractCodeContext({
1471
- source,
1472
- sourcePath,
1473
- relativePath: toDisplayPath(await (0, import_promises2.realpath)(options.root), sourcePath),
1474
- language: extension === ".tsx" ? "tsx" : "jsx",
1475
- line: options.request.line,
1476
- column: options.request.column,
1477
- maxLines: Math.min(options.request.maxLines, options.maxLines),
1478
- maxCharacters: options.maxCharacters
1479
- });
1480
- }
1481
-
1482
- // src/server/agent-request.ts
1483
- function compactSourceRef(source) {
1484
- return Object.freeze({
1485
- origin: source.origin,
1486
- confidence: source.confidence,
1487
- ...source.fileId === void 0 ? {} : { fileId: source.fileId },
1488
- ...source.relativePath === void 0 ? {} : { relativePath: source.relativePath },
1489
- ...source.line === void 0 ? {} : { line: source.line },
1490
- ...source.column === void 0 ? {} : { column: source.column }
1491
- });
1492
- }
1493
- async function authorizeSourceRef(source, registry, root) {
1494
- const markerOrigin = source.origin === "jsx-host" || source.origin === "dom-ancestor";
1495
- if (markerOrigin && (source.fileId === void 0 || source.line === void 0 || source.column === void 0)) {
1496
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
1497
- }
1498
- if (source.fileId === void 0) {
1499
- if (source.origin === "none" && source.relativePath !== void 0) {
1500
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
1501
- }
1502
- return compactSourceRef(source);
1503
- }
1504
- const sourcePath = await resolveSourceFile({
1505
- fileId: source.fileId,
1506
- registry,
1507
- root
1508
- });
1509
- const relativePath = import_node_path5.default.relative(await (0, import_promises3.realpath)(root), sourcePath).split(import_node_path5.default.sep).join("/");
1510
- if (source.relativePath !== void 0 && source.relativePath !== relativePath) {
1511
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
1512
- }
1513
- return Object.freeze({
1514
- ...compactSourceRef(source),
1515
- relativePath
1516
- });
1517
- }
1518
- function freezeMatchedRule(rule) {
1519
- return Object.freeze({
1520
- selector: rule.selector,
1521
- declarations: rule.declarations,
1522
- ...rule.source === void 0 ? {} : { source: rule.source },
1523
- ...rule.media === void 0 ? {} : { media: rule.media }
1524
- });
1525
- }
1526
- function targetIdentity(target) {
1527
- const source = target.source;
1528
- if (source.fileId !== void 0 && source.line !== void 0 && source.column !== void 0) {
1529
- return `source:${source.fileId}:${String(source.line)}:${String(source.column)}`;
1530
- }
1531
- return [
1532
- "element",
1533
- source.origin,
1534
- source.relativePath ?? "",
1535
- target.element.selector,
1536
- target.element.sanitizedHtml
1537
- ].join("\0");
1538
- }
1539
- async function authorizeTarget(target, input) {
1540
- const source = await authorizeSourceRef(target.source, input.registry, input.root);
1541
- const reactSourceInput = target.react.source;
1542
- const reactSource = reactSourceInput === void 0 ? void 0 : await authorizeSourceRef(reactSourceInput, input.registry, input.root);
1543
- const marker = source.fileId === void 0 || source.line === void 0 || source.column === void 0 ? void 0 : Object.freeze({
1544
- fileId: source.fileId,
1545
- line: source.line,
1546
- column: source.column,
1547
- maxLines: input.options.budget.maxCodeLines
1548
- });
1549
- if (marker === void 0 && target.code !== void 0) {
1550
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
1551
- }
1552
- const code = marker === void 0 ? void 0 : await readSourceContext({
1553
- request: marker,
1554
- registry: input.registry,
1555
- root: input.root,
1556
- maxCharacters: input.options.budget.codeCharacters,
1557
- maxLines: input.options.budget.maxCodeLines
1558
- });
1559
- if (target.code !== void 0 && target.code.relativePath !== code?.relativePath) {
1560
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
1561
- }
1562
- return Object.freeze({
1563
- instruction: target.instruction,
1564
- source,
1565
- react: Object.freeze({
1566
- supported: target.react.supported,
1567
- ...target.react.version === void 0 ? {} : { version: target.react.version },
1568
- ...target.react.componentName === void 0 ? {} : { componentName: target.react.componentName },
1569
- componentStack: Object.freeze([...target.react.componentStack]),
1570
- ...reactSource === void 0 ? {} : { source: reactSource }
1571
- }),
1572
- element: Object.freeze({
1573
- tagName: target.element.tagName,
1574
- selector: target.element.selector,
1575
- sanitizedHtml: target.element.sanitizedHtml,
1576
- ...target.element.textPreview === void 0 ? {} : { textPreview: target.element.textPreview },
1577
- ...target.element.role === void 0 ? {} : { role: target.element.role },
1578
- rect: Object.freeze({ ...target.element.rect })
1579
- }),
1580
- styles: Object.freeze({
1581
- classNames: Object.freeze([...target.styles.classNames]),
1582
- ...target.styles.inlineStyle === void 0 ? {} : { inlineStyle: target.styles.inlineStyle },
1583
- matchedRules: Object.freeze(target.styles.matchedRules.map(freezeMatchedRule)),
1584
- computed: Object.freeze({ ...target.styles.computed }),
1585
- warnings: Object.freeze([...target.styles.warnings])
1586
- }),
1587
- ...code === void 0 ? {} : { code: Object.freeze({ ...code }) },
1588
- warnings: Object.freeze([...target.warnings])
1589
- });
1590
- }
1591
- async function authorizeAgentJobRequest(input) {
1592
- const requestedTargets = input.request.annotation.targets;
1593
- if (requestedTargets.length > input.options.maxTargets) {
1594
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
1595
- }
1596
- const identities = requestedTargets.map(targetIdentity);
1597
- if (new Set(identities).size !== identities.length) {
1598
- throw new import_shared5.SpotPatchError(import_shared5.ERROR_CODES.INVALID_REQUEST);
1599
- }
1600
- const targets = Object.freeze(
1601
- await Promise.all(requestedTargets.map((target) => authorizeTarget(target, input)))
1602
- );
1603
- const annotation = Object.freeze({
1604
- schemaVersion: 3,
1605
- id: input.request.annotation.id,
1606
- locale: input.request.annotation.locale,
1607
- page: Object.freeze({ ...input.request.annotation.page }),
1608
- targets,
1609
- createdAt: input.request.annotation.createdAt
1610
- });
1611
- return Object.freeze({
1612
- annotation,
1613
- providerProfileId: input.request.providerProfileId,
1614
- modelProfileId: input.request.modelProfileId,
1615
- providerDataConsent: true,
1616
- workingTreeMode: input.request.workingTreeMode
1617
- });
1618
- }
1619
-
1620
- // src/server/request-body.ts
1621
- var import_shared6 = require("@spotpatch/shared");
1622
- function isJsonContentType(value) {
1623
- return value?.split(";", 1)[0]?.trim().toLowerCase() === "application/json";
1624
- }
1625
- async function readJsonRequestBody(request, maximumBytes = MAX_REQUEST_BODY_BYTES) {
1626
- if (!isJsonContentType(request.headers["content-type"])) {
1627
- throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.INVALID_REQUEST);
1628
- }
1629
- const declaredLength = Number(request.headers["content-length"]);
1630
- if (Number.isFinite(declaredLength) && declaredLength > maximumBytes) {
1631
- throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.INVALID_REQUEST);
1632
- }
1633
- const chunks = [];
1634
- let byteLength = 0;
1635
- let exceededLimit = false;
1636
- for await (const rawChunk of request) {
1637
- const chunk = rawChunk;
1638
- if (typeof chunk !== "string" && !(chunk instanceof Uint8Array)) {
1639
- throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.INVALID_REQUEST);
1640
- }
1641
- const buffer = Buffer.from(chunk);
1642
- byteLength += buffer.byteLength;
1643
- if (byteLength > maximumBytes) {
1644
- exceededLimit = true;
1645
- continue;
1646
- }
1647
- chunks.push(buffer);
1648
- }
1649
- if (exceededLimit || byteLength === 0) {
1650
- throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.INVALID_REQUEST);
1651
- }
1652
- try {
1653
- return JSON.parse(Buffer.concat(chunks).toString("utf8"));
1654
- } catch (error) {
1655
- throw new import_shared6.SpotPatchError(import_shared6.ERROR_CODES.INVALID_REQUEST, void 0, {
1656
- cause: error
1657
- });
1658
- }
1659
- }
1660
-
1661
- // src/server/agent-http.ts
1662
- var AGENT_JOB_ID_PATTERN = /^[A-Za-z0-9_-]{22,128}$/;
1663
- var AGENT_JOB_ACTIONS = /* @__PURE__ */ new Set([
1664
- "events",
1665
- "result",
1666
- "cancel",
1667
- "apply",
1668
- "revert"
1669
- ]);
1670
- var EVENT_STREAM_END_STATUSES = /* @__PURE__ */ new Set([
1671
- "awaiting-review",
1672
- "applied",
1673
- "completed",
1674
- "cancelled",
1675
- "reverted",
1676
- "failed"
1677
- ]);
1678
- function matchAgentRequestPath(path11) {
1679
- if (path11 === import_shared7.SPOTPATCH_ENDPOINTS.agentCapability) {
1680
- return Object.freeze({ kind: "capability" });
1681
- }
1682
- if (path11 === import_shared7.SPOTPATCH_ENDPOINTS.agentWorkspaceHealth) {
1683
- return Object.freeze({ kind: "workspace-health" });
1684
- }
1685
- if (path11 === import_shared7.SPOTPATCH_ENDPOINTS.agentJobs) {
1686
- return Object.freeze({ kind: "create-job" });
1687
- }
1688
- const prefix = `${import_shared7.SPOTPATCH_ENDPOINTS.agentJobs}/`;
1689
- if (!path11.startsWith(prefix)) {
1690
- return void 0;
1691
- }
1692
- const segments = path11.slice(prefix.length).split("/");
1693
- const jobId = segments[0];
1694
- const action = segments[1];
1695
- if (segments.length !== 2 || jobId === void 0 || !AGENT_JOB_ID_PATTERN.test(jobId) || action === void 0 || !AGENT_JOB_ACTIONS.has(action)) {
1696
- return void 0;
1697
- }
1698
- return Object.freeze({
1699
- kind: "job-action",
1700
- action,
1701
- jobId
1702
- });
1703
- }
1704
- function requireAgentManager(options) {
1705
- if (options.agentManager === void 0 || options.options.ai === false) {
1706
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.AI_DISABLED);
1707
- }
1708
- return options.agentManager;
1709
- }
1710
- function writeNdjsonEvent(response, event) {
1711
- response.write(`${JSON.stringify(event)}
1712
- `);
1713
- }
1714
- function streamAgentJobEvents(response, manager, jobId) {
1715
- const events = manager.events(jobId);
1716
- const current = manager.result(jobId).snapshot;
1717
- response.statusCode = 200;
1718
- response.setHeader("Cache-Control", "no-store");
1719
- response.setHeader("Content-Type", "application/x-ndjson; charset=utf-8");
1720
- response.setHeader("X-Content-Type-Options", "nosniff");
1721
- for (const event of events) {
1722
- writeNdjsonEvent(response, event);
1723
- }
1724
- if (EVENT_STREAM_END_STATUSES.has(current.status)) {
1725
- response.end();
1726
- return;
1727
- }
1728
- let settled = false;
1729
- let unsubscribe = () => void 0;
1730
- const heartbeat = setInterval(() => {
1731
- if (!settled) {
1732
- response.write("\n");
1733
- }
1734
- }, 15e3);
1735
- heartbeat.unref();
1736
- const cleanup = () => {
1737
- if (settled) {
1738
- return;
1739
- }
1740
- settled = true;
1741
- clearInterval(heartbeat);
1742
- unsubscribe();
1743
- };
1744
- unsubscribe = manager.subscribe(jobId, (event) => {
1745
- if (settled) {
1746
- return;
1747
- }
1748
- writeNdjsonEvent(response, event);
1749
- if (event.type === "snapshot" && EVENT_STREAM_END_STATUSES.has(event.data.snapshot.status)) {
1750
- cleanup();
1751
- response.end();
1752
- }
1753
- });
1754
- response.once("close", cleanup);
1755
- response.once("error", cleanup);
1756
- }
1757
- async function handleCapability(request, response, options, writeSuccess) {
1758
- if (request.method !== "POST") {
1759
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
1760
- }
1761
- const parsed = import_shared7.agentCapabilityRequestSchema.safeParse(
1762
- await readJsonRequestBody(request)
1763
- );
1764
- if (!parsed.success) {
1765
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
1766
- }
1767
- const controller = new AbortController();
1768
- const abort = () => {
1769
- controller.abort("agent-capability-client-disconnected");
1770
- };
1771
- response.once("close", abort);
1772
- try {
1773
- const data = await requireAgentManager(options).probe(
1774
- parsed.data,
1775
- controller.signal
1776
- );
1777
- writeSuccess(response, 200, data);
1778
- } finally {
1779
- response.removeListener("close", abort);
1780
- }
1781
- }
1782
- async function handleCreateJob(request, response, options, writeSuccess) {
1783
- if (request.method !== "POST") {
1784
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
1785
- }
1786
- const parsed = import_shared7.agentJobCreateRequestSchema.safeParse(
1787
- await readJsonRequestBody(request, MAX_AGENT_REQUEST_BODY_BYTES)
1788
- );
1789
- if (!parsed.success) {
1790
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
1791
- }
1792
- const authorizedRequest = await authorizeAgentJobRequest({
1793
- request: parsed.data,
1794
- options: options.options,
1795
- registry: options.registry,
1796
- root: options.root
1797
- });
1798
- const data = requireAgentManager(options).create(authorizedRequest);
1799
- writeSuccess(response, 202, data);
1800
- }
1801
- async function handleWorkspaceHealth(request, response, options, writeSuccess) {
1802
- if (request.method !== "POST") {
1803
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
1804
- }
1805
- const parsed = import_shared7.agentWorkspaceHealthRequestSchema.safeParse(
1806
- await readJsonRequestBody(request)
1807
- );
1808
- if (!parsed.success) {
1809
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
1810
- }
1811
- const controller = new AbortController();
1812
- const abort = () => {
1813
- controller.abort("agent-workspace-health-client-disconnected");
1814
- };
1815
- response.once("close", abort);
1816
- try {
1817
- const data = await requireAgentManager(options).workspaceHealth(controller.signal);
1818
- writeSuccess(response, 200, data);
1819
- } finally {
1820
- response.removeListener("close", abort);
1821
- }
1822
- }
1823
- async function handleJobAction(request, response, options, route, writeSuccess) {
1824
- const manager = requireAgentManager(options);
1825
- if (request.method !== "POST") {
1826
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
1827
- }
1828
- const parsed = import_shared7.agentJobActionRequestSchema.safeParse(
1829
- await readJsonRequestBody(request)
1830
- );
1831
- if (!parsed.success) {
1832
- throw new import_shared7.SpotPatchError(import_shared7.ERROR_CODES.INVALID_REQUEST);
1833
- }
1834
- if (route.action === "events") {
1835
- streamAgentJobEvents(response, manager, route.jobId);
1836
- return;
1837
- }
1838
- if (route.action === "result") {
1839
- writeSuccess(response, 200, manager.result(route.jobId));
1840
- return;
1841
- }
1842
- const data = route.action === "cancel" ? manager.cancel(route.jobId) : route.action === "apply" ? await manager.apply(route.jobId) : await manager.revert(route.jobId);
1843
- writeSuccess(response, 200, data);
1844
- }
1845
- async function handleAgentRequest(request, response, options, route, writeSuccess) {
1846
- if (route.kind === "capability") {
1847
- await handleCapability(request, response, options, writeSuccess);
1848
- return;
1849
- }
1850
- if (route.kind === "create-job") {
1851
- await handleCreateJob(request, response, options, writeSuccess);
1852
- return;
1853
- }
1854
- if (route.kind === "workspace-health") {
1855
- await handleWorkspaceHealth(request, response, options, writeSuccess);
1856
- return;
1857
- }
1858
- await handleJobAction(request, response, options, route, writeSuccess);
1859
- }
1860
-
1861
- // src/server/editor.ts
1862
- var import_node_child_process = require("child_process");
1863
- var import_launch_editor = __toESM(require("launch-editor"), 1);
1864
- var EDITOR_STARTUP_GRACE_MS = 300;
1865
- function normalizedEditorEnvironment(environment) {
1866
- return [
1867
- environment.TERM_PROGRAM,
1868
- environment.VSCODE_GIT_ASKPASS_NODE,
1869
- environment.VSCODE_GIT_ASKPASS_MAIN,
1870
- environment.GIT_ASKPASS
1871
- ].filter((value) => typeof value === "string").join("\n").replaceAll("\\", "/").toLowerCase();
1872
- }
1873
- function detectIntegratedEditor(environment) {
1874
- const signature = normalizedEditorEnvironment(environment);
1875
- if (signature === "cursor" || signature.includes("/cursor")) {
1876
- return "cursor";
1877
- }
1878
- if (signature === "vscode" || signature.includes("visual studio code") || /(^|\/)(code|code-insiders)(\.exe)?($|\/)/u.test(signature)) {
1879
- return "vscode";
1880
- }
1881
- return void 0;
1882
- }
1883
- function editorCommand(editor) {
1884
- return editor === "cursor" ? "cursor" : "code";
1885
- }
1886
- var DEFAULT_DEPENDENCIES2 = Object.freeze({
1887
- environment: process.env,
1888
- fallbackLauncher: import_launch_editor.default,
1889
- processSpawner: (command, arguments_, options) => (0, import_node_child_process.spawn)(command, [...arguments_], options),
1890
- startupGraceMs: EDITOR_STARTUP_GRACE_MS
1891
- });
1892
- function createEditorLauncher(dependencies = DEFAULT_DEPENDENCIES2) {
1893
- return (target, configuredEditor) => {
1894
- const integratedEditor = detectIntegratedEditor(dependencies.environment);
1895
- const resolvedEditor = configuredEditor === "auto" ? integratedEditor : configuredEditor;
1896
- return new Promise((resolve, reject) => {
1897
- let settled = false;
1898
- const settle = (error, editor = resolvedEditor ?? "auto") => {
1899
- if (settled) {
1900
- return;
1901
- }
1902
- settled = true;
1903
- clearTimeout(startupTimer);
1904
- if (error === void 0) {
1905
- resolve(editor);
1906
- } else {
1907
- reject(error);
1908
- }
1909
- };
1910
- const startupTimer = setTimeout(settle, dependencies.startupGraceMs);
1911
- const rejectStartup = () => {
1912
- settle(new Error("The configured editor could not be started."));
1913
- };
1914
- try {
1915
- if (resolvedEditor === void 0) {
1916
- dependencies.fallbackLauncher(target, void 0, rejectStartup);
1917
- return;
1918
- }
1919
- const child = dependencies.processSpawner(
1920
- editorCommand(resolvedEditor),
1921
- ["--goto", target],
1922
- {
1923
- env: dependencies.environment,
1924
- stdio: "ignore",
1925
- windowsHide: true
1926
- }
1927
- );
1928
- child.once("error", rejectStartup);
1929
- child.once("exit", (code, signal) => {
1930
- if (code === 0) {
1931
- settle(void 0, resolvedEditor);
1932
- return;
1933
- }
1934
- if (code !== null || signal !== null) {
1935
- rejectStartup();
1936
- }
1937
- });
1938
- } catch {
1939
- rejectStartup();
1940
- }
1941
- });
1942
- };
1943
- }
1944
- var launchConfiguredEditor = createEditorLauncher();
1945
-
1946
- // src/server/request-security.ts
1947
- var import_node_crypto3 = require("crypto");
1948
- var import_node_net = require("net");
1949
- var import_shared8 = require("@spotpatch/shared");
1950
- function getSingleHeader(request, name) {
1951
- const value = request.headers[name.toLowerCase()];
1952
- return Array.isArray(value) ? value[0] : value;
1953
- }
1954
- function tokensMatch(actual, expected) {
1955
- if (actual === void 0) {
1956
- return false;
1957
- }
1958
- const actualBytes = Buffer.from(actual);
1959
- const expectedBytes = Buffer.from(expected);
1960
- return actualBytes.byteLength === expectedBytes.byteLength && (0, import_node_crypto3.timingSafeEqual)(actualBytes, expectedBytes);
1961
- }
1962
- function isLoopbackHostname(hostname) {
1963
- const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
1964
- if (normalized === "localhost" || normalized.endsWith(".localhost")) {
1965
- return true;
1966
- }
1967
- if (normalized === "::1") {
1968
- return true;
1969
- }
1970
- if ((0, import_node_net.isIP)(normalized) === 4) {
1971
- return normalized.split(".")[0] === "127";
1972
- }
1973
- return normalized.startsWith("::ffff:127.");
1974
- }
1975
- function parseHost(value) {
1976
- try {
1977
- return new URL(`http://${value}`);
1978
- } catch {
1979
- return void 0;
1980
- }
1981
- }
1982
- function parseOrigin(value) {
1983
- try {
1984
- const origin = new URL(value);
1985
- if (origin.protocol !== "http:" && origin.protocol !== "https:" || origin.username.length > 0 || origin.password.length > 0 || origin.origin === "null") {
1986
- return void 0;
1987
- }
1988
- return origin;
1989
- } catch {
1990
- return void 0;
1991
- }
1992
- }
1993
- function assertRequestAuthorized(request, options) {
1994
- const actualToken = getSingleHeader(request, import_shared8.SPOTPATCH_TOKEN_HEADER);
1995
- if (!tokensMatch(actualToken, options.sessionToken)) {
1996
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.INVALID_TOKEN);
1997
- }
1998
- const hostHeader = getSingleHeader(request, "host");
1999
- const originHeader = getSingleHeader(request, "origin");
2000
- const host = hostHeader === void 0 ? void 0 : parseHost(hostHeader);
2001
- const origin = originHeader === void 0 ? void 0 : parseOrigin(originHeader);
2002
- if (host === void 0 || origin === void 0) {
2003
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.ORIGIN_NOT_ALLOWED);
2004
- }
2005
- const hostIsLoopback = isLoopbackHostname(host.hostname);
2006
- const originIsLoopback = isLoopbackHostname(origin.hostname);
2007
- if (!options.allowLan) {
2008
- if (!hostIsLoopback || !originIsLoopback) {
2009
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.ORIGIN_NOT_ALLOWED);
2010
- }
2011
- return;
2012
- }
2013
- if (!originIsLoopback && origin.host.toLowerCase() !== host.host.toLowerCase()) {
2014
- throw new import_shared8.SpotPatchError(import_shared8.ERROR_CODES.ORIGIN_NOT_ALLOWED);
2015
- }
2016
- }
2017
-
2018
- // src/server/middleware.ts
2019
- var STATUS_BY_ERROR = Object.freeze({
2020
- [import_shared9.ERROR_CODES.INVALID_REQUEST]: 400,
2021
- [import_shared9.ERROR_CODES.INVALID_TOKEN]: 401,
2022
- [import_shared9.ERROR_CODES.ORIGIN_NOT_ALLOWED]: 403,
2023
- [import_shared9.ERROR_CODES.SOURCE_NOT_FOUND]: 404,
2024
- [import_shared9.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: 403,
2025
- [import_shared9.ERROR_CODES.SOURCE_TOO_LARGE]: 413,
2026
- [import_shared9.ERROR_CODES.EDITOR_OPEN_FAILED]: 500,
2027
- [import_shared9.ERROR_CODES.AI_DISABLED]: 404,
2028
- [import_shared9.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: 503,
2029
- [import_shared9.ERROR_CODES.PROVIDER_AUTH_FAILED]: 502,
2030
- [import_shared9.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: 502,
2031
- [import_shared9.ERROR_CODES.MODEL_NOT_ALLOWED]: 400,
2032
- [import_shared9.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: 422,
2033
- [import_shared9.ERROR_CODES.PROVIDER_RATE_LIMITED]: 429,
2034
- [import_shared9.ERROR_CODES.AGENT_BUSY]: 409,
2035
- [import_shared9.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: 413,
2036
- [import_shared9.ERROR_CODES.AGENT_CANCELLED]: 409,
2037
- [import_shared9.ERROR_CODES.WORKTREE_DIRTY]: 409,
2038
- [import_shared9.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: 409,
2039
- [import_shared9.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: 409,
2040
- [import_shared9.ERROR_CODES.WORKTREE_CONFLICTED]: 409,
2041
- [import_shared9.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: 413,
2042
- [import_shared9.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: 409,
2043
- [import_shared9.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: 409,
2044
- [import_shared9.ERROR_CODES.TOOL_DENIED]: 403,
2045
- [import_shared9.ERROR_CODES.TOOL_INPUT_INVALID]: 422,
2046
- [import_shared9.ERROR_CODES.TOOL_PATH_DENIED]: 403,
2047
- [import_shared9.ERROR_CODES.PATCH_REJECTED]: 422,
2048
- [import_shared9.ERROR_CODES.VALIDATION_FAILED]: 422,
2049
- [import_shared9.ERROR_CODES.APPLY_CONFLICT]: 409,
2050
- [import_shared9.ERROR_CODES.INTERNAL_ERROR]: 500
2051
- });
2052
- var PUBLIC_MESSAGES = Object.freeze({
2053
- [import_shared9.ERROR_CODES.INVALID_REQUEST]: "The request is invalid.",
2054
- [import_shared9.ERROR_CODES.INVALID_TOKEN]: "The session token is invalid.",
2055
- [import_shared9.ERROR_CODES.ORIGIN_NOT_ALLOWED]: "The request origin is not allowed.",
2056
- [import_shared9.ERROR_CODES.SOURCE_NOT_FOUND]: "The source file is unavailable.",
2057
- [import_shared9.ERROR_CODES.SOURCE_OUTSIDE_ROOT]: "The source file is outside the project root.",
2058
- [import_shared9.ERROR_CODES.SOURCE_TOO_LARGE]: "The source file exceeds the size limit.",
2059
- [import_shared9.ERROR_CODES.EDITOR_OPEN_FAILED]: "The editor request could not be started.",
2060
- [import_shared9.ERROR_CODES.AI_DISABLED]: "AI execution is not enabled.",
2061
- [import_shared9.ERROR_CODES.PROVIDER_NOT_CONFIGURED]: "The AI provider is unavailable.",
2062
- [import_shared9.ERROR_CODES.PROVIDER_AUTH_FAILED]: "The AI provider rejected authentication.",
2063
- [import_shared9.ERROR_CODES.PROVIDER_PROTOCOL_UNSUPPORTED]: "The AI provider protocol is unsupported.",
2064
- [import_shared9.ERROR_CODES.MODEL_NOT_ALLOWED]: "The selected model is not allowed.",
2065
- [import_shared9.ERROR_CODES.MODEL_TOOL_CALL_UNSUPPORTED]: "The selected model cannot run SpotPatch tools.",
2066
- [import_shared9.ERROR_CODES.PROVIDER_RATE_LIMITED]: "The AI provider is rate limited.",
2067
- [import_shared9.ERROR_CODES.AGENT_BUSY]: "Another Agent job is already running.",
2068
- [import_shared9.ERROR_CODES.AGENT_LIMIT_EXCEEDED]: "The Agent job exceeded a safety limit.",
2069
- [import_shared9.ERROR_CODES.AGENT_CANCELLED]: "The Agent job was cancelled.",
2070
- [import_shared9.ERROR_CODES.WORKTREE_DIRTY]: "Local changes require explicit inclusion consent.",
2071
- [import_shared9.ERROR_CODES.WORKTREE_NOT_REPOSITORY]: "The project root is not a Git repository.",
2072
- [import_shared9.ERROR_CODES.WORKTREE_OPERATION_IN_PROGRESS]: "A Git operation is currently in progress.",
2073
- [import_shared9.ERROR_CODES.WORKTREE_CONFLICTED]: "The local workspace contains unresolved merge conflicts.",
2074
- [import_shared9.ERROR_CODES.WORKTREE_LOCAL_CHANGES_TOO_LARGE]: "The local workspace exceeds the safe isolation size limit.",
2075
- [import_shared9.ERROR_CODES.WORKTREE_UNTRACKED_UNSUPPORTED]: "An untracked path cannot be isolated safely.",
2076
- [import_shared9.ERROR_CODES.WORKTREE_LOCAL_CHANGES_UNSUPPORTED]: "The local workspace state cannot be isolated safely.",
2077
- [import_shared9.ERROR_CODES.TOOL_DENIED]: "The Agent tool request was denied.",
2078
- [import_shared9.ERROR_CODES.TOOL_INPUT_INVALID]: "The Agent tool input was invalid.",
2079
- [import_shared9.ERROR_CODES.TOOL_PATH_DENIED]: "The Agent tool path was denied.",
2080
- [import_shared9.ERROR_CODES.PATCH_REJECTED]: "The proposed patch was rejected.",
2081
- [import_shared9.ERROR_CODES.VALIDATION_FAILED]: "The proposed change failed validation.",
2082
- [import_shared9.ERROR_CODES.APPLY_CONFLICT]: "The change conflicts with the current worktree.",
2083
- [import_shared9.ERROR_CODES.INTERNAL_ERROR]: "The request could not be completed."
2084
- });
2085
- function writeJson(response, status, payload) {
2086
- response.statusCode = status;
2087
- response.setHeader("Cache-Control", "no-store");
2088
- response.setHeader("Content-Type", "application/json; charset=utf-8");
2089
- response.end(JSON.stringify(payload));
2090
- }
2091
- function asSpotPatchError(error) {
2092
- return error instanceof import_shared9.SpotPatchError ? error : new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.INTERNAL_ERROR, void 0, { cause: error });
2093
- }
2094
- function writeError(response, error, logger) {
2095
- const normalized = asSpotPatchError(error);
2096
- if (normalized.code === import_shared9.ERROR_CODES.INTERNAL_ERROR) {
2097
- logger?.warn("[spotpatch:server] Internal request failure.");
2098
- }
2099
- writeJson(response, STATUS_BY_ERROR[normalized.code], {
2100
- ok: false,
2101
- error: {
2102
- code: normalized.code,
2103
- message: PUBLIC_MESSAGES[normalized.code]
2104
- }
2105
- });
2106
- }
2107
- function requestPath(request) {
2108
- try {
2109
- return new URL(request.url ?? "/", "http://spotpatch.invalid").pathname;
2110
- } catch {
2111
- return "";
2112
- }
2113
- }
2114
- async function handleSourceContext(request, options) {
2115
- const parsed = import_shared9.sourceContextRequestSchema.safeParse(
2116
- await readJsonRequestBody(request)
2117
- );
2118
- if (!parsed.success) {
2119
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.INVALID_REQUEST);
2120
- }
2121
- return readSourceContext({
2122
- request: parsed.data,
2123
- registry: options.registry,
2124
- root: options.root,
2125
- maxCharacters: options.options.budget.codeCharacters,
2126
- maxLines: options.options.budget.maxCodeLines
2127
- });
2128
- }
2129
- async function handleOpenEditor(request, options) {
2130
- const parsed = import_shared9.openEditorRequestSchema.safeParse(await readJsonRequestBody(request));
2131
- if (!parsed.success) {
2132
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.INVALID_REQUEST);
2133
- }
2134
- const body = parsed.data;
2135
- const sourcePath = await resolveSourceFile({
2136
- fileId: body.fileId,
2137
- registry: options.registry,
2138
- root: options.root
2139
- });
2140
- const target = `${sourcePath}:${String(body.line)}:${String(body.column)}`;
2141
- const editorLauncher = options.editorLauncher ?? launchConfiguredEditor;
2142
- try {
2143
- const editor = await editorLauncher(target, options.options.editor);
2144
- return Object.freeze({ editor });
2145
- } catch (error) {
2146
- options.logger?.warn(
2147
- `[spotpatch:server] ${options.options.editor === "auto" ? "The detected editor" : options.options.editor} rejected an editor request.`
2148
- );
2149
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.EDITOR_OPEN_FAILED, void 0, {
2150
- cause: error
2151
- });
2152
- }
2153
- }
2154
- function createSpotPatchMiddleware(options) {
2155
- return (request, response, next) => {
2156
- const path11 = requestPath(request);
2157
- const agentRoute = matchAgentRequestPath(path11);
2158
- if (path11 !== import_shared9.SPOTPATCH_ENDPOINTS.sourceContext && path11 !== import_shared9.SPOTPATCH_ENDPOINTS.openEditor && agentRoute === void 0 && !path11.startsWith(`${import_shared9.SPOTPATCH_API_BASE}/`)) {
2159
- next();
2160
- return;
2161
- }
2162
- const handle = async () => {
2163
- assertRequestAuthorized(request, {
2164
- allowLan: options.options.allowLan,
2165
- sessionToken: options.session.token
2166
- });
2167
- if (path11 === import_shared9.SPOTPATCH_ENDPOINTS.sourceContext) {
2168
- if (request.method !== "POST") {
2169
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.INVALID_REQUEST);
2170
- }
2171
- const data = await handleSourceContext(request, options);
2172
- writeJson(response, 200, { ok: true, data });
2173
- return;
2174
- }
2175
- if (path11 === import_shared9.SPOTPATCH_ENDPOINTS.openEditor) {
2176
- if (request.method !== "POST") {
2177
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.INVALID_REQUEST);
2178
- }
2179
- const data = await handleOpenEditor(request, options);
2180
- writeJson(response, 200, { ok: true, data });
2181
- return;
2182
- }
2183
- if (agentRoute === void 0) {
2184
- throw new import_shared9.SpotPatchError(import_shared9.ERROR_CODES.INVALID_REQUEST);
2185
- }
2186
- await handleAgentRequest(
2187
- request,
2188
- response,
2189
- options,
2190
- agentRoute,
2191
- (target, status, data) => {
2192
- writeJson(target, status, { ok: true, data });
2193
- }
2194
- );
2195
- };
2196
- void handle().catch((error) => {
2197
- writeError(response, error, options.logger);
2198
- });
2199
- };
2200
- }
2201
-
2202
- // src/server/server-plugin.ts
209
+ var import_node_path2 = __toESM(require("path"), 1);
210
+ var import_dev_server2 = require("@spotpatch/dev-server");
2203
211
  function createServerPlugin(input) {
2204
212
  let agentManager;
2205
213
  let config;
@@ -2219,15 +227,15 @@ function createServerPlugin(input) {
2219
227
  if (config === void 0) {
2220
228
  throw new Error("SpotPatch server initialized before Vite config resolution.");
2221
229
  }
2222
- const root = import_node_path6.default.resolve(config.root);
230
+ const root = import_node_path2.default.resolve(config.root);
2223
231
  const options = input.context.getOptions();
2224
- agentManager = options.ai === false ? void 0 : createAgentJobManager({
232
+ agentManager = options.ai === false ? void 0 : (0, import_dev_server2.createAgentJobManager)({
2225
233
  ai: options.ai,
2226
234
  environment: input.context.getCredentialEnvironment(),
2227
235
  root
2228
236
  });
2229
237
  server.middlewares.use(
2230
- createSpotPatchMiddleware({
238
+ (0, import_dev_server2.createSpotPatchMiddleware)({
2231
239
  ...agentManager === void 0 ? {} : { agentManager },
2232
240
  options,
2233
241
  registry: input.registry,
@@ -2249,186 +257,40 @@ function createServerPlugin(input) {
2249
257
  };
2250
258
  }
2251
259
 
2252
- // src/session/session.ts
2253
- var import_node_crypto4 = require("crypto");
2254
- function createSession() {
2255
- return Object.freeze({
2256
- token: (0, import_node_crypto4.randomBytes)(16).toString("base64url")
2257
- });
2258
- }
2259
-
2260
260
  // src/transform/transform-plugin.ts
2261
- var import_node_crypto5 = require("crypto");
2262
- var import_node_path9 = __toESM(require("path"), 1);
2263
-
2264
- // src/transform/inject-source-markers.ts
2265
- var import_node_path7 = __toESM(require("path"), 1);
2266
- var import_shared11 = require("@spotpatch/shared");
2267
- var import_magic_string = __toESM(require("magic-string"), 1);
2268
- var import_oxc_parser2 = require("oxc-parser");
2269
-
2270
- // src/transform/intrinsic-element.ts
2271
- var import_shared10 = require("@spotpatch/shared");
2272
- function isIntrinsicOpeningElement(node) {
2273
- if (node.name.type !== "JSXIdentifier") {
2274
- return false;
2275
- }
2276
- const { name } = node.name;
2277
- const firstCharacter = name[0];
2278
- return name.includes("-") || firstCharacter?.toLowerCase() === firstCharacter;
2279
- }
2280
- function hasSourceMarkerAttribute(node) {
2281
- return node.attributes.some(
2282
- (attribute) => attribute.type === "JSXAttribute" && attribute.name.type === "JSXIdentifier" && attribute.name.name === import_shared10.SOURCE_MARKER_ATTRIBUTE
2283
- );
2284
- }
2285
-
2286
- // src/transform/source-position.ts
2287
- function createLineStarts(code) {
2288
- const starts = [0];
2289
- for (let index = 0; index < code.length; index += 1) {
2290
- if (code.charCodeAt(index) === 10) {
2291
- starts.push(index + 1);
2292
- }
2293
- }
2294
- return starts;
2295
- }
2296
- function getSourcePosition(lineStarts, offset) {
2297
- let lower = 0;
2298
- let upper = lineStarts.length - 1;
2299
- while (lower <= upper) {
2300
- const middle = Math.floor((lower + upper) / 2);
2301
- const start = lineStarts[middle];
2302
- if (start === void 0) {
2303
- break;
2304
- }
2305
- if (start <= offset) {
2306
- lower = middle + 1;
2307
- } else {
2308
- upper = middle - 1;
2309
- }
2310
- }
2311
- const lineIndex = Math.max(0, upper);
2312
- const lineStart = lineStarts[lineIndex] ?? 0;
2313
- return Object.freeze({
2314
- line: lineIndex + 1,
2315
- column: offset - lineStart + 1
2316
- });
2317
- }
2318
-
2319
- // src/transform/inject-source-markers.ts
2320
- function findAttributeInsertionOffset(code, node) {
2321
- let cursor = node.end - 2;
2322
- while (cursor >= node.start && /\s/u.test(code[cursor] ?? "")) {
2323
- cursor -= 1;
2324
- }
2325
- if (code[cursor] === "/") {
2326
- cursor -= 1;
2327
- while (cursor >= node.start && /\s/u.test(code[cursor] ?? "")) {
2328
- cursor -= 1;
2329
- }
2330
- }
2331
- return cursor + 1;
2332
- }
2333
- function normalizeRelativePath(root, absolutePath) {
2334
- return import_node_path7.default.relative(root, absolutePath).split(import_node_path7.default.sep).join("/");
2335
- }
2336
- function createMarker(fileId, line, column) {
2337
- return Object.freeze({ fileId, line, column });
2338
- }
2339
- function injectSourceMarkers(input) {
2340
- const parseResult = (0, import_oxc_parser2.parseSync)(input.absolutePath, input.code, {
2341
- sourceType: "module"
2342
- });
2343
- const parseError = parseResult.errors[0];
2344
- if (parseError !== void 0) {
2345
- throw new SyntaxError(parseError.message);
2346
- }
2347
- const magicString = new import_magic_string.default(input.code);
2348
- const lineStarts = createLineStarts(input.code);
2349
- let markerCount = 0;
2350
- const visitor = new import_oxc_parser2.Visitor({
2351
- JSXOpeningElement(node) {
2352
- if (!isIntrinsicOpeningElement(node)) {
2353
- return;
2354
- }
2355
- const position = getSourcePosition(lineStarts, node.start);
2356
- if (hasSourceMarkerAttribute(node)) {
2357
- input.onWarning?.({
2358
- code: "EXISTING_SOURCE_MARKER",
2359
- line: position.line,
2360
- column: position.column
2361
- });
2362
- return;
2363
- }
2364
- const value = (0, import_shared11.formatSourceMarker)(
2365
- createMarker(input.fileId, position.line, position.column)
2366
- );
2367
- const insertionOffset = findAttributeInsertionOffset(input.code, node);
2368
- magicString.appendLeft(
2369
- insertionOffset,
2370
- ` ${import_shared11.SOURCE_MARKER_ATTRIBUTE}=${JSON.stringify(value)}`
2371
- );
2372
- markerCount += 1;
2373
- }
2374
- });
2375
- visitor.visit(parseResult.program);
2376
- if (markerCount === 0) {
2377
- return void 0;
2378
- }
2379
- return Object.freeze({
2380
- code: magicString.toString(),
2381
- map: magicString.generateMap({
2382
- hires: true,
2383
- includeContent: true,
2384
- source: normalizeRelativePath(input.root, input.absolutePath)
2385
- }),
2386
- markerCount
2387
- });
2388
- }
261
+ var import_node_crypto = require("crypto");
262
+ var import_node_path4 = __toESM(require("path"), 1);
263
+ var import_compiler3 = require("@spotpatch/compiler");
2389
264
 
2390
265
  // src/transform/transform-filter.ts
2391
- var import_node_path8 = __toESM(require("path"), 1);
2392
- var import_pluginutils = require("@rollup/pluginutils");
2393
- var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".jsx", ".tsx"]);
266
+ var import_node_path3 = __toESM(require("path"), 1);
267
+ var import_compiler = require("@spotpatch/compiler");
268
+ var import_compiler2 = require("@spotpatch/compiler");
2394
269
  function stripViteQuery(id) {
2395
270
  const queryIndex = id.indexOf("?");
2396
271
  return queryIndex === -1 ? id : id.slice(0, queryIndex);
2397
272
  }
2398
- function isInsideRoot(root, candidate, pathApi = import_node_path8.default) {
2399
- const relative = pathApi.relative(pathApi.resolve(root), pathApi.resolve(candidate));
2400
- return relative === "" || !relative.startsWith(`..${pathApi.sep}`) && relative !== ".." && !pathApi.isAbsolute(relative);
2401
- }
2402
273
  function createTransformFilter(root, options) {
2403
- const matchesConfiguredFilter = (0, import_pluginutils.createFilter)(options.include, options.exclude);
274
+ const sourceFilter = (0, import_compiler.createSourceFilter)(root, options);
2404
275
  return Object.freeze({
2405
276
  shouldTransform(id, code) {
2406
- if (id.startsWith("\0") || id.includes("virtual:spotpatch")) {
277
+ if (id.startsWith("\0") || id.includes("virtual:spotpatch") || id.includes("/packages/vite/") || id.includes("\\packages\\vite\\")) {
2407
278
  return false;
2408
279
  }
2409
280
  const cleanId = stripViteQuery(id);
2410
- if (!SUPPORTED_EXTENSIONS.has(import_node_path8.default.extname(cleanId).toLowerCase())) {
2411
- return false;
2412
- }
2413
- if (cleanId.includes("/node_modules/") || cleanId.includes("\\node_modules\\") || cleanId.includes("/packages/vite/") || cleanId.includes("\\packages\\vite\\")) {
2414
- return false;
2415
- }
2416
- if (!isInsideRoot(root, cleanId) || !matchesConfiguredFilter(cleanId)) {
2417
- return false;
2418
- }
2419
- return code.includes("<");
281
+ return sourceFilter.shouldTransform(import_node_path3.default.resolve(cleanId), code);
2420
282
  }
2421
283
  });
2422
284
  }
2423
285
 
2424
286
  // src/transform/transform-plugin.ts
2425
287
  function createCacheKey(id, code) {
2426
- const hash = (0, import_node_crypto5.createHash)("sha256").update(code).digest("base64url");
288
+ const hash = (0, import_node_crypto.createHash)("sha256").update(code).digest("base64url");
2427
289
  return `${id}\0${hash}`;
2428
290
  }
2429
291
  function getDisplayPath(root, id) {
2430
- const relative = import_node_path9.default.relative(root, stripViteQuery(id));
2431
- return relative.split(import_node_path9.default.sep).join("/");
292
+ const relative = import_node_path4.default.relative(root, stripViteQuery(id));
293
+ return relative.split(import_node_path4.default.sep).join("/");
2432
294
  }
2433
295
  function createTransformPlugin(input) {
2434
296
  let root = process.cwd();
@@ -2444,7 +306,7 @@ function createTransformPlugin(input) {
2444
306
  input.configure?.(config, environment);
2445
307
  },
2446
308
  configResolved(config) {
2447
- root = import_node_path9.default.resolve(config.root);
309
+ root = import_node_path4.default.resolve(config.root);
2448
310
  filter = createTransformFilter(root, input.context.getOptions());
2449
311
  logger = config.logger;
2450
312
  },
@@ -2452,7 +314,7 @@ function createTransformPlugin(input) {
2452
314
  if (!filter.shouldTransform(id, code)) {
2453
315
  return null;
2454
316
  }
2455
- const cleanId = import_node_path9.default.resolve(stripViteQuery(id));
317
+ const cleanId = import_node_path4.default.resolve(stripViteQuery(id));
2456
318
  const cacheKey = createCacheKey(cleanId, code);
2457
319
  if (cache.has(cacheKey)) {
2458
320
  return cache.get(cacheKey) ?? null;
@@ -2460,7 +322,7 @@ function createTransformPlugin(input) {
2460
322
  const startedAt = performance.now();
2461
323
  const options = input.context.getOptions();
2462
324
  try {
2463
- const result = injectSourceMarkers({
325
+ const result = (0, import_compiler3.injectSourceMarkers)({
2464
326
  code,
2465
327
  absolutePath: cleanId,
2466
328
  root,
@@ -2499,41 +361,23 @@ function createTransformPlugin(input) {
2499
361
 
2500
362
  // src/plugin.ts
2501
363
  function spotPatch(userOptions = {}) {
2502
- let options = resolveOptions(userOptions);
364
+ let options = (0, import_dev_server3.resolveOptions)(userOptions);
2503
365
  let credentialEnvironment = Object.freeze({});
2504
366
  if (!options.enabled) {
2505
367
  return [];
2506
368
  }
2507
- const registry = createSourceRegistry();
2508
- const session = createSession();
369
+ const registry = (0, import_dev_server3.createSourceRegistry)();
370
+ const session = (0, import_dev_server3.createSession)();
2509
371
  const context = Object.freeze({
2510
372
  getCredentialEnvironment: () => credentialEnvironment,
2511
373
  getOptions: () => options
2512
374
  });
2513
375
  const configure = (config, environment) => {
2514
- const root = import_node_path10.default.resolve(process.cwd(), config.root ?? ".");
2515
- const loadedEnvironment = config.envDir === false ? process.env : (0, import_vite2.loadEnv)(environment.mode, import_node_path10.default.resolve(root, config.envDir ?? "."), "");
2516
- const environmentAi = userOptions.ai === void 0 ? resolveEnvironmentAiConfiguration(loadedEnvironment).ai : false;
2517
- options = resolveOptions(userOptions, environmentAi);
2518
- if (options.ai === false) {
2519
- credentialEnvironment = Object.freeze({});
2520
- return;
2521
- }
2522
- const names = new Set(
2523
- Object.values(options.ai.providers).map((provider) => provider.apiKeyEnv)
2524
- );
2525
- const missing = [...names].filter((name) => {
2526
- const value = loadedEnvironment[name];
2527
- return value === void 0 || value.trim().length === 0;
2528
- });
2529
- if (missing.length > 0) {
2530
- throw new RangeError(
2531
- `SpotPatch AI credential environment is missing ${missing.join(", ")}.`
2532
- );
2533
- }
2534
- credentialEnvironment = Object.freeze(
2535
- Object.fromEntries([...names].map((name) => [name, loadedEnvironment[name]]))
2536
- );
376
+ const root = import_node_path5.default.resolve(process.cwd(), config.root ?? ".");
377
+ const loadedEnvironment = config.envDir === false ? process.env : (0, import_vite2.loadEnv)(environment.mode, import_node_path5.default.resolve(root, config.envDir ?? "."), "");
378
+ const environmentAi = userOptions.ai === void 0 ? (0, import_dev_server3.resolveEnvironmentAiConfiguration)(loadedEnvironment).ai : false;
379
+ options = (0, import_dev_server3.resolveOptions)(userOptions, environmentAi);
380
+ credentialEnvironment = (0, import_dev_server3.resolveCredentialEnvironment)(options, loadedEnvironment);
2537
381
  };
2538
382
  return [
2539
383
  createTransformPlugin({ configure, context, registry }),
@@ -2542,8 +386,11 @@ function spotPatch(userOptions = {}) {
2542
386
  ];
2543
387
  }
2544
388
 
389
+ // src/options.ts
390
+ var import_dev_server4 = require("@spotpatch/dev-server");
391
+
2545
392
  // src/index.ts
2546
- var import_shared12 = require("@spotpatch/shared");
393
+ var import_shared = require("@spotpatch/shared");
2547
394
  // Annotate the CommonJS export names for ESM import in node:
2548
395
  0 && (module.exports = {
2549
396
  DEFAULT_AGENT_LIMITS,