experimental-ash 0.61.0 → 0.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/dist/docs/public/advanced/hooks.mdx +8 -2
  3. package/dist/docs/public/advanced/runs-and-streaming.md +6 -3
  4. package/dist/docs/public/advanced/typescript-api.md +32 -0
  5. package/dist/docs/public/channels/custom.mdx +23 -0
  6. package/dist/docs/public/frontend/README.md +8 -4
  7. package/dist/docs/public/frontend/meta.json +9 -1
  8. package/dist/docs/public/frontend/nextjs.md +4 -4
  9. package/dist/docs/public/frontend/nuxt.md +168 -0
  10. package/dist/docs/public/frontend/sveltekit.md +177 -0
  11. package/dist/docs/public/frontend/use-ash-agent-svelte.md +185 -0
  12. package/dist/docs/public/frontend/use-ash-agent-vue.md +236 -0
  13. package/dist/docs/public/frontend/use-ash-agent.md +14 -14
  14. package/dist/docs/public/getting-started.mdx +2 -0
  15. package/dist/skills/ash-add-agent/SKILL.md +29 -17
  16. package/dist/skills/ash-add-next/SKILL.md +58 -8
  17. package/dist/src/channel/websocket-upgrade-server.d.ts +26 -0
  18. package/dist/src/channel/websocket-upgrade-server.js +1 -0
  19. package/dist/src/chunks/use-ash-agent-BQJLh7KU.js +1224 -0
  20. package/dist/src/chunks/use-ash-agent-CRWVA4i-.js +1192 -0
  21. package/dist/src/client/ash-agent-store.d.ts +61 -0
  22. package/dist/src/client/ash-agent-store.js +2 -0
  23. package/dist/src/client/index.d.ts +2 -0
  24. package/dist/src/client/index.js +1 -1
  25. package/dist/src/compiled/.vendor-stamp.json +9 -9
  26. package/dist/src/compiled/@ai-sdk/anthropic/_provider-utils.d.ts +1 -1
  27. package/dist/src/compiled/@ai-sdk/anthropic/index.d.ts +3 -3
  28. package/dist/src/compiled/@ai-sdk/anthropic/index.js +2 -2
  29. package/dist/src/compiled/@ai-sdk/google/index.d.ts +52 -2
  30. package/dist/src/compiled/@ai-sdk/google/index.js +6 -6
  31. package/dist/src/compiled/@ai-sdk/mcp/index.js +1 -1
  32. package/dist/src/compiled/@ai-sdk/openai/index.d.ts +32 -2
  33. package/dist/src/compiled/@ai-sdk/openai/index.js +2 -2
  34. package/dist/src/compiled/@ai-sdk/provider/index.d.ts +507 -1
  35. package/dist/src/compiled/@chat-adapter/slack/index.js +25 -25
  36. package/dist/src/compiled/@workflow/core/events-consumer.d.ts +8 -0
  37. package/dist/src/compiled/@workflow/core/index.js +2 -2
  38. package/dist/src/compiled/@workflow/core/runtime/constants.d.ts +1 -0
  39. package/dist/src/compiled/@workflow/core/runtime.js +29 -29
  40. package/dist/src/compiled/@workflow/core/version.d.ts +1 -1
  41. package/dist/src/compiled/@workflow/core/workflow.js +1 -1
  42. package/dist/src/compiled/@workflow/errors/error-codes.d.ts +2 -0
  43. package/dist/src/compiled/@workflow/errors/index.d.ts +14 -0
  44. package/dist/src/compiled/@workflow/errors/index.js +1 -1
  45. package/dist/src/compiled/@workflow/world/queue.d.ts +8 -0
  46. package/dist/src/compiled/_chunks/workflow/{dist-Chj-QcBs.js → dist-gEXVSMPU.js} +1 -1
  47. package/dist/src/compiled/_chunks/workflow/dist-zpK2YVVA.js +3 -0
  48. package/dist/src/compiled/_chunks/workflow/resume-hook-BFK9mgsb.js +12 -0
  49. package/dist/src/compiled/_chunks/workflow/{sleep-Bg0t23kF.js → sleep-CeJckNg2.js} +1 -1
  50. package/dist/src/compiled/_chunks/workflow/{symbols-u476uwyR.js → symbols-BWCAoPHE.js} +1 -1
  51. package/dist/src/compiler/manifest.d.ts +12 -0
  52. package/dist/src/compiler/manifest.js +1 -1
  53. package/dist/src/compiler/normalize-connection.d.ts +10 -2
  54. package/dist/src/compiler/normalize-connection.js +1 -1
  55. package/dist/src/execution/sandbox/bindings/local.js +1 -1
  56. package/dist/src/execution/sandbox/bindings/vercel.js +1 -1
  57. package/dist/src/internal/application/package.d.ts +1 -0
  58. package/dist/src/internal/application/package.js +1 -1
  59. package/dist/src/internal/authored-definition/connection.d.ts +9 -0
  60. package/dist/src/internal/authored-definition/connection.js +1 -1
  61. package/dist/src/internal/nitro/host/build-application.js +1 -1
  62. package/dist/src/internal/nitro/host/build-vercel-agent-summary.js +1 -1
  63. package/dist/src/internal/nitro/host/channel-routes.js +2 -2
  64. package/dist/src/internal/vercel-agent-summary.d.ts +6 -4
  65. package/dist/src/internal/workflow-bundle/ash-service-route-output.js +11 -1
  66. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  67. package/dist/src/public/channels/auth.d.ts +1 -1
  68. package/dist/src/public/channels/index.d.ts +1 -0
  69. package/dist/src/public/channels/index.js +1 -1
  70. package/dist/src/public/connections/index.d.ts +3 -2
  71. package/dist/src/public/connections/index.js +1 -1
  72. package/dist/src/public/definitions/connections/mcp.d.ts +4 -12
  73. package/dist/src/public/definitions/connections/mcp.js +1 -1
  74. package/dist/src/public/definitions/connections/openapi.d.ts +100 -0
  75. package/dist/src/public/definitions/connections/openapi.js +1 -0
  76. package/dist/src/public/definitions/connections/protocol.d.ts +12 -0
  77. package/dist/src/public/definitions/connections/protocol.js +1 -0
  78. package/dist/src/public/next/index.d.ts +6 -6
  79. package/dist/src/public/next/index.js +1 -1
  80. package/dist/src/public/next/{vercel-json.d.ts → vercel-output-config.d.ts} +3 -3
  81. package/dist/src/public/next/vercel-output-config.js +1 -0
  82. package/dist/src/public/nuxt/dev-server.d.ts +24 -0
  83. package/dist/src/public/nuxt/dev-server.js +1 -0
  84. package/dist/src/public/nuxt/index.d.ts +1 -0
  85. package/dist/src/public/nuxt/index.js +1 -0
  86. package/dist/src/public/nuxt/module.d.ts +31 -0
  87. package/dist/src/public/nuxt/module.js +1 -0
  88. package/dist/src/public/nuxt/routing.d.ts +55 -0
  89. package/dist/src/public/nuxt/routing.js +1 -0
  90. package/dist/src/public/nuxt/vercel-json.d.ts +17 -0
  91. package/dist/src/public/{next → nuxt}/vercel-json.js +1 -1
  92. package/dist/src/public/sveltekit/dev-server.d.ts +24 -0
  93. package/dist/src/public/sveltekit/dev-server.js +1 -0
  94. package/dist/src/public/sveltekit/index.d.ts +39 -0
  95. package/dist/src/public/sveltekit/index.js +1 -0
  96. package/dist/src/public/sveltekit/routing.d.ts +32 -0
  97. package/dist/src/public/sveltekit/routing.js +1 -0
  98. package/dist/src/public/sveltekit/vercel-json.d.ts +17 -0
  99. package/dist/src/public/sveltekit/vercel-json.js +1 -0
  100. package/dist/src/react/use-ash-agent.d.ts +5 -27
  101. package/dist/src/react/use-ash-agent.js +1 -2
  102. package/dist/src/runtime/connections/openapi-client.d.ts +43 -0
  103. package/dist/src/runtime/connections/openapi-client.js +1 -0
  104. package/dist/src/runtime/connections/openapi-operations.d.ts +30 -0
  105. package/dist/src/runtime/connections/openapi-operations.js +1 -0
  106. package/dist/src/runtime/connections/openapi-schema.d.ts +39 -0
  107. package/dist/src/runtime/connections/openapi-schema.js +1 -0
  108. package/dist/src/runtime/connections/openapi-security.d.ts +41 -0
  109. package/dist/src/runtime/connections/openapi-security.js +1 -0
  110. package/dist/src/runtime/connections/openapi-spec.d.ts +20 -0
  111. package/dist/src/runtime/connections/openapi-spec.js +1 -0
  112. package/dist/src/runtime/connections/registry.d.ts +5 -7
  113. package/dist/src/runtime/connections/registry.js +1 -1
  114. package/dist/src/runtime/connections/types.d.ts +23 -0
  115. package/dist/src/runtime/resolve-connection.js +1 -1
  116. package/dist/src/runtime/types.d.ts +15 -1
  117. package/dist/src/shared/sandbox-session.d.ts +1 -1
  118. package/dist/src/shared/vercel-output-directory.d.ts +2 -0
  119. package/dist/src/shared/vercel-output-directory.js +1 -0
  120. package/dist/src/svelte/index.d.ts +3 -0
  121. package/dist/src/svelte/index.js +3 -0
  122. package/dist/src/svelte/use-ash-agent.d.ts +80 -0
  123. package/dist/src/svelte/use-ash-agent.js +3 -0
  124. package/dist/src/vue/index.d.ts +3 -0
  125. package/dist/src/vue/index.js +3 -0
  126. package/dist/src/vue/use-ash-agent.d.ts +78 -0
  127. package/dist/src/vue/use-ash-agent.js +3 -0
  128. package/package.json +59 -14
  129. package/dist/src/compiled/_chunks/workflow/dist-C4EHshZE.js +0 -3
  130. package/dist/src/compiled/_chunks/workflow/resume-hook-BlALLgSA.js +0 -12
@@ -0,0 +1 @@
1
+ const PROTOCOL_KEY=Symbol.for(`experimental-ash.connection-protocol`);function stampConnectionProtocol(e,t){Object.defineProperty(e,PROTOCOL_KEY,{configurable:!0,value:t})}function readConnectionProtocol(e){if(typeof e==`object`&&e&&PROTOCOL_KEY in e){let t=e[PROTOCOL_KEY];if(t!==void 0)return t}return`mcp`}export{readConnectionProtocol,stampConnectionProtocol};
@@ -53,15 +53,15 @@ export interface WithAshOptions {
53
53
  */
54
54
  readonly ashBuildCommand?: string;
55
55
  /**
56
- * Set to `false` to skip creating or updating `vercel.json`.
56
+ * Set to `false` to skip creating or updating Vercel Build Output config.
57
57
  *
58
- * By default `withAsh` ensures `vercel.json` contains `experimentalServices`
59
- * for the Next.js app and Ash app.
58
+ * By default `withAsh` ensures `.vercel/output/config.json` contains
59
+ * `experimentalServices` for the Next.js app and Ash app.
60
60
  */
61
- readonly configureVercelJson?: boolean;
61
+ readonly configureVercelOutput?: boolean;
62
62
  /**
63
63
  * Private Vercel service prefix for the Ash deployment. This must match the
64
- * Ash service's `routePrefix` in `vercel.json`.
64
+ * Ash service's mount in Vercel Build Output config.
65
65
  */
66
66
  readonly servicePrefix?: string;
67
67
  }
