@datalayer/agent-runtimes 1.3.79 → 1.3.81
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/lib/chat/assistant/AssistantStage.d.ts +9 -4
- package/lib/chat/assistant/AssistantStage.js +18 -6
- package/lib/chat/assistant/SpriteCharacter.d.ts +25 -3
- package/lib/chat/assistant/SpriteCharacter.js +68 -3
- package/lib/chat/assistant/formats/acf.d.ts +56 -0
- package/lib/chat/assistant/formats/acf.js +317 -0
- package/lib/chat/assistant/formats/acs.d.ts +85 -7
- package/lib/chat/assistant/formats/acs.js +111 -39
- package/lib/chat/assistant/formats/files.d.ts +6 -2
- package/lib/chat/assistant/formats/files.js +18 -4
- package/lib/chat/assistant/formats/index.d.ts +6 -3
- package/lib/chat/assistant/formats/index.js +4 -2
- package/lib/chat/assistant/formats/types.d.ts +14 -0
- package/lib/chat/assistant/state.d.ts +2 -0
- package/lib/chat/assistant/state.js +9 -0
- package/lib/chat/base/ChatBase.js +7 -0
- package/lib/chat/base/loopStep.d.ts +40 -0
- package/lib/chat/base/loopStep.js +73 -0
- package/lib/loop/apps/appspec.js +32 -2
- package/lib/loop/apps/checks.js +53 -0
- package/lib/loop/apps/hostTools.d.ts +17 -0
- package/lib/loop/apps/hostTools.js +20 -0
- package/lib/loop/apps/rules.d.ts +22 -1
- package/lib/loop/apps/rules.js +49 -1
- package/lib/loop/embed/AppEmbed.d.ts +57 -2
- package/lib/loop/embed/AppEmbed.js +71 -8
- package/lib/loop/embed/element.js +40 -1
- package/lib/loop/embed/hostBridge.d.ts +79 -0
- package/lib/loop/embed/hostBridge.js +104 -0
- package/lib/loop/index.d.ts +2 -1
- package/lib/loop/index.js +3 -1
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.d.ts +5 -2
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +7 -1
- package/lib/loop/plugins/a2ui-surface/index.d.ts +1 -1
- package/lib/loop/plugins/a2ui-surface/index.js +4 -4
- package/lib/loop/plugins/a2ui-surface/toolResult.d.ts +27 -0
- package/lib/loop/plugins/a2ui-surface/toolResult.js +22 -0
- package/lib/specs/agents/agents.js +6 -25
- package/lib/specs/apps.js +22 -8
- package/lib/specs/appspecSchema.js +67 -0
- package/lib/types/agentspecs.d.ts +28 -0
- package/package.json +1 -1
- package/scripts/codegen/generate_apps.py +7 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AssistantCharacterData } from './types';
|
|
1
|
+
import { type AssistantCharacterData, type AssistantMouthShape } from './types';
|
|
2
2
|
export declare const ACS_SIGNATURE = 2882382787;
|
|
3
3
|
export declare const ACF_SIGNATURE = 2882382788;
|
|
4
4
|
export interface AcsLocator {
|
|
@@ -10,6 +10,26 @@ export interface AcsFrameImage {
|
|
|
10
10
|
x: number;
|
|
11
11
|
y: number;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* A mouth overlay (ACSOVERLAYINFO), drawn only while the character speaks:
|
|
15
|
+
* its image over the frame's top image, or in its place.
|
|
16
|
+
*/
|
|
17
|
+
export interface AcsOverlay {
|
|
18
|
+
/** 0 closed, 1 to 4 wide open, 5 medium, 6 narrow (`MOUTH_SHAPES`). */
|
|
19
|
+
type: number;
|
|
20
|
+
/** Whether the overlay takes the place of the frame's top image. */
|
|
21
|
+
replaceTop: boolean;
|
|
22
|
+
image: number;
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
/**
|
|
26
|
+
* What the mouth is drawn over in place of the frame's images, when the
|
|
27
|
+
* file says (an `.aca` gives the frame without its top image).
|
|
28
|
+
*/
|
|
29
|
+
base?: AcsFrameImage[];
|
|
30
|
+
}
|
|
31
|
+
/** The mouth shapes of the format, by overlay type. */
|
|
32
|
+
export declare const MOUTH_SHAPES: readonly AssistantMouthShape[];
|
|
13
33
|
export interface AcsFrame {
|
|
14
34
|
/** Images, as stored: the first is drawn on top. */
|
|
15
35
|
images: AcsFrameImage[];
|
|
@@ -23,6 +43,8 @@ export interface AcsFrame {
|
|
|
23
43
|
frameIndex: number;
|
|
24
44
|
probability: number;
|
|
25
45
|
}[];
|
|
46
|
+
/** Its mouth overlays, by shape; none for most frames. */
|
|
47
|
+
overlays: AcsOverlay[];
|
|
26
48
|
}
|
|
27
49
|
export interface AcsAnimation {
|
|
28
50
|
name: string;
|
|
@@ -57,6 +79,33 @@ export interface AcsImage {
|
|
|
57
79
|
height: number;
|
|
58
80
|
pixels: Uint8Array;
|
|
59
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Little-endian reads that refuse, in a sentence naming the file kind, to
|
|
84
|
+
* run past the end. `terminated`: whether a STRING ends with a null
|
|
85
|
+
* character, as in an `.acs` (an `.acf`'s do not).
|
|
86
|
+
*/
|
|
87
|
+
export declare class AgentReader {
|
|
88
|
+
readonly bytes: Uint8Array;
|
|
89
|
+
readonly kind: string;
|
|
90
|
+
readonly terminated: boolean;
|
|
91
|
+
private readonly view;
|
|
92
|
+
pos: number;
|
|
93
|
+
constructor(bytes: Uint8Array, kind?: string, terminated?: boolean);
|
|
94
|
+
need(count: number, what: string): void;
|
|
95
|
+
seek(locator: AcsLocator, what: string): void;
|
|
96
|
+
u8(what: string): number;
|
|
97
|
+
u16(what: string): number;
|
|
98
|
+
i16(what: string): number;
|
|
99
|
+
u32(what: string): number;
|
|
100
|
+
skip(count: number, what: string): void;
|
|
101
|
+
take(count: number, what: string): Uint8Array;
|
|
102
|
+
locator(what: string): AcsLocator;
|
|
103
|
+
/**
|
|
104
|
+
* STRING: a ULONG count of UTF-16 characters, then, in an `.acs`, a null
|
|
105
|
+
* terminator.
|
|
106
|
+
*/
|
|
107
|
+
string(what: string): string;
|
|
108
|
+
}
|
|
60
109
|
/**
|
|
61
110
|
* Read the tables of an `.acs`: the character, its animations and frames,
|
|
62
111
|
* where its images and sounds are. Images are decoded by `decodeAcsImage`.
|
|
@@ -64,6 +113,23 @@ export interface AcsImage {
|
|
|
64
113
|
export declare function parseAcs(buffer: ArrayBuffer): AcsFile;
|
|
65
114
|
/** Decode one image of an `.acs` into palette indices, top row first. */
|
|
66
115
|
export declare function decodeAcsImage(file: AcsFile, index: number): AcsImage;
|
|
116
|
+
/**
|
|
117
|
+
* What a Microsoft Agent character is drawn from, whichever file it came in:
|
|
118
|
+
* an `.acs` (`parseAcs`), or an `.acf` and its `.aca` files (`readAcfCharacter`).
|
|
119
|
+
*/
|
|
120
|
+
export interface AgentCharacterSource {
|
|
121
|
+
name: string;
|
|
122
|
+
width: number;
|
|
123
|
+
height: number;
|
|
124
|
+
transparentIndex: number;
|
|
125
|
+
palette: Uint32Array;
|
|
126
|
+
states: Record<string, string[]>;
|
|
127
|
+
animations: AcsAnimation[];
|
|
128
|
+
/** Image `index`, decoded. */
|
|
129
|
+
decode: (index: number) => AcsImage;
|
|
130
|
+
/** The bytes of sound `index` (RIFF WAVE). */
|
|
131
|
+
sound: (index: number) => Uint8Array;
|
|
132
|
+
}
|
|
67
133
|
/** One cell of the sprite sheet: the frames that look the same share it. */
|
|
68
134
|
export interface AcsSpriteLayout {
|
|
69
135
|
columns: number;
|
|
@@ -72,17 +138,29 @@ export interface AcsSpriteLayout {
|
|
|
72
138
|
cells: AcsFrameImage[][];
|
|
73
139
|
/** For each animation, for each frame, its cell, or -1 for an empty frame. */
|
|
74
140
|
frameCells: number[][];
|
|
141
|
+
/**
|
|
142
|
+
* For each animation, for each frame, the cell of each mouth it has — the
|
|
143
|
+
* frame with that overlay drawn over its top image, or in its place.
|
|
144
|
+
*/
|
|
145
|
+
mouthCells: Array<Array<Partial<Record<AssistantMouthShape, number>>>>;
|
|
75
146
|
}
|
|
76
|
-
/**
|
|
77
|
-
export declare function
|
|
147
|
+
/** The images of a frame with a mouth: over the top image, or in its place. */
|
|
148
|
+
export declare function mouthImages(frame: Pick<AcsFrame, 'images'>, overlay: AcsOverlay): AcsFrameImage[];
|
|
149
|
+
/** Assign a cell to each distinct, non-empty frame, and to each mouth. */
|
|
150
|
+
export declare function layoutAcsSprite(file: Pick<AgentCharacterSource, 'width' | 'height' | 'animations'>): AcsSpriteLayout;
|
|
78
151
|
/**
|
|
79
152
|
* Composite a cell as Agent draws a frame: its images from last to first,
|
|
80
153
|
* each at its offset, the transparent colour left clear. RGBA, frame-sized.
|
|
81
154
|
*/
|
|
82
|
-
export declare function composeAcsCell(file:
|
|
155
|
+
export declare function composeAcsCell(file: Pick<AgentCharacterSource, 'width' | 'height' | 'transparentIndex' | 'palette'>, images: AcsFrameImage[], decode?: (index: number) => AcsImage): Uint8ClampedArray;
|
|
156
|
+
/**
|
|
157
|
+
* Draw a Microsoft Agent character in the page: its sprite sheet (an object
|
|
158
|
+
* URL), its animations with their mouths, its sounds as object URLs.
|
|
159
|
+
*/
|
|
160
|
+
export declare function drawAgentCharacter(source: AgentCharacterSource): Promise<AssistantCharacterData>;
|
|
83
161
|
/**
|
|
84
|
-
* Read a Microsoft Agent character (`.acs`) a person picked: its animations
|
|
85
|
-
* a sprite sheet drawn in the page (an object URL), its
|
|
86
|
-
* URLs. Nothing leaves the page.
|
|
162
|
+
* Read a Microsoft Agent character (`.acs`) a person picked: its animations
|
|
163
|
+
* and their mouths, a sprite sheet drawn in the page (an object URL), its
|
|
164
|
+
* sounds as object URLs. Nothing leaves the page.
|
|
87
165
|
*/
|
|
88
166
|
export declare function readAcsCharacter(buffer: ArrayBuffer): Promise<AssistantCharacterData>;
|
|
@@ -27,22 +27,41 @@ const STYLE_BALLOON = 0x200;
|
|
|
27
27
|
/** The widest a sprite sheet is laid out, and the largest it may be. */
|
|
28
28
|
const SHEET_MAX_WIDTH = 4096;
|
|
29
29
|
const SHEET_MAX_SIDE = 16384;
|
|
30
|
-
|
|
30
|
+
/** The mouth shapes of the format, by overlay type. */
|
|
31
|
+
export const MOUTH_SHAPES = [
|
|
32
|
+
'closed',
|
|
33
|
+
'wide1',
|
|
34
|
+
'wide2',
|
|
35
|
+
'wide3',
|
|
36
|
+
'wide4',
|
|
37
|
+
'medium',
|
|
38
|
+
'narrow',
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* Little-endian reads that refuse, in a sentence naming the file kind, to
|
|
42
|
+
* run past the end. `terminated`: whether a STRING ends with a null
|
|
43
|
+
* character, as in an `.acs` (an `.acf`'s do not).
|
|
44
|
+
*/
|
|
45
|
+
export class AgentReader {
|
|
31
46
|
bytes;
|
|
47
|
+
kind;
|
|
48
|
+
terminated;
|
|
32
49
|
view;
|
|
33
50
|
pos = 0;
|
|
34
|
-
constructor(bytes) {
|
|
51
|
+
constructor(bytes, kind = '.acs', terminated = true) {
|
|
35
52
|
this.bytes = bytes;
|
|
53
|
+
this.kind = kind;
|
|
54
|
+
this.terminated = terminated;
|
|
36
55
|
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
37
56
|
}
|
|
38
57
|
need(count, what) {
|
|
39
58
|
if (count < 0 || this.pos + count > this.bytes.length) {
|
|
40
|
-
throw new AssistantCharacterFormatError(`This .
|
|
59
|
+
throw new AssistantCharacterFormatError(`This ${this.kind} ends early: ${what} at byte ${this.pos} runs past the end of the file.`);
|
|
41
60
|
}
|
|
42
61
|
}
|
|
43
62
|
seek(locator, what) {
|
|
44
63
|
if (locator.offset + locator.size > this.bytes.length) {
|
|
45
|
-
throw new AssistantCharacterFormatError(`This .
|
|
64
|
+
throw new AssistantCharacterFormatError(`This ${this.kind} is damaged: ${what} is said to be at byte ${locator.offset}, past the end of the file.`);
|
|
46
65
|
}
|
|
47
66
|
this.pos = locator.offset;
|
|
48
67
|
}
|
|
@@ -81,17 +100,21 @@ class Reader {
|
|
|
81
100
|
locator(what) {
|
|
82
101
|
return { offset: this.u32(what), size: this.u32(what) };
|
|
83
102
|
}
|
|
84
|
-
/**
|
|
103
|
+
/**
|
|
104
|
+
* STRING: a ULONG count of UTF-16 characters, then, in an `.acs`, a null
|
|
105
|
+
* terminator.
|
|
106
|
+
*/
|
|
85
107
|
string(what) {
|
|
86
108
|
const count = this.u32(what);
|
|
87
109
|
if (count === 0)
|
|
88
110
|
return '';
|
|
89
|
-
this.
|
|
111
|
+
const stored = this.terminated ? count + 1 : count;
|
|
112
|
+
this.need(stored * 2, what);
|
|
90
113
|
let out = '';
|
|
91
114
|
for (let k = 0; k < count; k++) {
|
|
92
115
|
out += String.fromCharCode(this.view.getUint16(this.pos + k * 2, true));
|
|
93
116
|
}
|
|
94
|
-
this.pos +=
|
|
117
|
+
this.pos += stored * 2;
|
|
95
118
|
return out;
|
|
96
119
|
}
|
|
97
120
|
}
|
|
@@ -190,17 +213,26 @@ function readFrames(r, animation) {
|
|
|
190
213
|
for (let k = 0; k < branchCount; k++) {
|
|
191
214
|
branches.push({ frameIndex: r.u16(where), probability: r.u16(where) });
|
|
192
215
|
}
|
|
216
|
+
const overlays = [];
|
|
193
217
|
const overlayCount = r.u8(where);
|
|
194
218
|
for (let k = 0; k < overlayCount; k++) {
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
|
|
219
|
+
// A mouth overlay, drawn only while the character speaks: its type,
|
|
220
|
+
// whether it replaces the top image, its image, an unknown byte, the
|
|
221
|
+
// region flag, its offset, its size (halved, and not needed: the
|
|
222
|
+
// image has its own), its region.
|
|
223
|
+
const type = r.u8(where);
|
|
224
|
+
const replaceTop = r.u8(where) !== 0;
|
|
225
|
+
const image = r.u16(where);
|
|
226
|
+
r.skip(1, where);
|
|
198
227
|
const hasRegion = r.u8(where) !== 0;
|
|
199
|
-
r.
|
|
228
|
+
const x = r.i16(where);
|
|
229
|
+
const y = r.i16(where);
|
|
230
|
+
r.skip(4, where);
|
|
200
231
|
if (hasRegion)
|
|
201
232
|
r.skip(r.u32(where), where);
|
|
233
|
+
overlays.push({ type, replaceTop, image, x, y });
|
|
202
234
|
}
|
|
203
|
-
frames.push({ images, sound, duration, exitBranch, branches });
|
|
235
|
+
frames.push({ images, sound, duration, exitBranch, branches, overlays });
|
|
204
236
|
}
|
|
205
237
|
return frames;
|
|
206
238
|
}
|
|
@@ -213,10 +245,10 @@ export function parseAcs(buffer) {
|
|
|
213
245
|
if (bytes.length < 36) {
|
|
214
246
|
throw new AssistantCharacterFormatError(`This is not a Microsoft Agent character: the file is ${bytes.length} bytes, shorter than the header of an .acs.`);
|
|
215
247
|
}
|
|
216
|
-
const r = new
|
|
248
|
+
const r = new AgentReader(bytes);
|
|
217
249
|
const signature = r.u32('the signature');
|
|
218
250
|
if (signature === ACF_SIGNATURE) {
|
|
219
|
-
throw new AssistantCharacterFormatError('This is a Microsoft Agent .acf, whose animations are in separate .aca files: .acf
|
|
251
|
+
throw new AssistantCharacterFormatError('This is a Microsoft Agent .acf, whose animations are in separate .aca files: pick the .acf together with its .aca files.');
|
|
220
252
|
}
|
|
221
253
|
if (signature !== ACS_SIGNATURE) {
|
|
222
254
|
throw new AssistantCharacterFormatError(`This is not a Microsoft Agent character (.acs): it starts with 0x${signature.toString(16).padStart(8, '0')}, not 0xabcdabc3.`);
|
|
@@ -276,6 +308,11 @@ export function parseAcs(buffer) {
|
|
|
276
308
|
if (frame.sound >= sounds.length) {
|
|
277
309
|
throw new AssistantCharacterFormatError(`This .acs is damaged: frame ${f} of ${name} plays sound ${frame.sound}, and there are ${sounds.length}.`);
|
|
278
310
|
}
|
|
311
|
+
for (const overlay of frame.overlays) {
|
|
312
|
+
if (overlay.image >= images.length) {
|
|
313
|
+
throw new AssistantCharacterFormatError(`This .acs is damaged: a mouth of frame ${f} of ${name} draws image ${overlay.image}, and there are ${images.length}.`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
279
316
|
for (const branch of frame.branches) {
|
|
280
317
|
if (branch.frameIndex >= frames.length) {
|
|
281
318
|
throw new AssistantCharacterFormatError(`This .acs is damaged: frame ${f} of ${name} branches to frame ${branch.frameIndex}, and there are ${frames.length}.`);
|
|
@@ -304,7 +341,7 @@ export function decodeAcsImage(file, index) {
|
|
|
304
341
|
if (!entry) {
|
|
305
342
|
throw new AssistantCharacterFormatError(`This .acs has no image ${index}.`);
|
|
306
343
|
}
|
|
307
|
-
const r = new
|
|
344
|
+
const r = new AgentReader(file.bytes);
|
|
308
345
|
const what = `image ${index}`;
|
|
309
346
|
r.seek(entry.locator, what);
|
|
310
347
|
r.skip(1, what);
|
|
@@ -334,21 +371,39 @@ export function decodeAcsImage(file, index) {
|
|
|
334
371
|
}
|
|
335
372
|
return { width, height, pixels };
|
|
336
373
|
}
|
|
337
|
-
/**
|
|
374
|
+
/** The images of a frame with a mouth: over the top image, or in its place. */
|
|
375
|
+
export function mouthImages(frame, overlay) {
|
|
376
|
+
const mouth = { image: overlay.image, x: overlay.x, y: overlay.y };
|
|
377
|
+
const under = overlay.base ?? (overlay.replaceTop ? frame.images.slice(1) : frame.images);
|
|
378
|
+
return [mouth, ...under];
|
|
379
|
+
}
|
|
380
|
+
/** Assign a cell to each distinct, non-empty frame, and to each mouth. */
|
|
338
381
|
export function layoutAcsSprite(file) {
|
|
339
382
|
const cells = [];
|
|
340
383
|
const byKey = new Map();
|
|
341
|
-
const
|
|
342
|
-
if (
|
|
384
|
+
const cellOf = (images) => {
|
|
385
|
+
if (images.length === 0)
|
|
343
386
|
return -1;
|
|
344
|
-
const key =
|
|
387
|
+
const key = images.map(i => `${i.image},${i.x},${i.y}`).join(';');
|
|
345
388
|
let cell = byKey.get(key);
|
|
346
389
|
if (cell === undefined) {
|
|
347
390
|
cell = cells.length;
|
|
348
|
-
cells.push(
|
|
391
|
+
cells.push(images);
|
|
349
392
|
byKey.set(key, cell);
|
|
350
393
|
}
|
|
351
394
|
return cell;
|
|
395
|
+
};
|
|
396
|
+
const frameCells = file.animations.map(animation => animation.frames.map(frame => cellOf(frame.images)));
|
|
397
|
+
const mouthCells = file.animations.map(animation => animation.frames.map((frame, f) => {
|
|
398
|
+
const mouths = {};
|
|
399
|
+
for (const overlay of frame.overlays) {
|
|
400
|
+
const shape = MOUTH_SHAPES[overlay.type];
|
|
401
|
+
if (!shape) {
|
|
402
|
+
throw new AssistantCharacterFormatError(`This character is damaged: a mouth of frame ${f} of ${animation.name} is of type ${overlay.type}, where 0 to 6 are expected.`);
|
|
403
|
+
}
|
|
404
|
+
mouths[shape] = cellOf(mouthImages(frame, overlay));
|
|
405
|
+
}
|
|
406
|
+
return mouths;
|
|
352
407
|
}));
|
|
353
408
|
const count = Math.max(1, cells.length);
|
|
354
409
|
// Near square, and no wider than SHEET_MAX_WIDTH.
|
|
@@ -356,9 +411,9 @@ export function layoutAcsSprite(file) {
|
|
|
356
411
|
const rows = Math.ceil(count / columns);
|
|
357
412
|
if (columns * file.width > SHEET_MAX_SIDE ||
|
|
358
413
|
rows * file.height > SHEET_MAX_SIDE) {
|
|
359
|
-
throw new AssistantCharacterFormatError(`This
|
|
414
|
+
throw new AssistantCharacterFormatError(`This character has ${cells.length} distinct frames of ${file.width}×${file.height} pixels, more than one sprite sheet can hold.`);
|
|
360
415
|
}
|
|
361
|
-
return { columns, rows, cells, frameCells };
|
|
416
|
+
return { columns, rows, cells, frameCells, mouthCells };
|
|
362
417
|
}
|
|
363
418
|
/**
|
|
364
419
|
* Composite a cell as Agent draws a frame: its images from last to first,
|
|
@@ -403,7 +458,7 @@ function makeSheet(width, height) {
|
|
|
403
458
|
canvas = element;
|
|
404
459
|
toBlob = () => new Promise((resolve, reject) => element.toBlob(blob => blob
|
|
405
460
|
? resolve(blob)
|
|
406
|
-
: reject(new AssistantCharacterFormatError('The sprite sheet of this
|
|
461
|
+
: reject(new AssistantCharacterFormatError('The sprite sheet of this character could not be drawn.')), 'image/png'));
|
|
407
462
|
}
|
|
408
463
|
else {
|
|
409
464
|
throw new AssistantCharacterFormatError('This page has no canvas to draw the character on.');
|
|
@@ -415,19 +470,17 @@ function makeSheet(width, height) {
|
|
|
415
470
|
return { context, toBlob };
|
|
416
471
|
}
|
|
417
472
|
/**
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
* URLs. Nothing leaves the page.
|
|
473
|
+
* Draw a Microsoft Agent character in the page: its sprite sheet (an object
|
|
474
|
+
* URL), its animations with their mouths, its sounds as object URLs.
|
|
421
475
|
*/
|
|
422
|
-
export async function
|
|
423
|
-
const
|
|
424
|
-
const
|
|
425
|
-
const { width, height } = file;
|
|
476
|
+
export async function drawAgentCharacter(source) {
|
|
477
|
+
const layout = layoutAcsSprite(source);
|
|
478
|
+
const { width, height } = source;
|
|
426
479
|
const decoded = new Map();
|
|
427
480
|
const decode = (index) => {
|
|
428
481
|
let image = decoded.get(index);
|
|
429
482
|
if (!image) {
|
|
430
|
-
image =
|
|
483
|
+
image = source.decode(index);
|
|
431
484
|
decoded.set(index, image);
|
|
432
485
|
}
|
|
433
486
|
return image;
|
|
@@ -439,17 +492,18 @@ export async function readAcsCharacter(buffer) {
|
|
|
439
492
|
});
|
|
440
493
|
layout.cells.forEach((images, cell) => {
|
|
441
494
|
const data = sheet.context.createImageData(width, height);
|
|
442
|
-
data.data.set(composeAcsCell(
|
|
495
|
+
data.data.set(composeAcsCell(source, images, decode));
|
|
443
496
|
const { x, y } = cellAt(cell);
|
|
444
497
|
sheet.context.putImageData(data, x, y);
|
|
445
498
|
});
|
|
446
499
|
const sprite = objectUrl(await sheet.toBlob());
|
|
447
500
|
const used = new Set();
|
|
448
501
|
const animations = {};
|
|
449
|
-
|
|
502
|
+
source.animations.forEach((animation, a) => {
|
|
450
503
|
animations[animation.name] = {
|
|
451
504
|
frames: animation.frames.map((frame, f) => {
|
|
452
505
|
const cell = layout.frameCells[a][f];
|
|
506
|
+
const mouths = Object.entries(layout.mouthCells[a][f]);
|
|
453
507
|
if (frame.sound >= 0)
|
|
454
508
|
used.add(frame.sound);
|
|
455
509
|
return {
|
|
@@ -468,6 +522,11 @@ export async function readAcsCharacter(buffer) {
|
|
|
468
522
|
frame.exitBranch < animation.frames.length
|
|
469
523
|
? { exitBranch: frame.exitBranch }
|
|
470
524
|
: {}),
|
|
525
|
+
...(mouths.length
|
|
526
|
+
? {
|
|
527
|
+
mouths: Object.fromEntries(mouths.map(([shape, at]) => [shape, cellAt(at)])),
|
|
528
|
+
}
|
|
529
|
+
: {}),
|
|
471
530
|
};
|
|
472
531
|
}),
|
|
473
532
|
};
|
|
@@ -476,18 +535,31 @@ export async function readAcsCharacter(buffer) {
|
|
|
476
535
|
if (used.size > 0) {
|
|
477
536
|
sounds = {};
|
|
478
537
|
for (const index of [...used].sort((x, y) => x - y)) {
|
|
479
|
-
|
|
480
|
-
sounds[String(index)] = objectUrl(new Blob([file.bytes.slice(offset, offset + size)], {
|
|
481
|
-
type: 'audio/wav',
|
|
482
|
-
}));
|
|
538
|
+
sounds[String(index)] = objectUrl(new Blob([source.sound(index).slice()], { type: 'audio/wav' }));
|
|
483
539
|
}
|
|
484
540
|
}
|
|
485
541
|
return {
|
|
486
|
-
name:
|
|
542
|
+
name: source.name,
|
|
487
543
|
frameSize: { width, height },
|
|
488
544
|
sprite,
|
|
489
545
|
animations,
|
|
490
546
|
...(sounds ? { sounds } : {}),
|
|
491
|
-
authoredStates:
|
|
547
|
+
authoredStates: source.states,
|
|
492
548
|
};
|
|
493
549
|
}
|
|
550
|
+
/**
|
|
551
|
+
* Read a Microsoft Agent character (`.acs`) a person picked: its animations
|
|
552
|
+
* and their mouths, a sprite sheet drawn in the page (an object URL), its
|
|
553
|
+
* sounds as object URLs. Nothing leaves the page.
|
|
554
|
+
*/
|
|
555
|
+
export async function readAcsCharacter(buffer) {
|
|
556
|
+
const file = parseAcs(buffer);
|
|
557
|
+
return drawAgentCharacter({
|
|
558
|
+
...file,
|
|
559
|
+
decode: index => decodeAcsImage(file, index),
|
|
560
|
+
sound: index => {
|
|
561
|
+
const { offset, size } = file.sounds[index];
|
|
562
|
+
return file.bytes.subarray(offset, offset + size);
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type AssistantCharacterData } from './types';
|
|
2
2
|
/** What a file picker for a character accepts. */
|
|
3
|
-
export declare const CHARACTER_FILES_ACCEPT = ".acs,.js,.png,.gif,.webp,.jpg,.jpeg";
|
|
3
|
+
export declare const CHARACTER_FILES_ACCEPT = ".acs,.acf,.aca,.js,.png,.gif,.webp,.jpg,.jpeg";
|
|
4
4
|
/** The sentence for a pick that is not a character. */
|
|
5
|
-
export declare const CHARACTER_FILES_EXPECTED = "Pick one .acs file
|
|
5
|
+
export declare const CHARACTER_FILES_EXPECTED = "Pick one .acs file; or an .acf file with its .aca files; or a clippy.js character: its agent.js and its map image (and a sounds file if you have one).";
|
|
6
6
|
/** A picked file, as much of `File` as the reader needs. */
|
|
7
7
|
export type PickedFile = Blob & {
|
|
8
8
|
name: string;
|
|
@@ -11,6 +11,10 @@ export type PickedFile = Blob & {
|
|
|
11
11
|
export declare function characterFilesOf(files: ReadonlyArray<PickedFile>): {
|
|
12
12
|
kind: 'acs';
|
|
13
13
|
acs: PickedFile;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'acf';
|
|
16
|
+
acf: PickedFile;
|
|
17
|
+
acas: PickedFile[];
|
|
14
18
|
} | {
|
|
15
19
|
kind: 'clippy';
|
|
16
20
|
agentJs: PickedFile;
|
|
@@ -4,25 +4,32 @@
|
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* The files a person picked, read as one character (LOOP T-26): one
|
|
7
|
-
* Microsoft Agent `.acs
|
|
8
|
-
*
|
|
7
|
+
* Microsoft Agent `.acs`; a Microsoft Agent `.acf` with the `.aca` files it
|
|
8
|
+
* names; or a clippy.js character's `agent.js` and map image (and its
|
|
9
|
+
* sounds file, if there is one). Read in the page; nothing is
|
|
9
10
|
* sent anywhere.
|
|
10
11
|
*
|
|
11
12
|
* @module chat/assistant/formats/files
|
|
12
13
|
*/
|
|
14
|
+
import { readAcfCharacter } from './acf';
|
|
13
15
|
import { readAcsCharacter } from './acs';
|
|
14
16
|
import { readClippyCharacter } from './clippy';
|
|
15
17
|
import { AssistantCharacterFormatError, } from './types';
|
|
16
18
|
/** What a file picker for a character accepts. */
|
|
17
|
-
export const CHARACTER_FILES_ACCEPT = '.acs,.js,.png,.gif,.webp,.jpg,.jpeg';
|
|
19
|
+
export const CHARACTER_FILES_ACCEPT = '.acs,.acf,.aca,.js,.png,.gif,.webp,.jpg,.jpeg';
|
|
18
20
|
/** The sentence for a pick that is not a character. */
|
|
19
|
-
export const CHARACTER_FILES_EXPECTED = 'Pick one .acs file
|
|
21
|
+
export const CHARACTER_FILES_EXPECTED = 'Pick one .acs file; or an .acf file with its .aca files; or a clippy.js character: its agent.js and its map image (and a sounds file if you have one).';
|
|
20
22
|
/** Which of the picked files make the character, or why none do. */
|
|
21
23
|
export function characterFilesOf(files) {
|
|
22
24
|
const acs = files.find(file => file.name.toLowerCase().endsWith('.acs'));
|
|
23
25
|
if (acs) {
|
|
24
26
|
return { kind: 'acs', acs };
|
|
25
27
|
}
|
|
28
|
+
const acf = files.find(file => file.name.toLowerCase().endsWith('.acf'));
|
|
29
|
+
if (acf) {
|
|
30
|
+
const acas = files.filter(file => file.name.toLowerCase().endsWith('.aca'));
|
|
31
|
+
return { kind: 'acf', acf, acas };
|
|
32
|
+
}
|
|
26
33
|
const agentJs = files.find(file => file.name.toLowerCase() === 'agent.js');
|
|
27
34
|
const map = files.find(file => /\.(png|gif|webp|jpe?g)$/i.test(file.name));
|
|
28
35
|
const sounds = files.find(file => /^sounds-.*\.js$/i.test(file.name));
|
|
@@ -40,6 +47,13 @@ export async function readCharacterFiles(files) {
|
|
|
40
47
|
if (picked.kind === 'acs') {
|
|
41
48
|
return readAcsCharacter(await picked.acs.arrayBuffer());
|
|
42
49
|
}
|
|
50
|
+
if (picked.kind === 'acf') {
|
|
51
|
+
const acas = {};
|
|
52
|
+
for (const aca of picked.acas) {
|
|
53
|
+
acas[aca.name] = await aca.arrayBuffer();
|
|
54
|
+
}
|
|
55
|
+
return readAcfCharacter(await picked.acf.arrayBuffer(), acas);
|
|
56
|
+
}
|
|
43
57
|
return readClippyCharacter({
|
|
44
58
|
agentJs: await picked.agentJs.text(),
|
|
45
59
|
mapPng: picked.map,
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Character files a person brings (LOOP T-26): clippy.js sprite maps and
|
|
3
|
-
* Microsoft Agent `.acs` files,
|
|
3
|
+
* Microsoft Agent `.acs` files, or `.acf` files with their `.aca` files,
|
|
4
|
+
* read in the page into one shape.
|
|
4
5
|
*
|
|
5
6
|
* @module chat/assistant/formats
|
|
6
7
|
*/
|
|
7
8
|
export * from './types';
|
|
8
9
|
export { readClippyCharacter, readClippyAgent, readClippySounds, } from './clippy';
|
|
9
10
|
export type { ClippyCharacterFiles } from './clippy';
|
|
10
|
-
export { readAcsCharacter, parseAcs, decodeAcsImage, layoutAcsSprite, composeAcsCell, } from './acs';
|
|
11
|
-
export type { AcsFile, AcsAnimation, AcsFrame, AcsImage } from './acs';
|
|
11
|
+
export { readAcsCharacter, parseAcs, decodeAcsImage, layoutAcsSprite, composeAcsCell, drawAgentCharacter, mouthImages, MOUTH_SHAPES, } from './acs';
|
|
12
|
+
export type { AcsFile, AcsAnimation, AcsFrame, AcsImage, AcsOverlay, AgentCharacterSource, } from './acs';
|
|
13
|
+
export { readAcfCharacter, parseAcf, parseAca } from './acf';
|
|
14
|
+
export type { AcfFile, AcfAnimationEntry, AcaFile } from './acf';
|
|
12
15
|
export { decompressAgentData } from './agentCompression';
|
|
13
16
|
export { stateAnimations } from './stateAnimations';
|
|
14
17
|
export { CHARACTER_FILES_ACCEPT, CHARACTER_FILES_EXPECTED, characterFilesOf, readCharacterFiles, } from './files';
|
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* Character files a person brings (LOOP T-26): clippy.js sprite maps and
|
|
7
|
-
* Microsoft Agent `.acs` files,
|
|
7
|
+
* Microsoft Agent `.acs` files, or `.acf` files with their `.aca` files,
|
|
8
|
+
* read in the page into one shape.
|
|
8
9
|
*
|
|
9
10
|
* @module chat/assistant/formats
|
|
10
11
|
*/
|
|
11
12
|
export * from './types';
|
|
12
13
|
export { readClippyCharacter, readClippyAgent, readClippySounds, } from './clippy';
|
|
13
|
-
export { readAcsCharacter, parseAcs, decodeAcsImage, layoutAcsSprite, composeAcsCell, } from './acs';
|
|
14
|
+
export { readAcsCharacter, parseAcs, decodeAcsImage, layoutAcsSprite, composeAcsCell, drawAgentCharacter, mouthImages, MOUTH_SHAPES, } from './acs';
|
|
15
|
+
export { readAcfCharacter, parseAcf, parseAca } from './acf';
|
|
14
16
|
export { decompressAgentData } from './agentCompression';
|
|
15
17
|
export { stateAnimations } from './stateAnimations';
|
|
16
18
|
export { CHARACTER_FILES_ACCEPT, CHARACTER_FILES_EXPECTED, characterFilesOf, readCharacterFiles, } from './files';
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @module chat/assistant/formats/types
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* The shapes of a mouth while the character speaks, as Microsoft Agent
|
|
9
|
+
* names its overlays: closed, wide open (four), medium, narrow.
|
|
10
|
+
*/
|
|
11
|
+
export type AssistantMouthShape = 'closed' | 'wide1' | 'wide2' | 'wide3' | 'wide4' | 'medium' | 'narrow';
|
|
7
12
|
/** One frame of an animation. */
|
|
8
13
|
export interface AssistantCharacterFrame {
|
|
9
14
|
/** How long the frame shows, in milliseconds. */
|
|
@@ -26,6 +31,15 @@ export interface AssistantCharacterFrame {
|
|
|
26
31
|
}[];
|
|
27
32
|
/** The frame to go to when the animation is asked to end early. */
|
|
28
33
|
exitBranch?: number;
|
|
34
|
+
/**
|
|
35
|
+
* The frame with each mouth the character has for it, drawn while it
|
|
36
|
+
* speaks in place of `images`: the sprite offset of each (Microsoft Agent's
|
|
37
|
+
* mouth overlays, composited into the sheet). Most frames have none.
|
|
38
|
+
*/
|
|
39
|
+
mouths?: Partial<Record<AssistantMouthShape, {
|
|
40
|
+
x: number;
|
|
41
|
+
y: number;
|
|
42
|
+
}>>;
|
|
29
43
|
}
|
|
30
44
|
/** A named sequence of frames. */
|
|
31
45
|
export interface AssistantCharacterAnimation {
|
|
@@ -103,6 +103,8 @@ export declare function latestSaying(items: readonly unknown[]): AssistantSaying
|
|
|
103
103
|
* popup's button calls it back — for the session, or for good; the header's
|
|
104
104
|
* *Floating assistant* always calls it back.
|
|
105
105
|
*/
|
|
106
|
+
/** The openings of the mouth, by level: shut, a little, half, wide. */
|
|
107
|
+
export declare function mouthOpening(level: number): 0 | 1 | 2 | 3;
|
|
106
108
|
export type AssistantAway = 'none' | 'page' | 'session' | 'always';
|
|
107
109
|
/** Where the choice is kept: the session's storage, or for good. */
|
|
108
110
|
export declare const ASSISTANT_AWAY_KEY = "datalayer-assistant-away";
|
|
@@ -125,6 +125,15 @@ export function latestSaying(items) {
|
|
|
125
125
|
}
|
|
126
126
|
return undefined;
|
|
127
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* How long the assistant is away, once sent (T-27): for the page — the
|
|
130
|
+
* popup's button calls it back — for the session, or for good; the header's
|
|
131
|
+
* *Floating assistant* always calls it back.
|
|
132
|
+
*/
|
|
133
|
+
/** The openings of the mouth, by level: shut, a little, half, wide. */
|
|
134
|
+
export function mouthOpening(level) {
|
|
135
|
+
return level < 0.08 ? 0 : level < 0.3 ? 1 : level < 0.6 ? 2 : 3;
|
|
136
|
+
}
|
|
128
137
|
/** Where the choice is kept: the session's storage, or for good. */
|
|
129
138
|
export const ASSISTANT_AWAY_KEY = 'datalayer-assistant-away';
|
|
130
139
|
/** The away a page starts with, from what was kept. Storage may be refused. */
|
|
@@ -61,6 +61,7 @@ import { useNotebookTools } from '../../tools/adapters/agent-runtimes/notebookHo
|
|
|
61
61
|
import { useAgentInspectorSink } from '../../components/inspector/agentInspector';
|
|
62
62
|
import { chatInspectorRecorder, } from '../../components/inspector/chatInspect';
|
|
63
63
|
import { loopMessageChange, speakerOf, withLoopMessage } from './loopMessage';
|
|
64
|
+
import { loopStepOf, withLoopStep } from './loopStep';
|
|
64
65
|
// Tracks pending prompts already auto-sent for a given conversation scope.
|
|
65
66
|
// This prevents layout-driven unmount/remount cycles from re-sending prompts.
|
|
66
67
|
const sentPendingPromptKeys = new Set();
|
|
@@ -2684,6 +2685,12 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
|
|
|
2684
2685
|
}
|
|
2685
2686
|
break;
|
|
2686
2687
|
case 'activity': {
|
|
2688
|
+
// A step of an application's code, drawn as it runs (LOOP P-16).
|
|
2689
|
+
const step = loopStepOf(event.activity);
|
|
2690
|
+
if (step) {
|
|
2691
|
+
setDisplayItems(prev => withLoopStep(prev, step));
|
|
2692
|
+
break;
|
|
2693
|
+
}
|
|
2687
2694
|
// An application's code changed a message it sent: its author
|
|
2688
2695
|
// said, or the message taken away (LOOP P-15).
|
|
2689
2696
|
const change = loopMessageChange(event.activity);
|