@kubb/studio 5.3.16 → 5.3.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/protocol.cjs CHANGED
@@ -37,10 +37,30 @@ const generationEventTypes = [
37
37
  */
38
38
  const GENERATION_GONE_MESSAGE = "The files of this generation are no longer kept on the agent, run the generation again";
39
39
  /**
40
+ * Close codes Studio sends when it ends an agent's connection on purpose. Any other code is an
41
+ * ordinary drop, and the agent reconnects.
42
+ */
43
+ const AgentCloseCode = {
44
+ /** Register again before reconnecting, such as after Studio forgot this agent's machine token. */
45
+ REAUTHENTICATE: 4001,
46
+ /** Another instance of the same agent took the connection over. Reconnecting would take it back. */
47
+ SUPERSEDED: 4002,
48
+ /** This agent is too old for Studio, or was deleted. Reconnecting cannot succeed. */
49
+ INCOMPATIBLE: 4003
50
+ };
51
+ /**
40
52
  * How many files a single `readFiles` request may ask for at once.
41
53
  */
42
54
  const MAX_FILES_PER_REQUEST = 50;
55
+ /**
56
+ * Header naming which process of an agent a socket belongs to, next to the agent's bearer token.
57
+ * Several processes may share one token (CI runners, sandbox containers). For a kind that allows
58
+ * only one, a new id supersedes the old socket with {@link AgentCloseCode.SUPERSEDED}.
59
+ */
60
+ const AGENT_INSTANCE_HEADER = "x-kubb-instance-id";
43
61
  //#endregion
62
+ exports.AGENT_INSTANCE_HEADER = AGENT_INSTANCE_HEADER;
63
+ exports.AgentCloseCode = AgentCloseCode;
44
64
  exports.GENERATION_GONE_MESSAGE = GENERATION_GONE_MESSAGE;
45
65
  exports.MAX_FILES_PER_REQUEST = MAX_FILES_PER_REQUEST;
46
66
  exports.generationEventTypes = generationEventTypes;
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.cjs","names":[],"sources":["../src/protocol/index.ts"],"sourcesContent":["import type { 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).\n * Always honored for a 'sandbox' agent. For a non-sandbox agent it is honored only when the\n * agent opts in with `KUBB_AGENT_ALLOW_INPUT`; otherwise the spec is read from disk and this is ignored.\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\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 } })`.\n */\n | { operation: 'set'; config?: ConfigRef; plugin: string; path: Array<string>; value: unknown }\n /**\n * Drop an option so the plugin falls back to its default.\n */\n | { operation: 'remove'; config?: ConfigRef; 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; importName?: 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 plugin factory call the agent found in the `plugins` array of a `defineConfig(...)`.\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\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 ConfigSavedMessage}.\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 plus the rewritten file. `changed` is false when every edit was a no-op, so a\n * caller can skip reloading.\n */\nexport type SaveResult = { outcomes: Array<ConfigEditOutcome>; changed: boolean; file?: ConfigFileView }\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\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\n/**\n * Describes the package to pack and where to PUT it. `uploadPath` is resolved against the Studio\n * origin, so it cannot redirect the upload elsewhere.\n */\nexport type PublishSnapshotInput = { name: string; version: string; bundledDependencies?: Array<string>; uploadPath: string }\n\n/**\n * Identifies the uploaded snapshot. `integrity` is the subresource hash Studio verifies against.\n */\nexport type PublishSnapshotResult = { integrity: string; peerDependencies: Record<string, string> }\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 * Operations Studio can invoke on an agent through a host-provided RPC transport.\n */\nexport type AgentApi = {\n connect: () => Promise<ConnectMessagePayload>\n startGeneration: (input: GenerateInput) => GenerationRun\n saveConfig: (input: SaveConfigInput) => Promise<SaveResult>\n publishSnapshot: (input: PublishSnapshotInput) => Promise<PublishSnapshotResult>\n readFiles: (input: ReadFilesInput) => Promise<{ files: Record<string, string> }>\n}\n\n/**\n * Operations an agent can invoke on Studio through a host-provided RPC transport.\n */\nexport type StudioApi = {\n ping: () => Promise<void>\n}\n\n/**\n * A live RPC session. `closed` settles when the transport drops, whichever side ended it.\n */\nexport type RpcConnection = {\n studio: StudioApi\n closed: Promise<void>\n close: () => void\n}\n\n/**\n * Opens a transport and hands both sides their peer. Swapping this is how a test drives a session\n * without a socket.\n */\nexport type RpcConnector = (input: { url: string; token: string; local: AgentApi }) => Promise<RpcConnection>\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/**\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 (`KUBB_AGENT_ROOT`, or the working directory when unset). This is the\n * 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 configured (`KUBB_AGENT_CONFIG`), 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 their serialized options.\n */\n plugins?: Array<{\n name: string\n options?: object\n }>\n }\n permissions: AgentPermissions\n}\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 session narrows them further.\n */\nexport type AgentPermissions = {\n /**\n * Whether the agent writes generated files to disk. False for a sandbox agent. For a local\n * agent it mirrors the agent's `KUBB_AGENT_ALLOW_WRITE`.\n */\n allowWrite: boolean\n /**\n * Whether the agent will accept and generate from an OpenAPI spec supplied by Studio.\n * Always true for a sandbox agent, otherwise it mirrors the agent's own opt-in. Studio reads\n * this to decide whether to send `input`.\n */\n allowInput: boolean\n /**\n * Whether the agent runs the formatter, the linter, and `output.postGenerate` as child\n * processes after a generation. Always true for the Docker agent, where the image bounds what\n * can run. The CLI runs in the user's own project and defaults it off.\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 agent; for a local agent it mirrors the agent's own opt-in.\n */\n allowRead: boolean\n}\n\n/**\n * Response returned by the Studio `/api/agent/sessions` endpoint.\n */\nexport type AgentConnectResponse = {\n /**\n * URL the agent opens to reach the session, with the session token embedded.\n */\n url: string\n /**\n * When the session expires and the url stops working (ISO 8601).\n */\n expiresAt: string\n /**\n * When the session was revoked (ISO 8601), or null while it is still valid.\n */\n revokedAt: string | null\n /**\n * Opaque session token, also embedded in `url`. Store it to revoke the session later.\n */\n sessionId: string\n /**\n * Short readable identifier for this connection, used in logs (e.g. brave-otter).\n */\n slug: string | null\n /**\n * Whether this session belongs to a shared sandbox agent rather than an owned one.\n */\n isSandbox: boolean\n /**\n * The Studio instance's own version. Returned with the RPC session so the agent can name both\n * sides from the first connection.\n * Absent when Studio predates the field.\n */\n version?: string\n /**\n * This agent's slug, so a reconnect refreshes it the same way pairing did.\n * Absent when Studio predates the field.\n */\n agentSlug?: string\n /**\n * This agent's organization slug, absent for a sandbox or global agent, which has none, or when\n * Studio predates the field.\n */\n organizationSlug?: string\n}\n"],"mappings":";;;;;;AA4JA,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;;;;AAoJA,MAAa,0BAA0B;;;;AA2DvC,MAAa,wBAAwB"}
1
+ {"version":3,"file":"protocol.cjs","names":[],"sources":["../src/protocol/index.ts"],"sourcesContent":["import type { 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).\n * Always honored for a 'sandbox' agent. For a non-sandbox agent it is honored only when the\n * agent opts in with `KUBB_AGENT_ALLOW_INPUT`; otherwise the spec is read from disk and this is ignored.\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\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 } })`.\n */\n | { operation: 'set'; config?: ConfigRef; plugin: string; path: Array<string>; value: unknown }\n /**\n * Drop an option so the plugin falls back to its default.\n */\n | { operation: 'remove'; config?: ConfigRef; 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; importName?: 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 plugin factory call the agent found in the `plugins` array of a `defineConfig(...)`.\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\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 ConfigSavedMessage}.\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 plus the rewritten file. `changed` is false when every edit was a no-op, so a\n * caller can skip reloading.\n */\nexport type SaveResult = { outcomes: Array<ConfigEditOutcome>; changed: boolean; file?: ConfigFileView }\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\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\n/**\n * Describes the package to pack and where to PUT it. `uploadPath` is resolved against the Studio\n * origin, so it cannot redirect the upload elsewhere.\n */\nexport type PublishSnapshotInput = { name: string; version: string; bundledDependencies?: Array<string>; uploadPath: string }\n\n/**\n * Identifies the uploaded snapshot. `integrity` is the subresource hash Studio verifies against.\n */\nexport type PublishSnapshotResult = { integrity: string; peerDependencies: Record<string, string> }\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 * Operations Studio can invoke on an agent through a host-provided RPC transport.\n */\nexport type AgentApi = {\n connect: () => Promise<ConnectMessagePayload>\n startGeneration: (input: GenerateInput) => GenerationRun\n saveConfig: (input: SaveConfigInput) => Promise<SaveResult>\n publishSnapshot: (input: PublishSnapshotInput) => Promise<PublishSnapshotResult>\n readFiles: (input: ReadFilesInput) => Promise<{ files: Record<string, string> }>\n /**\n * Cancels the job named by `jobId`, if it is the one this agent is currently running. Reachable\n * by id alone, unlike {@link GenerationRun.cancel}, so a caller that no longer holds that object\n * (Studio, after its own restart re-attaches to a job by id) can still cancel it.\n */\n cancel: (jobId: string) => Promise<void>\n}\n\n/**\n * Operations an agent can invoke on Studio through a host-provided RPC transport.\n */\nexport type StudioApi = {\n /**\n * Keeps the connection alive. `load` reports what the agent is carrying right now. Absent from an\n * agent that predates it.\n */\n ping: (load?: AgentLoad) => Promise<void>\n}\n\n/**\n * What an agent is carrying at one heartbeat.\n */\nexport type AgentLoad = {\n /** Jobs running on this connection right now. */\n running: number\n /** Resident memory of the agent process, in megabytes. */\n rssMb: number\n /** Bytes of past generations the agent keeps for file reads and snapshots. */\n storeBytes: number\n /** Whether the agent takes new jobs. Always `true` today; Studio skips a connection that reports `false`. */\n accepting: boolean\n}\n\n/**\n * Close codes Studio sends when it ends an agent's connection on purpose. Any other code is an\n * ordinary drop, and the agent reconnects.\n */\nexport const AgentCloseCode = {\n /** Register again before reconnecting, such as after Studio forgot this agent's machine token. */\n REAUTHENTICATE: 4001,\n /** Another instance of the same agent took the connection over. Reconnecting would take it back. */\n SUPERSEDED: 4002,\n /** This agent is too old for Studio, or was deleted. Reconnecting cannot succeed. */\n INCOMPATIBLE: 4003,\n} as const\n\n/**\n * Why a transport closed. Absent when the transport cannot tell, such as an in-process test pair.\n */\nexport type RpcClose = { code: number; reason: string }\n\n/**\n * A live RPC session. `closed` settles when the transport drops, whichever side ended it.\n */\nexport type RpcConnection = {\n studio: StudioApi\n closed: Promise<RpcClose | void>\n close: () => void\n}\n\n/**\n * Opens a transport and hands both sides their peer. Swapping this is how a test drives a session\n * without a socket.\n */\nexport type RpcConnector = (input: { url: string; token: string; instanceId: string; local: AgentApi }) => Promise<RpcConnection>\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/**\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 (`KUBB_AGENT_ROOT`, or the working directory when unset). This is the\n * 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 configured (`KUBB_AGENT_CONFIG`), 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 their serialized options.\n */\n plugins?: Array<{\n name: string\n options?: object\n }>\n }\n permissions: AgentPermissions\n}\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 session narrows them further.\n */\nexport type AgentPermissions = {\n /**\n * Whether the agent writes generated files to disk. False for a sandbox agent. For a local\n * agent it mirrors the agent's `KUBB_AGENT_ALLOW_WRITE`.\n */\n allowWrite: boolean\n /**\n * Whether the agent will accept and generate from an OpenAPI spec supplied by Studio.\n * Always true for a sandbox agent, otherwise it mirrors the agent's own opt-in. Studio reads\n * this to decide whether to send `input`.\n */\n allowInput: boolean\n /**\n * Whether the agent runs the formatter, the linter, and `output.postGenerate` as child\n * processes after a generation. Always true for the Docker agent, where the image bounds what\n * can run. The CLI runs in the user's own project and defaults it off.\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 agent; for a local agent it mirrors the agent's own opt-in.\n */\n allowRead: boolean\n}\n\n/**\n * Header naming which process of an agent a socket belongs to, next to the agent's bearer token.\n * Several processes may share one token (CI runners, sandbox containers). For a kind that allows\n * only one, a new id supersedes the old socket with {@link AgentCloseCode.SUPERSEDED}.\n */\nexport const AGENT_INSTANCE_HEADER = 'x-kubb-instance-id'\n\n/**\n * What an agent process can take on, reported at registration.\n */\nexport type AgentCapacity = {\n /** Jobs the process runs at once. Studio clamps it to the agent kind's own cap. */\n maxConcurrent: number\n}\n\n/**\n * Body of `POST /api/agent/connect`, authenticated with the agent's bearer token.\n */\nexport type AgentRegisterInput = {\n /** Hash of this machine's secret, binding the token to one machine identity. */\n machineToken: string\n /** This process, sent again as {@link AGENT_INSTANCE_HEADER} when it opens its socket. */\n instanceId: string\n capacity: AgentCapacity\n}\n\n/**\n * Response of `POST /api/agent/connect`: where this process opens its one socket, and what Studio\n * knows about the agent.\n */\nexport type AgentRegisterResponse = {\n /** `wss:` URL of the agent socket. The agent authenticates it with its bearer token and instance id. */\n socketUrl: string\n /** Whether the agent is a shared sandbox rather than one someone owns. */\n isSandbox: boolean\n /** The Studio instance's own version, so both sides can be named from the first connection. */\n version?: string\n /** This agent's slug, so a reconnect refreshes it the same way pairing did. */\n agentSlug?: string\n /** This agent's organization slug. Absent for a sandbox or global agent, which has none. */\n organizationSlug?: string\n}\n"],"mappings":";;;;;;AA4JA,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;;;;AAoJA,MAAa,0BAA0B;;;;;AAqEvC,MAAa,iBAAiB;;CAE5B,gBAAgB;;CAEhB,YAAY;;CAEZ,cAAc;AAChB;;;;AAyBA,MAAa,wBAAwB;;;;;;AA0FrC,MAAa,wBAAwB"}
@@ -417,19 +417,61 @@ export type AgentApi = {
417
417
  readFiles: (input: ReadFilesInput) => Promise<{
418
418
  files: Record<string, string>;
419
419
  }>;
420
+ /**
421
+ * Cancels the job named by `jobId`, if it is the one this agent is currently running. Reachable
422
+ * by id alone, unlike {@link GenerationRun.cancel}, so a caller that no longer holds that object
423
+ * (Studio, after its own restart re-attaches to a job by id) can still cancel it.
424
+ */
425
+ cancel: (jobId: string) => Promise<void>;
420
426
  };