@@ -72,7 +72,7 @@ export interface WithAshOptions {
72
72
  * In development, `withAsh` starts `ash dev --no-repl --port 0` for the Ash
73
73
  * app and rewrites Ash protocol endpoints to that local URL. In production on
74
74
  * Vercel, it rewrites to the private Ash service prefix configured in
75
- * `vercel.json`. Outside Vercel production, it serves an existing
75
+ * `.vercel/output/config.json`. Outside Vercel production, it serves an existing
76
76
  * `.output/server/index.mjs` Ash build on a stable local port when present;
77
77
  * otherwise set `ASH_NEXT_PRODUCTION_ORIGIN` to the origin that serves the Ash
78
78
  * service namespace.
@@ -1 +1 @@
1
- import{resolveAshDestinationPrefix}from"./server.js";import{ensureAshVercelJson}from"./vercel-json.js";import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";import{isAbsolute,resolve}from"node:path";const ASH_NEXT_SERVICE_PREFIX=`/_ash_internal/ash`,ASH_NEXT_PRODUCTION_PORT_ENV=`ASH_NEXT_PRODUCTION_PORT`,ASH_PROXY_REWRITE_SOURCES=[`${ASH_ROUTE_PREFIX}/:path+`];function resolveApplicationRoot(e){return e===void 0||e.length===0?process.cwd():isAbsolute(e)?e:resolve(process.cwd(),e)}function normalizeRoutePrefix(e){let t=(e.startsWith(`/`)?e:`/${e}`).replace(/\/+$/,``);if(t.length===0)throw Error(`Ash Next.js service prefix cannot resolve to the root route.`);return t}function joinRoutePrefix(e,t){return`${e.replace(/\/+$/,``)}/${t.replace(/^\/+/,``)}`}function normalizeOrigin(e){return new URL(e.trim()).origin}function readLocalProductionPort(){let e=process.env[ASH_NEXT_PRODUCTION_PORT_ENV];if(e===void 0||e.trim().length===0)return 4274;let t=Number.parseInt(e,10);if(String(t)!==e.trim()||t<1||t>65535)throw Error(`${ASH_NEXT_PRODUCTION_PORT_ENV} must be an integer between 1 and 65535.`);return t}function resolveProductionDestination(e){if(process.env.VERCEL)return{destinationPrefix:e};let t=process.env.ASH_NEXT_PRODUCTION_ORIGIN;if(t!==void 0&&t.trim().length>0)return{destinationPrefix:joinRoutePrefix(normalizeOrigin(t),e)};let n=`http://127.0.0.1:${String(readLocalProductionPort())}`;return{destinationPrefix:n,localServerOrigin:n}}function createAshRewriteRules(e){return ASH_PROXY_REWRITE_SOURCES.map(t=>({destination:joinRoutePrefix(e,t),source:t}))}async function resolveExistingRewrites(e){return await e?.()}function mergeRewriteRules(e,t){return e===void 0?{beforeFiles:t}:isRewriteSections(e)?{...e,beforeFiles:[...t,...e.beforeFiles??[]]}:{afterFiles:e,beforeFiles:t}}function isRewriteSections(e){return!Array.isArray(e)}async function resolveNextConfig(e,t,n){return typeof e==`function`?await e(t,n):e}function withAsh(n,r={}){let i=process.cwd(),a=resolveApplicationRoot(r.ashRoot),o=normalizeRoutePrefix(r.servicePrefix??`/_ash_internal/ash`),s=r.configureVercelJson!==!1;return async function(c,l){let u=await resolveNextConfig(n,c,l),d=u.rewrites,f=resolveProductionDestination((s?await ensureAshVercelJson({appRoot:a,ashBuildCommand:r.ashBuildCommand??`ash build`,nextRoot:i,servicePrefix:o}):{servicePrefix:o}).servicePrefix);return{...u,async rewrites(){let[t,n]=await Promise.all([resolveExistingRewrites(d),resolveAshDestinationPrefix({appRoot:a,phase:c,productionDestinationPrefix:f.destinationPrefix,productionServerOrigin:f.localServerOrigin})]);return mergeRewriteRules(t,createAshRewriteRules(n))}}}}export{ASH_NEXT_SERVICE_PREFIX,withAsh};
1
+ import{resolveAshDestinationPrefix}from"./server.js";import{ensureAshVercelOutputConfig}from"./vercel-output-config.js";import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";import{isAbsolute,resolve}from"node:path";const ASH_NEXT_SERVICE_PREFIX=`/_ash_internal/ash`,ASH_NEXT_PRODUCTION_PORT_ENV=`ASH_NEXT_PRODUCTION_PORT`,ASH_PROXY_REWRITE_SOURCES=[`${ASH_ROUTE_PREFIX}/:path+`];function resolveApplicationRoot(e){return e===void 0||e.length===0?process.cwd():isAbsolute(e)?e:resolve(process.cwd(),e)}function normalizeRoutePrefix(e){let t=(e.startsWith(`/`)?e:`/${e}`).replace(/\/+$/,``);if(t.length===0)throw Error(`Ash Next.js service prefix cannot resolve to the root route.`);return t}function joinRoutePrefix(e,t){return`${e.replace(/\/+$/,``)}/${t.replace(/^\/+/,``)}`}function normalizeOrigin(e){return new URL(e.trim()).origin}function readLocalProductionPort(){let e=process.env[ASH_NEXT_PRODUCTION_PORT_ENV];if(e===void 0||e.trim().length===0)return 4274;let t=Number.parseInt(e,10);if(String(t)!==e.trim()||t<1||t>65535)throw Error(`${ASH_NEXT_PRODUCTION_PORT_ENV} must be an integer between 1 and 65535.`);return t}function resolveProductionDestination(e){if(process.env.VERCEL)return{destinationPrefix:e};let t=process.env.ASH_NEXT_PRODUCTION_ORIGIN;if(t!==void 0&&t.trim().length>0)return{destinationPrefix:joinRoutePrefix(normalizeOrigin(t),e)};let n=`http://127.0.0.1:${String(readLocalProductionPort())}`;return{destinationPrefix:n,localServerOrigin:n}}function createAshRewriteRules(e){return ASH_PROXY_REWRITE_SOURCES.map(t=>({destination:joinRoutePrefix(e,t),source:t}))}async function resolveExistingRewrites(e){return await e?.()}function mergeRewriteRules(e,t){return e===void 0?{beforeFiles:t}:isRewriteSections(e)?{...e,beforeFiles:[...t,...e.beforeFiles??[]]}:{afterFiles:e,beforeFiles:t}}function isRewriteSections(e){return!Array.isArray(e)}async function resolveNextConfig(e,t,n){return typeof e==`function`?await e(t,n):e}function withAsh(n,r={}){let i=process.cwd(),a=resolveApplicationRoot(r.ashRoot),o=normalizeRoutePrefix(r.servicePrefix??`/_ash_internal/ash`),s=r.configureVercelOutput!==!1;return async function(c,l){let u=await resolveNextConfig(n,c,l),d=u.rewrites,f=resolveProductionDestination((s?await ensureAshVercelOutputConfig({appRoot:a,ashBuildCommand:r.ashBuildCommand??`ash build`,nextRoot:i,servicePrefix:o}):{servicePrefix:o}).servicePrefix);return{...u,async rewrites(){let[t,n]=await Promise.all([resolveExistingRewrites(d),resolveAshDestinationPrefix({appRoot:a,phase:c,productionDestinationPrefix:f.destinationPrefix,productionServerOrigin:f.localServerOrigin})]);return mergeRewriteRules(t,createAshRewriteRules(n))}}}}export{ASH_NEXT_SERVICE_PREFIX,withAsh};
@@ -1,9 +1,9 @@
1
- export interface EnsureVercelJsonResult {
1
+ export interface EnsureVercelOutputConfigResult {
2
2
  readonly servicePrefix: string;
3
3
  }
4
- export declare function ensureAshVercelJson(input: {
4
+ export declare function ensureAshVercelOutputConfig(input: {
5
5
  readonly appRoot: string;
6
6
  readonly ashBuildCommand: string;
7
7
  readonly nextRoot: string;
8
8
  readonly servicePrefix: string;
9
- }): Promise<EnsureVercelJsonResult>;
9
+ }): Promise<EnsureVercelOutputConfigResult>;
@@ -0,0 +1 @@
1
+ import{dirname,join,relative}from"node:path";import{mkdir,readFile,writeFile}from"node:fs/promises";import{findClosestLinkedVercelDirectory,findClosestVercelOutputDirectory}from"#shared/vercel-output-directory.js";const VERCEL_JSON_FILE_NAME=`vercel.json`,VERCEL_OUTPUT_CONFIG_FILE_NAME=`.vercel/output/config.json`;function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function hasServices(e){return e!==void 0&&Object.keys(e).length>0}function resolveRelativeEntrypoint(e,t){let r=relative(e,t);return r.length===0?`.`:r.replaceAll(`\\`,`/`)}async function resolveVercelOutputConfigLocation(n){let r=await findClosestLinkedVercelDirectory(n),i=r===void 0?n:dirname(r),a=await findClosestVercelOutputDirectory(n);return a===void 0?r===void 0?{outputConfigPath:join(n,VERCEL_OUTPUT_CONFIG_FILE_NAME),projectRoot:i}:{outputConfigPath:join(r,`output`,`config.json`),projectRoot:i}:{outputConfigPath:join(a,`config.json`),projectRoot:i}}function normalizeVercelServicesConfig(e,t){if(!isRecord(e))throw Error(`${t} must contain a JSON object.`);let n=e.experimentalServices;if(n!==void 0&&!isRecord(n))throw Error(`${t} experimentalServices must be a JSON object.`);return e}async function readVercelServicesConfig(e,t){try{return normalizeVercelServicesConfig(JSON.parse(await readFile(e,`utf8`)),t)}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return{};throw e}}function findServiceByFramework(e,t){return Object.values(e).find(e=>e.framework===t)}function resolveServicePrefix(e){if(e!==void 0){if(typeof e.routePrefix==`string`&&e.routePrefix.trim().length>0)return e.routePrefix.trim();if(typeof e.mount==`string`&&e.mount.trim().length>0)return e.mount.trim();if(isRecord(e.mount)&&typeof e.mount.path==`string`&&e.mount.path.trim().length>0)return e.mount.path.trim()}}function resolveConfiguredServicePrefix(e){return resolveServicePrefix(findServiceByFramework(e.services,`ash`))??e.servicePrefix}function assertRootServicesAreComplete(e){let t=findServiceByFramework(e.services,`ash`),n=findServiceByFramework(e.services,`nextjs`);if(t!==void 0&&n!==void 0)return resolveServicePrefix(t)??e.servicePrefix;throw Error(`${VERCEL_JSON_FILE_NAME} already defines experimentalServices, so withAsh cannot add generated services through ${VERCEL_OUTPUT_CONFIG_FILE_NAME}. Add both the Next.js and Ash services to ${VERCEL_JSON_FILE_NAME}, or remove experimentalServices from ${VERCEL_JSON_FILE_NAME}.`)}async function ensureAshVercelOutputConfig(n){let{outputConfigPath:i,projectRoot:o}=await resolveVercelOutputConfigLocation(n.nextRoot),s=(await readVercelServicesConfig(join(o,VERCEL_JSON_FILE_NAME),VERCEL_JSON_FILE_NAME)).experimentalServices;if(hasServices(s))return{servicePrefix:assertRootServicesAreComplete({services:s,servicePrefix:n.servicePrefix})};let c=await readVercelServicesConfig(i,VERCEL_OUTPUT_CONFIG_FILE_NAME),l=resolveRelativeEntrypoint(n.nextRoot,n.appRoot),u=c.experimentalServices??{},d=findServiceByFramework(u,`ash`),f=findServiceByFramework(u,`nextjs`),p=resolveConfiguredServicePrefix({services:u,servicePrefix:n.servicePrefix}),m={...u};f===void 0&&(m.web={entrypoint:`.`,framework:`nextjs`,mount:`/`,type:`web`}),d===void 0&&(m.ash={buildCommand:n.ashBuildCommand,entrypoint:l,framework:`ash`,mount:p,type:`web`});let h={...c,version:3,experimentalServices:m};return JSON.stringify(c)!==JSON.stringify(h)&&(await mkdir(dirname(i),{recursive:!0}),await writeFile(i,`${JSON.stringify(h,null,2)}\n`)),{servicePrefix:p}}export{ensureAshVercelOutputConfig};
@@ -0,0 +1,24 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ export declare const ASH_BASE_URL_ENV = "ASH_BASE_URL";
3
+ export interface AshProcessHandle {
4
+ readonly origin: string;
5
+ readonly process?: ChildProcess;
6
+ }
7
+ export interface AshDevServerRegistry {
8
+ readonly appRoot: string;
9
+ readonly origin: string;
10
+ readonly pid: number | null;
11
+ readonly updatedAt: string;
12
+ }
13
+ /**
14
+ * Parse and validate a persisted dev-server registry record. Returns
15
+ * `undefined` for anything that is not a well-formed registry so callers fall
16
+ * back to spawning a fresh server.
17
+ */
18
+ export declare function normalizeDevServerRegistry(value: unknown): AshDevServerRegistry | undefined;
19
+ /**
20
+ * Resolve a shared Ash dev server for {@link appRoot}, reusing a healthy
21
+ * registered server when one exists and otherwise spawning a new one behind a
22
+ * cross-process lock so concurrent Nuxt processes don't each boot Ash.
23
+ */
24
+ export declare function resolveSharedAshDevServer(appRoot: string): Promise<AshProcessHandle>;
@@ -0,0 +1 @@
1
+ import{joinRoutePrefix,normalizeOrigin}from"./routing.js";import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";import{join}from"node:path";import{mkdir,open,readFile,rm,stat,writeFile}from"node:fs/promises";import{spawn}from"node:child_process";import{resolvePackageRoot}from"#internal/application/package.js";const ASH_BASE_URL_ENV=`ASH_BASE_URL`,DEFAULT_SERVER_READY_TIMEOUT_MS=3e4,DEV_SERVER_REGISTRY_TIMEOUT_MS=3e4,LOCAL_SERVER_URL_PATTERN=/https?:\/\/(?:\[[^\]\s]+\]|[^\s/:[\]]+)(?::\d+)?/;function isNodeErrorWithCode(e,t){return e instanceof Error&&`code`in e&&e.code===t}function delay(e){return new Promise(t=>setTimeout(t,e))}function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function resolveAshCacheDirectory(e){return join(e,`.ash`)}function resolveAshDevServerRegistryPath(e){return join(resolveAshCacheDirectory(e),`nuxt-dev-server.json`)}function resolveAshDevServerLockPath(e){return join(resolveAshCacheDirectory(e),`nuxt-dev-server.lock`)}function normalizeDevServerRegistry(e){if(isRecord(e)&&!(typeof e.appRoot!=`string`||typeof e.origin!=`string`||typeof e.updatedAt!=`string`)&&!(e.pid!==null&&typeof e.pid!=`number`))try{return{appRoot:e.appRoot,origin:normalizeOrigin(e.origin),pid:e.pid,updatedAt:e.updatedAt}}catch{return}}async function isAshServerHealthy(t){let r=new AbortController,i=setTimeout(()=>r.abort(),1e3);try{return(await fetch(joinRoutePrefix(t,`${ASH_ROUTE_PREFIX}/health`),{signal:r.signal})).ok}catch{return!1}finally{clearTimeout(i)}}async function readUsableAshDevServerRegistry(e){try{let t=normalizeDevServerRegistry(JSON.parse(await readFile(resolveAshDevServerRegistryPath(e),`utf8`)));return t===void 0||t.appRoot!==e||!await isAshServerHealthy(t.origin)?void 0:t.origin}catch(e){if(isNodeErrorWithCode(e,`ENOENT`))return;throw e}}async function writeAshDevServerRegistry(e,t){await mkdir(resolveAshCacheDirectory(e),{recursive:!0}),await writeFile(resolveAshDevServerRegistryPath(e),`${JSON.stringify({appRoot:e,origin:t.origin,pid:t.process?.pid??null,updatedAt:new Date().toISOString()},null,2)}\n`)}async function removeStaleAshDevServerLock(e){try{let t=await stat(e);Date.now()-t.mtimeMs>3e4&&await rm(e,{force:!0})}catch(e){if(!isNodeErrorWithCode(e,`ENOENT`))throw e}}async function acquireAshDevServerLock(e){let t=resolveAshCacheDirectory(e),n=resolveAshDevServerLockPath(e),r=Date.now()+DEV_SERVER_REGISTRY_TIMEOUT_MS;for(await mkdir(t,{recursive:!0});;)try{let e=await open(n,`wx`);return await e.writeFile(`${String(process.pid)}\n`),await e.close(),async()=>{await rm(n,{force:!0})}}catch(t){if(!isNodeErrorWithCode(t,`EEXIST`))throw t;if(await readUsableAshDevServerRegistry(e)!==void 0)return async()=>{};if(await removeStaleAshDevServerLock(n),Date.now()>r)throw Error(`Timed out after ${DEV_SERVER_REGISTRY_TIMEOUT_MS}ms waiting for another Nuxt process to start Ash.`);await delay(100)}}function createAshBinaryPath(){return join(resolvePackageRoot(),`bin`,`ash.js`)}function startServerProcess(e){return new Promise((n,r)=>{let i=spawn(e.command,e.args,{cwd:e.cwd,env:{...process.env,...e.env},stdio:[`ignore`,`pipe`,`pipe`]}),a=setTimeout(()=>{i.kill(),r(Error(`Timed out after ${DEFAULT_SERVER_READY_TIMEOUT_MS}ms waiting for Ash to print its server URL.`))},DEFAULT_SERVER_READY_TIMEOUT_MS),cleanup=()=>{clearTimeout(a),i.off(`error`,handleError),i.off(`exit`,handleEarlyExit)},handleError=e=>{cleanup(),r(e)},handleEarlyExit=(e,t)=>{cleanup(),r(Error(`Ash server process exited before printing its server URL (code ${String(e)}, signal ${String(t)}).`))},o=!1,handleOutput=e=>{if(o)return;let r=LOCAL_SERVER_URL_PATTERN.exec(e.toString(`utf8`));r!==null&&(o=!0,cleanup(),n({origin:normalizeOrigin(r[0]),process:i}))};i.once(`error`,handleError),i.once(`exit`,handleEarlyExit),i.stdout.on(`data`,e=>{process.stdout.write(e),handleOutput(e)}),i.stderr.on(`data`,e=>{process.stderr.write(e),handleOutput(e)})})}function installProcessShutdown(e){let t=e.process;if(t===void 0)return e;let close=()=>{process.off(`beforeExit`,close),process.off(`exit`,close),t.killed||t.kill()};return process.once(`beforeExit`,close),process.once(`exit`,close),e}function startAshDevServer(e){return startServerProcess({args:[createAshBinaryPath(),`dev`,`--no-repl`,`--port`,`0`],command:process.execPath,cwd:e}).then(e=>(process.env[ASH_BASE_URL_ENV]=e.origin,installProcessShutdown(e)))}async function resolveSharedAshDevServer(e){let t=await readUsableAshDevServerRegistry(e);if(t!==void 0)return process.env[ASH_BASE_URL_ENV]=t,{origin:t};let n=await acquireAshDevServerLock(e);try{let t=await readUsableAshDevServerRegistry(e);if(t!==void 0)return process.env[ASH_BASE_URL_ENV]=t,{origin:t};let n=await startAshDevServer(e);return await writeAshDevServerRegistry(e,n),n}finally{await n()}}export{ASH_BASE_URL_ENV,normalizeDevServerRegistry,resolveSharedAshDevServer};
@@ -0,0 +1 @@
1
+ export { default, ASH_NUXT_SERVICE_PREFIX, type AshNuxtModuleOptions } from "./module.js";
@@ -0,0 +1 @@
1
+ import{ASH_NUXT_SERVICE_PREFIX}from"./routing.js";import module_default from"./module.js";export{ASH_NUXT_SERVICE_PREFIX,module_default as default};
@@ -0,0 +1,31 @@
1
+ import { ASH_NUXT_SERVICE_PREFIX } from "./routing.js";
2
+ export { ASH_NUXT_SERVICE_PREFIX };
3
+ /**
4
+ * Options for the Ash Nuxt module.
5
+ */
6
+ export interface AshNuxtModuleOptions {
7
+ /**
8
+ * Path to the Ash application root, relative to the Nuxt project root
9
+ * unless absolute. Defaults to the Nuxt project root.
10
+ */
11
+ ashRoot?: string;
12
+ /**
13
+ * Build command for the generated Ash Vercel service.
14
+ * Defaults to `"ash build"`.
15
+ */
16
+ ashBuildCommand?: string;
17
+ /**
18
+ * Set to `false` to skip creating or updating `vercel.json`.
19
+ *
20
+ * By default the module ensures `vercel.json` contains
21
+ * `experimentalServices` for the Nuxt app and Ash app.
22
+ */
23
+ configureVercelJson?: boolean;
24
+ /**
25
+ * Private Vercel service prefix for the Ash deployment. Must match the
26
+ * Ash service's `routePrefix` in `vercel.json`.
27
+ */
28
+ servicePrefix?: string;
29
+ }
30
+ declare const _default: import("nuxt/schema").NuxtModule<AshNuxtModuleOptions, AshNuxtModuleOptions, false>;
31
+ export default _default;
@@ -0,0 +1 @@
1
+ import{ASH_NUXT_SERVICE_PREFIX,createAshVercelRewriteRoute,joinRoutePrefix,normalizeOrigin,normalizeRoutePrefix,resolveProductionTarget}from"./routing.js";import{ASH_BASE_URL_ENV,resolveSharedAshDevServer}from"./dev-server.js";import{ensureAshVercelJson}from"./vercel-json.js";import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";import{isAbsolute,resolve}from"node:path";import{addImports,defineNuxtModule,extendRouteRules}from"@nuxt/kit";function resolveApplicationRoot(e,t){return t===void 0||t.length===0?e:isAbsolute(t)?t:resolve(e,t)}async function resolveAshProxyTarget(e){if(!e.dev)return resolveProductionTarget(e.servicePrefix);let t=process.env[ASH_BASE_URL_ENV]?.trim();if(t&&t.length>0)return joinRoutePrefix(normalizeOrigin(t),ASH_ROUTE_PREFIX);let i=await resolveSharedAshDevServer(e.appRoot);return i.process!==void 0&&e.onDevServerSpawned?.(i.process),joinRoutePrefix(i.origin,ASH_ROUTE_PREFIX)}var module_default=defineNuxtModule({meta:{name:`experimental-ash`,configKey:`ash`,compatibility:{nuxt:`>=4.0.0`}},defaults:{},async setup(e,n){let r=n.options.rootDir,a=resolveApplicationRoot(r,e.ashRoot),o=normalizeRoutePrefix(e.servicePrefix??`/_ash_internal/ash`),s=e.configureVercelJson!==!1;if(addImports({name:`useAshAgent`,from:`experimental-ash/vue`}),!n.options.dev&&process.env.VERCEL){let e=createAshVercelRewriteRoute(o),r=n.options.nitro,i=r.vercel?.config;r.vercel={...r.vercel,config:{version:3,...i,routes:[e,...i?.routes??[]]}}}else n.hook(`modules:done`,async()=>{let e=await resolveAshProxyTarget({appRoot:a,dev:n.options.dev,servicePrefix:o,onDevServerSpawned:e=>{n.hook(`close`,()=>{e.killed||e.kill()})}});extendRouteRules(`${ASH_ROUTE_PREFIX}/**`,{proxy:`${e}/**`})});s&&await ensureAshVercelJson({appRoot:a,ashBuildCommand:e.ashBuildCommand??`ash build`,nuxtRoot:r,servicePrefix:o})}});export{ASH_NUXT_SERVICE_PREFIX,module_default as default};
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Private route namespace used when a Vercel deployment hosts Ash as a
3
+ * separate experimental service behind the Nuxt app.
4
+ */
5
+ export declare const ASH_NUXT_SERVICE_PREFIX = "/_ash_internal/ash";
6
+ /**
7
+ * Normalize a user-supplied service prefix into a leading-slash, no-trailing-
8
+ * slash route. Throws when the prefix resolves to the root route, which would
9
+ * collide with the Nuxt web service.
10
+ */
11
+ export declare function normalizeRoutePrefix(prefix: string): string;
12
+ /**
13
+ * Join a route prefix and a path with exactly one separating slash.
14
+ */
15
+ export declare function joinRoutePrefix(prefix: string, path: string): string;
16
+ /**
17
+ * Reduce an origin string to its canonical `protocol://host[:port]` form.
18
+ */
19
+ export declare function normalizeOrigin(origin: string): string;
20
+ /**
21
+ * Resolve the local production port the module proxies to when an Ash service
22
+ * runs alongside a non-Vercel Nuxt deployment. Defaults to
23
+ * {@link DEFAULT_ASH_NUXT_PRODUCTION_PORT}.
24
+ */
25
+ export declare function readLocalProductionPort(): number;
26
+ /**
27
+ * An edge-level Vercel rewrite expressed in Build Output API v3 form.
28
+ */
29
+ export interface AshVercelRewriteRoute {
30
+ readonly src: string;
31
+ readonly dest: string;
32
+ /**
33
+ * Re-run route matching against the rewritten `dest`. Required so the
34
+ * rewritten Ash service path is routed to the sibling Ash service instead of
35
+ * being resolved inside the host service's own filesystem (which 404s).
36
+ */
37
+ readonly check: true;
38
+ }
39
+ /**
40
+ * Build the edge-level Vercel rewrite that forwards Ash transport requests
41
+ * (`/ash/v1/**`) to the Ash service prefix (`/_ash_internal/ash/ash/v1/**`).
42
+ *
43
+ * Mirrors the Next.js integration's `beforeFiles` rewrite. A Nitro runtime
44
+ * `proxy` route rule cannot reach a sibling Vercel service — the proxied
45
+ * request loops back into the Nuxt function and 404s — so production routing
46
+ * must happen at the edge via the build output config instead.
47
+ */
48
+ export declare function createAshVercelRewriteRoute(servicePrefix: string): AshVercelRewriteRoute;
49
+ /**
50
+ * Resolve the proxy destination for Ash routes in production.
51
+ *
52
+ * On Vercel the destination is the private service prefix. Off Vercel it is an
53
+ * explicit origin override (`ASH_NUXT_PRODUCTION_ORIGIN`) or a local port.
54
+ */
55
+ export declare function resolveProductionTarget(servicePrefix: string): string;
@@ -0,0 +1 @@
1
+ import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";const ASH_NUXT_SERVICE_PREFIX=`/_ash_internal/ash`,ASH_NUXT_PRODUCTION_PORT_ENV=`ASH_NUXT_PRODUCTION_PORT`;function normalizeRoutePrefix(e){let t=(e.startsWith(`/`)?e:`/${e}`).replace(/\/+$/,``);if(t.length===0)throw Error(`Ash Nuxt service prefix cannot resolve to the root route.`);return t}function joinRoutePrefix(e,t){return`${e.replace(/\/+$/,``)}/${t.replace(/^\/+/,``)}`}function normalizeOrigin(e){return new URL(e.trim()).origin}function readLocalProductionPort(){let e=process.env[ASH_NUXT_PRODUCTION_PORT_ENV];if(e===void 0||e.trim().length===0)return 4274;let t=Number.parseInt(e,10);if(String(t)!==e.trim()||t<1||t>65535)throw Error(`${ASH_NUXT_PRODUCTION_PORT_ENV} must be an integer between 1 and 65535.`);return t}function createAshVercelRewriteRoute(t){let n=joinRoutePrefix(t,ASH_ROUTE_PREFIX);return{src:`^${ASH_ROUTE_PREFIX}/(.*)$`,dest:`${n}/$1`,check:!0}}function resolveProductionTarget(t){if(process.env.VERCEL)return joinRoutePrefix(t,ASH_ROUTE_PREFIX);let n=process.env.ASH_NUXT_PRODUCTION_ORIGIN;return n!==void 0&&n.trim().length>0?joinRoutePrefix(normalizeOrigin(n),ASH_ROUTE_PREFIX):joinRoutePrefix(`http://127.0.0.1:${String(readLocalProductionPort())}`,ASH_ROUTE_PREFIX)}export{ASH_NUXT_SERVICE_PREFIX,createAshVercelRewriteRoute,joinRoutePrefix,normalizeOrigin,normalizeRoutePrefix,readLocalProductionPort,resolveProductionTarget};
@@ -0,0 +1,17 @@
1
+ export interface EnsureVercelJsonResult {
2
+ readonly servicePrefix: string;
3
+ }
4
+ /**
5
+ * Ensure `vercel.json` declares the Nuxt web service and the Ash agent
6
+ * service so a Vercel deployment ships both from one project.
7
+ *
8
+ * Existing services are preserved untouched; an already-configured Ash
9
+ * service's `routePrefix` wins over {@link input.servicePrefix}. The file is
10
+ * only rewritten when the resulting config differs from what is on disk.
11
+ */
12
+ export declare function ensureAshVercelJson(input: {
13
+ readonly appRoot: string;
14
+ readonly ashBuildCommand: string;
15
+ readonly nuxtRoot: string;
16
+ readonly servicePrefix: string;
17
+ }): Promise<EnsureVercelJsonResult>;
@@ -1 +1 @@
1
- import{join,relative}from"node:path";import{readFile,writeFile}from"node:fs/promises";const VERCEL_JSON_FILE_NAME=`vercel.json`;function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function resolveRelativeEntrypoint(e,n){let r=relative(e,n);return r.length===0?`.`:r.replaceAll(`\\`,`/`)}function normalizeVercelJsonConfig(e){if(!isRecord(e))throw Error(`${VERCEL_JSON_FILE_NAME} must contain a JSON object.`);let t=e.experimentalServices;if(t!==void 0&&!isRecord(t))throw Error(`${VERCEL_JSON_FILE_NAME} experimentalServices must be a JSON object.`);return e}async function readVercelJsonConfig(e){try{return normalizeVercelJsonConfig(JSON.parse(await readFile(e,`utf8`)))}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return{};throw e}}function findServiceByFramework(e,t){return Object.values(e).find(e=>e.framework===t)}async function ensureAshVercelJson(t){let n=join(t.nextRoot,VERCEL_JSON_FILE_NAME),i=await readVercelJsonConfig(n),a=resolveRelativeEntrypoint(t.nextRoot,t.appRoot),o=i.experimentalServices??{},s=findServiceByFramework(o,`ash`),c=findServiceByFramework(o,`nextjs`),l=s?.routePrefix??t.servicePrefix,u={...o};c===void 0&&(u.web={entrypoint:`.`,framework:`nextjs`,routePrefix:`/`}),s===void 0&&(u.ash={buildCommand:t.ashBuildCommand,entrypoint:a,framework:`ash`,routePrefix:l});let d={...i,$schema:i.$schema??`https://openapi.vercel.sh/vercel.json`,experimentalServices:u};return JSON.stringify(i)!==JSON.stringify(d)&&await writeFile(n,`${JSON.stringify(d,null,2)}\n`),{servicePrefix:l}}export{ensureAshVercelJson};
1
+ import{join,relative}from"node:path";import{readFile,writeFile}from"node:fs/promises";const VERCEL_JSON_FILE_NAME=`vercel.json`;function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function resolveRelativeEntrypoint(e,n){let r=relative(e,n);return r.length===0?`.`:r.replaceAll(`\\`,`/`)}function normalizeVercelJsonConfig(e){if(!isRecord(e))throw Error(`${VERCEL_JSON_FILE_NAME} must contain a JSON object.`);let t=e.experimentalServices;if(t!==void 0&&!isRecord(t))throw Error(`${VERCEL_JSON_FILE_NAME} experimentalServices must be a JSON object.`);return e}async function readVercelJsonConfig(e){try{return normalizeVercelJsonConfig(JSON.parse(await readFile(e,`utf8`)))}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return{};throw e}}function findServiceByFramework(e,t){return Object.values(e).find(e=>e.framework===t)}async function ensureAshVercelJson(t){let n=join(t.nuxtRoot,VERCEL_JSON_FILE_NAME),i=await readVercelJsonConfig(n),a=resolveRelativeEntrypoint(t.nuxtRoot,t.appRoot),o=i.experimentalServices??{},s=findServiceByFramework(o,`ash`),c=findServiceByFramework(o,`nuxtjs`),l=s?.routePrefix??t.servicePrefix,u={...o};c===void 0&&(u.web={entrypoint:`.`,framework:`nuxtjs`,routePrefix:`/`}),s===void 0&&(u.ash={buildCommand:t.ashBuildCommand,entrypoint:a,framework:`ash`,routePrefix:l});let d={...i,$schema:i.$schema??`https://openapi.vercel.sh/vercel.json`,experimentalServices:u};return JSON.stringify(i)!==JSON.stringify(d)&&await writeFile(n,`${JSON.stringify(d,null,2)}\n`),{servicePrefix:l}}export{ensureAshVercelJson};
@@ -0,0 +1,24 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ export declare const ASH_BASE_URL_ENV = "ASH_BASE_URL";
3
+ export interface AshProcessHandle {
4
+ readonly origin: string;
5
+ readonly process?: ChildProcess;
6
+ }
7
+ export interface AshDevServerRegistry {
8
+ readonly appRoot: string;
9
+ readonly origin: string;
10
+ readonly pid: number | null;
11
+ readonly updatedAt: string;
12
+ }
13
+ /**
14
+ * Parse and validate a persisted dev-server registry record. Returns
15
+ * `undefined` for anything that is not a well-formed registry so callers fall
16
+ * back to spawning a fresh server.
17
+ */
18
+ export declare function normalizeDevServerRegistry(value: unknown): AshDevServerRegistry | undefined;
19
+ /**
20
+ * Resolve a shared Ash dev server for {@link appRoot}, reusing a healthy
21
+ * registered server when one exists and otherwise spawning a new one behind a
22
+ * cross-process lock so concurrent SvelteKit processes don't each boot Ash.
23
+ */
24
+ export declare function resolveSharedAshDevServer(appRoot: string): Promise<AshProcessHandle>;
@@ -0,0 +1 @@
1
+ import{joinRoutePrefix,normalizeOrigin}from"./routing.js";import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";import{join}from"node:path";import{mkdir,open,readFile,rm,stat,writeFile}from"node:fs/promises";import{spawn}from"node:child_process";import{resolvePackageRoot}from"#internal/application/package.js";const ASH_BASE_URL_ENV=`ASH_BASE_URL`,DEFAULT_SERVER_READY_TIMEOUT_MS=3e4,DEV_SERVER_REGISTRY_TIMEOUT_MS=3e4,LOCAL_SERVER_URL_PATTERN=/https?:\/\/(?:\[[^\]\s]+\]|[^\s/:[\]]+)(?::\d+)?/;function isNodeErrorWithCode(e,t){return e instanceof Error&&`code`in e&&e.code===t}function delay(e){return new Promise(t=>setTimeout(t,e))}function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function resolveAshCacheDirectory(e){return join(e,`.ash`)}function resolveAshDevServerRegistryPath(e){return join(resolveAshCacheDirectory(e),`sveltekit-dev-server.json`)}function resolveAshDevServerLockPath(e){return join(resolveAshCacheDirectory(e),`sveltekit-dev-server.lock`)}function normalizeDevServerRegistry(e){if(isRecord(e)&&!(typeof e.appRoot!=`string`||typeof e.origin!=`string`||typeof e.updatedAt!=`string`)&&!(e.pid!==null&&typeof e.pid!=`number`))try{return{appRoot:e.appRoot,origin:normalizeOrigin(e.origin),pid:e.pid,updatedAt:e.updatedAt}}catch{return}}async function isAshServerHealthy(t){let r=new AbortController,i=setTimeout(()=>r.abort(),1e3);try{return(await fetch(joinRoutePrefix(t,`${ASH_ROUTE_PREFIX}/health`),{signal:r.signal})).ok}catch{return!1}finally{clearTimeout(i)}}async function readUsableAshDevServerRegistry(e){try{let t=normalizeDevServerRegistry(JSON.parse(await readFile(resolveAshDevServerRegistryPath(e),`utf8`)));return t===void 0||t.appRoot!==e||!await isAshServerHealthy(t.origin)?void 0:t.origin}catch(e){if(isNodeErrorWithCode(e,`ENOENT`))return;throw e}}async function writeAshDevServerRegistry(e,t){await mkdir(resolveAshCacheDirectory(e),{recursive:!0}),await writeFile(resolveAshDevServerRegistryPath(e),`${JSON.stringify({appRoot:e,origin:t.origin,pid:t.process?.pid??null,updatedAt:new Date().toISOString()},null,2)}\n`)}async function removeStaleAshDevServerLock(e){try{let t=await stat(e);Date.now()-t.mtimeMs>3e4&&await rm(e,{force:!0})}catch(e){if(!isNodeErrorWithCode(e,`ENOENT`))throw e}}async function acquireAshDevServerLock(e){let t=resolveAshCacheDirectory(e),n=resolveAshDevServerLockPath(e),r=Date.now()+DEV_SERVER_REGISTRY_TIMEOUT_MS;for(await mkdir(t,{recursive:!0});;)try{let e=await open(n,`wx`);return await e.writeFile(`${String(process.pid)}\n`),await e.close(),async()=>{await rm(n,{force:!0})}}catch(t){if(!isNodeErrorWithCode(t,`EEXIST`))throw t;if(await readUsableAshDevServerRegistry(e)!==void 0)return async()=>{};if(await removeStaleAshDevServerLock(n),Date.now()>r)throw Error(`Timed out after ${DEV_SERVER_REGISTRY_TIMEOUT_MS}ms waiting for another SvelteKit process to start Ash.`);await delay(100)}}function createAshBinaryPath(){return join(resolvePackageRoot(),`bin`,`ash.js`)}function startServerProcess(e){return new Promise((n,r)=>{let i=spawn(e.command,e.args,{cwd:e.cwd,env:{...process.env,...e.env},stdio:[`ignore`,`pipe`,`pipe`]}),a=setTimeout(()=>{i.kill(),r(Error(`Timed out after ${DEFAULT_SERVER_READY_TIMEOUT_MS}ms waiting for Ash to print its server URL.`))},DEFAULT_SERVER_READY_TIMEOUT_MS),cleanup=()=>{clearTimeout(a),i.off(`error`,handleError),i.off(`exit`,handleEarlyExit)},handleError=e=>{cleanup(),r(e)},handleEarlyExit=(e,t)=>{cleanup(),r(Error(`Ash server process exited before printing its server URL (code ${String(e)}, signal ${String(t)}).`))},o=!1,handleOutput=e=>{if(o)return;let r=LOCAL_SERVER_URL_PATTERN.exec(e.toString(`utf8`));r!==null&&(o=!0,cleanup(),n({origin:normalizeOrigin(r[0]),process:i}))};i.once(`error`,handleError),i.once(`exit`,handleEarlyExit),i.stdout.on(`data`,e=>{process.stdout.write(e),handleOutput(e)}),i.stderr.on(`data`,e=>{process.stderr.write(e),handleOutput(e)})})}function installProcessShutdown(e){let t=e.process;if(t===void 0)return e;let close=()=>{process.off(`beforeExit`,close),process.off(`exit`,close),t.killed||t.kill()};return process.once(`beforeExit`,close),process.once(`exit`,close),e}function startAshDevServer(e){return startServerProcess({args:[createAshBinaryPath(),`dev`,`--no-repl`,`--port`,`0`],command:process.execPath,cwd:e}).then(e=>(process.env[ASH_BASE_URL_ENV]=e.origin,installProcessShutdown(e)))}async function resolveSharedAshDevServer(e){let t=await readUsableAshDevServerRegistry(e);if(t!==void 0)return process.env[ASH_BASE_URL_ENV]=t,{origin:t};let n=await acquireAshDevServerLock(e);try{let t=await readUsableAshDevServerRegistry(e);if(t!==void 0)return process.env[ASH_BASE_URL_ENV]=t,{origin:t};let n=await startAshDevServer(e);return await writeAshDevServerRegistry(e,n),n}finally{await n()}}export{ASH_BASE_URL_ENV,normalizeDevServerRegistry,resolveSharedAshDevServer};
@@ -0,0 +1,39 @@
1
+ import type { Plugin } from "vite";
2
+ import { ASH_SVELTEKIT_SERVICE_PREFIX } from "./routing.js";
3
+ export { ASH_SVELTEKIT_SERVICE_PREFIX };
4
+ /**
5
+ * Options for the Ash SvelteKit Vite plugin.
6
+ */
7
+ export interface AshSvelteKitPluginOptions {
8
+ /**
9
+ * Path to the Ash application root, relative to the SvelteKit project root
10
+ * unless absolute. Defaults to the SvelteKit project root.
11
+ */
12
+ readonly ashRoot?: string;
13
+ /**
14
+ * Build command for the generated Ash Vercel service.
15
+ * Defaults to `"ash build"`.
16
+ */
17
+ readonly ashBuildCommand?: string;
18
+ /**
19
+ * Set to `false` to skip creating or updating `vercel.json`.
20
+ *
21
+ * By default the plugin ensures `vercel.json` contains `experimentalServices`
22
+ * for the SvelteKit app and Ash app.
23
+ */
24
+ readonly configureVercelJson?: boolean;
25
+ /**
26
+ * Private Vercel service prefix for the Ash deployment. Must match the
27
+ * Ash service's `routePrefix` in `vercel.json`.
28
+ */
29
+ readonly servicePrefix?: string;
30
+ }
31
+ /**
32
+ * Vite plugin for running an Ash agent alongside a SvelteKit app.
33
+ *
34
+ * In development and local preview, `ashSvelteKit` starts
35
+ * `ash dev --no-repl --port 0` for the Ash app and proxies Ash protocol
36
+ * endpoints to that local URL. During builds it can ensure `vercel.json`
37
+ * deploys the SvelteKit app and Ash agent as sibling Vercel services.
38
+ */
39
+ export declare function ashSvelteKit(options?: AshSvelteKitPluginOptions): Plugin;
@@ -0,0 +1 @@
1
+ import{ASH_SVELTEKIT_SERVICE_PREFIX,normalizeOrigin,normalizeRoutePrefix}from"./routing.js";import{ASH_BASE_URL_ENV,resolveSharedAshDevServer}from"./dev-server.js";import{ensureAshVercelJson}from"./vercel-json.js";import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";import{isAbsolute,resolve}from"node:path";function resolveApplicationRoot(e,t){return t===void 0||t.length===0?e:isAbsolute(t)?t:resolve(e,t)}function mergeProxyConfig(e,t){return{...e,[ASH_ROUTE_PREFIX]:{changeOrigin:!0,target:t}}}async function resolveAshDevProxyTarget(e){let n=process.env[ASH_BASE_URL_ENV]?.trim();return n&&n.length>0?normalizeOrigin(n):(await resolveSharedAshDevServer(e)).origin}function ashSvelteKit(e={}){let t=process.cwd(),r=resolveApplicationRoot(t,e.ashRoot),i=normalizeRoutePrefix(e.servicePrefix??`/_ash_internal/ash`),a=e.configureVercelJson!==!1;return{name:`experimental-ash:sveltekit`,async config(n,o){if(t=n.root===void 0?process.cwd():resolve(process.cwd(),n.root),r=resolveApplicationRoot(t,e.ashRoot),a&&o.command===`build`&&await ensureAshVercelJson({appRoot:r,ashBuildCommand:e.ashBuildCommand??`ash build`,servicePrefix:i,svelteKitRoot:t}),o.command!==`serve`)return{};let s=await resolveAshDevProxyTarget(r);return o.isPreview?{preview:{proxy:mergeProxyConfig(n.preview?.proxy,s)}}:{server:{proxy:mergeProxyConfig(n.server?.proxy,s)}}}}}export{ASH_SVELTEKIT_SERVICE_PREFIX,ashSvelteKit};
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Private route namespace used when a Vercel deployment hosts Ash as a
3
+ * separate experimental service behind the SvelteKit app.
4
+ */
5
+ export declare const ASH_SVELTEKIT_SERVICE_PREFIX = "/_ash_internal/ash";
6
+ /**
7
+ * Normalize a user-supplied service prefix into a leading-slash, no-trailing-
8
+ * slash route. Throws when the prefix resolves to the root route, which would
9
+ * collide with the SvelteKit web service.
10
+ */
11
+ export declare function normalizeRoutePrefix(prefix: string): string;
12
+ /**
13
+ * Join a route prefix and a path with exactly one separating slash.
14
+ */
15
+ export declare function joinRoutePrefix(prefix: string, path: string): string;
16
+ /**
17
+ * Reduce an origin string to its canonical `protocol://host[:port]` form.
18
+ */
19
+ export declare function normalizeOrigin(origin: string): string;
20
+ /**
21
+ * A Vercel rewrite that forwards Ash transport requests (`/ash/v1/**`) to the
22
+ * private Ash service prefix (`/_ash_internal/ash/ash/v1/**`).
23
+ */
24
+ export interface AshVercelRewrite {
25
+ readonly destination: string;
26
+ readonly source: string;
27
+ }
28
+ /**
29
+ * Build the Vercel rewrite that forwards browser Ash transport requests to the
30
+ * sibling Ash service.
31
+ */
32
+ export declare function createAshVercelRewrite(servicePrefix: string): AshVercelRewrite;
@@ -0,0 +1 @@
1
+ import{ASH_ROUTE_PREFIX}from"#protocol/routes.js";const ASH_SVELTEKIT_SERVICE_PREFIX=`/_ash_internal/ash`;function normalizeRoutePrefix(e){let t=(e.startsWith(`/`)?e:`/${e}`).replace(/\/+$/,``);if(t.length===0)throw Error(`Ash SvelteKit service prefix cannot resolve to the root route.`);return t}function joinRoutePrefix(e,t){return`${e.replace(/\/+$/,``)}/${t.replace(/^\/+/,``)}`}function normalizeOrigin(e){return new URL(e.trim()).origin}function createAshVercelRewrite(t){return{destination:`${joinRoutePrefix(t,ASH_ROUTE_PREFIX)}/:path*`,source:`${ASH_ROUTE_PREFIX}/:path*`}}export{ASH_SVELTEKIT_SERVICE_PREFIX,createAshVercelRewrite,joinRoutePrefix,normalizeOrigin,normalizeRoutePrefix};
@@ -0,0 +1,17 @@
1
+ export interface EnsureVercelJsonResult {
2
+ readonly servicePrefix: string;
3
+ }
4
+ /**
5
+ * Ensure `vercel.json` declares the SvelteKit web service and the Ash agent
6
+ * service so a Vercel deployment ships both from one project.
7
+ *
8
+ * Existing services are preserved untouched; an already-configured Ash
9
+ * service's `routePrefix` wins over {@link input.servicePrefix}. The file is
10
+ * only rewritten when the resulting config differs from what is on disk.
11
+ */
12
+ export declare function ensureAshVercelJson(input: {
13
+ readonly appRoot: string;
14
+ readonly ashBuildCommand: string;
15
+ readonly servicePrefix: string;
16
+ readonly svelteKitRoot: string;
17
+ }): Promise<EnsureVercelJsonResult>;
@@ -0,0 +1 @@
1
+ import{createAshVercelRewrite}from"./routing.js";import{join,relative}from"node:path";import{readFile,writeFile}from"node:fs/promises";const VERCEL_JSON_FILE_NAME=`vercel.json`;function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function resolveRelativeEntrypoint(e,t){let r=relative(e,t);return r.length===0?`.`:r.replaceAll(`\\`,`/`)}function normalizeVercelJsonConfig(e){if(!isRecord(e))throw Error(`${VERCEL_JSON_FILE_NAME} must contain a JSON object.`);let t=e.experimentalServices;if(t!==void 0&&!isRecord(t))throw Error(`${VERCEL_JSON_FILE_NAME} experimentalServices must be a JSON object.`);let n=e.rewrites;if(n!==void 0&&!Array.isArray(n))throw Error(`${VERCEL_JSON_FILE_NAME} rewrites must be an array.`);return e}async function readVercelJsonConfig(e){try{return normalizeVercelJsonConfig(JSON.parse(await readFile(e,`utf8`)))}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return{};throw e}}function findServiceByFramework(e,t){return Object.values(e).find(e=>e.framework===t)}function hasRewrite(e,t){return e.some(e=>e.source===t.source&&e.destination===t.destination)}async function ensureAshVercelJson(n){let r=join(n.svelteKitRoot,VERCEL_JSON_FILE_NAME),i=await readVercelJsonConfig(r),a=resolveRelativeEntrypoint(n.svelteKitRoot,n.appRoot),o=i.experimentalServices??{},s=findServiceByFramework(o,`ash`),c=findServiceByFramework(o,`sveltekit`),l=s?.routePrefix??n.servicePrefix,u={...o};c===void 0&&(u.web={entrypoint:`.`,framework:`sveltekit`,routePrefix:`/`}),s===void 0&&(u.ash={buildCommand:n.ashBuildCommand,entrypoint:a,framework:`ash`,routePrefix:l});let d=createAshVercelRewrite(l),f=i.rewrites??[],p={...i,$schema:i.$schema??`https://openapi.vercel.sh/vercel.json`,experimentalServices:u,rewrites:hasRewrite(f,d)?f:[d,...f]};return JSON.stringify(i)!==JSON.stringify(p)&&await writeFile(r,`${JSON.stringify(p,null,2)}\n`),{servicePrefix:l}}export{ensureAshVercelJson};
@@ -1,36 +1,15 @@
1
+ import { type AshAgentStoreCallbacks, type AshAgentStoreSnapshot, type AshAgentStoreStatus, type PrepareSend } from "#client/ash-agent-store.js";
1
2
  import type { AshAgentReducer } from "#client/reducer.js";
