From c8e4f325d78c6626ed94f14028a663a3b56256d3 Mon Sep 17 00:00:00 2001 From: Domenic Denicola Date: Mon, 10 Mar 2025 15:11:47 +0900 Subject: [PATCH] Remove the "AI" namespace Closes #9. Also abstract out a few more steps for availability and creation, so that we don't have to repeat them for every spec. --- README.md | 34 ++-- index.bs | 538 +++++++++++++++++++++++++++--------------------------- 2 files changed, 281 insertions(+), 291 deletions(-) diff --git a/README.md b/README.md index 6dbe922..a791428 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Both of these potential goals could pose challenges to interoperability, so we w All three APIs share the same format: create a summarizer/writer/rewriter object customized as necessary, and call its appropriate method: ```js -const summarizer = await ai.summarizer.create({ +const summarizer = await Summarizer.create({ sharedContext: "An article from the Daily Economic News magazine", type: "headline", length: "short" @@ -93,7 +93,7 @@ const summary = await summarizer.summarize(articleEl.textContent, { ``` ```js -const writer = await ai.writer.create({ +const writer = await Writer.create({ tone: "formal" }); @@ -103,7 +103,7 @@ const result = await writer.write( ``` ```js -const rewriter = await ai.rewriter.create({ +const rewriter = await Rewriter.create({ sharedContext: "A review for the Flux Capacitor 3000 from TimeMachines Inc." }); @@ -117,7 +117,7 @@ const result = await rewriter.rewrite(reviewEl.textContent, { All three of the APIs support streaming output, via counterpart methods `summarizeStreaming()` / `writeStreaming()` / `rewriteStreaming()` that return `ReadableStream`s of strings. A sample usage would be: ```js -const writer = await ai.writer.create({ tone: "formal", length: "long" }); +const writer = await Writer.create({ tone: "formal", length: "long" }); const stream = await writer.writeStreaming( "A draft for an inquiry to my bank about how to enable wire transfers on my account" @@ -133,7 +133,7 @@ for (const chunk of stream) { A created summarizer/writer/rewriter object can be used multiple times. **The only shared state is the initial configuration options**; the inputs do not build on each other. (See more discussion [below](#one-shot-functions-instead-of-summarizer--writer--rewriter-objects).) ```js -const summarizer = await ai.summarize.create({ type: "tl;dr" }); +const summarizer = await Summarizer.create({ type: "tl;dr" }); const reviewSummaries = await Promise.all( Array.from( @@ -150,7 +150,7 @@ The default behavior for the summarizer/writer/rewriter objects assumes that the It's better practice, if possible, to supply the `create()` method with information about the expected languages in use. This allows the implementation to download any necessary supporting material, such as fine-tunings or safety-checking models, and to immediately reject the promise returned by `create()` if the web developer needs to use languages that the browser is not capable of supporting: ```js -const summarizer = await ai.summarize.create({ +const summarizer = await Summarizer.create({ type: "key-points", expectedInputLanguages: ["ja", "ko"], expectedContextLanguages: ["en", "ja", "ko"], @@ -189,7 +189,7 @@ Whenever any API call fails due to too-large input, it is rejected with a `Quota This allows detecting failures due to overlarge inputs and giving clear feedback to the user, with code such as the following: ```js -const summarizer = await ai.summarizer.create(); +const summarizer = await Summarizer.create(); try { console.log(await summarizer.summarize(potentiallyLargeInput)); @@ -205,16 +205,16 @@ try { Note that all of the following methods can reject (or error the relevant stream) with this type of exception: -* `ai.summarizer.create()`, if `sharedContext` is too large; +* `Summarizer.create()`, if `sharedContext` is too large; -* `ai.summarizer.summarize()`/`summarizeStreaming()`, if the combination of the creation-time `sharedContext`, the current method call's `input` argument, and the current method call's `context` is too large; +* `summarize()`/`summarizeStreaming()`, if the combination of the creation-time `sharedContext`, the current method call's `input` argument, and the current method call's `context` is too large; * Similarly for writer creation / writing, and rewriter creation / rewriting. In some cases, instead of providing errors after the fact, the developer needs to be able to communicate to the user how close they are to the limit. For this, they can use the `inputQuota` property and the `measureInputUsage()` method on the summarizer/writer/rewriter objects: ```js -const rewriter = await ai.rewriter.create(); +const rewriter = await Rewriter.create(); meterEl.max = rewriter.inputQuota; textbox.addEventListener("input", () => { @@ -264,7 +264,7 @@ An example usage is the following: ```js const options = { type: "teaser", expectedInputLanguages: ["ja"] }; -const availability = await ai.summarizer.availability(options); +const availability = await Summarizer.availability(options); if (availability !== "unavailable") { // We're good! Let's do the summarization using the built-in API. @@ -272,7 +272,7 @@ if (availability !== "unavailable") { console.log("Sit tight, we need to do some downloading..."); } - const summarizer = await ai.summarizer.create(options); + const summarizer = await Summarizer.create(options); console.log(await summarizer.summarize(articleEl.textContent)); } else { // Either the API overall, or the combination of teaser + Japanese input, is not available. @@ -286,7 +286,7 @@ if (availability !== "unavailable") { For cases where using the API is only possible after a download, you can monitor the download progress (e.g. in order to show your users a progress bar) using code such as the following: ```js -const writer = await ai.writer.create({ +const writer = await Writer.create({ ...otherOptions, monitor(m) { m.addEventListener("downloadprogress", e => { @@ -322,7 +322,7 @@ Each API comes equipped with a couple of `signal` options that accept `AbortSign const controller = new AbortController(); stopButton.onclick = () => controller.abort(); -const rewriter = await ai.rewriter.create({ signal: controller.signal }); +const rewriter = await Rewriter.create({ signal: controller.signal }); await rewriter.rewrite(document.body.textContent, { signal: controller.signal }); ``` @@ -393,12 +393,6 @@ Although the APIs contain support for streaming output, they don't support strea However, we believe that streaming input would not be a good fit for these APIs. Attempting to summarize or rewrite input as more input streams in will likely result in multiple wasteful rounds of revision. The underlying language model technology does not support streaming input, so the implementation would be buffering the input stream anyway, then repeatedly feeding new versions of the buffered text to the language model. If a developer wants to achieve such results, they can do so themselves, at the cost of writing code which makes the wastefulness of the operation more obvious. Developers can also customize such code, e.g. by only asking for new summaries every 5 seconds (or whatever interval makes the most sense for their use case). -### Alternative API spellings - -In [the TAG review of the translation and language detection APIs](https://github.com/w3ctag/design-reviews/issues/948), some TAG members suggested slightly different patterns than the `ai.something.create()` pattern, such as `AISomething.create()` or `Something.create()`. - -We are open to such surface-level tweaks to the API entry points, and intend to gather more data from web developers on what they find more understandable and clear. - ### Directly exposing a "prompt API" The same team that is working on these APIs is also prototyping an experimental [prompt API](https://github.com/webmachinelearning/prompt-api/). A natural question is how these efforts related. Couldn't one easily accomplish summarization/writing/rewriting by directly prompting a language model, thus making these higher-level APIs redundant? diff --git a/index.bs b/index.bs index b8bca9d..5eeff2c 100644 --- a/index.bs +++ b/index.bs @@ -29,6 +29,8 @@ urlPrefix: https://tc39.es/ecma402/; spec: ECMA-402 urlPrefix: https://tc39.es/ecma262/; spec: ECMA-262 type: abstract-op text: floor; url: eqn-floor + type: dfn + text: current realm; url: current-realm urlPrefix: https://whatpr.org/webidl/1465.html; spec: WEBIDL type: interface text: QuotaExceededError; url: quotaexceedederror @@ -53,39 +55,28 @@ For now, see the [explainer](https://github.com/webmachinelearning/writing-assis

Shared APIs