421
427
  /**
422
428
  * Operations an agent can invoke on Studio through a host-provided RPC transport.
423
429
  */
424
430
  export type StudioApi = {
425
- ping: () => Promise<void>;
431
+ /**
432
+ * Keeps the connection alive. `load` reports what the agent is carrying right now. Absent from an
433
+ * agent that predates it.
434
+ */
435
+ ping: (load?: AgentLoad) => Promise<void>;
436
+ };
437
+ /**
438
+ * What an agent is carrying at one heartbeat.
439
+ */
440
+ export type AgentLoad = {
441
+ /** Jobs running on this connection right now. */
442
+ running: number;
443
+ /** Resident memory of the agent process, in megabytes. */
444
+ rssMb: number;
445
+ /** Bytes of past generations the agent keeps for file reads and snapshots. */
446
+ storeBytes: number;
447
+ /** Whether the agent takes new jobs. Always `true` today; Studio skips a connection that reports `false`. */
448
+ accepting: boolean;
449
+ };
450
+ /**
451
+ * Close codes Studio sends when it ends an agent's connection on purpose. Any other code is an
452
+ * ordinary drop, and the agent reconnects.
453
+ */
454
+ export declare const AgentCloseCode: {
455
+ /** Register again before reconnecting, such as after Studio forgot this agent's machine token. */
456
+ readonly REAUTHENTICATE: 4001;
457
+ /** Another instance of the same agent took the connection over. Reconnecting would take it back. */
458
+ readonly SUPERSEDED: 4002;
459
+ /** This agent is too old for Studio, or was deleted. Reconnecting cannot succeed. */
460
+ readonly INCOMPATIBLE: 4003;
461
+ };
462
+ /**
463
+ * Why a transport closed. Absent when the transport cannot tell, such as an in-process test pair.
464
+ */
465
+ export type RpcClose = {
466
+ code: number;
467
+ reason: string;
426
468
  };