2
3
  import type { ClientSession } from "#client/session.js";
3
4
  import { type AshMessageData } from "#client/message-reducer.js";
4
5
  import type { HandleMessageStreamEvent } from "#protocol/message.js";
5
6
  import type { ClientAuth, HeadersValue, SendMessageOptions, SendTurnInput, SessionState } from "#client/types.js";
6
- export type UseAshAgentStatus = "error" | "ready" | "streaming" | "submitted";
7
- /**
8
- * Prepares one outbound turn immediately before the client sends it.
9
- *
10
- * Use this to attach fresh, one-turn client state such as page context via
11
- * `clientContext`.
12
- */
13
- export type PrepareSend = (input: SendTurnInput) => SendTurnInput | Promise<SendTurnInput>;
7
+ export type { PrepareSend };
8
+ export type UseAshAgentStatus = AshAgentStoreStatus;
14
9
  /**
15
10
  * Current projected state for an Ash agent session.
16
11
  */
17
- export interface UseAshAgentSnapshot<TData> {
18
- readonly data: TData;
19
- readonly error: Error | undefined;
20
- readonly events: readonly HandleMessageStreamEvent[];
21
- readonly session: SessionState;
22
- readonly status: UseAshAgentStatus;
23
- }
24
- /**
25
- * Lifecycle callbacks invoked while `useAshAgent` processes a turn.
26
- */
27
- interface UseAshAgentCallbacks<TData> {
28
- readonly onError?: (error: Error) => void;
29
- readonly onEvent?: (event: HandleMessageStreamEvent) => void;
30
- readonly onFinish?: (snapshot: UseAshAgentSnapshot<TData>) => void;
31
- readonly onSessionChange?: (session: SessionState) => void;
32
- readonly prepareSend?: PrepareSend;
33
- }
12
+ export type UseAshAgentSnapshot<TData> = AshAgentStoreSnapshot<TData>;
34
13
  /**
35
14
  * Snapshot plus commands returned by `useAshAgent`.
36
15
  */
