@kubb/studio 5.10.0 → 5.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -129,10 +129,17 @@ type PluginView = {
129
129
  * value found in the file. An option marked `literal: false` holds a function or a reference the
130
130
  * agent will not overwrite, so Studio shows the control disabled rather than hiding it, and
131
131
  * `value` is absent since there is nothing safe to display as the current value.
132
+ *
133
+ * An object option that holds code in only some members, such as
134
+ * `output: { path: './gen', banner: () => '' }`, also carries `value` with its literal members
135
+ * and `locked` with the paths of the members that are code, relative to the option
136
+ * (`[['banner']]`). A `set` at any deeper path outside `locked` succeeds, so Studio can edit
137
+ * `output.path` there.
132
138
  */
133
139
  options: Record<string, {
134
140
  literal: boolean;
135
141
  value?: OptionValue;
142
+ locked?: Array<Array<string>>;
136
143
  }>;
137
144
  /**
138
145
  * Set when the plugin call is commented out in the file. Its options stay on disk but are not
@@ -376,14 +383,18 @@ type SaveResult = {
376
383
  * What a file read returns for a job: the files that job generated (`output`), or what the output
377
384
  * directory held on disk before that job ran (`disk`, only on an agent with a project on disk).
378
385
  */
379
- type FileSource = 'output' | 'disk';
386
+ declare const FileSource: {
387
+ readonly OUTPUT: "output";
388
+ readonly DISK: "disk";
389
+ };
390
+ type FileSource = (typeof FileSource)[keyof typeof FileSource];
380
391
  /**
381
392
  * Asks the agent to read files of one generation job back.
382
393
  */
383
394
  type ReadFilesInput = {
384
395
  /**
385
396
  * The job whose files to read, the only lookup key, so the caller decides whose jobs a reader may
386
- * see. A job the agent no longer keeps fails with {@link GENERATION_GONE_MESSAGE}.
397
+ * see. A job the agent no longer keeps fails with {@link AgentMessage.GENERATION_GONE}.
387
398
  */
388
399
  jobId: string;
389
400
  /**
@@ -418,7 +429,38 @@ type ReadFilesResult = {
418
429
  pages?: Record<string, FilePage>;
419
430
  };
420
431
  /**
421
- * Asks for the Kubb AST one job parsed its spec into. A job the agent no longer keeps fails with {@link GENERATION_GONE_MESSAGE}.
432
+ * Asks for one page of a file's line diff between two kept sets, so a large file never leaves the agent whole.
433
+ * `jobId`'s `output` is the newer side, `base` the older one. `cursor` and `limit` page the patch lines.
434
+ */
435
+ type ReadDiffInput = {
436
+ jobId: string;
437
+ path: string;
438
+ base: {
439
+ jobId: string;
440
+ source: FileSource;
441
+ };
442
+ cursor?: number;
443
+ limit: number;
444
+ };
445
+ /**
446
+ * One hunk of a diff page. A hunk split across pages keeps its `index` and starts, so the pages join back.
447
+ */
448
+ type DiffHunk = {
449
+ index: number;
450
+ oldStart: number;
451
+ newStart: number;
452
+ lines: Array<string>;
453
+ };
454
+ /**
455
+ * One page of a diff. `hasBase` is false, and the page empty, when the base set does not hold the path.
456
+ */
457
+ type ReadDiffResult = {
458
+ hunks: Array<DiffHunk>;
459
+ nextCursor: number | null;
460
+ hasBase: boolean;
461
+ };
462
+ /**
463
+ * Asks for the Kubb AST one job parsed its spec into. A job the agent no longer keeps fails with {@link AgentMessage.GENERATION_GONE}.
422
464
  */
423
465
  type ReadInputInput = {
424
466
  jobId: string;
@@ -430,13 +472,21 @@ type ReadInputResult = {
430
472
  input: InputNode;
431
473
  };
432
474
  /**
433
- * The error a file read fails with once the agent no longer keeps the job's files.
475
+ * Why the agent refuses a request. Studio matches on these to answer with the right status.
434
476
  */
435
- declare const GENERATION_GONE_MESSAGE = "The files of this generation are no longer kept on the agent, run the generation again";
436
- declare const READ_NOT_ALLOWED_MESSAGE = "The agent was not granted permission to read generated files";
437
- /** Why `readInput` refuses: reading the parsed spec is its own permission. */
438
- declare const READ_AST_NOT_ALLOWED_MESSAGE = "The agent was not granted permission to read the parsed spec";
439
- declare const NO_INPUT_MESSAGE = "This job kept no API input";
477
+ declare const AgentMessage: {
478
+ /**
479
+ * A read for a job whose files the agent no longer keeps.
480
+ */
481
+ readonly GENERATION_GONE: "The files of this generation are no longer kept on the agent, run the generation again";
482
+ readonly READ_NOT_ALLOWED: "The agent was not granted permission to read generated files";
483
+ /**
484
+ * Reading the parsed spec is its own permission.
485
+ */
486
+ readonly READ_AST_NOT_ALLOWED: "The agent was not granted permission to read the parsed spec";
487
+ readonly NO_INPUT: "This job kept no API input";
488
+ };
489
+ type AgentMessage = (typeof AgentMessage)[keyof typeof AgentMessage];
440
490
  /**
441
491
  * A generation in flight. Cap'n Web keeps the three calls pointed at the same run, so a caller can
442
492
  * read `events()` while `result()` is still pending and `cancel()` stops it early.
@@ -478,6 +528,10 @@ type AgentApi = {
478
528
  * Refused unless the agent was granted `allowRead`, like {@link AgentApi.readFiles}.
479
529
  */
480
530
  readInput: (input: ReadInputInput) => Promise<ReadInputResult>;
531
+ /**
532
+ * Refused unless the agent was granted `allowRead`, like {@link AgentApi.readFiles}.
533
+ */
534
+ readDiff: (input: ReadDiffInput) => Promise<ReadDiffResult>;
481
535
  };
482
536
  /**
483
537
  * Operations an agent invokes on Studio over its socket.
@@ -611,6 +665,17 @@ type AgentPermissions = {
611
665
  */
612
666
  allowReadAst: boolean;
613
667
  };
668
+ /**
669
+ * The keys of {@link AgentPermissions}, such as the permission a refused request names.
670
+ */
671
+ declare const AgentPermission: {
672
+ readonly READ: "allowRead";
673
+ readonly READ_AST: "allowReadAst";
674
+ readonly WRITE: "allowWrite";
675
+ readonly CONFIG_EDIT: "allowConfigEdit";
676
+ readonly EXEC: "allowExec";
677
+ };
678
+ type AgentPermission = (typeof AgentPermission)[keyof typeof AgentPermission];
614
679
  /**
615
680
  * Metadata part of `POST /api/snapshots`. The `tarball` part carries the packed package.
616
681
  */
@@ -690,5 +755,5 @@ type PublishedSnapshot = {
690
755
  branchChanges?: BranchSnapshotChanges;
691
756
  };
692
757
  //#endregion
693
- export { OptionValue as A, SaveResult as B, GenerationRun as C, MAX_PAGE_LINES as D, MAX_FILES_PER_REQUEST as E, ReadFilesInput as F, SnapshotUploadMetadata as H, ReadFilesResult as I, ReadInputInput as L, PublishedSnapshot as M, READ_AST_NOT_ALLOWED_MESSAGE as N, MAX_SNAPSHOT_BYTES as O, READ_NOT_ALLOWED_MESSAGE as P, ReadInputResult as R, GenerationEventType as S, JSONKubbConfig as T, StudioApi as U, SnapshotChanges as V, generationEventTypes as W, GENERATION_GONE_MESSAGE as _, AgentConnectInfo as a, GenerationEvent as b, ConfigEdit as c, ConfigRef as d, ConfigView as f, FileSource as g, FilePage as h, AgentCloseCode as i, PluginView as j, NO_INPUT_MESSAGE as k, ConfigEditOutcome as l, ExtensionEntry as m, AGENT_SOCKET_PATH as n, AgentPermissions as o, ConnectMessagePayload as p, AgentApi as r, BranchSnapshotChanges as s, AGENT_INSTANCE_HEADER as t, ConfigFileView as u, GenerateInput as v, InstalledExtensions as w, GenerationEventPayloads as x, GenerateResult as y, SaveConfigInput as z };
694
- //# sourceMappingURL=protocol-C1PwN9fa.d.ts.map
758
+ export { MAX_SNAPSHOT_BYTES as A, SaveConfigInput as B, GenerationEventPayloads as C, JSONKubbConfig as D, InstalledExtensions as E, ReadDiffResult as F, generationEventTypes as G, SnapshotChanges as H, ReadFilesInput as I, ReadFilesResult as L, PluginView as M, PublishedSnapshot as N, MAX_FILES_PER_REQUEST as O, ReadDiffInput as P, ReadInputInput as R, GenerationEvent as S, GenerationRun as T, SnapshotUploadMetadata as U, SaveResult as V, StudioApi as W, ExtensionEntry as _, AgentConnectInfo as a, GenerateInput as b, AgentPermissions as c, ConfigEditOutcome as d, ConfigFileView as f, DiffHunk as g, ConnectMessagePayload as h, AgentCloseCode as i, OptionValue as j, MAX_PAGE_LINES as k, BranchSnapshotChanges as l, ConfigView as m, AGENT_SOCKET_PATH as n, AgentMessage as o, ConfigRef as p, AgentApi as r, AgentPermission as s, AGENT_INSTANCE_HEADER as t, ConfigEdit as u, FilePage as v, GenerationEventType as w, GenerateResult as x, FileSource as y, ReadInputResult as z };
759
+ //# sourceMappingURL=protocol-DQfI1aLV.d.ts.map
package/dist/protocol.cjs CHANGED
@@ -33,13 +33,28 @@ const generationEventTypes = [
33
33
  "kubb:hook:end"
34
34
  ];
35
35
  /**
36
- * The error a file read fails with once the agent no longer keeps the job's files.
36
+ * What a file read returns for a job: the files that job generated (`output`), or what the output
37
+ * directory held on disk before that job ran (`disk`, only on an agent with a project on disk).
37
38
  */
38
- const GENERATION_GONE_MESSAGE = "The files of this generation are no longer kept on the agent, run the generation again";
39
- const READ_NOT_ALLOWED_MESSAGE = "The agent was not granted permission to read generated files";
40
- /** Why `readInput` refuses: reading the parsed spec is its own permission. */
41
- const READ_AST_NOT_ALLOWED_MESSAGE = "The agent was not granted permission to read the parsed spec";
42
- const NO_INPUT_MESSAGE = "This job kept no API input";
39
+ const FileSource = {
40
+ OUTPUT: "output",
41
+ DISK: "disk"
42
+ };
43
+ /**
44
+ * Why the agent refuses a request. Studio matches on these to answer with the right status.
45
+ */
46
+ const AgentMessage = {
47
+ /**
48
+ * A read for a job whose files the agent no longer keeps.
49
+ */
50
+ GENERATION_GONE: "The files of this generation are no longer kept on the agent, run the generation again",
51
+ READ_NOT_ALLOWED: "The agent was not granted permission to read generated files",
52
+ /**
53
+ * Reading the parsed spec is its own permission.
54
+ */
55
+ READ_AST_NOT_ALLOWED: "The agent was not granted permission to read the parsed spec",
56
+ NO_INPUT: "This job kept no API input"
57
+ };
43
58
  /**
44
59
  * Close codes Studio sends when it ends an agent socket on purpose. `UNAUTHORIZED` means the token
45
60
  * was refused, so pair again. `SUPERSEDED` and `INCOMPATIBLE` mean stop. Any other code, `1011`
@@ -62,17 +77,26 @@ const MAX_FILES_PER_REQUEST = 50;
62
77
  /** Most lines one page may ask for. */
63
78
  const MAX_PAGE_LINES = 2e3;
64
79
  const MAX_SNAPSHOT_BYTES = 52428800;
80
+ /**
81
+ * The keys of {@link AgentPermissions}, such as the permission a refused request names.
82
+ */
83
+ const AgentPermission = {
84
+ READ: "allowRead",
85
+ READ_AST: "allowReadAst",
86
+ WRITE: "allowWrite",
87
+ CONFIG_EDIT: "allowConfigEdit",
88
+ EXEC: "allowExec"
89
+ };
65
90
  //#endregion
66
91
  exports.AGENT_INSTANCE_HEADER = AGENT_INSTANCE_HEADER;
67
92
  exports.AGENT_SOCKET_PATH = AGENT_SOCKET_PATH;
68
93
  exports.AgentCloseCode = AgentCloseCode;
69
- exports.GENERATION_GONE_MESSAGE = GENERATION_GONE_MESSAGE;
94
+ exports.AgentMessage = AgentMessage;
95
+ exports.AgentPermission = AgentPermission;
96
+ exports.FileSource = FileSource;
70
97
  exports.MAX_FILES_PER_REQUEST = MAX_FILES_PER_REQUEST;
71
98
  exports.MAX_PAGE_LINES = MAX_PAGE_LINES;
72
99
  exports.MAX_SNAPSHOT_BYTES = MAX_SNAPSHOT_BYTES;
73
- exports.NO_INPUT_MESSAGE = NO_INPUT_MESSAGE;
74
- exports.READ_AST_NOT_ALLOWED_MESSAGE = READ_AST_NOT_ALLOWED_MESSAGE;
75
- exports.READ_NOT_ALLOWED_MESSAGE = READ_NOT_ALLOWED_MESSAGE;
76
100
  exports.generationEventTypes = generationEventTypes;
77
101
 
78
102
  //# sourceMappingURL=protocol.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.cjs","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["import type { InputNode } from '@kubb/ast'\nimport type { ExtensionKind, ExtensionManifest, KubbHooks } from '@kubb/core'\n\n/**\n * JSON-serializable Kubb config exchanged over RPC. A live `kubb/kit` config holds\n * functions and class instances that cannot survive JSON, so both sides pass this flattened shape\n * and rebuild the real config from it.\n */\nexport type JSONKubbConfig = {\n /**\n * Plugins with their serialized options. `name` is the package name (e.g. `@kubb/plugin-ts`)\n * and `options` is an opaque blob the agent forwards unchanged to the plugin factory. An entry\n * with `disabled: true` is dropped even when the disk config's `plugins` array still lists it.\n */\n plugins?: Array<{\n name: string\n options?: object\n disabled?: boolean\n }>\n /**\n * Raw OpenAPI / Swagger spec content (YAML or JSON string), never a file path or URL.\n * Required by a 'sandbox' agent, which has no disk config of its own and refuses anything else.\n * A non-sandbox agent always reads its spec from disk, so this is ignored with a `kubb:warn`.\n */\n input?: string\n /**\n * Adapter option overrides sent from Studio UI. Merged into the disk config's adapter options\n * and re-applied through the same adapter factory, since an adapter instance's functions\n * (`parse`, `getImports`, ...) can't survive JSON serialization over the WebSocket.\n */\n adapter?: object\n /**\n * Parser option overrides from Studio, matched to the disk config's parsers by `name` and\n * re-applied through each parser's factory, the same way `adapter` is.\n */\n parsers?: Array<{\n name: string\n options?: object\n }>\n}\n\n/**\n * Which `defineConfig(...)` entry an edit targets, for a config file that exports an array.\n *\n * A number selects by position, a string matches the entry's `name`. Omitted targets the only\n * entry, or the first one when the file exports an array.\n */\nexport type ConfigRef = string | number\n\n/**\n * A value the agent can read out of a plugin option in `kubb.config.ts` and round-trip through JSON.\n */\nexport type OptionValue = string | number | boolean | null | Array<OptionValue> | { [key: string]: OptionValue }\n\n/**\n * One change to a plugin's options in the user's `kubb.config.ts`.\n *\n * `plugin` is the package name (`@kubb/plugin-ts`), the same identity used in {@link JSONKubbConfig}.\n * The agent applies these to the file with an AST patch, so only the targeted values are rewritten.\n *\n * Declared here rather than in `configFile.ts` because this is the wire contract, and the patcher\n * imports it from here. Type-only, so nothing pulls `magicast` into this entry point.\n */\nexport type ConfigEdit =\n /**\n * Write a literal option value. `path` walks nested objects, so `['enum', 'type']` targets\n * `pluginTs({ enum: { type } })`. `kind` names which factory call `plugin` is: one in `plugins`\n * when left out, the config's `adapter`, or one in `parsers`.\n */\n | { operation: 'set'; config?: ConfigRef; kind?: ExtensionKind; plugin: string; path: Array<string>; value: unknown }\n /**\n * Drop an option so the extension falls back to its default. `kind` as for `set`.\n */\n | { operation: 'remove'; config?: ConfigRef; kind?: ExtensionKind; plugin: string; path: Array<string> }\n /**\n * Add a plugin factory call and its import to the `plugins` array.\n */\n | { operation: 'add-plugin'; config?: ConfigRef; plugin: string; options?: Record<string, unknown> }\n /**\n * Comment the plugin call out, keeping its options in the file so enabling it again restores them.\n */\n | { operation: 'disable-plugin'; config?: ConfigRef; plugin: string }\n /**\n * Uncomment a plugin call a previous `disable-plugin` commented out.\n */\n | { operation: 'enable-plugin'; config?: ConfigRef; plugin: string }\n\n/**\n * A factory call the agent found in a `defineConfig(...)`: a plugin in `plugins`, the `adapter`, or\n * a parser in `parsers`.\n */\nexport type PluginView = {\n /**\n * Local identifier of the factory in the file, e.g. `pluginTs`. This is the alias when the plugin\n * was imported under one.\n */\n importName: string\n /**\n * Module the factory is imported from, e.g. `@kubb/plugin-ts`.\n */\n packageName: string\n /**\n * Top-level option keys, each flagged with whether the agent may write it and, when it can, the\n * value found in the file. An option marked `literal: false` holds a function or a reference the\n * agent will not overwrite, so Studio shows the control disabled rather than hiding it, and\n * `value` is absent since there is nothing safe to display as the current value.\n */\n options: Record<string, { literal: boolean; value?: OptionValue }>\n /**\n * Set when the plugin call is commented out in the file. Its options stay on disk but are not\n * readable, so `options` is empty until it is enabled again.\n */\n disabled?: true\n}\n\n/**\n * One `defineConfig(...)` entry. A config file that exports a single object has exactly one.\n */\nexport type ConfigView = {\n /**\n * The entry's `name`, when it sets one. Studio labels the config picker with it.\n */\n name?: string\n /**\n * Each plugin call in the entry, with its top-level option keys.\n */\n plugins: Array<PluginView>\n /**\n * The `adapter` call, when the entry sets one through an imported factory.\n */\n adapter?: PluginView\n /**\n * The parser calls in `parsers`, when the entry lists any through imported factories.\n */\n parsers?: Array<PluginView>\n}\n\n/**\n * What the agent found in the user's config file, so Studio knows which controls it may offer.\n * Absent when the agent could not read the file at all.\n */\nexport type ConfigFileView =\n | {\n managed: true\n /**\n * One entry per config the file exports, in source order. Every {@link ConfigEdit} names\n * which of these it targets through its `config` field.\n */\n configs: Array<ConfigView>\n }\n | {\n managed: false\n /**\n * Why the file is outside what the agent edits, for example a default export that is not a\n * `defineConfig(...)` call. Studio shows this and offers no property-level controls.\n */\n reason: string\n }\n\n/**\n * Outcome of a single {@link ConfigEdit}, returned in a {@link SaveResult}.\n */\nexport type ConfigEditOutcome = {\n edit: ConfigEdit\n applied: boolean\n /**\n * Why the edit was refused, absent when it was applied.\n */\n reason?: string\n}\n\n/**\n * The public, JSON-safe subset of Kubb lifecycle hooks. The core registry remains extensible;\n * adding a core hook does not publish it to Studio until it is listed here and projected below.\n */\nexport const generationEventTypes = [\n 'kubb:plugin:start',\n 'kubb:plugin:end',\n 'kubb:build:start',\n 'kubb:build:end',\n 'kubb:files:processing:start',\n 'kubb:files:processing:update',\n 'kubb:files:processing:end',\n 'kubb:info',\n 'kubb:success',\n 'kubb:warn',\n 'kubb:error',\n 'kubb:diagnostic',\n 'kubb:generation:start',\n 'kubb:generation:end',\n 'kubb:generation:summary',\n 'kubb:lifecycle:start',\n 'kubb:lifecycle:end',\n 'kubb:format:start',\n 'kubb:format:end',\n 'kubb:lint:start',\n 'kubb:lint:end',\n 'kubb:hooks:start',\n 'kubb:hooks:end',\n 'kubb:hook:start',\n 'kubb:hook:line',\n 'kubb:hook:end',\n] as const satisfies ReadonlyArray<keyof KubbHooks>\n\n/**\n * One of the lifecycle hooks {@link generationEventTypes} publishes.\n */\nexport type GenerationEventType = (typeof generationEventTypes)[number]\n\n/**\n * The JSON-safe payload each published event carries. These are flattened on purpose: a core hook\n * context holds live objects (a `Config`, a `Storage`) that cannot cross the wire.\n */\nexport type GenerationEventPayloads = {\n 'kubb:plugin:start': [ctx: { plugin: { name: string } }]\n 'kubb:plugin:end': [ctx: { plugin: { name: string }; duration: number; success: boolean }]\n 'kubb:build:start': [ctx: { config: { name?: string }; adapter: { name: string } }]\n 'kubb:build:end': [ctx: { files: Array<{ path: string; name: string }>; outputDir: string }]\n 'kubb:files:processing:start': [ctx: { total: number }]\n 'kubb:files:processing:update': [\n ctx: {\n files: Array<{\n file: string\n processed: number\n total: number\n percentage: number\n }>\n },\n ]\n 'kubb:files:processing:end': [ctx: { total: number }]\n 'kubb:info': [ctx: { message: string; info?: string }]\n 'kubb:success': [ctx: { message: string; info?: string }]\n 'kubb:warn': [ctx: { message: string; info?: string }]\n 'kubb:error': [ctx: { message: string; stack?: string }]\n 'kubb:diagnostic': [\n ctx: {\n code: string\n message: string\n severity: string\n location?: { kind: string; pointer?: string; ref?: string }\n help?: string\n plugin?: string\n stack?: string\n },\n ]\n 'kubb:generation:start': [ctx: { name?: string; plugins: number }]\n /**\n * A run finished. See `kubb:build:end` for files, `kubb:generation:summary` for the count, and\n * `readFiles` for contents.\n */\n 'kubb:generation:end': []\n 'kubb:generation:summary': [ctx: { duration: number; fileCount: number; failedPlugins: number; status: 'success' | 'failed' }]\n 'kubb:lifecycle:start': []\n 'kubb:lifecycle:end': []\n 'kubb:format:start': []\n 'kubb:format:end': []\n 'kubb:lint:start': []\n 'kubb:lint:end': []\n 'kubb:hooks:start': []\n 'kubb:hooks:end': []\n 'kubb:hook:start': [ctx: { id?: string; command: string; args?: Array<string> }]\n 'kubb:hook:line': [ctx: { id: string; line: string }]\n 'kubb:hook:end': [\n ctx: {\n id?: string\n command: string\n args?: Array<string>\n success: boolean\n error?: { message: string; stack?: string }\n },\n ]\n}\n\n/**\n * Versioned envelope around one lifecycle event. Cap'n Web streams preserve the order the agent\n * emitted them in, so the receiver replays a run by reading the stream straight through.\n */\nexport type GenerationEvent = {\n [Type in GenerationEventType]: { type: Type; data: GenerationEventPayloads[Type] }\n}[GenerationEventType] & {\n version: 1\n jobId: string\n timestamp: number\n}\n\n/**\n * Asks the agent to run one generation. `jobId` tags every event the run emits so a caller\n * watching several runs can tell them apart.\n */\nexport type GenerateInput = { jobId: string; config: JSONKubbConfig }\n\n/**\n * What a finished run produced.\n */\nexport type GenerateResult = {\n status: 'success' | 'failed'\n /**\n * Paths of the generated files, relative to the output directory.\n */\n files: Array<string>\n fileCount: number\n /**\n * Content fingerprint per file in `files`, to tell changed files apart without reading them.\n */\n hashes: Record<string, string>\n /**\n * Fingerprints of the output directory on disk before the run, for an agent with a project on disk.\n */\n disk?: { hashes: Record<string, string> }\n}\n\n/**\n * Asks the agent to apply a batch of edits to the config file on disk.\n */\nexport type SaveConfigInput = { edits: Array<ConfigEdit> }\n\n/**\n * Per-edit outcomes. `changed` is false when every edit was a no-op, so a caller can skip reloading.\n */\nexport type SaveResult = { outcomes: Array<ConfigEditOutcome>; changed: boolean }\n\n/**\n * What a file read returns for a job: the files that job generated (`output`), or what the output\n * directory held on disk before that job ran (`disk`, only on an agent with a project on disk).\n */\nexport type FileSource = 'output' | 'disk'\n\n/**\n * Asks the agent to read files of one generation job back.\n */\nexport type ReadFilesInput = {\n /**\n * The job whose files to read, the only lookup key, so the caller decides whose jobs a reader may\n * see. A job the agent no longer keeps fails with {@link GENERATION_GONE_MESSAGE}.\n */\n jobId: string\n /**\n * At most {@link MAX_FILES_PER_REQUEST} paths, each checked against what the job's set holds.\n */\n paths: Array<string>\n /**\n * Which of the job's sets to read, `output` when left out.\n */\n source?: FileSource\n /**\n * The line to start from (0-based), the `nextCursor` of the previous page. Defaults to 0.\n */\n cursor?: number\n /**\n * Lines to read per file, at most {@link MAX_PAGE_LINES}. Left out, the whole file.\n */\n limit?: number\n}\n\n/**\n * Where a paged read stands for one file: the cursor of the next page, `null` after the last one.\n */\nexport type FilePage = { nextCursor: number | null; totalLines: number }\n\n/**\n * What a file read returns. `pages` is set only for a paged read.\n */\nexport type ReadFilesResult = { files: Record<string, string>; pages?: Record<string, FilePage> }\n\n/**\n * Asks for the Kubb AST one job parsed its spec into. A job the agent no longer keeps fails with {@link GENERATION_GONE_MESSAGE}.\n */\nexport type ReadInputInput = { jobId: string }\n\n/**\n * The job's `InputNode`, as JSON. A ref schema names its target, which `input.schemas` holds, without a copy.\n */\nexport type ReadInputResult = { input: InputNode }\n\n/**\n * The error a file read fails with once the agent no longer keeps the job's files.\n */\nexport const GENERATION_GONE_MESSAGE = 'The files of this generation are no longer kept on the agent, run the generation again'\n\nexport const READ_NOT_ALLOWED_MESSAGE = 'The agent was not granted permission to read generated files'\n\n/** Why `readInput` refuses: reading the parsed spec is its own permission. */\nexport const READ_AST_NOT_ALLOWED_MESSAGE = 'The agent was not granted permission to read the parsed spec'\n\nexport const NO_INPUT_MESSAGE = 'This job kept no API input'\n\n/**\n * A generation in flight. Cap'n Web keeps the three calls pointed at the same run, so a caller can\n * read `events()` while `result()` is still pending and `cancel()` stops it early.\n */\nexport type GenerationRun = {\n events: () => Promise<ReadableStream<GenerationEvent>>\n result: () => Promise<GenerateResult>\n cancel: () => Promise<void>\n}\n\n/**\n * What Studio knows about the agent on the other end of a socket. Studio passes it with every\n * {@link AgentApi.connect} call, so it stays the authority on whether the agent is a sandbox.\n */\nexport type AgentConnectInfo = {\n /**\n * A shared `sandbox` or `global` agent. It keeps generations in memory, runs each job in a\n * temporary root, generates from the spec Studio sends, and never writes to disk or edits config.\n */\n sandbox: boolean\n agentSlug: string\n /**\n * Absent for a sandbox or global agent, which belongs to no organization.\n */\n organizationSlug?: string\n studioVersion: string\n}\n\n/**\n * Operations Studio invokes on an agent over its socket.\n */\nexport type AgentApi = {\n /**\n * The first call on every socket, and again whenever Studio needs the current config.\n */\n connect: (info: AgentConnectInfo) => Promise<ConnectMessagePayload>\n startGeneration: (input: GenerateInput) => GenerationRun\n saveConfig: (input: SaveConfigInput) => Promise<SaveResult>\n readFiles: (input: ReadFilesInput) => Promise<ReadFilesResult>\n /**\n * Refused unless the agent was granted `allowRead`, like {@link AgentApi.readFiles}.\n */\n readInput: (input: ReadInputInput) => Promise<ReadInputResult>\n}\n\n/**\n * Operations an agent invokes on Studio over its socket.\n */\nexport type StudioApi = {\n /**\n * Keeps the connection alive. A ping that does not settle in time makes the agent reconnect.\n */\n ping: () => Promise<void>\n}\n\n/**\n * Close codes Studio sends when it ends an agent socket on purpose. `UNAUTHORIZED` means the token\n * was refused, so pair again. `SUPERSEDED` and `INCOMPATIBLE` mean stop. Any other code, `1011`\n * included, is an ordinary drop and the agent reconnects.\n */\nexport const AgentCloseCode = {\n UNAUTHORIZED: 4001,\n SUPERSEDED: 4002,\n INCOMPATIBLE: 4003,\n} as const\n\nexport const AGENT_SOCKET_PATH = '/api/agent/socket'\n\n/**\n * Optional header naming the process a socket belongs to, for agents that run several processes.\n */\nexport const AGENT_INSTANCE_HEADER = 'x-kubb-instance-id'\n\n/**\n * How many files a single `readFiles` request may ask for at once.\n */\nexport const MAX_FILES_PER_REQUEST = 50\n\n/** Most lines one page may ask for. */\nexport const MAX_PAGE_LINES = 2000\n\nexport const MAX_SNAPSHOT_BYTES = 50 * 1024 * 1024\n\n/**\n * Connection payload returned by {@link AgentApi.connect}. Carries only what Studio renders, with\n * everything about the config under one key.\n */\nexport type ConnectMessagePayload = {\n /**\n * Always sent, so a mismatch is visible on both sides: Studio badges the connection with these\n * and the host prints them.\n */\n versions: {\n /**\n * The version of the `@kubb/studio` runtime the agent runs.\n */\n kubb: string\n /**\n * The version of the host itself (the `kubb.agent` package or the `kubb` CLI).\n */\n agent: string\n }\n /**\n * The agent's project root. This is the workspace that generation runs against.\n */\n root: string\n /**\n * The baseline every generation starts from.\n */\n config: {\n /**\n * The config path as the host gave it, relative to `root` unless absolute.\n */\n path: string\n /**\n * What the agent read out of the config file itself, so Studio can render the plugin editor\n * against the real file. Absent when the agent could not read it, or was not granted\n * `allowConfigEdit`.\n */\n file?: ConfigFileView\n /**\n * Plugins the config registers, with a JSON copy of their options: anything JSON cannot hold,\n * such as a function, is left out. A plugin defined in the project rather than installed as a\n * `@kubb/plugin-*` package keeps its own `name`.\n */\n plugins?: Array<{\n name: string\n options?: object\n }>\n /**\n * The adapter and the parsers the config registers, named like `plugins` and with the same JSON\n * copy of their options, so Studio can render their manifest options.\n */\n adapter?: {\n name: string\n options?: object\n }\n parsers?: Array<{\n name: string\n options?: object\n }>\n /** Installed packages that ship a `kubb-extension.json`, including plugins the config does not use yet. */\n extensions?: Array<ExtensionEntry>\n }\n permissions: AgentPermissions\n}\n\n/** An installed extension package and the manifest it ships. */\nexport type ExtensionEntry = { package: string; version: string; manifest: ExtensionManifest }\n\n/** Installed extension packages by package name, the only ones Studio may import besides `@kubb/plugin-*`. */\nexport type InstalledExtensions = ReadonlyMap<string, ExtensionEntry>\n\n/**\n * What an agent may do in the project it serves. Every one is off unless the host granted it, and\n * a sandbox narrows them further.\n */\nexport type AgentPermissions = {\n /**\n * Whether the agent writes generated files to disk. Always false for a sandbox.\n */\n allowWrite: boolean\n /**\n * Whether the agent runs the formatter, the linter, and `output.postGenerate` as child\n * processes after a generation.\n */\n allowExec: boolean\n /**\n * Whether the agent may change plugin options in the user's `kubb.config.ts`. Separate from\n * `allowWrite`, which covers generated output: this one edits a hand-authored source file.\n */\n allowConfigEdit: boolean\n /**\n * Whether the agent hands back file source in response to `readFiles`. Always true for a\n * sandbox.\n */\n allowRead: boolean\n /**\n * Whether the agent hands back the parsed spec (the Kubb AST) in response to `readInput`.\n * Separate from `allowRead`, which covers generated files. Always true for a sandbox.\n */\n allowReadAst: boolean\n}\n\n/**\n * Metadata part of `POST /api/snapshots`. The `tarball` part carries the packed package.\n */\nexport type SnapshotUploadMetadata = {\n /**\n * Stable CI identity, such as `gh:<repoId>:<pr>`. Snapshots with the same `id` share one CI agent.\n */\n id: string\n name: string\n packageName: string\n version: string\n /**\n * The commit the snapshot is built from, so the next one can name it as its base.\n */\n commit?: string\n /**\n * The `id` of another CI agent, such as the base branch's, to also compare with.\n */\n baseId?: string\n /**\n * Content fingerprint per generated file, keyed by path relative to the project root.\n */\n hashes: Record<string, string>\n peerDependencies: Record<string, string>\n}\n\n/**\n * How a snapshot's files differ from an earlier snapshot of the same package.\n */\nexport type SnapshotChanges = {\n /**\n * The snapshot these changes are measured against, `null` when there is none to compare with.\n */\n base: { id: string; version: string | null; commit?: string; createdAt: string } | null\n added: Array<string>\n changed: Array<string>\n removed: Array<string>\n}\n\n/**\n * {@link SnapshotChanges} against another CI agent. `baseFound` is `false` when Studio has no CI\n * agent with that id yet, and `true` when it has one without a snapshot of this package.\n */\nexport type BranchSnapshotChanges = SnapshotChanges & { baseFound: boolean }\n\n/**\n * Response body of `POST /api/snapshots`: `{ snapshot: PublishedSnapshot }`.\n */\nexport type PublishedSnapshot = {\n id: string\n name: string | null\n version: string | null\n integrity: string | null\n agent: { id: string; name: string; slug: string | null }\n /**\n * Readable download path, often `/packages/{agentSlug}/{name}.tgz`.\n */\n url: string\n snapshotIdUrl: string\n createdAt: string\n expiresAt: string\n /**\n * What changed since the previous snapshot of this package on the same CI agent.\n */\n changes?: SnapshotChanges\n /**\n * What differs from the latest snapshot of the CI agent `baseId` names. Absent without a base.\n */\n branchChanges?: BranchSnapshotChanges\n}\n"],"mappings":";;;;;;AA+KA,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;AA+KA,MAAa,0BAA0B;AAEvC,MAAa,2BAA2B;;AAGxC,MAAa,+BAA+B;AAE5C,MAAa,mBAAmB;;;;;;AA8DhC,MAAa,iBAAiB;CAC5B,cAAc;CACd,YAAY;CACZ,cAAc;AAChB;AAEA,MAAa,oBAAoB;;;;AAKjC,MAAa,wBAAwB;;;;AAKrC,MAAa,wBAAwB;;AAGrC,MAAa,iBAAiB;AAE9B,MAAa,qBAAqB"}
1
+ {"version":3,"file":"protocol.cjs","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["import type { InputNode } from '@kubb/ast'\nimport type { ExtensionKind, ExtensionManifest, KubbHooks } from '@kubb/core'\n\n/**\n * JSON-serializable Kubb config exchanged over RPC. A live `kubb/kit` config holds\n * functions and class instances that cannot survive JSON, so both sides pass this flattened shape\n * and rebuild the real config from it.\n */\nexport type JSONKubbConfig = {\n /**\n * Plugins with their serialized options. `name` is the package name (e.g. `@kubb/plugin-ts`)\n * and `options` is an opaque blob the agent forwards unchanged to the plugin factory. An entry\n * with `disabled: true` is dropped even when the disk config's `plugins` array still lists it.\n */\n plugins?: Array<{\n name: string\n options?: object\n disabled?: boolean\n }>\n /**\n * Raw OpenAPI / Swagger spec content (YAML or JSON string), never a file path or URL.\n * Required by a 'sandbox' agent, which has no disk config of its own and refuses anything else.\n * A non-sandbox agent always reads its spec from disk, so this is ignored with a `kubb:warn`.\n */\n input?: string\n /**\n * Adapter option overrides sent from Studio UI. Merged into the disk config's adapter options\n * and re-applied through the same adapter factory, since an adapter instance's functions\n * (`parse`, `getImports`, ...) can't survive JSON serialization over the WebSocket.\n */\n adapter?: object\n /**\n * Parser option overrides from Studio, matched to the disk config's parsers by `name` and\n * re-applied through each parser's factory, the same way `adapter` is.\n */\n parsers?: Array<{\n name: string\n options?: object\n }>\n}\n\n/**\n * Which `defineConfig(...)` entry an edit targets, for a config file that exports an array.\n *\n * A number selects by position, a string matches the entry's `name`. Omitted targets the only\n * entry, or the first one when the file exports an array.\n */\nexport type ConfigRef = string | number\n\n/**\n * A value the agent can read out of a plugin option in `kubb.config.ts` and round-trip through JSON.\n */\nexport type OptionValue = string | number | boolean | null | Array<OptionValue> | { [key: string]: OptionValue }\n\n/**\n * One change to a plugin's options in the user's `kubb.config.ts`.\n *\n * `plugin` is the package name (`@kubb/plugin-ts`), the same identity used in {@link JSONKubbConfig}.\n * The agent applies these to the file with an AST patch, so only the targeted values are rewritten.\n *\n * Declared here rather than in `configFile.ts` because this is the wire contract, and the patcher\n * imports it from here. Type-only, so nothing pulls `magicast` into this entry point.\n */\nexport type ConfigEdit =\n /**\n * Write a literal option value. `path` walks nested objects, so `['enum', 'type']` targets\n * `pluginTs({ enum: { type } })`. `kind` names which factory call `plugin` is: one in `plugins`\n * when left out, the config's `adapter`, or one in `parsers`.\n */\n | { operation: 'set'; config?: ConfigRef; kind?: ExtensionKind; plugin: string; path: Array<string>; value: unknown }\n /**\n * Drop an option so the extension falls back to its default. `kind` as for `set`.\n */\n | { operation: 'remove'; config?: ConfigRef; kind?: ExtensionKind; plugin: string; path: Array<string> }\n /**\n * Add a plugin factory call and its import to the `plugins` array.\n */\n | { operation: 'add-plugin'; config?: ConfigRef; plugin: string; options?: Record<string, unknown> }\n /**\n * Comment the plugin call out, keeping its options in the file so enabling it again restores them.\n */\n | { operation: 'disable-plugin'; config?: ConfigRef; plugin: string }\n /**\n * Uncomment a plugin call a previous `disable-plugin` commented out.\n */\n | { operation: 'enable-plugin'; config?: ConfigRef; plugin: string }\n\n/**\n * A factory call the agent found in a `defineConfig(...)`: a plugin in `plugins`, the `adapter`, or\n * a parser in `parsers`.\n */\nexport type PluginView = {\n /**\n * Local identifier of the factory in the file, e.g. `pluginTs`. This is the alias when the plugin\n * was imported under one.\n */\n importName: string\n /**\n * Module the factory is imported from, e.g. `@kubb/plugin-ts`.\n */\n packageName: string\n /**\n * Top-level option keys, each flagged with whether the agent may write it and, when it can, the\n * value found in the file. An option marked `literal: false` holds a function or a reference the\n * agent will not overwrite, so Studio shows the control disabled rather than hiding it, and\n * `value` is absent since there is nothing safe to display as the current value.\n *\n * An object option that holds code in only some members, such as\n * `output: { path: './gen', banner: () => '' }`, also carries `value` with its literal members\n * and `locked` with the paths of the members that are code, relative to the option\n * (`[['banner']]`). A `set` at any deeper path outside `locked` succeeds, so Studio can edit\n * `output.path` there.\n */\n options: Record<string, { literal: boolean; value?: OptionValue; locked?: Array<Array<string>> }>\n /**\n * Set when the plugin call is commented out in the file. Its options stay on disk but are not\n * readable, so `options` is empty until it is enabled again.\n */\n disabled?: true\n}\n\n/**\n * One `defineConfig(...)` entry. A config file that exports a single object has exactly one.\n */\nexport type ConfigView = {\n /**\n * The entry's `name`, when it sets one. Studio labels the config picker with it.\n */\n name?: string\n /**\n * Each plugin call in the entry, with its top-level option keys.\n */\n plugins: Array<PluginView>\n /**\n * The `adapter` call, when the entry sets one through an imported factory.\n */\n adapter?: PluginView\n /**\n * The parser calls in `parsers`, when the entry lists any through imported factories.\n */\n parsers?: Array<PluginView>\n}\n\n/**\n * What the agent found in the user's config file, so Studio knows which controls it may offer.\n * Absent when the agent could not read the file at all.\n */\nexport type ConfigFileView =\n | {\n managed: true\n /**\n * One entry per config the file exports, in source order. Every {@link ConfigEdit} names\n * which of these it targets through its `config` field.\n */\n configs: Array<ConfigView>\n }\n | {\n managed: false\n /**\n * Why the file is outside what the agent edits, for example a default export that is not a\n * `defineConfig(...)` call. Studio shows this and offers no property-level controls.\n */\n reason: string\n }\n\n/**\n * Outcome of a single {@link ConfigEdit}, returned in a {@link SaveResult}.\n */\nexport type ConfigEditOutcome = {\n edit: ConfigEdit\n applied: boolean\n /**\n * Why the edit was refused, absent when it was applied.\n */\n reason?: string\n}\n\n/**\n * The public, JSON-safe subset of Kubb lifecycle hooks. The core registry remains extensible;\n * adding a core hook does not publish it to Studio until it is listed here and projected below.\n */\nexport const generationEventTypes = [\n 'kubb:plugin:start',\n 'kubb:plugin:end',\n 'kubb:build:start',\n 'kubb:build:end',\n 'kubb:files:processing:start',\n 'kubb:files:processing:update',\n 'kubb:files:processing:end',\n 'kubb:info',\n 'kubb:success',\n 'kubb:warn',\n 'kubb:error',\n 'kubb:diagnostic',\n 'kubb:generation:start',\n 'kubb:generation:end',\n 'kubb:generation:summary',\n 'kubb:lifecycle:start',\n 'kubb:lifecycle:end',\n 'kubb:format:start',\n 'kubb:format:end',\n 'kubb:lint:start',\n 'kubb:lint:end',\n 'kubb:hooks:start',\n 'kubb:hooks:end',\n 'kubb:hook:start',\n 'kubb:hook:line',\n 'kubb:hook:end',\n] as const satisfies ReadonlyArray<keyof KubbHooks>\n\n/**\n * One of the lifecycle hooks {@link generationEventTypes} publishes.\n */\nexport type GenerationEventType = (typeof generationEventTypes)[number]\n\n/**\n * The JSON-safe payload each published event carries. These are flattened on purpose: a core hook\n * context holds live objects (a `Config`, a `Storage`) that cannot cross the wire.\n */\nexport type GenerationEventPayloads = {\n 'kubb:plugin:start': [ctx: { plugin: { name: string } }]\n 'kubb:plugin:end': [ctx: { plugin: { name: string }; duration: number; success: boolean }]\n 'kubb:build:start': [ctx: { config: { name?: string }; adapter: { name: string } }]\n 'kubb:build:end': [ctx: { files: Array<{ path: string; name: string }>; outputDir: string }]\n 'kubb:files:processing:start': [ctx: { total: number }]\n 'kubb:files:processing:update': [\n ctx: {\n files: Array<{\n file: string\n processed: number\n total: number\n percentage: number\n }>\n },\n ]\n 'kubb:files:processing:end': [ctx: { total: number }]\n 'kubb:info': [ctx: { message: string; info?: string }]\n 'kubb:success': [ctx: { message: string; info?: string }]\n 'kubb:warn': [ctx: { message: string; info?: string }]\n 'kubb:error': [ctx: { message: string; stack?: string }]\n 'kubb:diagnostic': [\n ctx: {\n code: string\n message: string\n severity: string\n location?: { kind: string; pointer?: string; ref?: string }\n help?: string\n plugin?: string\n stack?: string\n },\n ]\n 'kubb:generation:start': [ctx: { name?: string; plugins: number }]\n /**\n * A run finished. See `kubb:build:end` for files, `kubb:generation:summary` for the count, and\n * `readFiles` for contents.\n */\n 'kubb:generation:end': []\n 'kubb:generation:summary': [ctx: { duration: number; fileCount: number; failedPlugins: number; status: 'success' | 'failed' }]\n 'kubb:lifecycle:start': []\n 'kubb:lifecycle:end': []\n 'kubb:format:start': []\n 'kubb:format:end': []\n 'kubb:lint:start': []\n 'kubb:lint:end': []\n 'kubb:hooks:start': []\n 'kubb:hooks:end': []\n 'kubb:hook:start': [ctx: { id?: string; command: string; args?: Array<string> }]\n 'kubb:hook:line': [ctx: { id: string; line: string }]\n 'kubb:hook:end': [\n ctx: {\n id?: string\n command: string\n args?: Array<string>\n success: boolean\n error?: { message: string; stack?: string }\n },\n ]\n}\n\n/**\n * Versioned envelope around one lifecycle event. Cap'n Web streams preserve the order the agent\n * emitted them in, so the receiver replays a run by reading the stream straight through.\n */\nexport type GenerationEvent = {\n [Type in GenerationEventType]: { type: Type; data: GenerationEventPayloads[Type] }\n}[GenerationEventType] & {\n version: 1\n jobId: string\n timestamp: number\n}\n\n/**\n * Asks the agent to run one generation. `jobId` tags every event the run emits so a caller\n * watching several runs can tell them apart.\n */\nexport type GenerateInput = { jobId: string; config: JSONKubbConfig }\n\n/**\n * What a finished run produced.\n */\nexport type GenerateResult = {\n status: 'success' | 'failed'\n /**\n * Paths of the generated files, relative to the output directory.\n */\n files: Array<string>\n fileCount: number\n /**\n * Content fingerprint per file in `files`, to tell changed files apart without reading them.\n */\n hashes: Record<string, string>\n /**\n * Fingerprints of the output directory on disk before the run, for an agent with a project on disk.\n */\n disk?: { hashes: Record<string, string> }\n}\n\n/**\n * Asks the agent to apply a batch of edits to the config file on disk.\n */\nexport type SaveConfigInput = { edits: Array<ConfigEdit> }\n\n/**\n * Per-edit outcomes. `changed` is false when every edit was a no-op, so a caller can skip reloading.\n */\nexport type SaveResult = { outcomes: Array<ConfigEditOutcome>; changed: boolean }\n\n/**\n * What a file read returns for a job: the files that job generated (`output`), or what the output\n * directory held on disk before that job ran (`disk`, only on an agent with a project on disk).\n */\nexport const FileSource = { OUTPUT: 'output', DISK: 'disk' } as const\nexport type FileSource = (typeof FileSource)[keyof typeof FileSource]\n\n/**\n * Asks the agent to read files of one generation job back.\n */\nexport type ReadFilesInput = {\n /**\n * The job whose files to read, the only lookup key, so the caller decides whose jobs a reader may\n * see. A job the agent no longer keeps fails with {@link AgentMessage.GENERATION_GONE}.\n */\n jobId: string\n /**\n * At most {@link MAX_FILES_PER_REQUEST} paths, each checked against what the job's set holds.\n */\n paths: Array<string>\n /**\n * Which of the job's sets to read, `output` when left out.\n */\n source?: FileSource\n /**\n * The line to start from (0-based), the `nextCursor` of the previous page. Defaults to 0.\n */\n cursor?: number\n /**\n * Lines to read per file, at most {@link MAX_PAGE_LINES}. Left out, the whole file.\n */\n limit?: number\n}\n\n/**\n * Where a paged read stands for one file: the cursor of the next page, `null` after the last one.\n */\nexport type FilePage = { nextCursor: number | null; totalLines: number }\n\n/**\n * What a file read returns. `pages` is set only for a paged read.\n */\nexport type ReadFilesResult = { files: Record<string, string>; pages?: Record<string, FilePage> }\n\n/**\n * Asks for one page of a file's line diff between two kept sets, so a large file never leaves the agent whole.\n * `jobId`'s `output` is the newer side, `base` the older one. `cursor` and `limit` page the patch lines.\n */\nexport type ReadDiffInput = { jobId: string; path: string; base: { jobId: string; source: FileSource }; cursor?: number; limit: number }\n\n/**\n * One hunk of a diff page. A hunk split across pages keeps its `index` and starts, so the pages join back.\n */\nexport type DiffHunk = { index: number; oldStart: number; newStart: number; lines: Array<string> }\n\n/**\n * One page of a diff. `hasBase` is false, and the page empty, when the base set does not hold the path.\n */\nexport type ReadDiffResult = { hunks: Array<DiffHunk>; nextCursor: number | null; hasBase: boolean }\n\n/**\n * Asks for the Kubb AST one job parsed its spec into. A job the agent no longer keeps fails with {@link AgentMessage.GENERATION_GONE}.\n */\nexport type ReadInputInput = { jobId: string }\n\n/**\n * The job's `InputNode`, as JSON. A ref schema names its target, which `input.schemas` holds, without a copy.\n */\nexport type ReadInputResult = { input: InputNode }\n\n/**\n * Why the agent refuses a request. Studio matches on these to answer with the right status.\n */\nexport const AgentMessage = {\n /**\n * A read for a job whose files the agent no longer keeps.\n */\n GENERATION_GONE: 'The files of this generation are no longer kept on the agent, run the generation again',\n READ_NOT_ALLOWED: 'The agent was not granted permission to read generated files',\n /**\n * Reading the parsed spec is its own permission.\n */\n READ_AST_NOT_ALLOWED: 'The agent was not granted permission to read the parsed spec',\n NO_INPUT: 'This job kept no API input',\n} as const\nexport type AgentMessage = (typeof AgentMessage)[keyof typeof AgentMessage]\n\n/**\n * A generation in flight. Cap'n Web keeps the three calls pointed at the same run, so a caller can\n * read `events()` while `result()` is still pending and `cancel()` stops it early.\n */\nexport type GenerationRun = {\n events: () => Promise<ReadableStream<GenerationEvent>>\n result: () => Promise<GenerateResult>\n cancel: () => Promise<void>\n}\n\n/**\n * What Studio knows about the agent on the other end of a socket. Studio passes it with every\n * {@link AgentApi.connect} call, so it stays the authority on whether the agent is a sandbox.\n */\nexport type AgentConnectInfo = {\n /**\n * A shared `sandbox` or `global` agent. It keeps generations in memory, runs each job in a\n * temporary root, generates from the spec Studio sends, and never writes to disk or edits config.\n */\n sandbox: boolean\n agentSlug: string\n /**\n * Absent for a sandbox or global agent, which belongs to no organization.\n */\n organizationSlug?: string\n studioVersion: string\n}\n\n/**\n * Operations Studio invokes on an agent over its socket.\n */\nexport type AgentApi = {\n /**\n * The first call on every socket, and again whenever Studio needs the current config.\n */\n connect: (info: AgentConnectInfo) => Promise<ConnectMessagePayload>\n startGeneration: (input: GenerateInput) => GenerationRun\n saveConfig: (input: SaveConfigInput) => Promise<SaveResult>\n readFiles: (input: ReadFilesInput) => Promise<ReadFilesResult>\n /**\n * Refused unless the agent was granted `allowRead`, like {@link AgentApi.readFiles}.\n */\n readInput: (input: ReadInputInput) => Promise<ReadInputResult>\n /**\n * Refused unless the agent was granted `allowRead`, like {@link AgentApi.readFiles}.\n */\n readDiff: (input: ReadDiffInput) => Promise<ReadDiffResult>\n}\n\n/**\n * Operations an agent invokes on Studio over its socket.\n */\nexport type StudioApi = {\n /**\n * Keeps the connection alive. A ping that does not settle in time makes the agent reconnect.\n */\n ping: () => Promise<void>\n}\n\n/**\n * Close codes Studio sends when it ends an agent socket on purpose. `UNAUTHORIZED` means the token\n * was refused, so pair again. `SUPERSEDED` and `INCOMPATIBLE` mean stop. Any other code, `1011`\n * included, is an ordinary drop and the agent reconnects.\n */\nexport const AgentCloseCode = {\n UNAUTHORIZED: 4001,\n SUPERSEDED: 4002,\n INCOMPATIBLE: 4003,\n} as const\n\nexport const AGENT_SOCKET_PATH = '/api/agent/socket'\n\n/**\n * Optional header naming the process a socket belongs to, for agents that run several processes.\n */\nexport const AGENT_INSTANCE_HEADER = 'x-kubb-instance-id'\n\n/**\n * How many files a single `readFiles` request may ask for at once.\n */\nexport const MAX_FILES_PER_REQUEST = 50\n\n/** Most lines one page may ask for. */\nexport const MAX_PAGE_LINES = 2000\n\nexport const MAX_SNAPSHOT_BYTES = 50 * 1024 * 1024\n\n/**\n * Connection payload returned by {@link AgentApi.connect}. Carries only what Studio renders, with\n * everything about the config under one key.\n */\nexport type ConnectMessagePayload = {\n /**\n * Always sent, so a mismatch is visible on both sides: Studio badges the connection with these\n * and the host prints them.\n */\n versions: {\n /**\n * The version of the `@kubb/studio` runtime the agent runs.\n */\n kubb: string\n /**\n * The version of the host itself (the `kubb.agent` package or the `kubb` CLI).\n */\n agent: string\n }\n /**\n * The agent's project root. This is the workspace that generation runs against.\n */\n root: string\n /**\n * The baseline every generation starts from.\n */\n config: {\n /**\n * The config path as the host gave it, relative to `root` unless absolute.\n */\n path: string\n /**\n * What the agent read out of the config file itself, so Studio can render the plugin editor\n * against the real file. Absent when the agent could not read it, or was not granted\n * `allowConfigEdit`.\n */\n file?: ConfigFileView\n /**\n * Plugins the config registers, with a JSON copy of their options: anything JSON cannot hold,\n * such as a function, is left out. A plugin defined in the project rather than installed as a\n * `@kubb/plugin-*` package keeps its own `name`.\n */\n plugins?: Array<{\n name: string\n options?: object\n }>\n /**\n * The adapter and the parsers the config registers, named like `plugins` and with the same JSON\n * copy of their options, so Studio can render their manifest options.\n */\n adapter?: {\n name: string\n options?: object\n }\n parsers?: Array<{\n name: string\n options?: object\n }>\n /** Installed packages that ship a `kubb-extension.json`, including plugins the config does not use yet. */\n extensions?: Array<ExtensionEntry>\n }\n permissions: AgentPermissions\n}\n\n/** An installed extension package and the manifest it ships. */\nexport type ExtensionEntry = { package: string; version: string; manifest: ExtensionManifest }\n\n/** Installed extension packages by package name, the only ones Studio may import besides `@kubb/plugin-*`. */\nexport type InstalledExtensions = ReadonlyMap<string, ExtensionEntry>\n\n/**\n * What an agent may do in the project it serves. Every one is off unless the host granted it, and\n * a sandbox narrows them further.\n */\nexport type AgentPermissions = {\n /**\n * Whether the agent writes generated files to disk. Always false for a sandbox.\n */\n allowWrite: boolean\n /**\n * Whether the agent runs the formatter, the linter, and `output.postGenerate` as child\n * processes after a generation.\n */\n allowExec: boolean\n /**\n * Whether the agent may change plugin options in the user's `kubb.config.ts`. Separate from\n * `allowWrite`, which covers generated output: this one edits a hand-authored source file.\n */\n allowConfigEdit: boolean\n /**\n * Whether the agent hands back file source in response to `readFiles`. Always true for a\n * sandbox.\n */\n allowRead: boolean\n /**\n * Whether the agent hands back the parsed spec (the Kubb AST) in response to `readInput`.\n * Separate from `allowRead`, which covers generated files. Always true for a sandbox.\n */\n allowReadAst: boolean\n}\n\n/**\n * The keys of {@link AgentPermissions}, such as the permission a refused request names.\n */\nexport const AgentPermission = {\n READ: 'allowRead',\n READ_AST: 'allowReadAst',\n WRITE: 'allowWrite',\n CONFIG_EDIT: 'allowConfigEdit',\n EXEC: 'allowExec',\n} as const satisfies Record<string, keyof AgentPermissions>\nexport type AgentPermission = (typeof AgentPermission)[keyof typeof AgentPermission]\n\n/**\n * Metadata part of `POST /api/snapshots`. The `tarball` part carries the packed package.\n */\nexport type SnapshotUploadMetadata = {\n /**\n * Stable CI identity, such as `gh:<repoId>:<pr>`. Snapshots with the same `id` share one CI agent.\n */\n id: string\n name: string\n packageName: string\n version: string\n /**\n * The commit the snapshot is built from, so the next one can name it as its base.\n */\n commit?: string\n /**\n * The `id` of another CI agent, such as the base branch's, to also compare with.\n */\n baseId?: string\n /**\n * Content fingerprint per generated file, keyed by path relative to the project root.\n */\n hashes: Record<string, string>\n peerDependencies: Record<string, string>\n}\n\n/**\n * How a snapshot's files differ from an earlier snapshot of the same package.\n */\nexport type SnapshotChanges = {\n /**\n * The snapshot these changes are measured against, `null` when there is none to compare with.\n */\n base: { id: string; version: string | null; commit?: string; createdAt: string } | null\n added: Array<string>\n changed: Array<string>\n removed: Array<string>\n}\n\n/**\n * {@link SnapshotChanges} against another CI agent. `baseFound` is `false` when Studio has no CI\n * agent with that id yet, and `true` when it has one without a snapshot of this package.\n */\nexport type BranchSnapshotChanges = SnapshotChanges & { baseFound: boolean }\n\n/**\n * Response body of `POST /api/snapshots`: `{ snapshot: PublishedSnapshot }`.\n */\nexport type PublishedSnapshot = {\n id: string\n name: string | null\n version: string | null\n integrity: string | null\n agent: { id: string; name: string; slug: string | null }\n /**\n * Readable download path, often `/packages/{agentSlug}/{name}.tgz`.\n */\n url: string\n snapshotIdUrl: string\n createdAt: string\n expiresAt: string\n /**\n * What changed since the previous snapshot of this package on the same CI agent.\n */\n changes?: SnapshotChanges\n /**\n * What differs from the latest snapshot of the CI agent `baseId` names. Absent without a base.\n */\n branchChanges?: BranchSnapshotChanges\n}\n"],"mappings":";;;;;;AAqLA,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;AA2HA,MAAa,aAAa;CAAE,QAAQ;CAAU,MAAM;AAAO;;;;AAqE3D,MAAa,eAAe;;;;CAI1B,iBAAiB;CACjB,kBAAkB;;;;CAIlB,sBAAsB;CACtB,UAAU;AACZ;;;;;;AAmEA,MAAa,iBAAiB;CAC5B,cAAc;CACd,YAAY;CACZ,cAAc;AAChB;AAEA,MAAa,oBAAoB;;;;AAKjC,MAAa,wBAAwB;;;;AAKrC,MAAa,wBAAwB;;AAGrC,MAAa,iBAAiB;AAE9B,MAAa,qBAAqB;;;;AA0GlC,MAAa,kBAAkB;CAC7B,MAAM;CACN,UAAU;CACV,OAAO;CACP,aAAa;CACb,MAAM;AACR"}
@@ -1,2 +1,2 @@
1
- import { A as OptionValue, B as SaveResult, C as GenerationRun, D as MAX_PAGE_LINES, E as MAX_FILES_PER_REQUEST, F as ReadFilesInput, H as SnapshotUploadMetadata, I as ReadFilesResult, L as ReadInputInput, M as PublishedSnapshot, N as READ_AST_NOT_ALLOWED_MESSAGE, O as MAX_SNAPSHOT_BYTES, P as READ_NOT_ALLOWED_MESSAGE, R as ReadInputResult, S as GenerationEventType, T as JSONKubbConfig, U as StudioApi, V as SnapshotChanges, W as generationEventTypes, _ as GENERATION_GONE_MESSAGE, a as AgentConnectInfo, b as GenerationEvent, c as ConfigEdit, d as ConfigRef, f as ConfigView, g as FileSource, h as FilePage, i as AgentCloseCode, j as PluginView, k as NO_INPUT_MESSAGE, l as ConfigEditOutcome, m as ExtensionEntry, n as AGENT_SOCKET_PATH, o as AgentPermissions, p as ConnectMessagePayload, r as AgentApi, s as BranchSnapshotChanges, t as AGENT_INSTANCE_HEADER, u as ConfigFileView, v as GenerateInput, w as InstalledExtensions, x as GenerationEventPayloads, y as GenerateResult, z as SaveConfigInput } from "./protocol-C1PwN9fa.js";
2
- export { AGENT_INSTANCE_HEADER, AGENT_SOCKET_PATH, AgentApi, AgentCloseCode, AgentConnectInfo, AgentPermissions, BranchSnapshotChanges, ConfigEdit, ConfigEditOutcome, ConfigFileView, ConfigRef, ConfigView, ConnectMessagePayload, ExtensionEntry, FilePage, FileSource, GENERATION_GONE_MESSAGE, GenerateInput, GenerateResult, GenerationEvent, GenerationEventPayloads, GenerationEventType, GenerationRun, InstalledExtensions, JSONKubbConfig, MAX_FILES_PER_REQUEST, MAX_PAGE_LINES, MAX_SNAPSHOT_BYTES, NO_INPUT_MESSAGE, OptionValue, PluginView, PublishedSnapshot, READ_AST_NOT_ALLOWED_MESSAGE, READ_NOT_ALLOWED_MESSAGE, ReadFilesInput, ReadFilesResult, ReadInputInput, ReadInputResult, SaveConfigInput, SaveResult, SnapshotChanges, SnapshotUploadMetadata, StudioApi, generationEventTypes };
1
+ import { A as MAX_SNAPSHOT_BYTES, B as SaveConfigInput, C as GenerationEventPayloads, D as JSONKubbConfig, E as InstalledExtensions, F as ReadDiffResult, G as generationEventTypes, H as SnapshotChanges, I as ReadFilesInput, L as ReadFilesResult, M as PluginView, N as PublishedSnapshot, O as MAX_FILES_PER_REQUEST, P as ReadDiffInput, R as ReadInputInput, S as GenerationEvent, T as GenerationRun, U as SnapshotUploadMetadata, V as SaveResult, W as StudioApi, _ as ExtensionEntry, a as AgentConnectInfo, b as GenerateInput, c as AgentPermissions, d as ConfigEditOutcome, f as ConfigFileView, g as DiffHunk, h as ConnectMessagePayload, i as AgentCloseCode, j as OptionValue, k as MAX_PAGE_LINES, l as BranchSnapshotChanges, m as ConfigView, n as AGENT_SOCKET_PATH, o as AgentMessage, p as ConfigRef, r as AgentApi, s as AgentPermission, t as AGENT_INSTANCE_HEADER, u as ConfigEdit, v as FilePage, w as GenerationEventType, x as GenerateResult, y as FileSource, z as ReadInputResult } from "./protocol-DQfI1aLV.js";
2
+ export { AGENT_INSTANCE_HEADER, AGENT_SOCKET_PATH, AgentApi, AgentCloseCode, AgentConnectInfo, AgentMessage, AgentPermission, AgentPermissions, BranchSnapshotChanges, ConfigEdit, ConfigEditOutcome, ConfigFileView, ConfigRef, ConfigView, ConnectMessagePayload, DiffHunk, ExtensionEntry, FilePage, FileSource, GenerateInput, GenerateResult, GenerationEvent, GenerationEventPayloads, GenerationEventType, GenerationRun, InstalledExtensions, JSONKubbConfig, MAX_FILES_PER_REQUEST, MAX_PAGE_LINES, MAX_SNAPSHOT_BYTES, OptionValue, PluginView, PublishedSnapshot, ReadDiffInput, ReadDiffResult, ReadFilesInput, ReadFilesResult, ReadInputInput, ReadInputResult, SaveConfigInput, SaveResult, SnapshotChanges, SnapshotUploadMetadata, StudioApi, generationEventTypes };
package/dist/protocol.js CHANGED
@@ -32,13 +32,28 @@ const generationEventTypes = [
32
32
  "kubb:hook:end"
33
33
  ];
34
34
  /**
35
- * The error a file read fails with once the agent no longer keeps the job's files.
35
+ * What a file read returns for a job: the files that job generated (`output`), or what the output
36
+ * directory held on disk before that job ran (`disk`, only on an agent with a project on disk).
36
37
  */
37
- const GENERATION_GONE_MESSAGE = "The files of this generation are no longer kept on the agent, run the generation again";
38
- const READ_NOT_ALLOWED_MESSAGE = "The agent was not granted permission to read generated files";
39
- /** Why `readInput` refuses: reading the parsed spec is its own permission. */
40
- const READ_AST_NOT_ALLOWED_MESSAGE = "The agent was not granted permission to read the parsed spec";
41
- const NO_INPUT_MESSAGE = "This job kept no API input";
38
+ const FileSource = {
39
+ OUTPUT: "output",
40
+ DISK: "disk"
41
+ };
42
+ /**
43
+ * Why the agent refuses a request. Studio matches on these to answer with the right status.
44
+ */
45
+ const AgentMessage = {
46
+ /**
47
+ * A read for a job whose files the agent no longer keeps.
48
+ */
49
+ GENERATION_GONE: "The files of this generation are no longer kept on the agent, run the generation again",
50
+ READ_NOT_ALLOWED: "The agent was not granted permission to read generated files",
51
+ /**
52
+ * Reading the parsed spec is its own permission.
53
+ */
54
+ READ_AST_NOT_ALLOWED: "The agent was not granted permission to read the parsed spec",
55
+ NO_INPUT: "This job kept no API input"
56
+ };
42
57
  /**
43
58
  * Close codes Studio sends when it ends an agent socket on purpose. `UNAUTHORIZED` means the token
44
59
  * was refused, so pair again. `SUPERSEDED` and `INCOMPATIBLE` mean stop. Any other code, `1011`
@@ -61,7 +76,17 @@ const MAX_FILES_PER_REQUEST = 50;
61
76
  /** Most lines one page may ask for. */
62
77
  const MAX_PAGE_LINES = 2e3;
63
78
  const MAX_SNAPSHOT_BYTES = 52428800;
79
+ /**
80
+ * The keys of {@link AgentPermissions}, such as the permission a refused request names.
81
+ */
82
+ const AgentPermission = {
83
+ READ: "allowRead",
84
+ READ_AST: "allowReadAst",
85
+ WRITE: "allowWrite",
86
+ CONFIG_EDIT: "allowConfigEdit",
87
+ EXEC: "allowExec"
88
+ };
64
89
  //#endregion
65
- export { AGENT_INSTANCE_HEADER, AGENT_SOCKET_PATH, AgentCloseCode, GENERATION_GONE_MESSAGE, MAX_FILES_PER_REQUEST, MAX_PAGE_LINES, MAX_SNAPSHOT_BYTES, NO_INPUT_MESSAGE, READ_AST_NOT_ALLOWED_MESSAGE, READ_NOT_ALLOWED_MESSAGE, generationEventTypes };
90
+ export { AGENT_INSTANCE_HEADER, AGENT_SOCKET_PATH, AgentCloseCode, AgentMessage, AgentPermission, FileSource, MAX_FILES_PER_REQUEST, MAX_PAGE_LINES, MAX_SNAPSHOT_BYTES, generationEventTypes };
66
91
 
67
92
  //# sourceMappingURL=protocol.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["import type { InputNode } from '@kubb/ast'\nimport type { ExtensionKind, ExtensionManifest, KubbHooks } from '@kubb/core'\n\n/**\n * JSON-serializable Kubb config exchanged over RPC. A live `kubb/kit` config holds\n * functions and class instances that cannot survive JSON, so both sides pass this flattened shape\n * and rebuild the real config from it.\n */\nexport type JSONKubbConfig = {\n /**\n * Plugins with their serialized options. `name` is the package name (e.g. `@kubb/plugin-ts`)\n * and `options` is an opaque blob the agent forwards unchanged to the plugin factory. An entry\n * with `disabled: true` is dropped even when the disk config's `plugins` array still lists it.\n */\n plugins?: Array<{\n name: string\n options?: object\n disabled?: boolean\n }>\n /**\n * Raw OpenAPI / Swagger spec content (YAML or JSON string), never a file path or URL.\n * Required by a 'sandbox' agent, which has no disk config of its own and refuses anything else.\n * A non-sandbox agent always reads its spec from disk, so this is ignored with a `kubb:warn`.\n */\n input?: string\n /**\n * Adapter option overrides sent from Studio UI. Merged into the disk config's adapter options\n * and re-applied through the same adapter factory, since an adapter instance's functions\n * (`parse`, `getImports`, ...) can't survive JSON serialization over the WebSocket.\n */\n adapter?: object\n /**\n * Parser option overrides from Studio, matched to the disk config's parsers by `name` and\n * re-applied through each parser's factory, the same way `adapter` is.\n */\n parsers?: Array<{\n name: string\n options?: object\n }>\n}\n\n/**\n * Which `defineConfig(...)` entry an edit targets, for a config file that exports an array.\n *\n * A number selects by position, a string matches the entry's `name`. Omitted targets the only\n * entry, or the first one when the file exports an array.\n */\nexport type ConfigRef = string | number\n\n/**\n * A value the agent can read out of a plugin option in `kubb.config.ts` and round-trip through JSON.\n */\nexport type OptionValue = string | number | boolean | null | Array<OptionValue> | { [key: string]: OptionValue }\n\n/**\n * One change to a plugin's options in the user's `kubb.config.ts`.\n *\n * `plugin` is the package name (`@kubb/plugin-ts`), the same identity used in {@link JSONKubbConfig}.\n * The agent applies these to the file with an AST patch, so only the targeted values are rewritten.\n *\n * Declared here rather than in `configFile.ts` because this is the wire contract, and the patcher\n * imports it from here. Type-only, so nothing pulls `magicast` into this entry point.\n */\nexport type ConfigEdit =\n /**\n * Write a literal option value. `path` walks nested objects, so `['enum', 'type']` targets\n * `pluginTs({ enum: { type } })`. `kind` names which factory call `plugin` is: one in `plugins`\n * when left out, the config's `adapter`, or one in `parsers`.\n */\n | { operation: 'set'; config?: ConfigRef; kind?: ExtensionKind; plugin: string; path: Array<string>; value: unknown }\n /**\n * Drop an option so the extension falls back to its default. `kind` as for `set`.\n */\n | { operation: 'remove'; config?: ConfigRef; kind?: ExtensionKind; plugin: string; path: Array<string> }\n /**\n * Add a plugin factory call and its import to the `plugins` array.\n */\n | { operation: 'add-plugin'; config?: ConfigRef; plugin: string; options?: Record<string, unknown> }\n /**\n * Comment the plugin call out, keeping its options in the file so enabling it again restores them.\n */\n | { operation: 'disable-plugin'; config?: ConfigRef; plugin: string }\n /**\n * Uncomment a plugin call a previous `disable-plugin` commented out.\n */\n | { operation: 'enable-plugin'; config?: ConfigRef; plugin: string }\n\n/**\n * A factory call the agent found in a `defineConfig(...)`: a plugin in `plugins`, the `adapter`, or\n * a parser in `parsers`.\n */\nexport type PluginView = {\n /**\n * Local identifier of the factory in the file, e.g. `pluginTs`. This is the alias when the plugin\n * was imported under one.\n */\n importName: string\n /**\n * Module the factory is imported from, e.g. `@kubb/plugin-ts`.\n */\n packageName: string\n /**\n * Top-level option keys, each flagged with whether the agent may write it and, when it can, the\n * value found in the file. An option marked `literal: false` holds a function or a reference the\n * agent will not overwrite, so Studio shows the control disabled rather than hiding it, and\n * `value` is absent since there is nothing safe to display as the current value.\n */\n options: Record<string, { literal: boolean; value?: OptionValue }>\n /**\n * Set when the plugin call is commented out in the file. Its options stay on disk but are not\n * readable, so `options` is empty until it is enabled again.\n */\n disabled?: true\n}\n\n/**\n * One `defineConfig(...)` entry. A config file that exports a single object has exactly one.\n */\nexport type ConfigView = {\n /**\n * The entry's `name`, when it sets one. Studio labels the config picker with it.\n */\n name?: string\n /**\n * Each plugin call in the entry, with its top-level option keys.\n */\n plugins: Array<PluginView>\n /**\n * The `adapter` call, when the entry sets one through an imported factory.\n */\n adapter?: PluginView\n /**\n * The parser calls in `parsers`, when the entry lists any through imported factories.\n */\n parsers?: Array<PluginView>\n}\n\n/**\n * What the agent found in the user's config file, so Studio knows which controls it may offer.\n * Absent when the agent could not read the file at all.\n */\nexport type ConfigFileView =\n | {\n managed: true\n /**\n * One entry per config the file exports, in source order. Every {@link ConfigEdit} names\n * which of these it targets through its `config` field.\n */\n configs: Array<ConfigView>\n }\n | {\n managed: false\n /**\n * Why the file is outside what the agent edits, for example a default export that is not a\n * `defineConfig(...)` call. Studio shows this and offers no property-level controls.\n */\n reason: string\n }\n\n/**\n * Outcome of a single {@link ConfigEdit}, returned in a {@link SaveResult}.\n */\nexport type ConfigEditOutcome = {\n edit: ConfigEdit\n applied: boolean\n /**\n * Why the edit was refused, absent when it was applied.\n */\n reason?: string\n}\n\n/**\n * The public, JSON-safe subset of Kubb lifecycle hooks. The core registry remains extensible;\n * adding a core hook does not publish it to Studio until it is listed here and projected below.\n */\nexport const generationEventTypes = [\n 'kubb:plugin:start',\n 'kubb:plugin:end',\n 'kubb:build:start',\n 'kubb:build:end',\n 'kubb:files:processing:start',\n 'kubb:files:processing:update',\n 'kubb:files:processing:end',\n 'kubb:info',\n 'kubb:success',\n 'kubb:warn',\n 'kubb:error',\n 'kubb:diagnostic',\n 'kubb:generation:start',\n 'kubb:generation:end',\n 'kubb:generation:summary',\n 'kubb:lifecycle:start',\n 'kubb:lifecycle:end',\n 'kubb:format:start',\n 'kubb:format:end',\n 'kubb:lint:start',\n 'kubb:lint:end',\n 'kubb:hooks:start',\n 'kubb:hooks:end',\n 'kubb:hook:start',\n 'kubb:hook:line',\n 'kubb:hook:end',\n] as const satisfies ReadonlyArray<keyof KubbHooks>\n\n/**\n * One of the lifecycle hooks {@link generationEventTypes} publishes.\n */\nexport type GenerationEventType = (typeof generationEventTypes)[number]\n\n/**\n * The JSON-safe payload each published event carries. These are flattened on purpose: a core hook\n * context holds live objects (a `Config`, a `Storage`) that cannot cross the wire.\n */\nexport type GenerationEventPayloads = {\n 'kubb:plugin:start': [ctx: { plugin: { name: string } }]\n 'kubb:plugin:end': [ctx: { plugin: { name: string }; duration: number; success: boolean }]\n 'kubb:build:start': [ctx: { config: { name?: string }; adapter: { name: string } }]\n 'kubb:build:end': [ctx: { files: Array<{ path: string; name: string }>; outputDir: string }]\n 'kubb:files:processing:start': [ctx: { total: number }]\n 'kubb:files:processing:update': [\n ctx: {\n files: Array<{\n file: string\n processed: number\n total: number\n percentage: number\n }>\n },\n ]\n 'kubb:files:processing:end': [ctx: { total: number }]\n 'kubb:info': [ctx: { message: string; info?: string }]\n 'kubb:success': [ctx: { message: string; info?: string }]\n 'kubb:warn': [ctx: { message: string; info?: string }]\n 'kubb:error': [ctx: { message: string; stack?: string }]\n 'kubb:diagnostic': [\n ctx: {\n code: string\n message: string\n severity: string\n location?: { kind: string; pointer?: string; ref?: string }\n help?: string\n plugin?: string\n stack?: string\n },\n ]\n 'kubb:generation:start': [ctx: { name?: string; plugins: number }]\n /**\n * A run finished. See `kubb:build:end` for files, `kubb:generation:summary` for the count, and\n * `readFiles` for contents.\n */\n 'kubb:generation:end': []\n 'kubb:generation:summary': [ctx: { duration: number; fileCount: number; failedPlugins: number; status: 'success' | 'failed' }]\n 'kubb:lifecycle:start': []\n 'kubb:lifecycle:end': []\n 'kubb:format:start': []\n 'kubb:format:end': []\n 'kubb:lint:start': []\n 'kubb:lint:end': []\n 'kubb:hooks:start': []\n 'kubb:hooks:end': []\n 'kubb:hook:start': [ctx: { id?: string; command: string; args?: Array<string> }]\n 'kubb:hook:line': [ctx: { id: string; line: string }]\n 'kubb:hook:end': [\n ctx: {\n id?: string\n command: string\n args?: Array<string>\n success: boolean\n error?: { message: string; stack?: string }\n },\n ]\n}\n\n/**\n * Versioned envelope around one lifecycle event. Cap'n Web streams preserve the order the agent\n * emitted them in, so the receiver replays a run by reading the stream straight through.\n */\nexport type GenerationEvent = {\n [Type in GenerationEventType]: { type: Type; data: GenerationEventPayloads[Type] }\n}[GenerationEventType] & {\n version: 1\n jobId: string\n timestamp: number\n}\n\n/**\n * Asks the agent to run one generation. `jobId` tags every event the run emits so a caller\n * watching several runs can tell them apart.\n */\nexport type GenerateInput = { jobId: string; config: JSONKubbConfig }\n\n/**\n * What a finished run produced.\n */\nexport type GenerateResult = {\n status: 'success' | 'failed'\n /**\n * Paths of the generated files, relative to the output directory.\n */\n files: Array<string>\n fileCount: number\n /**\n * Content fingerprint per file in `files`, to tell changed files apart without reading them.\n */\n hashes: Record<string, string>\n /**\n * Fingerprints of the output directory on disk before the run, for an agent with a project on disk.\n */\n disk?: { hashes: Record<string, string> }\n}\n\n/**\n * Asks the agent to apply a batch of edits to the config file on disk.\n */\nexport type SaveConfigInput = { edits: Array<ConfigEdit> }\n\n/**\n * Per-edit outcomes. `changed` is false when every edit was a no-op, so a caller can skip reloading.\n */\nexport type SaveResult = { outcomes: Array<ConfigEditOutcome>; changed: boolean }\n\n/**\n * What a file read returns for a job: the files that job generated (`output`), or what the output\n * directory held on disk before that job ran (`disk`, only on an agent with a project on disk).\n */\nexport type FileSource = 'output' | 'disk'\n\n/**\n * Asks the agent to read files of one generation job back.\n */\nexport type ReadFilesInput = {\n /**\n * The job whose files to read, the only lookup key, so the caller decides whose jobs a reader may\n * see. A job the agent no longer keeps fails with {@link GENERATION_GONE_MESSAGE}.\n */\n jobId: string\n /**\n * At most {@link MAX_FILES_PER_REQUEST} paths, each checked against what the job's set holds.\n */\n paths: Array<string>\n /**\n * Which of the job's sets to read, `output` when left out.\n */\n source?: FileSource\n /**\n * The line to start from (0-based), the `nextCursor` of the previous page. Defaults to 0.\n */\n cursor?: number\n /**\n * Lines to read per file, at most {@link MAX_PAGE_LINES}. Left out, the whole file.\n */\n limit?: number\n}\n\n/**\n * Where a paged read stands for one file: the cursor of the next page, `null` after the last one.\n */\nexport type FilePage = { nextCursor: number | null; totalLines: number }\n\n/**\n * What a file read returns. `pages` is set only for a paged read.\n */\nexport type ReadFilesResult = { files: Record<string, string>; pages?: Record<string, FilePage> }\n\n/**\n * Asks for the Kubb AST one job parsed its spec into. A job the agent no longer keeps fails with {@link GENERATION_GONE_MESSAGE}.\n */\nexport type ReadInputInput = { jobId: string }\n\n/**\n * The job's `InputNode`, as JSON. A ref schema names its target, which `input.schemas` holds, without a copy.\n */\nexport type ReadInputResult = { input: InputNode }\n\n/**\n * The error a file read fails with once the agent no longer keeps the job's files.\n */\nexport const GENERATION_GONE_MESSAGE = 'The files of this generation are no longer kept on the agent, run the generation again'\n\nexport const READ_NOT_ALLOWED_MESSAGE = 'The agent was not granted permission to read generated files'\n\n/** Why `readInput` refuses: reading the parsed spec is its own permission. */\nexport const READ_AST_NOT_ALLOWED_MESSAGE = 'The agent was not granted permission to read the parsed spec'\n\nexport const NO_INPUT_MESSAGE = 'This job kept no API input'\n\n/**\n * A generation in flight. Cap'n Web keeps the three calls pointed at the same run, so a caller can\n * read `events()` while `result()` is still pending and `cancel()` stops it early.\n */\nexport type GenerationRun = {\n events: () => Promise<ReadableStream<GenerationEvent>>\n result: () => Promise<GenerateResult>\n cancel: () => Promise<void>\n}\n\n/**\n * What Studio knows about the agent on the other end of a socket. Studio passes it with every\n * {@link AgentApi.connect} call, so it stays the authority on whether the agent is a sandbox.\n */\nexport type AgentConnectInfo = {\n /**\n * A shared `sandbox` or `global` agent. It keeps generations in memory, runs each job in a\n * temporary root, generates from the spec Studio sends, and never writes to disk or edits config.\n */\n sandbox: boolean\n agentSlug: string\n /**\n * Absent for a sandbox or global agent, which belongs to no organization.\n */\n organizationSlug?: string\n studioVersion: string\n}\n\n/**\n * Operations Studio invokes on an agent over its socket.\n */\nexport type AgentApi = {\n /**\n * The first call on every socket, and again whenever Studio needs the current config.\n */\n connect: (info: AgentConnectInfo) => Promise<ConnectMessagePayload>\n startGeneration: (input: GenerateInput) => GenerationRun\n saveConfig: (input: SaveConfigInput) => Promise<SaveResult>\n readFiles: (input: ReadFilesInput) => Promise<ReadFilesResult>\n /**\n * Refused unless the agent was granted `allowRead`, like {@link AgentApi.readFiles}.\n */\n readInput: (input: ReadInputInput) => Promise<ReadInputResult>\n}\n\n/**\n * Operations an agent invokes on Studio over its socket.\n */\nexport type StudioApi = {\n /**\n * Keeps the connection alive. A ping that does not settle in time makes the agent reconnect.\n */\n ping: () => Promise<void>\n}\n\n/**\n * Close codes Studio sends when it ends an agent socket on purpose. `UNAUTHORIZED` means the token\n * was refused, so pair again. `SUPERSEDED` and `INCOMPATIBLE` mean stop. Any other code, `1011`\n * included, is an ordinary drop and the agent reconnects.\n */\nexport const AgentCloseCode = {\n UNAUTHORIZED: 4001,\n SUPERSEDED: 4002,\n INCOMPATIBLE: 4003,\n} as const\n\nexport const AGENT_SOCKET_PATH = '/api/agent/socket'\n\n/**\n * Optional header naming the process a socket belongs to, for agents that run several processes.\n */\nexport const AGENT_INSTANCE_HEADER = 'x-kubb-instance-id'\n\n/**\n * How many files a single `readFiles` request may ask for at once.\n */\nexport const MAX_FILES_PER_REQUEST = 50\n\n/** Most lines one page may ask for. */\nexport const MAX_PAGE_LINES = 2000\n\nexport const MAX_SNAPSHOT_BYTES = 50 * 1024 * 1024\n\n/**\n * Connection payload returned by {@link AgentApi.connect}. Carries only what Studio renders, with\n * everything about the config under one key.\n */\nexport type ConnectMessagePayload = {\n /**\n * Always sent, so a mismatch is visible on both sides: Studio badges the connection with these\n * and the host prints them.\n */\n versions: {\n /**\n * The version of the `@kubb/studio` runtime the agent runs.\n */\n kubb: string\n /**\n * The version of the host itself (the `kubb.agent` package or the `kubb` CLI).\n */\n agent: string\n }\n /**\n * The agent's project root. This is the workspace that generation runs against.\n */\n root: string\n /**\n * The baseline every generation starts from.\n */\n config: {\n /**\n * The config path as the host gave it, relative to `root` unless absolute.\n */\n path: string\n /**\n * What the agent read out of the config file itself, so Studio can render the plugin editor\n * against the real file. Absent when the agent could not read it, or was not granted\n * `allowConfigEdit`.\n */\n file?: ConfigFileView\n /**\n * Plugins the config registers, with a JSON copy of their options: anything JSON cannot hold,\n * such as a function, is left out. A plugin defined in the project rather than installed as a\n * `@kubb/plugin-*` package keeps its own `name`.\n */\n plugins?: Array<{\n name: string\n options?: object\n }>\n /**\n * The adapter and the parsers the config registers, named like `plugins` and with the same JSON\n * copy of their options, so Studio can render their manifest options.\n */\n adapter?: {\n name: string\n options?: object\n }\n parsers?: Array<{\n name: string\n options?: object\n }>\n /** Installed packages that ship a `kubb-extension.json`, including plugins the config does not use yet. */\n extensions?: Array<ExtensionEntry>\n }\n permissions: AgentPermissions\n}\n\n/** An installed extension package and the manifest it ships. */\nexport type ExtensionEntry = { package: string; version: string; manifest: ExtensionManifest }\n\n/** Installed extension packages by package name, the only ones Studio may import besides `@kubb/plugin-*`. */\nexport type InstalledExtensions = ReadonlyMap<string, ExtensionEntry>\n\n/**\n * What an agent may do in the project it serves. Every one is off unless the host granted it, and\n * a sandbox narrows them further.\n */\nexport type AgentPermissions = {\n /**\n * Whether the agent writes generated files to disk. Always false for a sandbox.\n */\n allowWrite: boolean\n /**\n * Whether the agent runs the formatter, the linter, and `output.postGenerate` as child\n * processes after a generation.\n */\n allowExec: boolean\n /**\n * Whether the agent may change plugin options in the user's `kubb.config.ts`. Separate from\n * `allowWrite`, which covers generated output: this one edits a hand-authored source file.\n */\n allowConfigEdit: boolean\n /**\n * Whether the agent hands back file source in response to `readFiles`. Always true for a\n * sandbox.\n */\n allowRead: boolean\n /**\n * Whether the agent hands back the parsed spec (the Kubb AST) in response to `readInput`.\n * Separate from `allowRead`, which covers generated files. Always true for a sandbox.\n */\n allowReadAst: boolean\n}\n\n/**\n * Metadata part of `POST /api/snapshots`. The `tarball` part carries the packed package.\n */\nexport type SnapshotUploadMetadata = {\n /**\n * Stable CI identity, such as `gh:<repoId>:<pr>`. Snapshots with the same `id` share one CI agent.\n */\n id: string\n name: string\n packageName: string\n version: string\n /**\n * The commit the snapshot is built from, so the next one can name it as its base.\n */\n commit?: string\n /**\n * The `id` of another CI agent, such as the base branch's, to also compare with.\n */\n baseId?: string\n /**\n * Content fingerprint per generated file, keyed by path relative to the project root.\n */\n hashes: Record<string, string>\n peerDependencies: Record<string, string>\n}\n\n/**\n * How a snapshot's files differ from an earlier snapshot of the same package.\n */\nexport type SnapshotChanges = {\n /**\n * The snapshot these changes are measured against, `null` when there is none to compare with.\n */\n base: { id: string; version: string | null; commit?: string; createdAt: string } | null\n added: Array<string>\n changed: Array<string>\n removed: Array<string>\n}\n\n/**\n * {@link SnapshotChanges} against another CI agent. `baseFound` is `false` when Studio has no CI\n * agent with that id yet, and `true` when it has one without a snapshot of this package.\n */\nexport type BranchSnapshotChanges = SnapshotChanges & { baseFound: boolean }\n\n/**\n * Response body of `POST /api/snapshots`: `{ snapshot: PublishedSnapshot }`.\n */\nexport type PublishedSnapshot = {\n id: string\n name: string | null\n version: string | null\n integrity: string | null\n agent: { id: string; name: string; slug: string | null }\n /**\n * Readable download path, often `/packages/{agentSlug}/{name}.tgz`.\n */\n url: string\n snapshotIdUrl: string\n createdAt: string\n expiresAt: string\n /**\n * What changed since the previous snapshot of this package on the same CI agent.\n */\n changes?: SnapshotChanges\n /**\n * What differs from the latest snapshot of the CI agent `baseId` names. Absent without a base.\n */\n branchChanges?: BranchSnapshotChanges\n}\n"],"mappings":";;;;;AA+KA,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;AA+KA,MAAa,0BAA0B;AAEvC,MAAa,2BAA2B;;AAGxC,MAAa,+BAA+B;AAE5C,MAAa,mBAAmB;;;;;;AA8DhC,MAAa,iBAAiB;CAC5B,cAAc;CACd,YAAY;CACZ,cAAc;AAChB;AAEA,MAAa,oBAAoB;;;;AAKjC,MAAa,wBAAwB;;;;AAKrC,MAAa,wBAAwB;;AAGrC,MAAa,iBAAiB;AAE9B,MAAa,qBAAqB"}
1
+ {"version":3,"file":"protocol.js","names":[],"sources":["../src/protocol.ts"],"sourcesContent":["import type { InputNode } from '@kubb/ast'\nimport type { ExtensionKind, ExtensionManifest, KubbHooks } from '@kubb/core'\n\n/**\n * JSON-serializable Kubb config exchanged over RPC. A live `kubb/kit` config holds\n * functions and class instances that cannot survive JSON, so both sides pass this flattened shape\n * and rebuild the real config from it.\n */\nexport type JSONKubbConfig = {\n /**\n * Plugins with their serialized options. `name` is the package name (e.g. `@kubb/plugin-ts`)\n * and `options` is an opaque blob the agent forwards unchanged to the plugin factory. An entry\n * with `disabled: true` is dropped even when the disk config's `plugins` array still lists it.\n */\n plugins?: Array<{\n name: string\n options?: object\n disabled?: boolean\n }>\n /**\n * Raw OpenAPI / Swagger spec content (YAML or JSON string), never a file path or URL.\n * Required by a 'sandbox' agent, which has no disk config of its own and refuses anything else.\n * A non-sandbox agent always reads its spec from disk, so this is ignored with a `kubb:warn`.\n */\n input?: string\n /**\n * Adapter option overrides sent from Studio UI. Merged into the disk config's adapter options\n * and re-applied through the same adapter factory, since an adapter instance's functions\n * (`parse`, `getImports`, ...) can't survive JSON serialization over the WebSocket.\n */\n adapter?: object\n /**\n * Parser option overrides from Studio, matched to the disk config's parsers by `name` and\n * re-applied through each parser's factory, the same way `adapter` is.\n */\n parsers?: Array<{\n name: string\n options?: object\n }>\n}\n\n/**\n * Which `defineConfig(...)` entry an edit targets, for a config file that exports an array.\n *\n * A number selects by position, a string matches the entry's `name`. Omitted targets the only\n * entry, or the first one when the file exports an array.\n */\nexport type ConfigRef = string | number\n\n/**\n * A value the agent can read out of a plugin option in `kubb.config.ts` and round-trip through JSON.\n */\nexport type OptionValue = string | number | boolean | null | Array<OptionValue> | { [key: string]: OptionValue }\n\n/**\n * One change to a plugin's options in the user's `kubb.config.ts`.\n *\n * `plugin` is the package name (`@kubb/plugin-ts`), the same identity used in {@link JSONKubbConfig}.\n * The agent applies these to the file with an AST patch, so only the targeted values are rewritten.\n *\n * Declared here rather than in `configFile.ts` because this is the wire contract, and the patcher\n * imports it from here. Type-only, so nothing pulls `magicast` into this entry point.\n */\nexport type ConfigEdit =\n /**\n * Write a literal option value. `path` walks nested objects, so `['enum', 'type']` targets\n * `pluginTs({ enum: { type } })`. `kind` names which factory call `plugin` is: one in `plugins`\n * when left out, the config's `adapter`, or one in `parsers`.\n */\n | { operation: 'set'; config?: ConfigRef; kind?: ExtensionKind; plugin: string; path: Array<string>; value: unknown }\n /**\n * Drop an option so the extension falls back to its default. `kind` as for `set`.\n */\n | { operation: 'remove'; config?: ConfigRef; kind?: ExtensionKind; plugin: string; path: Array<string> }\n /**\n * Add a plugin factory call and its import to the `plugins` array.\n */\n | { operation: 'add-plugin'; config?: ConfigRef; plugin: string; options?: Record<string, unknown> }\n /**\n * Comment the plugin call out, keeping its options in the file so enabling it again restores them.\n */\n | { operation: 'disable-plugin'; config?: ConfigRef; plugin: string }\n /**\n * Uncomment a plugin call a previous `disable-plugin` commented out.\n */\n | { operation: 'enable-plugin'; config?: ConfigRef; plugin: string }\n\n/**\n * A factory call the agent found in a `defineConfig(...)`: a plugin in `plugins`, the `adapter`, or\n * a parser in `parsers`.\n */\nexport type PluginView = {\n /**\n * Local identifier of the factory in the file, e.g. `pluginTs`. This is the alias when the plugin\n * was imported under one.\n */\n importName: string\n /**\n * Module the factory is imported from, e.g. `@kubb/plugin-ts`.\n */\n packageName: string\n /**\n * Top-level option keys, each flagged with whether the agent may write it and, when it can, the\n * value found in the file. An option marked `literal: false` holds a function or a reference the\n * agent will not overwrite, so Studio shows the control disabled rather than hiding it, and\n * `value` is absent since there is nothing safe to display as the current value.\n *\n * An object option that holds code in only some members, such as\n * `output: { path: './gen', banner: () => '' }`, also carries `value` with its literal members\n * and `locked` with the paths of the members that are code, relative to the option\n * (`[['banner']]`). A `set` at any deeper path outside `locked` succeeds, so Studio can edit\n * `output.path` there.\n */\n options: Record<string, { literal: boolean; value?: OptionValue; locked?: Array<Array<string>> }>\n /**\n * Set when the plugin call is commented out in the file. Its options stay on disk but are not\n * readable, so `options` is empty until it is enabled again.\n */\n disabled?: true\n}\n\n/**\n * One `defineConfig(...)` entry. A config file that exports a single object has exactly one.\n */\nexport type ConfigView = {\n /**\n * The entry's `name`, when it sets one. Studio labels the config picker with it.\n */\n name?: string\n /**\n * Each plugin call in the entry, with its top-level option keys.\n */\n plugins: Array<PluginView>\n /**\n * The `adapter` call, when the entry sets one through an imported factory.\n */\n adapter?: PluginView\n /**\n * The parser calls in `parsers`, when the entry lists any through imported factories.\n */\n parsers?: Array<PluginView>\n}\n\n/**\n * What the agent found in the user's config file, so Studio knows which controls it may offer.\n * Absent when the agent could not read the file at all.\n */\nexport type ConfigFileView =\n | {\n managed: true\n /**\n * One entry per config the file exports, in source order. Every {@link ConfigEdit} names\n * which of these it targets through its `config` field.\n */\n configs: Array<ConfigView>\n }\n | {\n managed: false\n /**\n * Why the file is outside what the agent edits, for example a default export that is not a\n * `defineConfig(...)` call. Studio shows this and offers no property-level controls.\n */\n reason: string\n }\n\n/**\n * Outcome of a single {@link ConfigEdit}, returned in a {@link SaveResult}.\n */\nexport type ConfigEditOutcome = {\n edit: ConfigEdit\n applied: boolean\n /**\n * Why the edit was refused, absent when it was applied.\n */\n reason?: string\n}\n\n/**\n * The public, JSON-safe subset of Kubb lifecycle hooks. The core registry remains extensible;\n * adding a core hook does not publish it to Studio until it is listed here and projected below.\n */\nexport const generationEventTypes = [\n 'kubb:plugin:start',\n 'kubb:plugin:end',\n 'kubb:build:start',\n 'kubb:build:end',\n 'kubb:files:processing:start',\n 'kubb:files:processing:update',\n 'kubb:files:processing:end',\n 'kubb:info',\n 'kubb:success',\n 'kubb:warn',\n 'kubb:error',\n 'kubb:diagnostic',\n 'kubb:generation:start',\n 'kubb:generation:end',\n 'kubb:generation:summary',\n 'kubb:lifecycle:start',\n 'kubb:lifecycle:end',\n 'kubb:format:start',\n 'kubb:format:end',\n 'kubb:lint:start',\n 'kubb:lint:end',\n 'kubb:hooks:start',\n 'kubb:hooks:end',\n 'kubb:hook:start',\n 'kubb:hook:line',\n 'kubb:hook:end',\n] as const satisfies ReadonlyArray<keyof KubbHooks>\n\n/**\n * One of the lifecycle hooks {@link generationEventTypes} publishes.\n */\nexport type GenerationEventType = (typeof generationEventTypes)[number]\n\n/**\n * The JSON-safe payload each published event carries. These are flattened on purpose: a core hook\n * context holds live objects (a `Config`, a `Storage`) that cannot cross the wire.\n */\nexport type GenerationEventPayloads = {\n 'kubb:plugin:start': [ctx: { plugin: { name: string } }]\n 'kubb:plugin:end': [ctx: { plugin: { name: string }; duration: number; success: boolean }]\n 'kubb:build:start': [ctx: { config: { name?: string }; adapter: { name: string } }]\n 'kubb:build:end': [ctx: { files: Array<{ path: string; name: string }>; outputDir: string }]\n 'kubb:files:processing:start': [ctx: { total: number }]\n 'kubb:files:processing:update': [\n ctx: {\n files: Array<{\n file: string\n processed: number\n total: number\n percentage: number\n }>\n },\n ]\n 'kubb:files:processing:end': [ctx: { total: number }]\n 'kubb:info': [ctx: { message: string; info?: string }]\n 'kubb:success': [ctx: { message: string; info?: string }]\n 'kubb:warn': [ctx: { message: string; info?: string }]\n 'kubb:error': [ctx: { message: string; stack?: string }]\n 'kubb:diagnostic': [\n ctx: {\n code: string\n message: string\n severity: string\n location?: { kind: string; pointer?: string; ref?: string }\n help?: string\n plugin?: string\n stack?: string\n },\n ]\n 'kubb:generation:start': [ctx: { name?: string; plugins: number }]\n /**\n * A run finished. See `kubb:build:end` for files, `kubb:generation:summary` for the count, and\n * `readFiles` for contents.\n */\n 'kubb:generation:end': []\n 'kubb:generation:summary': [ctx: { duration: number; fileCount: number; failedPlugins: number; status: 'success' | 'failed' }]\n 'kubb:lifecycle:start': []\n 'kubb:lifecycle:end': []\n 'kubb:format:start': []\n 'kubb:format:end': []\n 'kubb:lint:start': []\n 'kubb:lint:end': []\n 'kubb:hooks:start': []\n 'kubb:hooks:end': []\n 'kubb:hook:start': [ctx: { id?: string; command: string; args?: Array<string> }]\n 'kubb:hook:line': [ctx: { id: string; line: string }]\n 'kubb:hook:end': [\n ctx: {\n id?: string\n command: string\n args?: Array<string>\n success: boolean\n error?: { message: string; stack?: string }\n },\n ]\n}\n\n/**\n * Versioned envelope around one lifecycle event. Cap'n Web streams preserve the order the agent\n * emitted them in, so the receiver replays a run by reading the stream straight through.\n */\nexport type GenerationEvent = {\n [Type in GenerationEventType]: { type: Type; data: GenerationEventPayloads[Type] }\n}[GenerationEventType] & {\n version: 1\n jobId: string\n timestamp: number\n}\n\n/**\n * Asks the agent to run one generation. `jobId` tags every event the run emits so a caller\n * watching several runs can tell them apart.\n */\nexport type GenerateInput = { jobId: string; config: JSONKubbConfig }\n\n/**\n * What a finished run produced.\n */\nexport type GenerateResult = {\n status: 'success' | 'failed'\n /**\n * Paths of the generated files, relative to the output directory.\n */\n files: Array<string>\n fileCount: number\n /**\n * Content fingerprint per file in `files`, to tell changed files apart without reading them.\n */\n hashes: Record<string, string>\n /**\n * Fingerprints of the output directory on disk before the run, for an agent with a project on disk.\n */\n disk?: { hashes: Record<string, string> }\n}\n\n/**\n * Asks the agent to apply a batch of edits to the config file on disk.\n */\nexport type SaveConfigInput = { edits: Array<ConfigEdit> }\n\n/**\n * Per-edit outcomes. `changed` is false when every edit was a no-op, so a caller can skip reloading.\n */\nexport type SaveResult = { outcomes: Array<ConfigEditOutcome>; changed: boolean }\n\n/**\n * What a file read returns for a job: the files that job generated (`output`), or what the output\n * directory held on disk before that job ran (`disk`, only on an agent with a project on disk).\n */\nexport const FileSource = { OUTPUT: 'output', DISK: 'disk' } as const\nexport type FileSource = (typeof FileSource)[keyof typeof FileSource]\n\n/**\n * Asks the agent to read files of one generation job back.\n */\nexport type ReadFilesInput = {\n /**\n * The job whose files to read, the only lookup key, so the caller decides whose jobs a reader may\n * see. A job the agent no longer keeps fails with {@link AgentMessage.GENERATION_GONE}.\n */\n jobId: string\n /**\n * At most {@link MAX_FILES_PER_REQUEST} paths, each checked against what the job's set holds.\n */\n paths: Array<string>\n /**\n * Which of the job's sets to read, `output` when left out.\n */\n source?: FileSource\n /**\n * The line to start from (0-based), the `nextCursor` of the previous page. Defaults to 0.\n */\n cursor?: number\n /**\n * Lines to read per file, at most {@link MAX_PAGE_LINES}. Left out, the whole file.\n */\n limit?: number\n}\n\n/**\n * Where a paged read stands for one file: the cursor of the next page, `null` after the last one.\n */\nexport type FilePage = { nextCursor: number | null; totalLines: number }\n\n/**\n * What a file read returns. `pages` is set only for a paged read.\n */\nexport type ReadFilesResult = { files: Record<string, string>; pages?: Record<string, FilePage> }\n\n/**\n * Asks for one page of a file's line diff between two kept sets, so a large file never leaves the agent whole.\n * `jobId`'s `output` is the newer side, `base` the older one. `cursor` and `limit` page the patch lines.\n */\nexport type ReadDiffInput = { jobId: string; path: string; base: { jobId: string; source: FileSource }; cursor?: number; limit: number }\n\n/**\n * One hunk of a diff page. A hunk split across pages keeps its `index` and starts, so the pages join back.\n */\nexport type DiffHunk = { index: number; oldStart: number; newStart: number; lines: Array<string> }\n\n/**\n * One page of a diff. `hasBase` is false, and the page empty, when the base set does not hold the path.\n */\nexport type ReadDiffResult = { hunks: Array<DiffHunk>; nextCursor: number | null; hasBase: boolean }\n\n/**\n * Asks for the Kubb AST one job parsed its spec into. A job the agent no longer keeps fails with {@link AgentMessage.GENERATION_GONE}.\n */\nexport type ReadInputInput = { jobId: string }\n\n/**\n * The job's `InputNode`, as JSON. A ref schema names its target, which `input.schemas` holds, without a copy.\n */\nexport type ReadInputResult = { input: InputNode }\n\n/**\n * Why the agent refuses a request. Studio matches on these to answer with the right status.\n */\nexport const AgentMessage = {\n /**\n * A read for a job whose files the agent no longer keeps.\n */\n GENERATION_GONE: 'The files of this generation are no longer kept on the agent, run the generation again',\n READ_NOT_ALLOWED: 'The agent was not granted permission to read generated files',\n /**\n * Reading the parsed spec is its own permission.\n */\n READ_AST_NOT_ALLOWED: 'The agent was not granted permission to read the parsed spec',\n NO_INPUT: 'This job kept no API input',\n} as const\nexport type AgentMessage = (typeof AgentMessage)[keyof typeof AgentMessage]\n\n/**\n * A generation in flight. Cap'n Web keeps the three calls pointed at the same run, so a caller can\n * read `events()` while `result()` is still pending and `cancel()` stops it early.\n */\nexport type GenerationRun = {\n events: () => Promise<ReadableStream<GenerationEvent>>\n result: () => Promise<GenerateResult>\n cancel: () => Promise<void>\n}\n\n/**\n * What Studio knows about the agent on the other end of a socket. Studio passes it with every\n * {@link AgentApi.connect} call, so it stays the authority on whether the agent is a sandbox.\n */\nexport type AgentConnectInfo = {\n /**\n * A shared `sandbox` or `global` agent. It keeps generations in memory, runs each job in a\n * temporary root, generates from the spec Studio sends, and never writes to disk or edits config.\n */\n sandbox: boolean\n agentSlug: string\n /**\n * Absent for a sandbox or global agent, which belongs to no organization.\n */\n organizationSlug?: string\n studioVersion: string\n}\n\n/**\n * Operations Studio invokes on an agent over its socket.\n */\nexport type AgentApi = {\n /**\n * The first call on every socket, and again whenever Studio needs the current config.\n */\n connect: (info: AgentConnectInfo) => Promise<ConnectMessagePayload>\n startGeneration: (input: GenerateInput) => GenerationRun\n saveConfig: (input: SaveConfigInput) => Promise<SaveResult>\n readFiles: (input: ReadFilesInput) => Promise<ReadFilesResult>\n /**\n * Refused unless the agent was granted `allowRead`, like {@link AgentApi.readFiles}.\n */\n readInput: (input: ReadInputInput) => Promise<ReadInputResult>\n /**\n * Refused unless the agent was granted `allowRead`, like {@link AgentApi.readFiles}.\n */\n readDiff: (input: ReadDiffInput) => Promise<ReadDiffResult>\n}\n\n/**\n * Operations an agent invokes on Studio over its socket.\n */\nexport type StudioApi = {\n /**\n * Keeps the connection alive. A ping that does not settle in time makes the agent reconnect.\n */\n ping: () => Promise<void>\n}\n\n/**\n * Close codes Studio sends when it ends an agent socket on purpose. `UNAUTHORIZED` means the token\n * was refused, so pair again. `SUPERSEDED` and `INCOMPATIBLE` mean stop. Any other code, `1011`\n * included, is an ordinary drop and the agent reconnects.\n */\nexport const AgentCloseCode = {\n UNAUTHORIZED: 4001,\n SUPERSEDED: 4002,\n INCOMPATIBLE: 4003,\n} as const\n\nexport const AGENT_SOCKET_PATH = '/api/agent/socket'\n\n/**\n * Optional header naming the process a socket belongs to, for agents that run several processes.\n */\nexport const AGENT_INSTANCE_HEADER = 'x-kubb-instance-id'\n\n/**\n * How many files a single `readFiles` request may ask for at once.\n */\nexport const MAX_FILES_PER_REQUEST = 50\n\n/** Most lines one page may ask for. */\nexport const MAX_PAGE_LINES = 2000\n\nexport const MAX_SNAPSHOT_BYTES = 50 * 1024 * 1024\n\n/**\n * Connection payload returned by {@link AgentApi.connect}. Carries only what Studio renders, with\n * everything about the config under one key.\n */\nexport type ConnectMessagePayload = {\n /**\n * Always sent, so a mismatch is visible on both sides: Studio badges the connection with these\n * and the host prints them.\n */\n versions: {\n /**\n * The version of the `@kubb/studio` runtime the agent runs.\n */\n kubb: string\n /**\n * The version of the host itself (the `kubb.agent` package or the `kubb` CLI).\n */\n agent: string\n }\n /**\n * The agent's project root. This is the workspace that generation runs against.\n */\n root: string\n /**\n * The baseline every generation starts from.\n */\n config: {\n /**\n * The config path as the host gave it, relative to `root` unless absolute.\n */\n path: string\n /**\n * What the agent read out of the config file itself, so Studio can render the plugin editor\n * against the real file. Absent when the agent could not read it, or was not granted\n * `allowConfigEdit`.\n */\n file?: ConfigFileView\n /**\n * Plugins the config registers, with a JSON copy of their options: anything JSON cannot hold,\n * such as a function, is left out. A plugin defined in the project rather than installed as a\n * `@kubb/plugin-*` package keeps its own `name`.\n */\n plugins?: Array<{\n name: string\n options?: object\n }>\n /**\n * The adapter and the parsers the config registers, named like `plugins` and with the same JSON\n * copy of their options, so Studio can render their manifest options.\n */\n adapter?: {\n name: string\n options?: object\n }\n parsers?: Array<{\n name: string\n options?: object\n }>\n /** Installed packages that ship a `kubb-extension.json`, including plugins the config does not use yet. */\n extensions?: Array<ExtensionEntry>\n }\n permissions: AgentPermissions\n}\n\n/** An installed extension package and the manifest it ships. */\nexport type ExtensionEntry = { package: string; version: string; manifest: ExtensionManifest }\n\n/** Installed extension packages by package name, the only ones Studio may import besides `@kubb/plugin-*`. */\nexport type InstalledExtensions = ReadonlyMap<string, ExtensionEntry>\n\n/**\n * What an agent may do in the project it serves. Every one is off unless the host granted it, and\n * a sandbox narrows them further.\n */\nexport type AgentPermissions = {\n /**\n * Whether the agent writes generated files to disk. Always false for a sandbox.\n */\n allowWrite: boolean\n /**\n * Whether the agent runs the formatter, the linter, and `output.postGenerate` as child\n * processes after a generation.\n */\n allowExec: boolean\n /**\n * Whether the agent may change plugin options in the user's `kubb.config.ts`. Separate from\n * `allowWrite`, which covers generated output: this one edits a hand-authored source file.\n */\n allowConfigEdit: boolean\n /**\n * Whether the agent hands back file source in response to `readFiles`. Always true for a\n * sandbox.\n */\n allowRead: boolean\n /**\n * Whether the agent hands back the parsed spec (the Kubb AST) in response to `readInput`.\n * Separate from `allowRead`, which covers generated files. Always true for a sandbox.\n */\n allowReadAst: boolean\n}\n\n/**\n * The keys of {@link AgentPermissions}, such as the permission a refused request names.\n */\nexport const AgentPermission = {\n READ: 'allowRead',\n READ_AST: 'allowReadAst',\n WRITE: 'allowWrite',\n CONFIG_EDIT: 'allowConfigEdit',\n EXEC: 'allowExec',\n} as const satisfies Record<string, keyof AgentPermissions>\nexport type AgentPermission = (typeof AgentPermission)[keyof typeof AgentPermission]\n\n/**\n * Metadata part of `POST /api/snapshots`. The `tarball` part carries the packed package.\n */\nexport type SnapshotUploadMetadata = {\n /**\n * Stable CI identity, such as `gh:<repoId>:<pr>`. Snapshots with the same `id` share one CI agent.\n */\n id: string\n name: string\n packageName: string\n version: string\n /**\n * The commit the snapshot is built from, so the next one can name it as its base.\n */\n commit?: string\n /**\n * The `id` of another CI agent, such as the base branch's, to also compare with.\n */\n baseId?: string\n /**\n * Content fingerprint per generated file, keyed by path relative to the project root.\n */\n hashes: Record<string, string>\n peerDependencies: Record<string, string>\n}\n\n/**\n * How a snapshot's files differ from an earlier snapshot of the same package.\n */\nexport type SnapshotChanges = {\n /**\n * The snapshot these changes are measured against, `null` when there is none to compare with.\n */\n base: { id: string; version: string | null; commit?: string; createdAt: string } | null\n added: Array<string>\n changed: Array<string>\n removed: Array<string>\n}\n\n/**\n * {@link SnapshotChanges} against another CI agent. `baseFound` is `false` when Studio has no CI\n * agent with that id yet, and `true` when it has one without a snapshot of this package.\n */\nexport type BranchSnapshotChanges = SnapshotChanges & { baseFound: boolean }\n\n/**\n * Response body of `POST /api/snapshots`: `{ snapshot: PublishedSnapshot }`.\n */\nexport type PublishedSnapshot = {\n id: string\n name: string | null\n version: string | null\n integrity: string | null\n agent: { id: string; name: string; slug: string | null }\n /**\n * Readable download path, often `/packages/{agentSlug}/{name}.tgz`.\n */\n url: string\n snapshotIdUrl: string\n createdAt: string\n expiresAt: string\n /**\n * What changed since the previous snapshot of this package on the same CI agent.\n */\n changes?: SnapshotChanges\n /**\n * What differs from the latest snapshot of the CI agent `baseId` names. Absent without a base.\n */\n branchChanges?: BranchSnapshotChanges\n}\n"],"mappings":";;;;;AAqLA,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;AA2HA,MAAa,aAAa;CAAE,QAAQ;CAAU,MAAM;AAAO;;;;AAqE3D,MAAa,eAAe;;;;CAI1B,iBAAiB;CACjB,kBAAkB;;;;CAIlB,sBAAsB;CACtB,UAAU;AACZ;;;;;;AAmEA,MAAa,iBAAiB;CAC5B,cAAc;CACd,YAAY;CACZ,cAAc;AAChB;AAEA,MAAa,oBAAoB;;;;AAKjC,MAAa,wBAAwB;;;;AAKrC,MAAa,wBAAwB;;AAGrC,MAAa,iBAAiB;AAE9B,MAAa,qBAAqB;;;;AA0GlC,MAAa,kBAAkB;CAC7B,MAAM;CACN,UAAU;CACV,OAAO;CACP,aAAa;CACb,MAAM;AACR"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/studio",
3
- "version": "5.10.0",
3
+ "version": "5.10.2",
4
4
  "description": "Kubb Studio client runtime. Connects a Kubb project to Kubb Studio over one WebSocket and publishes CI snapshots, shared by the `kubb studio` CLI command and the Docker agent.",
5
5
  "keywords": [
6
6
  "agent",
@@ -62,16 +62,17 @@
62
62
  "registry": "https://registry.npmjs.org/"
63
63
  },
64
64
  "dependencies": {
65
- "@kubb/ast": "5.10.0",
66
- "@kubb/core": "5.10.0",
65
+ "@kubb/ast": "5.10.2",
66
+ "@kubb/core": "5.10.2",
67
67
  "capnweb": "^0.12.0",
68
+ "diff": "^9.0.0",
68
69
  "magicast": "^0.5.5",
69
70
  "ofetch": "^1.5.1",
70
71
  "tsdown": "^0.23.1"
71
72
  },
72
73
  "devDependencies": {
73
74
  "@internals/utils": "0.0.1",
74
- "@kubb/adapter-oas": "5.10.0"
75
+ "@kubb/adapter-oas": "5.10.2"
75
76
  },
76
77
  "engines": {
77
78
  "node": ">=22"