@wavebirdai/api 0.1.0-beta.11 → 0.1.0-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@ You need a saved Wavebird Test project and placement in Dashboard, a Server Test
9
9
  1. Install the package and generate the route files for your app directory:
10
10
 
11
11
  ```sh
12
- npm install @wavebirdai/api@0.1.0-beta.11
12
+ npm install @wavebirdai/api@0.1.0-beta.12
13
13
  ./node_modules/.bin/wavebird-next init --lang ts --root . --app-dir src/app
14
14
  ```
15
15
 
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  /** Resolve after the publisher has presented the result; After timing starts then. */
2
- export type GenerationConsumer<T> = (response: Response, lifecycle: { onDelta(): void }) => T | Promise<T>;
2
+ export type GenerationConsumer<T> = (response: Response, lifecycle: {
3
+ /** Streaming output progress. For Before and During Image, call after the first result is presented. */
4
+ onDelta(): void;
5
+ /** Explicitly mark the result as presented. Idempotent and bound to this generation. */
6
+ onPresented(): void;
7
+ }) => T | Promise<T>;
3
8
  export type PublisherAds<Bound extends boolean = boolean> = {
4
9
  ready: Promise<void>;
5
10
  connectGeneration<T>(options: { request: (headers: Record<string, string>) => Response | Promise<Response>; consume: GenerationConsumer<T>; signal?: AbortSignal }): Promise<T>;
package/dist/browser.js CHANGED
@@ -75,6 +75,10 @@ export function createPublisherAds(options) {
75
75
  sponsor = null; slot.hidden = true; slot.replaceChildren();
76
76
  }
77
77
  };
78
+ const notifyPresented = id => {
79
+ // Additive bridge: an already deployed renderer may predate this event.
80
+ if (typeof sponsor?.onPresented === "function") notify("onPresented", id);
81
+ };
78
82
  const current = job => !disposed && !job.cancelled && job.context === context && active === job;