427
469
  /**
428
470
  * A live RPC session. `closed` settles when the transport drops, whichever side ended it.
429
471
  */
430
472
  export type RpcConnection = {
431
473
  studio: StudioApi;
432
- closed: Promise<void>;
474
+ closed: Promise<RpcClose | void>;
433
475
  close: () => void;
434
476
  };
435
477
  /**
@@ -439,6 +481,7 @@ export type RpcConnection = {
439
481
  export type RpcConnector = (input: {
440
482
  url: string;
441
483
  token: string;
484
+ instanceId: string;
442
485
  local: AgentApi;
443
486
  }) => Promise<RpcConnection>;
444
487
  /**
@@ -527,48 +570,42 @@ export type AgentPermissions = {
527
570
  allowRead: boolean;
528
571
  };
529
572
  /**
530
- * Response returned by the Studio `/api/agent/sessions` endpoint.
573
+ * Header naming which process of an agent a socket belongs to, next to the agent's bearer token.
574
+ * Several processes may share one token (CI runners, sandbox containers). For a kind that allows
575
+ * only one, a new id supersedes the old socket with {@link AgentCloseCode.SUPERSEDED}.
531
576
  */
532
- export type AgentConnectResponse = {
533
- /**
534
- * URL the agent opens to reach the session, with the session token embedded.
535
- */
536
- url: string;
537
- /**
538
- * When the session expires and the url stops working (ISO 8601).
539
- */
540
- expiresAt: string;
541
- /**
542
- * When the session was revoked (ISO 8601), or null while it is still valid.
543
- */
544
- revokedAt: string | null;
545
- /**
546
- * Opaque session token, also embedded in `url`. Store it to revoke the session later.
547
- */
548
- sessionId: string;
549
- /**
550
- * Short readable identifier for this connection, used in logs (e.g. brave-otter).
551
- */
552
- slug: string | null;
553
- /**
554
- * Whether this session belongs to a shared sandbox agent rather than an owned one.
555
- */
577
+ export declare const AGENT_INSTANCE_HEADER = "x-kubb-instance-id";
578
+ /**
579
+ * What an agent process can take on, reported at registration.
580
+ */
581
+ export type AgentCapacity = {
582
+ /** Jobs the process runs at once. Studio clamps it to the agent kind's own cap. */
583
+ maxConcurrent: number;
584
+ };
585
+ /**
586
+ * Body of `POST /api/agent/connect`, authenticated with the agent's bearer token.
587
+ */
588
+ export type AgentRegisterInput = {
589
+ /** Hash of this machine's secret, binding the token to one machine identity. */
590
+ machineToken: string;
591
+ /** This process, sent again as {@link AGENT_INSTANCE_HEADER} when it opens its socket. */
592
+ instanceId: string;
593
+ capacity: AgentCapacity;
594
+ };
595
+ /**
596
+ * Response of `POST /api/agent/connect`: where this process opens its one socket, and what Studio
597
+ * knows about the agent.
598
+ */
599
+ export type AgentRegisterResponse = {
600
+ /** `wss:` URL of the agent socket. The agent authenticates it with its bearer token and instance id. */
601
+ socketUrl: string;
602
+ /** Whether the agent is a shared sandbox rather than one someone owns. */
556
603
  isSandbox: boolean;
557
- /**
558
- * The Studio instance's own version. Returned with the RPC session so the agent can name both
559
- * sides from the first connection.
560
- * Absent when Studio predates the field.
561
- */
604
+ /** The Studio instance's own version, so both sides can be named from the first connection. */
562
605
  version?: string;
563
- /**
564
- * This agent's slug, so a reconnect refreshes it the same way pairing did.
565
- * Absent when Studio predates the field.
566
- */
606
+ /** This agent's slug, so a reconnect refreshes it the same way pairing did. */
567
607
  agentSlug?: string;
568
- /**
569
- * This agent's organization slug, absent for a sandbox or global agent, which has none, or when
570
- * Studio predates the field.
571
- */
608
+ /** This agent's organization slug. Absent for a sandbox or global agent, which has none. */
572
609
  organizationSlug?: string;
573
610
  };
574
611
  //#endregion
package/dist/protocol.js CHANGED
@@ -36,10 +36,28 @@ const generationEventTypes = [
36
36
  */
37
37
  const GENERATION_GONE_MESSAGE = "The files of this generation are no longer kept on the agent, run the generation again";
38
38
  /**
39
+ * Close codes Studio sends when it ends an agent's connection on purpose. Any other code is an
40
+ * ordinary drop, and the agent reconnects.
41
+ */
42
+ const AgentCloseCode = {
43
+ /** Register again before reconnecting, such as after Studio forgot this agent's machine token. */
44
+ REAUTHENTICATE: 4001,
45
+ /** Another instance of the same agent took the connection over. Reconnecting would take it back. */
46
+ SUPERSEDED: 4002,
47
+ /** This agent is too old for Studio, or was deleted. Reconnecting cannot succeed. */
48
+ INCOMPATIBLE: 4003
49
+ };
50
+ /**
39
51
  * How many files a single `readFiles` request may ask for at once.
40
52
  */
41
53
  const MAX_FILES_PER_REQUEST = 50;
54
+ /**
55
+ * Header naming which process of an agent a socket belongs to, next to the agent's bearer token.
56
+ * Several processes may share one token (CI runners, sandbox containers). For a kind that allows
57
+ * only one, a new id supersedes the old socket with {@link AgentCloseCode.SUPERSEDED}.
58
+ */
59
+ const AGENT_INSTANCE_HEADER = "x-kubb-instance-id";
42
60
  //#endregion
43
- export { GENERATION_GONE_MESSAGE, MAX_FILES_PER_REQUEST, generationEventTypes };
61
+ export { AGENT_INSTANCE_HEADER, AgentCloseCode, GENERATION_GONE_MESSAGE, MAX_FILES_PER_REQUEST, generationEventTypes };
44
62
 
45
63
  //# sourceMappingURL=protocol.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","names":[],"sources":["../src/protocol/index.ts"],"sourcesContent":["import type { 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).\n * Always honored for a 'sandbox' agent. For a non-sandbox agent it is honored only when the\n * agent opts in with `KUBB_AGENT_ALLOW_INPUT`; otherwise the spec is read from disk and this is ignored.\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\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 } })`.\n */\n | { operation: 'set'; config?: ConfigRef; plugin: string; path: Array<string>; value: unknown }\n /**\n * Drop an option so the plugin falls back to its default.\n */\n | { operation: 'remove'; config?: ConfigRef; 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; importName?: 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 plugin factory call the agent found in the `plugins` array of a `defineConfig(...)`.\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\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 ConfigSavedMessage}.\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 plus the rewritten file. `changed` is false when every edit was a no-op, so a\n * caller can skip reloading.\n */\nexport type SaveResult = { outcomes: Array<ConfigEditOutcome>; changed: boolean; file?: ConfigFileView }\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\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\n/**\n * Describes the package to pack and where to PUT it. `uploadPath` is resolved against the Studio\n * origin, so it cannot redirect the upload elsewhere.\n */\nexport type PublishSnapshotInput = { name: string; version: string; bundledDependencies?: Array<string>; uploadPath: string }\n\n/**\n * Identifies the uploaded snapshot. `integrity` is the subresource hash Studio verifies against.\n */\nexport type PublishSnapshotResult = { integrity: string; peerDependencies: Record<string, string> }\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 * Operations Studio can invoke on an agent through a host-provided RPC transport.\n */\nexport type AgentApi = {\n connect: () => Promise<ConnectMessagePayload>\n startGeneration: (input: GenerateInput) => GenerationRun\n saveConfig: (input: SaveConfigInput) => Promise<SaveResult>\n publishSnapshot: (input: PublishSnapshotInput) => Promise<PublishSnapshotResult>\n readFiles: (input: ReadFilesInput) => Promise<{ files: Record<string, string> }>\n}\n\n/**\n * Operations an agent can invoke on Studio through a host-provided RPC transport.\n */\nexport type StudioApi = {\n ping: () => Promise<void>\n}\n\n/**\n * A live RPC session. `closed` settles when the transport drops, whichever side ended it.\n */\nexport type RpcConnection = {\n studio: StudioApi\n closed: Promise<void>\n close: () => void\n}\n\n/**\n * Opens a transport and hands both sides their peer. Swapping this is how a test drives a session\n * without a socket.\n */\nexport type RpcConnector = (input: { url: string; token: string; local: AgentApi }) => Promise<RpcConnection>\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/**\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 (`KUBB_AGENT_ROOT`, or the working directory when unset). This is the\n * 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 configured (`KUBB_AGENT_CONFIG`), 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 their serialized options.\n */\n plugins?: Array<{\n name: string\n options?: object\n }>\n }\n permissions: AgentPermissions\n}\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 session narrows them further.\n */\nexport type AgentPermissions = {\n /**\n * Whether the agent writes generated files to disk. False for a sandbox agent. For a local\n * agent it mirrors the agent's `KUBB_AGENT_ALLOW_WRITE`.\n */\n allowWrite: boolean\n /**\n * Whether the agent will accept and generate from an OpenAPI spec supplied by Studio.\n * Always true for a sandbox agent, otherwise it mirrors the agent's own opt-in. Studio reads\n * this to decide whether to send `input`.\n */\n allowInput: boolean\n /**\n * Whether the agent runs the formatter, the linter, and `output.postGenerate` as child\n * processes after a generation. Always true for the Docker agent, where the image bounds what\n * can run. The CLI runs in the user's own project and defaults it off.\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 agent; for a local agent it mirrors the agent's own opt-in.\n */\n allowRead: boolean\n}\n\n/**\n * Response returned by the Studio `/api/agent/sessions` endpoint.\n */\nexport type AgentConnectResponse = {\n /**\n * URL the agent opens to reach the session, with the session token embedded.\n */\n url: string\n /**\n * When the session expires and the url stops working (ISO 8601).\n */\n expiresAt: string\n /**\n * When the session was revoked (ISO 8601), or null while it is still valid.\n */\n revokedAt: string | null\n /**\n * Opaque session token, also embedded in `url`. Store it to revoke the session later.\n */\n sessionId: string\n /**\n * Short readable identifier for this connection, used in logs (e.g. brave-otter).\n */\n slug: string | null\n /**\n * Whether this session belongs to a shared sandbox agent rather than an owned one.\n */\n isSandbox: boolean\n /**\n * The Studio instance's own version. Returned with the RPC session so the agent can name both\n * sides from the first connection.\n * Absent when Studio predates the field.\n */\n version?: string\n /**\n * This agent's slug, so a reconnect refreshes it the same way pairing did.\n * Absent when Studio predates the field.\n */\n agentSlug?: string\n /**\n * This agent's organization slug, absent for a sandbox or global agent, which has none, or when\n * Studio predates the field.\n */\n organizationSlug?: string\n}\n"],"mappings":";;;;;AA4JA,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;;;;AAoJA,MAAa,0BAA0B;;;;AA2DvC,MAAa,wBAAwB"}
1
+ {"version":3,"file":"protocol.js","names":[],"sources":["../src/protocol/index.ts"],"sourcesContent":["import type { 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).\n * Always honored for a 'sandbox' agent. For a non-sandbox agent it is honored only when the\n * agent opts in with `KUBB_AGENT_ALLOW_INPUT`; otherwise the spec is read from disk and this is ignored.\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\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 } })`.\n */\n | { operation: 'set'; config?: ConfigRef; plugin: string; path: Array<string>; value: unknown }\n /**\n * Drop an option so the plugin falls back to its default.\n */\n | { operation: 'remove'; config?: ConfigRef; 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; importName?: 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 plugin factory call the agent found in the `plugins` array of a `defineConfig(...)`.\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\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 ConfigSavedMessage}.\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 plus the rewritten file. `changed` is false when every edit was a no-op, so a\n * caller can skip reloading.\n */\nexport type SaveResult = { outcomes: Array<ConfigEditOutcome>; changed: boolean; file?: ConfigFileView }\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\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\n/**\n * Describes the package to pack and where to PUT it. `uploadPath` is resolved against the Studio\n * origin, so it cannot redirect the upload elsewhere.\n */\nexport type PublishSnapshotInput = { name: string; version: string; bundledDependencies?: Array<string>; uploadPath: string }\n\n/**\n * Identifies the uploaded snapshot. `integrity` is the subresource hash Studio verifies against.\n */\nexport type PublishSnapshotResult = { integrity: string; peerDependencies: Record<string, string> }\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 * Operations Studio can invoke on an agent through a host-provided RPC transport.\n */\nexport type AgentApi = {\n connect: () => Promise<ConnectMessagePayload>\n startGeneration: (input: GenerateInput) => GenerationRun\n saveConfig: (input: SaveConfigInput) => Promise<SaveResult>\n publishSnapshot: (input: PublishSnapshotInput) => Promise<PublishSnapshotResult>\n readFiles: (input: ReadFilesInput) => Promise<{ files: Record<string, string> }>\n /**\n * Cancels the job named by `jobId`, if it is the one this agent is currently running. Reachable\n * by id alone, unlike {@link GenerationRun.cancel}, so a caller that no longer holds that object\n * (Studio, after its own restart re-attaches to a job by id) can still cancel it.\n */\n cancel: (jobId: string) => Promise<void>\n}\n\n/**\n * Operations an agent can invoke on Studio through a host-provided RPC transport.\n */\nexport type StudioApi = {\n /**\n * Keeps the connection alive. `load` reports what the agent is carrying right now. Absent from an\n * agent that predates it.\n */\n ping: (load?: AgentLoad) => Promise<void>\n}\n\n/**\n * What an agent is carrying at one heartbeat.\n */\nexport type AgentLoad = {\n /** Jobs running on this connection right now. */\n running: number\n /** Resident memory of the agent process, in megabytes. */\n rssMb: number\n /** Bytes of past generations the agent keeps for file reads and snapshots. */\n storeBytes: number\n /** Whether the agent takes new jobs. Always `true` today; Studio skips a connection that reports `false`. */\n accepting: boolean\n}\n\n/**\n * Close codes Studio sends when it ends an agent's connection on purpose. Any other code is an\n * ordinary drop, and the agent reconnects.\n */\nexport const AgentCloseCode = {\n /** Register again before reconnecting, such as after Studio forgot this agent's machine token. */\n REAUTHENTICATE: 4001,\n /** Another instance of the same agent took the connection over. Reconnecting would take it back. */\n SUPERSEDED: 4002,\n /** This agent is too old for Studio, or was deleted. Reconnecting cannot succeed. */\n INCOMPATIBLE: 4003,\n} as const\n\n/**\n * Why a transport closed. Absent when the transport cannot tell, such as an in-process test pair.\n */\nexport type RpcClose = { code: number; reason: string }\n\n/**\n * A live RPC session. `closed` settles when the transport drops, whichever side ended it.\n */\nexport type RpcConnection = {\n studio: StudioApi\n closed: Promise<RpcClose | void>\n close: () => void\n}\n\n/**\n * Opens a transport and hands both sides their peer. Swapping this is how a test drives a session\n * without a socket.\n */\nexport type RpcConnector = (input: { url: string; token: string; instanceId: string; local: AgentApi }) => Promise<RpcConnection>\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/**\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 (`KUBB_AGENT_ROOT`, or the working directory when unset). This is the\n * 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 configured (`KUBB_AGENT_CONFIG`), 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 their serialized options.\n */\n plugins?: Array<{\n name: string\n options?: object\n }>\n }\n permissions: AgentPermissions\n}\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 session narrows them further.\n */\nexport type AgentPermissions = {\n /**\n * Whether the agent writes generated files to disk. False for a sandbox agent. For a local\n * agent it mirrors the agent's `KUBB_AGENT_ALLOW_WRITE`.\n */\n allowWrite: boolean\n /**\n * Whether the agent will accept and generate from an OpenAPI spec supplied by Studio.\n * Always true for a sandbox agent, otherwise it mirrors the agent's own opt-in. Studio reads\n * this to decide whether to send `input`.\n */\n allowInput: boolean\n /**\n * Whether the agent runs the formatter, the linter, and `output.postGenerate` as child\n * processes after a generation. Always true for the Docker agent, where the image bounds what\n * can run. The CLI runs in the user's own project and defaults it off.\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 agent; for a local agent it mirrors the agent's own opt-in.\n */\n allowRead: boolean\n}\n\n/**\n * Header naming which process of an agent a socket belongs to, next to the agent's bearer token.\n * Several processes may share one token (CI runners, sandbox containers). For a kind that allows\n * only one, a new id supersedes the old socket with {@link AgentCloseCode.SUPERSEDED}.\n */\nexport const AGENT_INSTANCE_HEADER = 'x-kubb-instance-id'\n\n/**\n * What an agent process can take on, reported at registration.\n */\nexport type AgentCapacity = {\n /** Jobs the process runs at once. Studio clamps it to the agent kind's own cap. */\n maxConcurrent: number\n}\n\n/**\n * Body of `POST /api/agent/connect`, authenticated with the agent's bearer token.\n */\nexport type AgentRegisterInput = {\n /** Hash of this machine's secret, binding the token to one machine identity. */\n machineToken: string\n /** This process, sent again as {@link AGENT_INSTANCE_HEADER} when it opens its socket. */\n instanceId: string\n capacity: AgentCapacity\n}\n\n/**\n * Response of `POST /api/agent/connect`: where this process opens its one socket, and what Studio\n * knows about the agent.\n */\nexport type AgentRegisterResponse = {\n /** `wss:` URL of the agent socket. The agent authenticates it with its bearer token and instance id. */\n socketUrl: string\n /** Whether the agent is a shared sandbox rather than one someone owns. */\n isSandbox: boolean\n /** The Studio instance's own version, so both sides can be named from the first connection. */\n version?: string\n /** This agent's slug, so a reconnect refreshes it the same way pairing did. */\n agentSlug?: string\n /** This agent's organization slug. Absent for a sandbox or global agent, which has none. */\n organizationSlug?: string\n}\n"],"mappings":";;;;;AA4JA,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;;;;AAoJA,MAAa,0BAA0B;;;;;AAqEvC,MAAa,iBAAiB;;CAE5B,gBAAgB;;CAEhB,YAAY;;CAEZ,cAAc;AAChB;;;;AAyBA,MAAa,wBAAwB;;;;;;AA0FrC,MAAa,wBAAwB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/studio",
3
- "version": "5.3.16",
3
+ "version": "5.3.18",
4
4
  "description": "Kubb Studio client runtime. Connects a Kubb project to Kubb Studio over WebSocket and streams code generation events, shared by the `kubb studio` CLI command and the Docker agent.",
5
5
  "keywords": [
6
6
  "agent",
@@ -62,7 +62,7 @@
62
62
  "registry": "https://registry.npmjs.org/"
63
63
  },
64
64
  "dependencies": {
65
- "@kubb/core": "5.3.16",
65
+ "@kubb/core": "5.3.18",
66
66
  "capnweb": "^0.12.0",
67
67
  "magicast": "^0.5.5",
68
68
  "ofetch": "^1.5.1",
@@ -74,8 +74,8 @@
74
74
  },
75
75
  "devDependencies": {
76
76
  "@internals/utils": "0.0.1",
77
- "@kubb/adapter-oas": "5.3.16",
78
- "@kubb/ast": "5.3.16",
77
+ "@kubb/adapter-oas": "5.3.18",
78
+ "@kubb/ast": "5.3.18",
79
79
  "@types/ws": "^8.18.1"
80
80
  },
81
81
  "engines": {