@@ -52,7 +31,7 @@ export interface UseAshAgentHelpers<TData> extends UseAshAgentSnapshot<TData> {
52
31
  * values to `auth` or `headers`; the underlying client resolves those before
53
32
  * each HTTP request.
54
33
  */
55
- export interface UseAshAgentOptions<TData> extends UseAshAgentCallbacks<TData> {
34
+ export interface UseAshAgentOptions<TData> extends AshAgentStoreCallbacks<TData> {
56
35
  readonly auth?: ClientAuth;
57
36
  readonly headers?: HeadersValue;
58
37
  /**
@@ -85,4 +64,3 @@ export declare function useAshAgent(options?: UseAshAgentOptions<AshMessageData>
85
64
  export declare function useAshAgent<TData>(options: UseAshAgentOptions<TData> & {
86
65
  readonly reducer: AshAgentReducer<TData>;
87
66
  }): UseAshAgentHelpers<TData>;
88
- export {};
@@ -1,2 +1 @@
1
- import{toError}from"#shared/errors.js";import{Client}from"#client/client.js";import{defaultMessageReducer}from"#client/message-reducer.js";import{useCallback,useMemo,useRef,useSyncExternalStore}from"react";function useAshAgent(e={}){let t=useRef(void 0);if(!t.current){let r=e.reducer??defaultMessageReducer();t.current=new UseAshAgentStore({auth:e.auth,headers:e.headers,host:e.host,initialEvents:e.initialEvents,initialSession:e.initialSession,maxReconnectAttempts:e.maxReconnectAttempts,optimistic:e.optimistic,reducer:r,session:e.session})}let s=t.current;s.setCallbacks({onError:e.onError,onEvent:e.onEvent,onFinish:e.onFinish,onSessionChange:e.onSessionChange,prepareSend:e.prepareSend});let c=useSyncExternalStore(useCallback(e=>s.subscribe(e),[s]),()=>s.snapshot,()=>s.snapshot),l=useCallback(()=>s.reset(),[s]),u=useCallback((e,t)=>s.send(e,t),[s]),d=useCallback((e,t)=>s.sendMessage(e,t),[s]),f=useCallback(()=>s.stop(),[s]);return useMemo(()=>({...c,reset:l,send:u,sendMessage:d,stop:f}),[l,u,d,c,f])}var UseAshAgentStore=class{#e;#t;#n;#r=new Set;#i;#a={};#o;#s;#c;#l=0;#u;#d;#f;#p;#m=`ready`;constructor(e){this.#e=e.session?void 0:()=>new Client({auth:e.auth,headers:e.headers,host:e.host??``,maxReconnectAttempts:e.maxReconnectAttempts}).session(e.initialSession),this.#c=[...e.initialEvents??[]],this.#d=[...this.#c],this.#t=e.optimistic??!0,this.#n=e.reducer,this.#f=e.session??this.#h(),this.#o=this.#E(this.#d),this.#p=this.#D()}get snapshot(){return this.#p}setCallbacks(e){this.#a=e}subscribe(e){return this.#r.add(e),()=>{this.#r.delete(e)}}async sendMessage(e,t){await this.send({message:e},t)}async send(t,n){if(this.#m===`streaming`||this.#m===`submitted`)throw Error(`Ash session is already processing a turn.`);let r=this.#g(),i=new AbortController;this.#i=i,this.#s=void 0,this.#m=`submitted`,this.#O();try{let e=await this.#a.prepareSend?.(t)??t;if(!this.#v(r))return;this.#y(e),this.#b(e),this.#O();let a=await this.#f.send(e,{...n,signal:createAbortSignal(n?.signal,i.signal)}),o=!1;for await(let e of a){if(!this.#v(r))return;o||(o=!0,this.#m=`streaming`),this.#c=[...this.#c,e],this.#x(e),this.#a.onEvent?.(e),this.#S(e),this.#O()}if(!this.#v(r))return;this.#m=this.#s===void 0?`ready`:`error`}catch(t){if(!this.#v(r))return;isAbortError(t)?(this.#m=`ready`,this.#C(toError(t))):(this.#s=toError(t),this.#m=`error`,this.#C(this.#s),this.#a.onError?.(this.#s))}finally{this.#v(r)&&(this.#i=void 0,this.#a.onSessionChange?.(this.#f.state),this.#O(),this.#a.onFinish?.(this.#p))}}stop(){this.#i?.abort()}reset(){this.#_(),this.stop(),this.#i=void 0,this.#f=this.#e?.()??this.#f,this.#c=[],this.#u=void 0,this.#d=[],this.#o=this.#n.initial(),this.#s=void 0,this.#m=`ready`,this.#a.onSessionChange?.(this.#f.state),this.#O()}#h(){if(!this.#e)throw Error(`Cannot create an owned Ash session from an external session.`);return this.#e()}#g(){return this.#l+=1,this.#l}#_(){this.#l+=1}#v(e){return this.#l===e}#y(e){if(!this.#t||e.message===void 0)return;let t=createSubmissionId(),n={createdAt:Date.now(),id:t,message:summarizeUserContent(e.message)};this.#u=n,this.#w({data:{createdAt:n.createdAt,message:n.message,submissionId:n.id},type:`client.message.submitted`})}#b(e){e.inputResponses===void 0||e.inputResponses.length===0||this.#w({data:{createdAt:Date.now(),responses:e.inputResponses},type:`client.input.responded`})}#x(e){if(e.type===`message.received`&&this.#u!==void 0){let t=this.#u.id;this.#u=void 0,this.#T(e=>e.type===`client.message.submitted`&&e.data.submissionId===t,e);return}this.#w(e)}#S(e){let t=toTerminalStreamFailureError(e);t!==void 0&&(this.#m=`error`,this.#C(t),this.#s===void 0&&(this.#s=t,this.#a.onError?.(t)))}#C(e){let t=this.#u;t!==void 0&&(this.#u=void 0,this.#T(e=>e.type===`client.message.submitted`&&e.data.submissionId===t.id,{data:{createdAt:t.createdAt,error:{message:e.message},message:t.message,submissionId:t.id},type:`client.message.failed`}))}#w(e){this.#d=[...this.#d,e],this.#o=this.#n.reduce(this.#o,e)}#T(e,t){let n=!1;this.#d=this.#d.map(r=>!n&&e(r)?(n=!0,t):r),n||(this.#d=[...this.#d,t]),this.#o=this.#E(this.#d)}#E(e){let t=this.#n.initial();for(let n of e)t=this.#n.reduce(t,n);return t}#D(){return{data:this.#o,error:this.#s,events:this.#c,session:this.#f.state,status:this.#m}}#O(){this.#p=this.#D();for(let e of this.#r)e()}};let submissionSequence=0;function createSubmissionId(){let e=globalThis.crypto?.randomUUID;return e===void 0?(submissionSequence+=1,`submission_${submissionSequence.toString()}`):e.call(globalThis.crypto)}function createAbortSignal(e,t){return e?AbortSignal.any([e,t]):t}function summarizeUserContent(e){if(typeof e==`string`)return e;let t=[];for(let n of e){if(n.type===`text`){t.push(n.text);continue}n.type===`file`&&t.push(n.filename?`[file: ${n.filename}]`:`[file]`)}return t.join(`
2
- `)}function isAbortError(e){return e instanceof Error&&e.name===`AbortError`}function toTerminalStreamFailureError(e){if(e.type!==`session.failed`)return;let t=Error(e.data.message);return t.name=e.data.code,t}export{useAshAgent};
1
+ import{AshAgentStore}from"#client/ash-agent-store.js";import{defaultMessageReducer}from"#client/message-reducer.js";import{useCallback,useMemo,useRef,useSyncExternalStore}from"react";function useAshAgent(r={}){let i=useRef(void 0);if(!i.current){let n=r.reducer??defaultMessageReducer();i.current=new AshAgentStore({auth:r.auth,headers:r.headers,host:r.host,initialEvents:r.initialEvents,initialSession:r.initialSession,maxReconnectAttempts:r.maxReconnectAttempts,optimistic:r.optimistic,reducer:n,session:r.session})}let a=i.current;a.setCallbacks({onError:r.onError,onEvent:r.onEvent,onFinish:r.onFinish,onSessionChange:r.onSessionChange,prepareSend:r.prepareSend});let o=useSyncExternalStore(useCallback(e=>a.subscribe(e),[a]),()=>a.snapshot,()=>a.snapshot),s=useCallback(()=>a.reset(),[a]),c=useCallback((e,t)=>a.send(e,t),[a]),l=useCallback((e,t)=>a.sendMessage(e,t),[a]),u=useCallback(()=>a.stop(),[a]);return useMemo(()=>({...o,reset:s,send:c,sendMessage:l,stop:u}),[s,c,l,o,u])}export{useAshAgent};
@@ -0,0 +1,43 @@
1
+ import { type ToolSet } from "ai";
2
+ import type { ResolvedConnectionDefinition } from "#runtime/types.js";
3
+ import { type OpenApiOperation } from "#runtime/connections/openapi-operations.js";
4
+ import type { ConnectionClient, ConnectionToolMetadata } from "#runtime/connections/types.js";
5
+ interface OpenApiToolCache {
6
+ readonly metadata: readonly ConnectionToolMetadata[];
7
+ readonly operations: ReadonlyMap<string, OpenApiOperation>;
8
+ readonly tools: ToolSet;
9
+ readonly baseUrl: string;
10
+ }
11
+ /**
12
+ * Result of executing an OpenAPI operation. Returned to the model as the
13
+ * tool result so it can react to the status and body of any response,
14
+ * including non-2xx responses.
15
+ */
16
+ export interface OpenApiToolResult {
17
+ readonly status: number;
18
+ readonly statusText: string;
19
+ readonly body: unknown;
20
+ }
21
+ /**
22
+ * A {@link ConnectionClient} that turns an OpenAPI 3.x document into
23
+ * connection tools.
24
+ *
25
+ * Created lazily per-connection per-session. On first use it loads the
26
+ * document (fetching it when `spec` is a URL), dereferences local
27
+ * `$ref` pointers, and maps each operation to a tool whose name is the
28
+ * operation's `operationId`. Tool calls reconstruct the HTTP request —
29
+ * substituting path parameters, appending query parameters, attaching
30
+ * resolved auth and headers — and return the response as a serializable
31
+ * `{ status, statusText, body }`.
32
+ */
33
+ export declare class OpenApiConnectionClient implements ConnectionClient {
34
+ #private;
35
+ constructor(connection: ResolvedConnectionDefinition);
36
+ /** Loads and parses the OpenAPI document, sharing one in-flight load. */
37
+ connect(): Promise<OpenApiToolCache>;
38
+ getToolMetadata(): Promise<readonly ConnectionToolMetadata[]>;
39
+ getTools(): Promise<ToolSet>;
40
+ executeTool(toolName: string, args: unknown): Promise<OpenApiToolResult>;
41
+ close(): Promise<void>;
42
+ }
43
+ export {};
@@ -0,0 +1 @@
1
+ import{isObject}from"#shared/guards.js";import{jsonSchema,tool}from"ai";import{passesToolFilter,resolveHeaders}from"#runtime/connections/mcp-client.js";import{HTTP_METHODS,operationDescription,operationName,uniqueName}from"#runtime/connections/openapi-operations.js";import{buildInputSchema,deref,derefSchema,isArray}from"#runtime/connections/openapi-schema.js";import{applySecurity,resolveSecurity}from"#runtime/connections/openapi-security.js";import{extractServerUrl,parseSpecDocument}from"#runtime/connections/openapi-spec.js";var OpenApiConnectionClient=class{#e;#t;#n;constructor(e){this.#n=e}async connect(){return this.#r()}async getToolMetadata(){return(await this.#r()).metadata}async getTools(){return(await this.#r()).tools}async executeTool(t,n){let r=await this.#r(),i=r.operations.get(t);if(i===void 0)throw Error(`Tool "${t}" not found in connection "${this.#n.connectionName}".`);return this.#u(i,r.baseUrl,isObject(n)?n:{})}async close(){this.#e=void 0,this.#t=void 0}async#r(){if(this.#t!==void 0)return this.#t;if(this.#e!==void 0)return this.#e;this.#e=this.#i();try{return this.#t=await this.#e,this.#t}catch(e){throw this.#e=void 0,e}}async#i(){let i=await this.#o(),a=this.#a(i),o=this.#s(i),s=this.#n.tools,c=s===void 0?o:o.filter(e=>passesToolFilter(e.toolName,s)),l=[],u=new Map,d={};for(let r of c)u.set(r.toolName,r),l.push({description:r.description,inputSchema:r.inputSchema,name:r.toolName}),d[r.toolName]=tool({description:r.description,inputSchema:jsonSchema(r.inputSchema),execute:async t=>this.#u(r,a,isObject(t)?t:{})});return{metadata:l,operations:u,tools:d,baseUrl:a}}#a(e){let t=this.#n.url;if(typeof t==`string`&&t.trim().length>0)return t;let n=extractServerUrl(e,this.#n.spec);if(n!==void 0)return n;throw Error(`OpenAPI connection "${this.#n.connectionName}" has no base URL: set "baseUrl" or ensure the document declares an absolute "servers" entry.`)}async#o(){let t=this.#n.spec;if(t===void 0)throw Error(`OpenAPI connection "${this.#n.connectionName}" is missing its "spec" source.`);if(typeof t!=`string`)return t;let n;try{n=await fetch(t,{headers:{accept:`application/json, application/yaml, text/yaml, */*`}})}catch(e){throw Error(`OpenAPI connection "${this.#n.connectionName}" failed to fetch its spec from "${t}": ${String(e)}`)}if(!n.ok)throw Error(`OpenAPI connection "${this.#n.connectionName}" failed to fetch its spec from "${t}": HTTP ${n.status}.`);let r=await n.text(),i;try{i=parseSpecDocument(r)}catch(e){throw Error(`OpenAPI connection "${this.#n.connectionName}" spec at "${t}" could not be parsed as JSON or YAML: ${String(e)}`)}if(!isObject(i))throw Error(`OpenAPI connection "${this.#n.connectionName}" spec at "${t}" is not an OpenAPI document object.`);return i}#s(t){let n=t.paths;if(!isObject(n))return[];let r=[],i=new Set;for(let[u,d]of Object.entries(n)){if(!isObject(d))continue;let n=isArray(d.parameters)?d.parameters:[];for(let f of HTTP_METHODS){let a=d[f];if(!isObject(a))continue;let p=uniqueName(operationName(a,f,u),i),m=isArray(a.parameters)?a.parameters:[],h=this.#c(t,[...n,...m]),g=this.#l(t,a.requestBody);r.push({toolName:p,method:f,pathTemplate:u,description:operationDescription(a),parameters:h,requestBody:g,inputSchema:buildInputSchema(h,g),security:resolveSecurity(t,a)})}}return r}#c(t,n){let r=[];for(let i of n){let n=isObject(i)?deref(t,i):i;if(!isObject(n))continue;let a=n.in;if(a!==`path`&&a!==`query`&&a!==`header`&&a!==`cookie`||typeof n.name!=`string`)continue;let o=isObject(n.schema)?derefSchema(t,n.schema):{};r.push({name:n.name,location:a,required:n.required===!0||a===`path`,schema:o,description:typeof n.description==`string`?n.description:void 0})}return r}#l(t,n){if(!isObject(n))return;let r=deref(t,n);if(!isObject(r)||!isObject(r.content))return;let i=r.content,a=`application/json`in i?`application/json`:Object.keys(i)[0];if(a===void 0)return;let o=i[a],s=isObject(o)&&isObject(o.schema)?derefSchema(t,o.schema):{};return{required:r.required===!0,contentType:a,schema:s}}async#u(e,t,n){let r=await resolveHeaders(this.#n),a=e.pathTemplate,o=new URLSearchParams,s=[];for(let t of e.parameters){let e=n[t.name];e!=null&&(t.location===`path`?a=a.replace(`{${t.name}}`,encodeURIComponent(String(e))):t.location===`query`?appendQuery(o,t.name,e):t.location===`cookie`?s.push(`${t.name}=${encodeURIComponent(String(e))}`):r[t.name]=String(e))}if(applySecurity(e.security,this.#n,r,o,s),s.length>0){let e=r.cookie??r.Cookie;delete r.Cookie,r.cookie=[e,...s].filter(e=>!!e).join(`; `)}let c=new URL(joinPath(t,a));c.search=o.toString();let l;e.requestBody!==void 0&&n.body!==void 0&&(l=JSON.stringify(n.body),r[`content-type`]=e.requestBody.contentType);let u=await fetch(c,{method:e.method.toUpperCase(),headers:r,body:l});return{status:u.status,statusText:u.statusText,body:await readResponseBody(u)}}};function appendQuery(e,t,n){if(isArray(n)){for(let r of n)e.append(t,String(r));return}e.append(t,String(n))}function joinPath(e,t){return`${e.endsWith(`/`)?e.slice(0,-1):e}${t.startsWith(`/`)?t:`/${t}`}`}async function readResponseBody(e){let t=await e.text();if(t.length===0)return null;if((e.headers.get(`content-type`)??``).includes(`application/json`))try{return JSON.parse(t)}catch{return t}return t}export{OpenApiConnectionClient};
@@ -0,0 +1,30 @@
1
+ import type { OpenApiParameter, OpenApiRequestBody } from "#runtime/connections/openapi-schema.js";
2
+ import type { SecurityPlacement } from "#runtime/connections/openapi-security.js";
3
+ /** HTTP methods OpenAPI defines operations for. */
4
+ export declare const HTTP_METHODS: readonly ["get", "put", "post", "delete", "patch", "head", "options"];
5
+ export type HttpMethod = (typeof HTTP_METHODS)[number];
6
+ /** Internal descriptor for one resolved OpenAPI operation. */
7
+ export interface OpenApiOperation {
8
+ readonly toolName: string;
9
+ readonly method: HttpMethod;
10
+ readonly pathTemplate: string;
11
+ readonly description: string;
12
+ readonly parameters: readonly OpenApiParameter[];
13
+ readonly requestBody: OpenApiRequestBody | undefined;
14
+ readonly inputSchema: Record<string, unknown>;
15
+ readonly security: SecurityPlacement | undefined;
16
+ }
17
+ /**
18
+ * Derives a tool name for an operation that is legal for model providers.
19
+ *
20
+ * Provider tool-name rules (Anthropic, OpenAI) only permit
21
+ * `[a-zA-Z0-9_-]`, so an `operationId` is sanitized rather than used
22
+ * verbatim — characters like dots or slashes (common in real specs)
23
+ * would otherwise be rejected and break the whole connection. Operations
24
+ * without an `operationId` get a deterministic `<method>_<path>` name.
25
+ */
26
+ export declare function operationName(operation: Record<string, unknown>, method: HttpMethod, pathTemplate: string): string;
27
+ /** Disambiguates a tool name against names already used in the same connection. */
28
+ export declare function uniqueName(name: string, used: Set<string>): string;
29
+ /** Picks a human description for an operation, preferring `summary` over `description`. */
30
+ export declare function operationDescription(operation: Record<string, unknown>): string;
@@ -0,0 +1 @@
1
+ const HTTP_METHODS=[`get`,`put`,`post`,`delete`,`patch`,`head`,`options`];function operationName(e,t,n){if(typeof e.operationId==`string`&&e.operationId.length>0){let t=sanitizeToolName(e.operationId);if(t.length>0)return t}return sanitizeToolName(`${t}_${n.replace(/[{}]/g,``).replace(/[^a-zA-Z0-9]+/g,`_`).replace(/^_+|_+$/g,``)}`)}function sanitizeToolName(e){return e.replace(/[^a-zA-Z0-9_-]+/g,`_`).replace(/^[_-]+|[_-]+$/g,``).slice(0,64)}function uniqueName(e,t){let n=e,r=2;for(;t.has(n);)n=`${e}_${r}`,r+=1;return t.add(n),n}function operationDescription(e){return typeof e.summary==`string`&&e.summary.length>0?e.summary:typeof e.description==`string`?e.description:``}export{HTTP_METHODS,operationDescription,operationName,uniqueName};
@@ -0,0 +1,39 @@
1
+ /** A path, query, header, or cookie parameter resolved from an operation. */
2
+ export interface OpenApiParameter {
3
+ readonly name: string;
4
+ readonly location: "path" | "query" | "header" | "cookie";
5
+ readonly required: boolean;
6
+ readonly schema: Record<string, unknown>;
7
+ readonly description?: string;
8
+ }
9
+ /** A request body resolved from an operation. */
10
+ export interface OpenApiRequestBody {
11
+ readonly required: boolean;
12
+ readonly contentType: string;
13
+ readonly schema: Record<string, unknown>;
14
+ }
15
+ /**
16
+ * Builds the combined JSON Schema the model fills in: each path, query,
17
+ * and header parameter becomes a top-level property; the request body
18
+ * (when present) is nested under `body`.
19
+ */
20
+ export declare function buildInputSchema(parameters: readonly OpenApiParameter[], requestBody: OpenApiRequestBody | undefined): Record<string, unknown>;
21
+ /** Resolves a single `$ref` node one hop; returns the node unchanged otherwise. */
22
+ export declare function deref(document: Record<string, unknown>, node: Record<string, unknown>): unknown;
23
+ /**
24
+ * Deeply resolves local `$ref` pointers in a JSON Schema, truncating
25
+ * `$ref` cycles and over-deep nesting so the result stays finite and
26
+ * serializable.
27
+ *
28
+ * Truncation only ever happens at an **object** node — which is always a
29
+ * schema position — by replacing it with an empty schema (`{}`). Scalars
30
+ * (`type` strings, `required` entries, `enum` values) pass through
31
+ * unchanged and arrays are always preserved as arrays, so array-valued
32
+ * keywords like `oneOf`/`anyOf`/`allOf` and `type: [..., "null"]` keep
33
+ * their shape. This keeps the output valid JSON Schema (draft 2020-12)
34
+ * for strict model providers, while the depth bound prevents recursive
35
+ * specs (e.g. Notion blocks) from expanding without limit.
36
+ */
37
+ export declare function derefSchema(document: Record<string, unknown>, node: unknown, depth?: number, seen?: ReadonlySet<string>): unknown;
38
+ /** Narrows `unknown` to a readonly array, used pervasively when duck-typing spec nodes. */
39
+ export declare function isArray(value: unknown): value is readonly unknown[];
@@ -0,0 +1 @@
1
+ import{isObject}from"#shared/guards.js";function buildInputSchema(e,t){let n={},r=[];for(let t of e)n[t.name]=t.description===void 0?t.schema:{...t.schema,description:t.description},t.required&&r.push(t.name);t!==void 0&&(n.body=t.schema,t.required&&r.push(`body`));let i={type:`object`,properties:n};return r.length>0&&(i.required=r),i}function deref(e,t){return typeof t.$ref==`string`?resolveRef(e,t.$ref)??{}:t}function derefSchema(t,n,r=0,i=new Set){if(isArray(n))return n.map(e=>derefSchema(t,e,r+1,i));if(!isObject(n))return n;if(r>12)return{};if(typeof n.$ref==`string`){if(i.has(n.$ref))return{};let e=resolveRef(t,n.$ref);return e===void 0?{}:derefSchema(t,e,r+1,new Set([...i,n.$ref]))}let a={};for(let[e,o]of Object.entries(n))a[e]=derefSchema(t,o,r+1,i);return normalizeSchemaType(a),normalizeNullable(a),a}const VALID_JSON_SCHEMA_TYPES=new Set([`string`,`number`,`integer`,`boolean`,`object`,`array`,`null`]);function normalizeSchemaType(e){let n=e.type;if(typeof n==`string`&&!VALID_JSON_SCHEMA_TYPES.has(n))delete e.type;else if(isArray(n)){let r=n.filter(e=>typeof e==`string`&&VALID_JSON_SCHEMA_TYPES.has(e));r.length===0?delete e.type:e.type=r}}function normalizeNullable(e){if(!(`nullable`in e))return;let t=e.nullable===!0;if(delete e.nullable,!t)return;let n=e.type;typeof n==`string`?n!==`null`&&(e.type=[n,`null`]):isArray(n)?n.includes(`null`)||(e.type=[...n,`null`]):isArray(e.enum)&&!e.enum.includes(null)&&(e.enum=[...e.enum,null])}function resolveRef(t,n){if(!n.startsWith(`#/`))return;let r=n.slice(2).split(`/`).map(e=>e.replace(/~1/g,`/`).replace(/~0/g,`~`)),i=t;for(let t of r){if(!isObject(i))return;i=i[t]}return i}function isArray(e){return Array.isArray(e)}export{buildInputSchema,deref,derefSchema,isArray};