79
83
  const event = (job, type) => {
80
84
  if (!current(job)) return;
@@ -84,9 +88,15 @@ export function createPublisherAds(options) {
84
88
  } else if (type === "progress") {
85
89
  if (job.state === "started") { job.progressed = true; notify("onDelta", job.id); }
86
90
  } else if (type === "no_result") {
87
- if (job.state === "started") job.state = "no_result";
88
- } else if (["done", "error", "abort"].includes(type)) {
89
91
  if (job.state !== "started") return;
92
+ job.state = "no_result";
93
+ if (timing !== "after") notify("onError", job.id);
94
+ } else if (type === "presented") {
95
+ if (!["started", "done"].includes(job.state) || job.presented) return;
96
+ job.presented = true; notifyPresented(job.id);
97
+ } else if (["done", "error", "abort"].includes(type)) {
98
+ if (job.state !== "started" &&
99
+ !(job.state === "done" && !job.consumed && ["error", "abort"].includes(type))) return;
90
100
  job.state = type;
91
101
  notify(type === "done" ? "onDone" : type === "abort" ? "onAbort" : "onError", job.id);
92
102
  }
@@ -118,7 +128,7 @@ export function createPublisherAds(options) {
118
128
  const ready = loadRenderer(rendererUrl).then(() => {
119
129
  if (disposed) return;
120
130
  sponsor = window.wavebird.createChatSponsor({ target: slot, ...binding,
121
- clientId: options.clientId, surfaceId: options.surfaceId, timing, consentMode,
131
+ clientId: options.clientId, surfaceId: options.surfaceId, jobType: options.jobType, timing, consentMode,
122
132
  cleanupAfterClip: true,
123
133
  ...(turnBinding ? { turnBinding: true } : {}),
124
134
  ...(options.getFormat === undefined ? {} : { getFormat: options.getFormat }),
@@ -136,7 +146,8 @@ export function createPublisherAds(options) {
136
146
  }).catch(() => {});
137
147
 
138
148
  async function connectGeneration({ request, consume, signal }) {
139
- const job = { id: crypto.randomUUID(), sessionId: binding.sessionId, context, state: "pending", cancelled: false };
149
+ const job = { id: crypto.randomUUID(), sessionId: binding.sessionId, context, state: "pending", cancelled: false,
150
+ presented: false, consumed: false };
140
151
  if (active) event(active, "abort");
141
152
  active = job;
142
153
  let reader = null, ended = false, buffer = "";
@@ -195,7 +206,8 @@ export function createPublisherAds(options) {
195
206
  }
196
207
  if (["done", "error", "abort"].includes(record.type)) {
197
208
  ended = true;
198
- if (record.type !== "done" || timing !== "after") event(job, record.type);
209
+ if (record.type === "done" && [204, 205, 304].includes(metadata.status)) event(job, "no_result");
210
+ else if (record.type !== "done" || timing !== "after") event(job, record.type);
199
211
  if (record.type === "done" || !metadata.status.toString().startsWith("2")) controller.close();
200
212
  else controller.error(new Error("generation_" + record.type));
201
213
  return;
@@ -210,14 +222,20 @@ export function createPublisherAds(options) {
210
222
  const record = await nextRecord();
211
223
  if (!["done", "error", "abort"].includes(record.type)) throw new Error("generation_transport_invalid");
212
224
  ended = true;
213
- if (record.type !== "done" || timing !== "after") event(job, record.type);
225
+ if (record.type === "done") event(job, "no_result");
226
+ else event(job, record.type);
214
227
  }
215
228
  const noBody = [204, 205, 304].includes(metadata.status);
216
229
  const primary = new Response(noBody ? null : output, {
217
230
  status: metadata.status, statusText: metadata.status_text || "", headers: metadata.headers
218
231
  });
219
- const result = await consume(primary, { onDelta: () => event(job, "progress") });
220
- if (timing === "after" && ended) event(job, noBody ? "no_result" : "done");
232
+ const result = await consume(primary, { onDelta: () => {
233
+ event(job, "progress");
234
+ if (timing === "before" || (timing === "during" && options.jobType === "image")) event(job, "presented");
235
+ }, onPresented: () => event(job, "presented") });
236
+ job.consumed = true;
237
+ if (timing === "before" || (timing === "during" && options.jobType === "image")) event(job, "presented");
238
+ if (timing === "after" && ended && !noBody) event(job, "done");
221
239
  return result;
222
240
  } catch (error) {
223
241
  event(job, signal?.aborted ? "abort" : "error");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wavebirdai/api",
3
- "version": "0.1.0-beta.11",
3
+ "version": "0.1.0-beta.12",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Integrate Banner, Native, and Clip placements into authenticated chat apps with server-side keys, consent, and generation lifecycle handling",
package/src/browser.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  /** Resolve after the publisher has presented the result; After timing starts then. */
2
- export type GenerationConsumer<T> = (response: Response, lifecycle: { onDelta(): void }) => T | Promise<T>;
2
+ export type GenerationConsumer<T> = (response: Response, lifecycle: {
3
+ /** Streaming output progress. For Before and During Image, call after the first result is presented. */
4
+ onDelta(): void;
5
+ /** Explicitly mark the result as presented. Idempotent and bound to this generation. */
6
+ onPresented(): void;
7
+ }) => T | Promise<T>;
3
8
  export type PublisherAds<Bound extends boolean = boolean> = {
4
9
  ready: Promise<void>;
5
10
  connectGeneration<T>(options: { request: (headers: Record<string, string>) => Response | Promise<Response>; consume: GenerationConsumer<T>; signal?: AbortSignal }): Promise<T>;
package/src/browser.js CHANGED
@@ -75,6 +75,10 @@ export function createPublisherAds(options) {
75
75
  sponsor = null; slot.hidden = true; slot.replaceChildren();
76
76
  }
77
77
  };
78
+ const notifyPresented = id => {
79
+ // Additive bridge: an already deployed renderer may predate this event.
80
+ if (typeof sponsor?.onPresented === "function") notify("onPresented", id);
81
+ };
78
82
  const current = job => !disposed && !job.cancelled && job.context === context && active === job;
79
83
  const event = (job, type) => {
80
84
  if (!current(job)) return;
@@ -84,9 +88,15 @@ export function createPublisherAds(options) {
84
88
  } else if (type === "progress") {
85
89
  if (job.state === "started") { job.progressed = true; notify("onDelta", job.id); }
86
90
  } else if (type === "no_result") {
87
- if (job.state === "started") job.state = "no_result";
88
- } else if (["done", "error", "abort"].includes(type)) {
89
91
  if (job.state !== "started") return;
92
+ job.state = "no_result";
93
+ if (timing !== "after") notify("onError", job.id);
94
+ } else if (type === "presented") {
95
+ if (!["started", "done"].includes(job.state) || job.presented) return;
96
+ job.presented = true; notifyPresented(job.id);
97
+ } else if (["done", "error", "abort"].includes(type)) {
98
+ if (job.state !== "started" &&
99
+ !(job.state === "done" && !job.consumed && ["error", "abort"].includes(type))) return;
90
100
  job.state = type;
91
101
  notify(type === "done" ? "onDone" : type === "abort" ? "onAbort" : "onError", job.id);
92
102
  }
@@ -118,7 +128,7 @@ export function createPublisherAds(options) {
118
128
  const ready = loadRenderer(rendererUrl).then(() => {
119
129
  if (disposed) return;
120
130
  sponsor = window.wavebird.createChatSponsor({ target: slot, ...binding,
121
- clientId: options.clientId, surfaceId: options.surfaceId, timing, consentMode,
131
+ clientId: options.clientId, surfaceId: options.surfaceId, jobType: options.jobType, timing, consentMode,
122
132
  cleanupAfterClip: true,
123
133
  ...(turnBinding ? { turnBinding: true } : {}),
124
134
  ...(options.getFormat === undefined ? {} : { getFormat: options.getFormat }),
@@ -136,7 +146,8 @@ export function createPublisherAds(options) {
136
146
  }).catch(() => {});
137
147
 
138
148
  async function connectGeneration({ request, consume, signal }) {
139
- const job = { id: crypto.randomUUID(), sessionId: binding.sessionId, context, state: "pending", cancelled: false };
149
+ const job = { id: crypto.randomUUID(), sessionId: binding.sessionId, context, state: "pending", cancelled: false,
150
+ presented: false, consumed: false };
140
151
  if (active) event(active, "abort");
141
152
  active = job;
142
153
  let reader = null, ended = false, buffer = "";
@@ -195,7 +206,8 @@ export function createPublisherAds(options) {
195
206
  }
196
207
  if (["done", "error", "abort"].includes(record.type)) {
197
208
  ended = true;
198
- if (record.type !== "done" || timing !== "after") event(job, record.type);
209
+ if (record.type === "done" && [204, 205, 304].includes(metadata.status)) event(job, "no_result");
210
+ else if (record.type !== "done" || timing !== "after") event(job, record.type);
199
211
  if (record.type === "done" || !metadata.status.toString().startsWith("2")) controller.close();
200
212
  else controller.error(new Error("generation_" + record.type));
201
213
  return;
@@ -210,14 +222,20 @@ export function createPublisherAds(options) {
210
222
  const record = await nextRecord();
211
223
  if (!["done", "error", "abort"].includes(record.type)) throw new Error("generation_transport_invalid");
212
224
  ended = true;
213
- if (record.type !== "done" || timing !== "after") event(job, record.type);
225
+ if (record.type === "done") event(job, "no_result");
226
+ else event(job, record.type);
214
227
  }
215
228
  const noBody = [204, 205, 304].includes(metadata.status);
216
229
  const primary = new Response(noBody ? null : output, {
217
230
  status: metadata.status, statusText: metadata.status_text || "", headers: metadata.headers
218
231
  });
219
- const result = await consume(primary, { onDelta: () => event(job, "progress") });
220
- if (timing === "after" && ended) event(job, noBody ? "no_result" : "done");
232
+ const result = await consume(primary, { onDelta: () => {
233
+ event(job, "progress");
234
+ if (timing === "before" || (timing === "during" && options.jobType === "image")) event(job, "presented");
235
+ }, onPresented: () => event(job, "presented") });
236
+ job.consumed = true;
237
+ if (timing === "before" || (timing === "during" && options.jobType === "image")) event(job, "presented");
238
+ if (timing === "after" && ended && !noBody) event(job, "done");
221
239
  return result;
222
240
  } catch (error) {
223
241
  event(job, signal?.aborted ? "abort" : "error");