-partial interface WindowOrWorkerGlobalScope { - [Replaceable, SecureContext] readonly attribute AI ai; -}; - -[Exposed=(Window,Worker), SecureContext] -interface AI {}; - [Exposed=(Window,Worker), SecureContext] -interface AICreateMonitor : EventTarget { +interface CreateMonitor : EventTarget { attribute EventHandler ondownloadprogress; }; -callback AICreateMonitorCallback = undefined (AICreateMonitor monitor); +callback CreateMonitorCallback = undefined (CreateMonitor monitor); -enum AIAvailability { +enum Availability { "unavailable", "downloadable", "downloading", "available" }; -interface mixin AIDestroyable { +interface mixin DestroyableModel { undefined destroy(); }; -Every {{WindowOrWorkerGlobalScope}} has an AI namespace, an {{AI}} object. Upon creation of the {{WindowOrWorkerGlobalScope}} object, its [=WindowOrWorkerGlobalScope/AI namespace=] must be set to a [=new=] {{AI}} object created in the {{WindowOrWorkerGlobalScope}} object's [=relevant realm=]. - -The ai getter steps are to return [=this=]'s [=WindowOrWorkerGlobalScope/AI namespace=]. -
-The following are the [=event handlers=] (and their corresponding [=event handler event types=]) that must be supported, as [=event handler IDL attributes=], by all {{AICreateMonitor}} objects: +The following are the [=event handlers=] (and their corresponding [=event handler event types=]) that must be supported, as [=event handler IDL attributes=], by all {{CreateMonitor}} objects: @@ -94,34 +85,34 @@ The following are the [=event handlers=] (and their corresponding [=event handle -
[=Event handler event type=]
ondownloadprogress - downloadprogress + ondownloadprogress + downloadprogress

-Every [=interface=] [=interface/including=] the {{AIDestroyable}} interface mixin has a destruction abort controller, an {{AbortController}}, set by the [=initialize as a destroyable=] algorithm. +Every [=interface=] [=interface/including=] the {{DestroyableModel}} interface mixin has a destruction abort controller, an {{AbortController}}, set by the [=initialize as a destroyable=] algorithm. -

The [=AIDestroyable/destruction abort controller=] is only used internally, as a way of tracking calls to {{AIDestroyable/destroy()}}. Since it is easy to combine multiple {{AbortSignal}}s using [=create a dependent abort signal=], this lets us centralize handling of any {{AbortSignal}} the web developer provides to specific method calls, with any calls to {{AIDestroyable/destroy()}}. +

The [=DestroyableModel/destruction abort controller=] is only used internally, as a way of tracking calls to {{DestroyableModel/destroy()}}. Since it is easy to combine multiple {{AbortSignal}}s using [=create a dependent abort signal=], this lets us centralize handling of any {{AbortSignal}} the web developer provides to specific method calls, with any calls to {{DestroyableModel/destroy()}}.

- To initialize as a destroyable an {{AIDestroyable}} object |destroyable|: + To initialize as a destroyable an {{DestroyableModel}} object |destroyable|: 1. Let |controller| be a [=new=] {{AbortController}} created in |destroyable|'s [=relevant realm=]. 1. Set |controller|'s [=AbortController/signal=] to a [=new=] {{AbortSignal}} created in |destroyable|'s [=relevant realm=]. - 1. Set |destroyable|'s [=AIDestroyable/destruction abort controller=] to |controller|. + 1. Set |destroyable|'s [=DestroyableModel/destruction abort controller=] to |controller|.
-

The destroy() method steps are to [=AIDestroyable/destroy=] [=this=] given a new "{{AbortError}}" {{DOMException}}. +

The destroy() method steps are to [=DestroyableModel/destroy=] [=this=] given a new "{{AbortError}}" {{DOMException}}.

- To destroy an {{AIDestroyable}} |destroyable|, given a JavaScript value |reason|: + To destroy an {{DestroyableModel}} |destroyable|, given a JavaScript value |reason|: - 1. [=AbortController/Signal abort=] given |destroyable|'s [=AIDestroyable/destruction abort controller=] and |reason|. + 1. [=AbortController/Signal abort=] given |destroyable|'s [=DestroyableModel/destruction abort controller=] and |reason|. 1. The user agent should release any resources associated with |destroyable|, such as AI models loaded to support its operation, as long as those resources are not needed for other ongoing operations.
@@ -130,31 +121,24 @@ Every [=interface=] [=interface/including=] the {{AIDestroyable}} interface mixi

The summarizer API

-partial interface AI { - readonly attribute AISummarizerFactory summarizer; -}; - [Exposed=(Window,Worker), SecureContext] -interface AISummarizerFactory { - Promise<AISummarizer> create(optional AISummarizerCreateOptions options = {}); - Promise<AIAvailability> availability(optional AISummarizerCreateCoreOptions options = {}); -}; +interface Summarizer { + static Promise<Summarizer> create(optional SummarizerCreateOptions options = {}); + static Promise<Availability> availability(optional SummarizerCreateCoreOptions options = {}); -[Exposed=(Window,Worker), SecureContext] -interface AISummarizer { Promise<DOMString> summarize( DOMString input, - optional AISummarizerSummarizeOptions options = {} + optional SummarizerSummarizeOptions options = {} ); ReadableStream summarizeStreaming( DOMString input, - optional AISummarizerSummarizeOptions options = {} + optional SummarizerSummarizeOptions options = {} ); readonly attribute DOMString sharedContext; - readonly attribute AISummarizerType type; - readonly attribute AISummarizerFormat format; - readonly attribute AISummarizerLength length; + readonly attribute SummarizerType type; + readonly attribute SummarizerFormat format; + readonly attribute SummarizerLength length; readonly attribute FrozenArray<DOMString>? expectedInputLanguages; readonly attribute FrozenArray<DOMString>? expectedContextLanguages; @@ -162,71 +146,59 @@ interface AISummarizer { Promise<double> measureInputUsage( DOMString input, - optional AISummarizerSummarizeOptions options = {} + optional SummarizerSummarizeOptions options = {} ); readonly attribute unrestricted double inputQuota; }; -AISummarizer includes AIDestroyable; +Summarizer includes DestroyableModel; -dictionary AISummarizerCreateCoreOptions { - AISummarizerType type = "key-points"; - AISummarizerFormat format = "markdown"; - AISummarizerLength length = "short"; +dictionary SummarizerCreateCoreOptions { + SummarizerType type = "key-points"; + SummarizerFormat format = "markdown"; + SummarizerLength length = "short"; sequence<DOMString> expectedInputLanguages; sequence<DOMString> expectedContextLanguages; DOMString outputLanguage; }; -dictionary AISummarizerCreateOptions : AISummarizerCreateCoreOptions { +dictionary SummarizerCreateOptions : SummarizerCreateCoreOptions { AbortSignal signal; - AICreateMonitorCallback monitor; + CreateMonitorCallback monitor; DOMString sharedContext; }; -dictionary AISummarizerSummarizeOptions { +dictionary SummarizerSummarizeOptions { AbortSignal signal; DOMString context; }; -enum AISummarizerType { "tl;dr", "teaser", "key-points", "headline" }; -enum AISummarizerFormat { "plain-text", "markdown" }; -enum AISummarizerLength { "short", "medium", "long" }; +enum SummarizerType { "tl;dr", "teaser", "key-points", "headline" }; +enum SummarizerFormat { "plain-text", "markdown" }; +enum SummarizerLength { "short", "medium", "long" }; -Every {{AI}} has an summarizer factory, an {{AISummarizerFactory}} object. Upon creation of the {{AI}} object, its [=AI/summarizer factory=] must be set to a [=new=] {{AISummarizerFactory}} object created in the {{AI}} object's [=relevant realm=]. - -The summarizer getter steps are to return [=this=]'s [=AI/summarizer factory=]. -

Creation

- The create(|options|) method steps are: - - 1. If [=this=]'s [=relevant global object=] is a {{Window}} whose [=associated Document=] is not [=Document/fully active=], then return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. - - 1. If |options|["{{AISummarizerCreateOptions/signal}}"] [=map/exists=] and is [=AbortSignal/aborted=], then return [=a promise rejected with=] |options|["{{AISummarizerCreateOptions/signal}}"]'s [=AbortSignal/abort reason=]. - - 1. [=Validate and canonicalize summarizer options=] given |options|. If this throws an exception |e|, catch it, and return [=a promise rejected with=] |e|. + The static create(|options|) method steps are: -

This can mutate |options|. - - 1. Return the result of [=creating an AI model object=] given [=this=]'s [=relevant realm=], |options|, [=computing summarizer options availability=], [=download the summarization model=], [=initialize the summarization model=], and [=create a summarizer object=]. + 1. Return the result of [=creating an AI model object=] given |options|, [=validate and canonicalize summarizer options=], [=computing summarizer options availability=], [=download the summarization model=], [=initialize the summarization model=], and [=create a summarizer object=].

- To validate and canonicalize summarizer options given an {{AISummarizerCreateCoreOptions}} |options|, perform the following steps. They mutate |options| in place to canonicalize and deduplicate language tags, and throw a {{TypeError}} if any are invalid. + To validate and canonicalize summarizer options given an {{SummarizerCreateCoreOptions}} |options|, perform the following steps. They mutate |options| in place to canonicalize and deduplicate language tags, and throw a {{TypeError}} if any are invalid. - 1. [=Validate and canonicalize language tags=] given |options| and "{{AISummarizerCreateCoreOptions/expectedInputLanguages}}". + 1. [=Validate and canonicalize language tags=] given |options| and "{{SummarizerCreateCoreOptions/expectedInputLanguages}}". - 1. [=Validate and canonicalize language tags=] given |options| and "{{AISummarizerCreateCoreOptions/expectedContextLanguages}}". + 1. [=Validate and canonicalize language tags=] given |options| and "{{SummarizerCreateCoreOptions/expectedContextLanguages}}". - 1. [=Validate and canonicalize language tags=] given |options| and "{{AISummarizerCreateCoreOptions/outputLanguage}}". + 1. [=Validate and canonicalize language tags=] given |options| and "{{SummarizerCreateCoreOptions/outputLanguage}}".
- To download the summarization model, given an {{AISummarizerCreateCoreOptions}} |options|: + To download the summarization model, given an {{SummarizerCreateCoreOptions}} |options|: 1. [=Assert=]: these steps are running [=in parallel=]. @@ -238,13 +210,13 @@ The summarizer getter steps are to return [=this=]
- To initialize the summarization model, given an {{AISummarizerCreateOptions}} |options|: + To initialize the summarization model, given an {{SummarizerCreateOptions}} |options|: 1. [=Assert=]: these steps are running [=in parallel=]. 1. Perform any necessary initialization operations for the AI model backing the user agent's summarization capabilities. - This could include loading the model into memory, loading |options|["{{AISummarizerCreateOptions/sharedContext}}"] into the model's context window, or loading any fine-tunings necessary to support the other options expressed by |options|. + This could include loading the model into memory, loading |options|["{{SummarizerCreateOptions/sharedContext}}"] into the model's context window, or loading any fine-tunings necessary to support the other options expressed by |options|. 1. If initialization failed because the process of loading |options| resulted in using up all of the model's input quota, then: @@ -264,39 +236,39 @@ The summarizer getter steps are to return [=this=]
- To create a summarizer object, given a [=ECMAScript/realm=] |realm| and an {{AISummarizerCreateOptions}} |options|: + To create a summarizer object, given a [=ECMAScript/realm=] |realm| and an {{SummarizerCreateOptions}} |options|: 1. [=Assert=]: these steps are running on |realm|'s [=ECMAScript/surrounding agent=]'s [=agent/event loop=]. 1. Let |inputQuota| be the amount of input quota that is available to the user agent for future [=summarize|summarization=] operations. (This value is [=implementation-defined=], and may be +∞ if there are no specific limits beyond, e.g., the user's memory, or the limits of JavaScript strings.) -

For implementations that do not have infinite quota, this will generally vary for each {{AISummarizer}} instance, depending on how much input quota was used by encoding |options|. See this note on that encoding. +

For implementations that do not have infinite quota, this will generally vary for each {{Summarizer}} instance, depending on how much input quota was used by encoding |options|. See this note on that encoding. - 1. Return a new {{AISummarizer}} object, created in |realm|, with + 1. Return a new {{Summarizer}} object, created in |realm|, with

- : [=AISummarizer/shared context=] - :: |options|["{{AISummarizerCreateOptions/sharedContext}}"] if it [=map/exists=]; otherwise null + : [=Summarizer/shared context=] + :: |options|["{{SummarizerCreateOptions/sharedContext}}"] if it [=map/exists=]; otherwise null - : [=AISummarizer/summary type=] - :: |options|["{{AISummarizerCreateCoreOptions/type}}"] + : [=Summarizer/summary type=] + :: |options|["{{SummarizerCreateCoreOptions/type}}"] - : [=AISummarizer/summary format=] - :: |options|["{{AISummarizerCreateCoreOptions/format}}"] + : [=Summarizer/summary format=] + :: |options|["{{SummarizerCreateCoreOptions/format}}"] - : [=AISummarizer/summary length=] - :: |options|["{{AISummarizerCreateCoreOptions/length}}"] + : [=Summarizer/summary length=] + :: |options|["{{SummarizerCreateCoreOptions/length}}"] - : [=AISummarizer/expected input languages=] - :: the result of [=creating a frozen array=] given |options|["{{AISummarizerCreateCoreOptions/expectedInputLanguages}}"] if it [=set/is empty|is not empty=]; otherwise null + : [=Summarizer/expected input languages=] + :: the result of [=creating a frozen array=] given |options|["{{SummarizerCreateCoreOptions/expectedInputLanguages}}"] if it [=set/is empty|is not empty=]; otherwise null - : [=AISummarizer/expected context languages=] - :: the result of [=creating a frozen array=] given |options|["{{AISummarizerCreateCoreOptions/expectedContextLanguages}}"] if it [=set/is empty|is not empty=]; otherwise null + : [=Summarizer/expected context languages=] + :: the result of [=creating a frozen array=] given |options|["{{SummarizerCreateCoreOptions/expectedContextLanguages}}"] if it [=set/is empty|is not empty=]; otherwise null - : [=AISummarizer/output language=] - :: |options|["{{AISummarizerCreateCoreOptions/outputLanguage}}"] if it [=map/exists=]; otherwise null + : [=Summarizer/output language=] + :: |options|["{{SummarizerCreateCoreOptions/outputLanguage}}"] if it [=map/exists=]; otherwise null - : [=AISummarizer/input quota=] + : [=Summarizer/input quota=] :: |inputQuota|
@@ -304,57 +276,43 @@ The summarizer getter steps are to return [=this=]

Availability

- The availability(|options|) method steps are: - - 1. If [=this=]'s [=relevant global object=] is a {{Window}} whose [=associated Document=] is not [=Document/fully active=], then return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. - - 1. [=Validate and canonicalize summarizer options=] given |options|. - - 1. Let |promise| be [=a new promise=] created in [=this=]'s [=relevant realm=]. - - 1. [=In parallel=]: - - 1. Let |availability| be the result of [=computing summarizer options availability=] given |options|. + The static availability(|options|) method steps are: - 1. [=Queue a global task=] on the [=AI task source=] given [=this=]'s [=relevant global object=] to perform the following steps: - - 1. If |availability| is null, then [=reject=] |promise| with an "{{UnknownError}}" {{DOMException}}. - - 1. Otherwise, [=resolve=] |promise| with |availability|. + 1. Return the result of [=computing AI model availability=] given |options|, [=validate and canonicalize summarizer options=], and [=compute summarizer options availability=].
- To compute summarizer options availability given an {{AISummarizerCreateCoreOptions}} |options|, perform the following steps. They return either an {{AIAvailability}} value or null, and they mutate |options| in place to update language tags to their best-fit matches. + To compute summarizer options availability given an {{SummarizerCreateCoreOptions}} |options|, perform the following steps. They return either an {{Availability}} value or null, and they mutate |options| in place to update language tags to their best-fit matches. 1. [=Assert=]: this algorithm is running [=in parallel=]. - 1. Let |availability| be the [=summarizer non-language options availability=] given |options|["{{AISummarizerCreateCoreOptions/type}}"], |options|["{{AISummarizerCreateCoreOptions/format}}"], and |options|["{{AISummarizerCreateCoreOptions/length}}"]. + 1. Let |availability| be the [=summarizer non-language options availability=] given |options|["{{SummarizerCreateCoreOptions/type}}"], |options|["{{SummarizerCreateCoreOptions/format}}"], and |options|["{{SummarizerCreateCoreOptions/length}}"]. 1. Let |languageAvailabilities| be the [=summarizer language availabilities=]. 1. If |languageAvailabilities| is null, then return null. - 1. Let |inputLanguageAvailability| be the result of [=computing summarizer language availability=] given |options|["{{AISummarizerCreateCoreOptions/expectedInputLanguages}}"] and |languageAvailabilities|'s [=language availabilities/input languages=]. + 1. Let |inputLanguageAvailability| be the result of [=computing summarizer language availability=] given |options|["{{SummarizerCreateCoreOptions/expectedInputLanguages}}"] and |languageAvailabilities|'s [=language availabilities/input languages=]. - 1. Let |contextLanguagesAvailability| be the result of [=computing summarizer language availability=] given |options|["{{AISummarizerCreateCoreOptions/expectedContextLanguages}}"] and |languageAvailabilities|'s [=language availabilities/context languages=]. + 1. Let |contextLanguagesAvailability| be the result of [=computing summarizer language availability=] given |options|["{{SummarizerCreateCoreOptions/expectedContextLanguages}}"] and |languageAvailabilities|'s [=language availabilities/context languages=]. - 1. Let |outputLanguagesList| be « |options|["{{AISummarizerCreateCoreOptions/outputLanguage}}"] ». + 1. Let |outputLanguagesList| be « |options|["{{SummarizerCreateCoreOptions/outputLanguage}}"] ». 1. Let |outputLanguageAvailability| be the result of [=computing summarizer language availability=] given |outputLanguagesList| and |languageAvailabilities|'s [=language availabilities/output languages=]. - 1. Set |options|["{{AISummarizerCreateCoreOptions/outputLanguage}}"] to |outputLanguagesList|[0]. + 1. Set |options|["{{SummarizerCreateCoreOptions/outputLanguage}}"] to |outputLanguagesList|[0]. - 1. Return the [=AIAvailability/minimum availability=] given « |availability|, |inputLanguageAvailability|, |contextLanguagesAvailability|, |outputLanguageAvailability| ». + 1. Return the [=Availability/minimum availability=] given « |availability|, |inputLanguageAvailability|, |contextLanguagesAvailability|, |outputLanguageAvailability| ».
- To compute summarizer language availability given an [=ordered set=] of strings |requestedLanguages| and a [=map=] from {{AIAvailability}} values to [=sets=] of strings |availabilities|, perform the following steps. They return an {{AIAvailability}} value, and they mutate |requestedLanguages| in place to update language tags to their best-fit matches. + To compute summarizer language availability given an [=ordered set=] of strings |requestedLanguages| and a [=map=] from {{Availability}} values to [=sets=] of strings |availabilities|, perform the following steps. They return an {{Availability}} value, and they mutate |requestedLanguages| in place to update language tags to their best-fit matches. - 1. Let |availability| be "{{AIAvailability/available}}". + 1. Let |availability| be "{{Availability/available}}". 1. [=set/For each=] |language| of |requestedLanguages|: - 1. [=list/For each=] |availabilityToCheck| of « "{{AIAvailability/available}}", "{{AIAvailability/downloading}}", "{{AIAvailability/downloadable}}" »: + 1. [=list/For each=] |availabilityToCheck| of « "{{Availability/available}}", "{{Availability/downloading}}", "{{Availability/downloadable}}" »: 1. Let |languagesWithThisAvailability| be |availabilities|[|availabilityToCheck|]. @@ -364,29 +322,29 @@ The summarizer getter steps are to return [=this=] 1. [=list/Replace=] |language| with |bestMatch|.\[[locale]] in |requestedLanguages|. - 1. Set |availability| to the [=AIAvailability/minimum availability=] given |availability| and |availabilityToCheck|. + 1. Set |availability| to the [=Availability/minimum availability=] given |availability| and |availabilityToCheck|. 1. [=iteration/Break=]. - 1. Return "{{AIAvailability/unavailable}}". + 1. Return "{{Availability/unavailable}}". 1. Return |availability|.
- The summarizer non-language options availability, given a {{AISummarizerType}} |type|, {{AISummarizerFormat}} |format|, and an {{AISummarizerLength}} |length|, is given by the following steps. They return an {{AIAvailability}} value or null. + The summarizer non-language options availability, given a {{SummarizerType}} |type|, {{SummarizerFormat}} |format|, and an {{SummarizerLength}} |length|, is given by the following steps. They return an {{Availability}} value or null. 1. [=Assert=]: this algorithm is running [=in parallel=]. 1. If there is some error attempting to determine whether the user agent supports summarizing text, which the user agent believes to be transient (such that re-querying the [=summarizer non-language options availability=] could stop producing such an error), then return null. - 1. If the user agent supports summarizing text into the type of summary described by |type|, in the format described by |format|, and with the length guidance given by |length| without performing any downloading operations, then return "{{AIAvailability/available}}". + 1. If the user agent supports summarizing text into the type of summary described by |type|, in the format described by |format|, and with the length guidance given by |length| without performing any downloading operations, then return "{{Availability/available}}". - 1. If the user agent believes it can summarize text according to |type|, |format|, and |length|, but only after finishing a download (e.g., of an AI model or fine-tuning) that is already ongoing, then return "{{AIAvailability/downloadable}}". + 1. If the user agent believes it can summarize text according to |type|, |format|, and |length|, but only after finishing a download (e.g., of an AI model or fine-tuning) that is already ongoing, then return "{{Availability/downloadable}}". - 1. If the user agent believes it can summarize text according to |type|, |format|, and |length|, but only after performing a download (e.g., of an AI model or fine-tuning), then return "{{AIAvailability/downloadable}}". + 1. If the user agent believes it can summarize text according to |type|, |format|, and |length|, but only after performing a download (e.g., of an AI model or fine-tuning), then return "{{Availability/downloadable}}". - 1. Otherwise, return "{{AIAvailability/unavailable}}". + 1. Otherwise, return "{{Availability/unavailable}}".
A language availabilities is a [=struct=] with the following [=struct/items=]: @@ -395,7 +353,7 @@ A language availabilities is a [=struct=] with the following [=struct * context languages * output languages -All of these [=struct/items=] are [=maps=] from {{AIAvailability}} values to [=sets=] of strings representing [=Unicode canonicalized locale identifiers=]. Their [=map/keys=] will always be one of "{{AIAvailability/downloading}}", "{{AIAvailability/downloadable}}", or "{{AIAvailability/available}}" (i.e., they will never be "{{AIAvailability/unavailable}}"). [[!ECMA-402]] +All of these [=struct/items=] are [=maps=] from {{Availability}} values to [=sets=] of strings representing [=Unicode canonicalized locale identifiers=]. Their [=map/keys=] will always be one of "{{Availability/downloading}}", "{{Availability/downloadable}}", or "{{Availability/available}}" (i.e., they will never be "{{Availability/unavailable}}"). [[!ECMA-402]]
The summarizer language availabilities are given by the following steps. They return a [=language availabilities=] or null. @@ -421,9 +379,9 @@ All of these [=struct/items=] are [=maps=] from {{AIAvailability}} values to [=s
A common setup seen in today's software is to support two types of written Chinese: "traditional Chinese" and "simplified Chinese". Let's suppose that the user agent supports summarizing text written in traditional Chinese with no downloads, and simplified Chinese after a download. - One way this could be implemented would be for [=summarizer language availabilities=] to return that "`zh-Hant`" is in the [=language availabilities/input languages=]["{{AIAvailability/available}}"] set, and "`zh`" and "`zh-Hans`" are in the [=language availabilities/input languages=]["{{AIAvailability/downloadable}}"] set. This return value conforms to the requirements of the [=language tag set completeness rules=], in ensuring that "`zh`" is present. Per the "should"-level guidance, the implementation has determined that "`zh`" belongs in the set of downloadable input languages, with "`zh-Hans`", instead of in the set of available input languages, with "`zh-Hant`". + One way this could be implemented would be for [=summarizer language availabilities=] to return that "`zh-Hant`" is in the [=language availabilities/input languages=]["{{Availability/available}}"] set, and "`zh`" and "`zh-Hans`" are in the [=language availabilities/input languages=]["{{Availability/downloadable}}"] set. This return value conforms to the requirements of the [=language tag set completeness rules=], in ensuring that "`zh`" is present. Per the "should"-level guidance, the implementation has determined that "`zh`" belongs in the set of downloadable input languages, with "`zh-Hans`", instead of in the set of available input languages, with "`zh-Hant`". - Combined with the use of [$LookupMatchingLocaleByBestFit$], this means {{AISummarizerFactory/availability()}} will give the following answers: + Combined with the use of [$LookupMatchingLocaleByBestFit$], this means {{Summarizer/availability()}} will give the following answers: function a(languageTag) { @@ -445,70 +403,70 @@ All of these [=struct/items=] are [=maps=] from {{AIAvailability}} values to [=s
-

The {{AISummarizer}} class

+

The {{Summarizer}} class

-Every {{AISummarizer}} has a shared context, a [=string=]-or-null, set during creation. +Every {{Summarizer}} has a shared context, a [=string=]-or-null, set during creation. -Every {{AISummarizer}} has a summary type, an {{AISummarizerType}}, set during creation. +Every {{Summarizer}} has a summary type, an {{SummarizerType}}, set during creation. -Every {{AISummarizer}} has a summary format, an {{AISummarizerFormat}}, set during creation. +Every {{Summarizer}} has a summary format, an {{SummarizerFormat}}, set during creation. -Every {{AISummarizer}} has a summary length, an {{AISummarizerLength}}, set during creation. +Every {{Summarizer}} has a summary length, an {{SummarizerLength}}, set during creation. -Every {{AISummarizer}} has an expected input languages, a {{FrozenArray}}<{{DOMString}}> or null, set during creation. +Every {{Summarizer}} has an expected input languages, a {{FrozenArray}}<{{DOMString}}> or null, set during creation. -Every {{AISummarizer}} has an expected context languages, a {{FrozenArray}}<{{DOMString}}> or null, set during creation. +Every {{Summarizer}} has an expected context languages, a {{FrozenArray}}<{{DOMString}}> or null, set during creation. -Every {{AISummarizer}} has an output language, a [=string=] or null, set during creation. +Every {{Summarizer}} has an output language, a [=string=] or null, set during creation. -Every {{AISummarizer}} has a input quota, a number, set during creation. +Every {{Summarizer}} has a input quota, a number, set during creation.
-The sharedContext getter steps are to return [=this=]'s [=AISummarizer/shared context=]. +The sharedContext getter steps are to return [=this=]'s [=Summarizer/shared context=]. -The type getter steps are to return [=this=]'s [=AISummarizer/summary type=]. +The type getter steps are to return [=this=]'s [=Summarizer/summary type=]. -The format getter steps are to return [=this=]'s [=AISummarizer/summary format=]. +The format getter steps are to return [=this=]'s [=Summarizer/summary format=]. -The length getter steps are to return [=this=]'s [=AISummarizer/summary length=]. +The length getter steps are to return [=this=]'s [=Summarizer/summary length=]. -The expectedInputLanguages getter steps are to return [=this=]'s [=AISummarizer/expected input languages=]. +The expectedInputLanguages getter steps are to return [=this=]'s [=Summarizer/expected input languages=]. -The expectedContextLanguages getter steps are to return [=this=]'s [=AISummarizer/expected context languages=]. +The expectedContextLanguages getter steps are to return [=this=]'s [=Summarizer/expected context languages=]. -The outputLanguage getter steps are to return [=this=]'s [=AISummarizer/output language=]. +The outputLanguage getter steps are to return [=this=]'s [=Summarizer/output language=]. -The inputQuota getter steps are to return [=this=]'s [=AISummarizer/input quota=]. +The inputQuota getter steps are to return [=this=]'s [=Summarizer/input quota=].
- The summarize(|input|, |options|) method steps are: + The summarize(|input|, |options|) method steps are: - 1. Let |context| be |options|["{{AISummarizerSummarizeOptions/context}}"] if it [=map/exists=]; otherwise null. + 1. Let |context| be |options|["{{SummarizerSummarizeOptions/context}}"] if it [=map/exists=]; otherwise null. - 1. Let |operation| be an algorithm step which takes arguments |chunkProduced|, |done|, |error|, and |stopProducing|, and [=summarizes=] |input| given [=this=]'s [=AISummarizer/shared context=], |context|, [=this=]'s [=AISummarizer/summary type=], [=this=]'s [=AISummarizer/summary format=], [=this=]'s [=AISummarizer/summary length=], [=this=]'s [=AISummarizer/output language=], [=this=]'s [=AISummarizer/input quota=], |chunkProduced|, |done|, |error|, and |stopProducing|. + 1. Let |operation| be an algorithm step which takes arguments |chunkProduced|, |done|, |error|, and |stopProducing|, and [=summarizes=] |input| given [=this=]'s [=Summarizer/shared context=], |context|, [=this=]'s [=Summarizer/summary type=], [=this=]'s [=Summarizer/summary format=], [=this=]'s [=Summarizer/summary length=], [=this=]'s [=Summarizer/output language=], [=this=]'s [=Summarizer/input quota=], |chunkProduced|, |done|, |error|, and |stopProducing|. 1. Return the result of [=getting an aggregated AI model result=] given [=this=], |options|, and |operation|.
- The summarizeStreaming(|input|, |options|) method steps are: + The summarizeStreaming(|input|, |options|) method steps are: - 1. Let |context| be |options|["{{AISummarizerSummarizeOptions/context}}"] if it [=map/exists=]; otherwise null. + 1. Let |context| be |options|["{{SummarizerSummarizeOptions/context}}"] if it [=map/exists=]; otherwise null. - 1. Let |operation| be an algorithm step which takes arguments |chunkProduced|, |done|, |error|, and |stopProducing|, and [=summarizes=] |input| given [=this=]'s [=AISummarizer/shared context=], |context|, [=this=]'s [=AISummarizer/summary type=], [=this=]'s [=AISummarizer/summary format=], [=this=]'s [=AISummarizer/summary length=], [=this=]'s [=AISummarizer/output language=], [=this=]'s [=AISummarizer/input quota=], |chunkProduced|, |done|, |error|, and |stopProducing|. + 1. Let |operation| be an algorithm step which takes arguments |chunkProduced|, |done|, |error|, and |stopProducing|, and [=summarizes=] |input| given [=this=]'s [=Summarizer/shared context=], |context|, [=this=]'s [=Summarizer/summary type=], [=this=]'s [=Summarizer/summary format=], [=this=]'s [=Summarizer/summary length=], [=this=]'s [=Summarizer/output language=], [=this=]'s [=Summarizer/input quota=], |chunkProduced|, |done|, |error|, and |stopProducing|. 1. Return the result of [=getting a streaming AI model result=] given [=this=], |options|, and |operation|.
- The measureInputUsage(|input|, |options|) method steps are: + The measureInputUsage(|input|, |options|) method steps are: - 1. Let |context| be |options|["{{AISummarizerSummarizeOptions/context}}"] if it [=map/exists=]; otherwise null. + 1. Let |context| be |options|["{{SummarizerSummarizeOptions/context}}"] if it [=map/exists=]; otherwise null. - 1. Let |measureUsage| be an algorithm step which takes argument |stopMeasuring|, and returns the result of [=measuring summarizer input usage=] given |input|, [=this=]'s [=AISummarizer/shared context=], |context|, [=this=]'s [=AISummarizer/summary type=], [=this=]'s [=AISummarizer/summary format=], [=this=]'s [=AISummarizer/summary length=], [=this=]'s [=AISummarizer/output language=], and |stopMeasuring|. + 1. Let |measureUsage| be an algorithm step which takes argument |stopMeasuring|, and returns the result of [=measuring summarizer input usage=] given |input|, [=this=]'s [=Summarizer/shared context=], |context|, [=this=]'s [=Summarizer/summary type=], [=this=]'s [=Summarizer/summary format=], [=this=]'s [=Summarizer/summary length=], [=this=]'s [=Summarizer/output language=], and |stopMeasuring|. 1. Return the result of [=measuring AI model input usage=] given [=this=], |options|, and |measureUsage|.
@@ -523,11 +481,11 @@ The inputQuota getter steps are to retur * a [=string=] |input|, * a [=string=]-or-null |sharedContext|, * a [=string=]-or-null |context|, - * an {{AISummarizerType}} |type|, - * an {{AISummarizerFormat}} |format|, - * an {{AISummarizerLength}} |length|, + * an {{SummarizerType}} |type|, + * an {{SummarizerFormat}} |format|, + * an {{SummarizerLength}} |length|, * a [=string=]-or-null |outputLanguage|, - * a [=number=] |inputQuota|, + * a number |inputQuota|, * an algorithm |chunkProduced| that takes a [=string=] and returns nothing, * an algorithm |done| that takes no arguments and returns nothing, * an algorithm |error| that takes [=error information=] and returns nothing, and @@ -604,9 +562,9 @@ The inputQuota getter steps are to retur * a [=string=] |input|, * a [=string=]-or-null |sharedContext|, * a [=string=]-or-null |context|, - * an {{AISummarizerType}} |type|, - * an {{AISummarizerFormat}} |format|, - * an {{AISummarizerLength}} |length|, + * an {{SummarizerType}} |type|, + * an {{SummarizerFormat}} |format|, + * an {{SummarizerLength}} |length|, * a [=string=]-or-null |outputLanguage|, and * an algorithm |stopMeasuring| that takes no arguments and returns a boolean, @@ -616,7 +574,7 @@ The inputQuota getter steps are to retur 1. Let |inputToModel| be the [=implementation-defined=] string that would be sent to the underlying model in order to [=summarize=] given |input|, |sharedContext|, |context|, |type|, |format|, |length|, and |outputLanguage|. -

This might be something similar to the concatenation of |input| and |context|, if all of the other options were loaded into the model during initialization, and so the input usage for those was already accounted for when computing the [=AISummarizer/input quota=]. Or it might consist of more, if the options are sent along with every summarization call, or if there is a per-summarization wrapper prompt of some sort. +

This might be something similar to the concatenation of |input| and |context|, if all of the other options were loaded into the model during initialization, and so the input usage for those was already accounted for when computing the [=Summarizer/input quota=]. Or it might consist of more, if the options are sent along with every summarization call, or if there is a per-summarization wrapper prompt of some sort. If during this process |stopMeasuring| starts returning true, then return null. @@ -624,7 +582,7 @@ The inputQuota getter steps are to retur 1. Return the amount of input usage needed to represent |inputToModel| when given to the underlying model. The exact calculation procedure is [=implementation-defined=], subject to the following constraints. - The returned input usage must be nonnegative and finite. It must be 0, if there are no usage quotas for the summarization process (i.e., if the [=AISummarizer/input quota=] is +∞). Otherwise, it must be positive and should be roughly proportional to the [=string/length=] of |inputToModel|. + The returned input usage must be nonnegative and finite. It must be 0, if there are no usage quotas for the summarization process (i.e., if the [=Summarizer/input quota=] is +∞). Otherwise, it must be positive and should be roughly proportional to the [=string/length=] of |inputToModel|.

This might be the number of tokens needed to represent |input| in a language model tokenization scheme, or it might be |input|'s [=string/length=]. It could also be some variation of these which also counts the usage of any prefixes or suffixes necessary to give to the model. @@ -635,82 +593,82 @@ The inputQuota getter steps are to retur

Options

-The [=summarize=] algorithm's details are [=implementation-defined=], as they are expected to be powered by an AI model. However, it is intended to be controllable by the web developer through the {{AISummarizerType}}, {{AISummarizerFormat}}, and {{AISummarizerLength}} enumerations. +The [=summarize=] algorithm's details are [=implementation-defined=], as they are expected to be powered by an AI model. However, it is intended to be controllable by the web developer through the {{SummarizerType}}, {{SummarizerFormat}}, and {{SummarizerLength}} enumerations. This section gives normative guidance on how the implementation of [=summarize=] should use each enumeration value to guide the summarization process. - + - - - -
{{AISummarizerType}} values{{SummarizerType}} values
Value Meaning
"tl;dr" + "tl;dr"

The summary should be short and to the point, providing a quick overview of the input, suitable for a busy reader.

"teaser" + "teaser"

The summary should focus on the most interesting or intriguing parts of the input, designed to draw the reader in to read more.

"key-points" + "key-points"

The summary should extract the most important points from the input, presented as a bulleted list.

"headline" + "headline"

The summary should effectively contain the main point of the input in a single sentence, in the format of an article headline.

- + - - -
{{AISummarizerLength}} values{{SummarizerLength}} values
Value Meaning
"short" + "short" -

The guidance is dependent on the value of {{AISummarizerType}}: +

The guidance is dependent on the value of {{SummarizerType}}:

- : "{{AISummarizerType/tl;dr}}" - : "{{AISummarizerType/teaser}}" + : "{{SummarizerType/tl;dr}}" + : "{{SummarizerType/teaser}}" :: The summary should fit within 1 sentence. - : "{{AISummarizerType/key-points}}" + : "{{SummarizerType/key-points}}" :: The summary should consist of no more than 3 bullet points. - : "{{AISummarizerType/headline}}" + : "{{SummarizerType/headline}}" :: The summary should use no more than 12 words.
"medium" + "medium" -

The guidance is dependent on the value of {{AISummarizerType}}: +

The guidance is dependent on the value of {{SummarizerType}}:

- : "{{AISummarizerType/tl;dr}}" - : "{{AISummarizerType/teaser}}" + : "{{SummarizerType/tl;dr}}" + : "{{SummarizerType/teaser}}" :: The summary should fit within 1 short paragraph. - : "{{AISummarizerType/key-points}}" + : "{{SummarizerType/key-points}}" :: The summary should consist of no more than 5 bullet points. - : "{{AISummarizerType/headline}}" + : "{{SummarizerType/headline}}" :: The summary should use no more than 17 words.
"long" + "long" -

The guidance is dependent on the value of {{AISummarizerType}}: +

The guidance is dependent on the value of {{SummarizerType}}:

- : "{{AISummarizerType/tl;dr}}" - : "{{AISummarizerType/teaser}}" + : "{{SummarizerType/tl;dr}}" + : "{{SummarizerType/teaser}}" :: The summary should fit within 1 paragraph. - : "{{AISummarizerType/key-points}}" + : "{{SummarizerType/key-points}}" :: The summary should consist of no more than 7 bullet points. - : "{{AISummarizerType/headline}}" + : "{{SummarizerType/headline}}" :: The summary should use no more than 22 words.
@@ -718,18 +676,18 @@ This section gives normative guidance on how the implementation of [=summarize=]

As with all "should"-level guidance, user agents might not conform perfectly to these. Especially in the case of counting words, it's expected that language models might not conform perfectly. - + - -
{{AISummarizerFormat}} values{{SummarizerFormat}} values
Value Meaning
"plain-text" + "plain-text"

The summary should not contain any formatting or markup language.

"markdown" + "markdown"

The summary should be formatted using the Markdown markup language, ideally as valid CommonMark. [[!COMMONMARK]]

@@ -755,11 +713,11 @@ When summarization fails, the following possible reasons may be surfaced to the "{{NotSupportedError}}" -

The input to be summarized, or the context to be provided, was in a language that the user agent does not support, or was not provided properly in the call to {{AISummarizerFactory/create()}}. +

The input to be summarized, or the context to be provided, was in a language that the user agent does not support, or was not provided properly in the call to {{Summarizer/create()}}. -

The summarization output ended up being in a language that the user agent does not support (e.g., because the user agent has not performed sufficient quality control tests on that output language), or was not provided properly in the call to {{AISummarizerFactory/create()}}. +

The summarization output ended up being in a language that the user agent does not support (e.g., because the user agent has not performed sufficient quality control tests on that output language), or was not provided properly in the call to {{Summarizer/create()}}. -

The {{AISummarizerCreateCoreOptions/outputLanguage}} option was not set, and the language of the input text could not be determined, so the user agent did not have a good output language default available. +

The {{SummarizerCreateCoreOptions/outputLanguage}} option was not set, and the language of the input text could not be determined, so the user agent did not have a good output language default available. "{{UnknownError}}" @@ -774,20 +732,23 @@ Just IDL for now; full spec coming!

[Exposed=(Window,Worker), SecureContext] -interface AIWriterFactory { - Promise<AIWriter> create(optional AIWriterCreateOptions options = {}); - Promise<AIAvailability> availability(optional AIWriterCreateCoreOptions options = {}); -}; +interface Writer { + static Promise<Writer> create(optional WriterCreateOptions options = {}); + static Promise<Availability> availability(optional WriterCreateCoreOptions options = {}); -[Exposed=(Window,Worker), SecureContext] -interface AIWriter { - Promise<DOMString> write(DOMString writingTask, optional AIWriterWriteOptions options = {}); - ReadableStream writeStreaming(DOMString writingTask, optional AIWriterWriteOptions options = {}); + Promise<DOMString> write( + DOMString input, + optional WriterWriteOptions options = {} + ); + ReadableStream writeStreaming( + DOMString input, + optional WriterWriteOptions options = {} + ); readonly attribute DOMString sharedContext; - readonly attribute AIWriterTone tone; - readonly attribute AIWriterFormat format; - readonly attribute AIWriterLength length; + readonly attribute WriterTone tone; + readonly attribute WriterFormat format; + readonly attribute WriterLength length; readonly attribute FrozenArray<DOMString>? expectedInputLanguages; readonly attribute FrozenArray<DOMString>? expectedContextLanguages; @@ -795,37 +756,37 @@ interface AIWriter { Promise<double> measureInputUsage( DOMString input, - optional AIWriterWriteOptions options = {} + optional WriterWriteOptions options = {} ); readonly attribute unrestricted double inputQuota; }; -AIWriter includes AIDestroyable; +Writer includes DestroyableModel; -dictionary AIWriterCreateCoreOptions { - AIWriterTone tone = "neutral"; - AIWriterFormat format = "markdown"; - AIWriterLength length = "short"; +dictionary WriterCreateCoreOptions { + WriterTone tone = "neutral"; + WriterFormat format = "markdown"; + WriterLength length = "short"; sequence<DOMString> expectedInputLanguages; sequence<DOMString> expectedContextLanguages; DOMString outputLanguage; }; -dictionary AIWriterCreateOptions : AIWriterCreateCoreOptions { +dictionary WriterCreateOptions : WriterCreateCoreOptions { AbortSignal signal; - AICreateMonitorCallback monitor; + CreateMonitorCallback monitor; DOMString sharedContext; }; -dictionary AIWriterWriteOptions { +dictionary WriterWriteOptions { DOMString context; AbortSignal signal; }; -enum AIWriterTone { "formal", "neutral", "casual" }; -enum AIWriterFormat { "plain-text", "markdown" }; -enum AIWriterLength { "short", "medium", "long" }; +enum WriterTone { "formal", "neutral", "casual" }; +enum WriterFormat { "plain-text", "markdown" }; +enum WriterLength { "short", "medium", "long" };

The rewriter API

@@ -834,20 +795,23 @@ Just IDL for now; full spec coming! [Exposed=(Window,Worker), SecureContext] -interface AIRewriterFactory { - Promise<AIRewriter> create(optional AIRewriterCreateOptions options = {}); - Promise<AIAvailability> availability(optional AIRewriterCreateCoreOptions options = {}); -}; +interface Rewriter { + static Promise<Rewriter> create(optional RewriterCreateOptions options = {}); + static Promise<Availability> availability(optional RewriterCreateCoreOptions options = {}); -[Exposed=(Window,Worker), SecureContext] -interface AIRewriter { - Promise<DOMString> rewrite(DOMString input, optional AIRewriterRewriteOptions options = {}); - ReadableStream rewriteStreaming(DOMString input, optional AIRewriterRewriteOptions options = {}); + Promise<DOMString> rewrite( + DOMString input, + optional RewriterRewriteOptions options = {} + ); + ReadableStream rewriteStreaming( + DOMString input, + optional RewriterRewriteOptions options = {} + ); readonly attribute DOMString sharedContext; - readonly attribute AIRewriterTone tone; - readonly attribute AIRewriterFormat format; - readonly attribute AIRewriterLength length; + readonly attribute RewriterTone tone; + readonly attribute RewriterFormat format; + readonly attribute RewriterLength length; readonly attribute FrozenArray<DOMString>? expectedInputLanguages; readonly attribute FrozenArray<DOMString>? expectedContextLanguages; @@ -855,37 +819,37 @@ interface AIRewriter { Promise<double> measureInputUsage( DOMString input, - optional AIRewriterRewriteOptions options = {} + optional RewriterRewriteOptions options = {} ); readonly attribute unrestricted double inputQuota; }; -AIRewriter includes AIDestroyable; +Rewriter includes DestroyableModel; -dictionary AIRewriterCreateCoreOptions { - AIRewriterTone tone = "as-is"; - AIRewriterFormat format = "as-is"; - AIRewriterLength length = "as-is"; +dictionary RewriterCreateCoreOptions { + RewriterTone tone = "as-is"; + RewriterFormat format = "as-is"; + RewriterLength length = "as-is"; sequence<DOMString> expectedInputLanguages; sequence<DOMString> expectedContextLanguages; DOMString outputLanguage; }; -dictionary AIRewriterCreateOptions : AIRewriterCreateCoreOptions { +dictionary RewriterCreateOptions : RewriterCreateCoreOptions { AbortSignal signal; - AICreateMonitorCallback monitor; + CreateMonitorCallback monitor; DOMString sharedContext; }; -dictionary AIRewriterRewriteOptions { +dictionary RewriterRewriteOptions { DOMString context; AbortSignal signal; }; -enum AIRewriterTone { "as-is", "more-formal", "more-casual" }; -enum AIRewriterFormat { "as-is", "plain-text", "markdown" }; -enum AIRewriterLength { "as-is", "shorter", "longer" }; +enum RewriterTone { "as-is", "more-formal", "more-casual" }; +enum RewriterFormat { "as-is", "plain-text", "markdown" }; +enum RewriterLength { "as-is", "shorter", "longer" };

Shared infrastructure

@@ -895,20 +859,30 @@ enum AIRewriterLength { "as-is", "shorter", "longer" };
To create an AI model object given: - * a [=ECMAScript/realm=] |realm|, * an [=ordered map=] |options|, - * an algorithm |getAvailability| taking an [=ordered map=] and returning an {{AIAvailability}} or null, + * an algorithm |validateAndCanonicalizeOptions| taking an [=ordered map=] and returning nothing, + * an algorithm |getAvailability| taking an [=ordered map=] and returning an {{Availability}} or null, * an algorithm |startDownload| taking an [=ordered map=] and returning a boolean, * an algorithm |initialize| taking an [=ordered map=] and returning an error information or null, and * an algorithm |create| taking a [=ECMAScript/realm=] and an [=ordered map=] and returning a Web IDL object representing the model, perform the following steps: + 1. Let |realm| be the [=current realm=]. + + 1. If |realm|'s [=realm/global object=] is a {{Window}} whose [=associated Document=] is not [=Document/fully active=], then return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. + + 1. If |options|["`signal`"] [=map/exists=] and is [=AbortSignal/aborted=], then return [=a promise rejected with=] |options|["`signal`"]'s [=AbortSignal/abort reason=]. + + 1. Perform |validateAndCanonicalizeOptions| given |options|. If this throws an exception |e|, catch it, and return [=a promise rejected with=] |e|. + +

This can mutate |options|. + 1. Let |fireProgressEvent| be an algorithm taking two arguments that does nothing. 1. If |options|["`monitor`"] [=map/exists=], then: - 1. Let |monitor| be a [=new=] {{AICreateMonitor}} created in |realm|. + 1. Let |monitor| be a [=new=] {{CreateMonitor}} created in |realm|. 1. [=Invoke=] |options|["`monitor`"] with « |monitor| » and "`rethrow`". @@ -920,7 +894,7 @@ enum AIRewriterLength { "as-is", "shorter", "longer" }; 1. [=Queue a global task=] on the [=AI task source=] given |realm|'s [=realm/global object=] to perform the following steps: - 1. [=Fire an event=] named {{AICreateMonitor/downloadprogress}} at |monitor|, using {{ProgressEvent}}, with the {{ProgressEvent/loaded}} attribute initialized to |loaded|, the {{ProgressEvent/total}} attribute initialized to 1, and the {{ProgressEvent/lengthComputable}} attribute initialized to true. + 1. [=Fire an event=] named {{CreateMonitor/downloadprogress}} at |monitor|, using {{ProgressEvent}}, with the {{ProgressEvent/loaded}} attribute initialized to |loaded|, the {{ProgressEvent/total}} attribute initialized to 1, and the {{ProgressEvent/lengthComputable}} attribute initialized to true.

This assumes whatwg/xhr#394 is merged so that passing non-integer values for {{ProgressEvent/loaded}} works as expected.

@@ -949,20 +923,20 @@ enum AIRewriterLength { "as-is", "shorter", "longer" }; 1. Abort these steps. - : "{{AIAvailability/unavailable}}" + : "{{Availability/unavailable}}" :: 1. [=Reject=] |promise| with a "{{NotSupportedError}}" {{DOMException}}. 1. Abort these steps. - : "{{AIAvailability/available}}" + : "{{Availability/available}}" :: 1. [=Initialize and return an AI model object=] given |promise|, |options|, |fireProgressEvent|, |initialize|, and |create|. - : "{{AIAvailability/downloading}}" - : "{{AIAvailability/downloadable}}" + : "{{Availability/downloading}}" + : "{{Availability/downloadable}}" :: - 1. If |availability| is "{{AIAvailability/downloadable}}", then let |startDownloadResult| be the result of performing |startDownload| given |options|. + 1. If |availability| is "{{Availability/downloadable}}", then let |startDownloadResult| be the result of performing |startDownload| given |options|. 1. If |startDownloadResult| is false, then: @@ -1099,7 +1073,7 @@ enum AIRewriterLength { "as-is", "shorter", "longer" }; 1. If |bytesSoFar| equals |totalBytes|, then [=iteration/break=]. -

Since this is the only non-failure exit condition for the loop, we will never miss firing a {{AICreateMonitor/downloadprogress}} event for the 100% mark.

+

Since this is the only non-failure exit condition for the loop, we will never miss firing a {{CreateMonitor/downloadprogress}} event for the 100% mark.

1. Set |lastProgressFraction| to |progressFraction|. @@ -1150,13 +1124,13 @@ enum AIRewriterLength { "as-is", "shorter", "longer" }; 1. Let |model| be the result of performing |create| given |promise|'s [=relevant global object=] and |options|. - 1. [=Assert=]: |model| [=implements=] an [=interface=] that [=interface/includes=] {{AIDestroyable}}. + 1. [=Assert=]: |model| [=implements=] an [=interface=] that [=interface/includes=] {{DestroyableModel}}. 1. [=Initialize as a destroyable=] |model|. 1. If |options|["`signal`"] [=map/exists=], then [=AbortSignal/add|add the following abort steps=] to |options|["`signal`"]: - 1. [=AIDestroyable/Destroy=] |model| given |options|["`signal`"]'s [=AbortSignal/abort reason=]. + 1. [=DestroyableModel/Destroy=] |model| given |options|["`signal`"]'s [=AbortSignal/abort reason=]. 1. [=Resolve=] |promise| with |model|.
@@ -1164,11 +1138,11 @@ enum AIRewriterLength { "as-is", "shorter", "longer" };

Obtaining results and usage

- To get an aggregated AI model result given an {{AIDestroyable}} |modelObject|, an [=ordered map=] |options|, and an algorithm |operation|: + To get an aggregated AI model result given an {{DestroyableModel}} |modelObject|, an [=ordered map=] |options|, and an algorithm |operation|: 1. If |modelObject|'s [=relevant global object=] is a {{Window}} whose [=associated Document=] is not [=Document/fully active=], then return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. - 1. Let |signals| be « |modelObject|'s [=AIDestroyable/destruction abort controller=]'s [=AbortController/signal=] ». + 1. Let |signals| be « |modelObject|'s [=DestroyableModel/destruction abort controller=]'s [=AbortController/signal=] ». 1. If |options|["`signal`"] [=map/exists=], then [=set/append=] it to |signals|. @@ -1224,13 +1198,13 @@ enum AIRewriterLength { "as-is", "shorter", "longer" };
- To get a streaming AI model result given an {{AIDestroyable}} |modelObject|, an [=ordered map=] |options|, and an algorithm |operation|: + To get a streaming AI model result given an {{DestroyableModel}} |modelObject|, an [=ordered map=] |options|, and an algorithm |operation|: 1. If |modelObject|'s [=relevant global object=] is a {{Window}} whose [=associated Document=] is not [=Document/fully active=], then throw an "{{InvalidStateError}}" {{DOMException}}. - 1. Let |signals| be « |modelObject|'s [=AIDestroyable/destruction abort controller=]'s [=AbortController/signal=] ». + 1. Let |signals| be « |modelObject|'s [=DestroyableModel/destruction abort controller=]'s [=AbortController/signal=] ». - 1. If |options|["{{AISummarizerSummarizeOptions/signal}}"] [=map/exists=], then [=set/append=] it to |signals|. + 1. If |options|["{{SummarizerSummarizeOptions/signal}}"] [=map/exists=], then [=set/append=] it to |signals|. 1. Let |compositeSignal| be the result of [=creating a dependent abort signal=] given |signals| using {{AbortSignal}} and |modelObject|'s [=relevant realm=]. @@ -1292,11 +1266,11 @@ enum AIRewriterLength { "as-is", "shorter", "longer" };
- To measure AI model input usage given an {{AIDestroyable}} |modelObject|, an [=ordered map=] |options|, and an algorithm |measure|: + To measure AI model input usage given an {{DestroyableModel}} |modelObject|, an [=ordered map=] |options|, and an algorithm |measure|: 1. If |modelObject|'s [=relevant global object=] is a {{Window}} whose [=associated Document=] is not [=Document/fully active=], then return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. - 1. Let |signals| be « |modelObject|'s [=AIDestroyable/destruction abort controller=]'s [=AbortController/signal=] ». + 1. Let |signals| be « |modelObject|'s [=DestroyableModel/destruction abort controller=]'s [=AbortController/signal=] ». 1. If |options|["`signal`"] [=map/exists=], then [=set/append=] it to |signals|. @@ -1372,23 +1346,23 @@ enum AIRewriterLength { "as-is", "shorter", "longer" };
To get language availabilities given a description |purpose| of the purpose for which we're checking language availability: - 1. Let |availabilities| be «[ "{{AIAvailability/available}}" → an empty [=set=], "{{AIAvailability/downloading}}" → an empty [=set=], "{{AIAvailability/downloadable}}" → an empty [=set=] ]». + 1. Let |availabilities| be «[ "{{Availability/available}}" → an empty [=set=], "{{Availability/downloading}}" → an empty [=set=], "{{Availability/downloadable}}" → an empty [=set=] ]». 1. [=list/For each=] human language |languageTag|, represented as a [=Unicode canonicalized locale identifier=], for which the user agent supports |purpose|, without performing any downloading operations: - 1. [=set/Append=] |languageTag| to |availabilities|["{{AIAvailability/available}}"]. + 1. [=set/Append=] |languageTag| to |availabilities|["{{Availability/available}}"]. 1. [=list/For each=] human language |languageTag|, represented as a [=Unicode canonicalized locale identifier=], for which the user agent is currently downloading material (e.g., an AI model or fine-tuning) to support |purpose|: - 1. [=set/Append=] |languageTag| to |availabilities|["{{AIAvailability/downloading}}"]. + 1. [=set/Append=] |languageTag| to |availabilities|["{{Availability/downloading}}"]. 1. [=list/For each=] human language |languageTag|, represented as a [=Unicode canonicalized locale identifier=], for which the user agent believes it can support |purpose|, but only after performing a not-currently-ongoing download (e.g., of an AI model or fine-tuning): - 1. [=set/Append=] |languageTag| to |availabilities|["{{AIAvailability/downloadable}}"]. + 1. [=set/Append=] |languageTag| to |availabilities|["{{Availability/downloadable}}"]. - 1. [=Assert=]: |availabilities|["{{AIAvailability/available}}"], |availabilities|["{{AIAvailability/downloading}}"], and |availabilities|["{{AIAvailability/downloadable}}"] are disjoint. + 1. [=Assert=]: |availabilities|["{{Availability/available}}"], |availabilities|["{{Availability/downloading}}"], and |availabilities|["{{Availability/downloadable}}"] are disjoint. - 1. If the [=set/union=] of |availabilities|["{{AIAvailability/available}}"], |availabilities|["{{AIAvailability/downloading}}"], and |availabilities|["{{AIAvailability/downloadable}}"] does not meet the [=language tag set completeness rules=], then: + 1. If the [=set/union=] of |availabilities|["{{Availability/available}}"], |availabilities|["{{Availability/downloading}}"], and |availabilities|["{{Availability/downloadable}}"] does not meet the [=language tag set completeness rules=], then: 1. Let |missingLanguageTags| be the [=set=] of missing language tags necessary to meet the [=language tag set completeness rules=]. @@ -1414,17 +1388,39 @@ enum AIRewriterLength { "as-is", "shorter", "longer" };

Availability

- The minimum availability given a [=list=] of {{AIAvailability}}-or-null values |availabilities| is: + To compute AI model availability given |options|, an algorithm |validate|, and an algorithm |compute|: + + 1. Let |global| be the [=current global object=]. + + 1. If |global| is a {{Window}} whose [=associated Document=] is not [=Document/fully active=], then return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}. + + 1. Perform |validate| given |options|. + + 1. Let |promise| be [=a new promise=] created in the [=current realm=]. + + 1. [=In parallel=]: + + 1. Let |availability| be the result of |compute| given |options|. + + 1. [=Queue a global task=] on the [=AI task source=] given |global| to perform the following steps: + + 1. If |availability| is null, then [=reject=] |promise| with an "{{UnknownError}}" {{DOMException}}. + + 1. Otherwise, [=resolve=] |promise| with |availability|. +
+ +
+ The minimum availability given a [=list=] of {{Availability}}-or-null values |availabilities| is: 1. If |availabilities| [=list/contains=] null, then return null. - 1. If |availabilities| [=list/contains=] "{{AIAvailability/unavailable}}", then return "{{AIAvailability/unavailable}}". + 1. If |availabilities| [=list/contains=] "{{Availability/unavailable}}", then return "{{Availability/unavailable}}". - 1. If |availabilities| [=list/contains=] "{{AIAvailability/downloading}}", then return "{{AIAvailability/downloading}}". + 1. If |availabilities| [=list/contains=] "{{Availability/downloading}}", then return "{{Availability/downloading}}". - 1. If |availabilities| [=list/contains=] "{{AIAvailability/downloadable}}", then return "{{AIAvailability/downloadable}}". + 1. If |availabilities| [=list/contains=] "{{Availability/downloadable}}", then return "{{Availability/downloadable}}". - 1. Return "{{AIAvailability/available}}". + 1. Return "{{Availability/available}}".

Errors

@@ -1441,9 +1437,9 @@ A DOMException error information is a [=struct=] with the foll A quota exceeded error information is a [=struct=] with the following [=struct/items=]: : requested -:: a [=number=] that will be used for the {{QuotaExceededError}}'s [=QuotaExceededError/requested=]. +:: a number that will be used for the {{QuotaExceededError}}'s [=QuotaExceededError/requested=]. : quota -:: a [=number=] that will be used for the {{QuotaExceededError}}'s [=QuotaExceededError/quota=]. +:: a number that will be used for the {{QuotaExceededError}}'s [=QuotaExceededError/quota=].

The parts of this specification related to quota exceeded errors assume that whatwg/webidl#1465 will be merged.