@etazio/agent-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,297 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Phase 7f – Web-Anzeige am Fernseher: ein Chromium auf dem Server (Renderer-Dienst) zeigt eine Webseite,
4
+ * sein Bild geht als `web:frame` an die Clients in Sichtweite. Zwei Ziele:
5
+ * - **Ad-hoc-URL** (`TvState.url` bei `source: 'web'`) – flüchtiges Profil, jedes Mitglied darf bedienen,
6
+ * - **Kanal** (`TvState.web` = Kanal-Id, mehrere durch Komma für die Rotation) – benannt, persistentes Profil,
7
+ * Anmeldung bleibt erhalten; anlegen/ändern/bedienen verlangt `office.edit`.
8
+ *
9
+ * Bedienung ist echte Browser-Bedienung: wer die Steuerung übernimmt (exklusiv, `controlTtlMs`), schickt
10
+ * Maus-, Tastatur- und Radereignisse per `web:input` an die Seite (CDP `Input.dispatch*`).
11
+ */
12
+ export declare const WEB_LIMITS: {
13
+ readonly maxUrl: 1024;
14
+ readonly maxName: 60;
15
+ readonly maxCss: 4096;
16
+ /** Kanäle je Workspace */
17
+ readonly maxChannels: 12;
18
+ /** Kanäle gleichzeitig offen je Renderer-Instanz */
19
+ readonly maxContexts: 3;
20
+ /** Bildabstand im Standbildmodus (Sekunden) */
21
+ readonly minRefresh: 2;
22
+ readonly maxRefresh: 300;
23
+ readonly defaultRefresh: 10;
24
+ /** Sichtfenster des Server-Browsers */
25
+ readonly minViewport: 480;
26
+ readonly maxViewport: 2560;
27
+ readonly defaultViewport: {
28
+ readonly w: 1280;
29
+ readonly h: 720;
30
+ };
31
+ /** Zoom der Seite */
32
+ readonly minZoom: 0.25;
33
+ readonly maxZoom: 3;
34
+ /** Steuerung: Dauer der exklusiven Übernahme ohne Eingabe (ms) */
35
+ readonly controlTtlMs: number;
36
+ /** Eingabeereignisse je Sekunde und Teilnehmer */
37
+ readonly inputPerSecond: 60;
38
+ /** Zeichen, die auf einmal eingefügt werden dürfen (Zwischenablage) */
39
+ readonly maxPaste: 8192;
40
+ /** Ohne Zuschauer: pausieren nach … ms, Seite schließen nach … ms */
41
+ readonly idlePauseMs: 60000;
42
+ readonly idleCloseMs: number;
43
+ /** Bildgröße (Bytes, base64) */
44
+ readonly maxFrameBytes: 1500000;
45
+ /** Rotation mehrerer Kanäle: Sekunden je Kanal */
46
+ readonly minRotate: 10;
47
+ readonly maxRotate: 3600;
48
+ /** Bildrate im Bewegtbildmodus */
49
+ readonly maxLiveFps: 10;
50
+ };
51
+ export declare const WEB_MODES: readonly ['still', 'live'];
52
+ export type WebMode = (typeof WEB_MODES)[number];
53
+ export interface WebChannelDto {
54
+ id: string;
55
+ name: string;
56
+ url: string;
57
+ viewport: {
58
+ w: number;
59
+ h: number;
60
+ };
61
+ mode: WebMode;
62
+ refreshSeconds: number;
63
+ zoom: number;
64
+ scroll?: {
65
+ x: number;
66
+ y: number;
67
+ };
68
+ css?: string;
69
+ /** Anmeldung im Profil hinterlegt (Cookies im Volume) */
70
+ hasProfile: boolean;
71
+ /** Feste Kopfzeilen/Basic-Auth hinterlegt (Werte selbst werden nie ausgeliefert) */
72
+ hasAuth: boolean;
73
+ createdBy: string | null;
74
+ updatedAt: string;
75
+ lastLoginAt: string | null;
76
+ }
77
+ /** Zielbeschreibung einer Web-Anzeige: Kanal-Id oder Ad-hoc-Adresse */
78
+ export interface WebTarget {
79
+ /** `channel:<id>` oder `url:<adresse>` – Schlüssel für Abo, Frames und Steuerung */
80
+ key: string;
81
+ channelId?: string;
82
+ url?: string;
83
+ }
84
+ export declare const webKey: {
85
+ channel: (id: string) => string;
86
+ url: (u: string) => string;
87
+ };
88
+ /** Kanal-Ids einer Web-Anzeige (Rotation: durch Komma getrennt) */
89
+ export declare function webChannelIds(web: string | undefined): string[];
90
+ /**
91
+ * Aktuelles Ziel einer Web-Anzeige zur Serverzeit `now`. Mehrere Kanäle rotieren mit `rotate` Sekunden
92
+ * (synchron über `at`, wie die Diashow); ohne Kanal gilt die Ad-hoc-Adresse.
93
+ */
94
+ export declare function webTarget(state: {
95
+ web?: string;
96
+ url?: string;
97
+ rotate?: number;
98
+ at?: number;
99
+ }, now?: number): WebTarget | null;
100
+ /** Nur https (und `http` bleibt der serverseitigen Allowlist vorbehalten) – Vorprüfung im Client */
101
+ export declare function isWebUrl(u: string): boolean;
102
+ /** Eingabe des Nutzers zu einer Adresse ergänzen: „example.com/x“ → „https://example.com/x“ */
103
+ export declare function normalizeWebUrl(input: string): string | null;
104
+ /** Anzeigename einer Web-Anzeige (Kanalname setzt der Client ein) */
105
+ export declare function webLabel(title: string | null | undefined, url: string | null | undefined): string;
106
+ export declare const WEB_BUTTONS: readonly ['left', 'right', 'middle'];
107
+ export declare const webInputEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
108
+ kind: z.ZodLiteral<"mouse">;
109
+ type: z.ZodEnum<{
110
+ click: "click";
111
+ down: "down";
112
+ move: "move";
113
+ up: "up";
114
+ }>;
115
+ x: z.ZodNumber;
116
+ y: z.ZodNumber;
117
+ button: z.ZodOptional<z.ZodEnum<{
118
+ left: "left";
119
+ middle: "middle";
120
+ right: "right";
121
+ }>>;
122
+ clickCount: z.ZodOptional<z.ZodNumber>;
123
+ modifiers: z.ZodOptional<z.ZodNumber>;
124
+ }, z.core.$strip>, z.ZodObject<{
125
+ kind: z.ZodLiteral<"wheel">;
126
+ x: z.ZodNumber;
127
+ y: z.ZodNumber;
128
+ dx: z.ZodNumber;
129
+ dy: z.ZodNumber;
130
+ modifiers: z.ZodOptional<z.ZodNumber>;
131
+ }, z.core.$strip>, z.ZodObject<{
132
+ kind: z.ZodLiteral<"key">;
133
+ type: z.ZodEnum<{
134
+ char: "char";
135
+ down: "down";
136
+ up: "up";
137
+ }>;
138
+ key: z.ZodString;
139
+ code: z.ZodOptional<z.ZodString>;
140
+ text: z.ZodOptional<z.ZodString>;
141
+ modifiers: z.ZodOptional<z.ZodNumber>;
142
+ }, z.core.$strip>, z.ZodObject<{
143
+ kind: z.ZodLiteral<"text">;
144
+ text: z.ZodString;
145
+ }, z.core.$strip>], "kind">;
146
+ export type WebInputEvent = z.infer<typeof webInputEventSchema>;
147
+ export declare const webNavigateSchema: z.ZodObject<{
148
+ action: z.ZodEnum<{
149
+ back: "back";
150
+ forward: "forward";
151
+ home: "home";
152
+ reload: "reload";
153
+ url: "url";
154
+ }>;
155
+ url: z.ZodOptional<z.ZodString>;
156
+ }, z.core.$strip>;
157
+ export type WebNavigate = z.infer<typeof webNavigateSchema>;
158
+ export interface WebWatchPayload {
159
+ placementId: string;
160
+ /** false = Abo beenden */
161
+ watch: boolean;
162
+ }
163
+ export interface WebFramePayload {
164
+ placementId: string;
165
+ /** Ziel-Schlüssel (Kanal oder Adresse) – Frames alter Ziele lassen sich so verwerfen */
166
+ key: string;
167
+ seq: number;
168
+ /** JPEG/PNG als base64 (ohne data:-Präfix) */
169
+ data: string;
170
+ mime: string;
171
+ w: number;
172
+ h: number;
173
+ }
174
+ export type WebPhase = 'starting' | 'loading' | 'ready' | 'blocked' | 'error' | 'login' | 'stopped';
175
+ export interface WebStatusPayload {
176
+ placementId: string;
177
+ key: string;
178
+ phase: WebPhase;
179
+ /** Adresse, auf der die Seite gerade steht (Mitglieder; Gäste bekommen null) */
180
+ url: string | null;
181
+ title: string | null;
182
+ canGoBack: boolean;
183
+ canGoForward: boolean;
184
+ /** Teilnehmer mit der Steuerung (null = frei) */
185
+ controller: string | null;
186
+ controllerName: string | null;
187
+ /** Fehlertext für die Zwischenanzeige */
188
+ message?: string;
189
+ }
190
+ export interface WebControlPayload {
191
+ placementId: string;
192
+ key: string;
193
+ controller: string | null;
194
+ controllerName: string | null;
195
+ /** Serverzeit, zu der die Übernahme ohne Eingabe abläuft */
196
+ until: number | null;
197
+ }
198
+ export declare const webWatchSchema: z.ZodObject<{
199
+ placementId: z.ZodString;
200
+ watch: z.ZodBoolean;
201
+ }, z.core.$strip>;
202
+ export declare const webControlSchema: z.ZodObject<{
203
+ placementId: z.ZodString;
204
+ take: z.ZodBoolean;
205
+ }, z.core.$strip>;
206
+ export declare const webInputSchema: z.ZodObject<{
207
+ placementId: z.ZodString;
208
+ event: z.ZodDiscriminatedUnion<[z.ZodObject<{
209
+ kind: z.ZodLiteral<"mouse">;
210
+ type: z.ZodEnum<{
211
+ click: "click";
212
+ down: "down";
213
+ move: "move";
214
+ up: "up";
215
+ }>;
216
+ x: z.ZodNumber;
217
+ y: z.ZodNumber;
218
+ button: z.ZodOptional<z.ZodEnum<{
219
+ left: "left";
220
+ middle: "middle";
221
+ right: "right";
222
+ }>>;
223
+ clickCount: z.ZodOptional<z.ZodNumber>;
224
+ modifiers: z.ZodOptional<z.ZodNumber>;
225
+ }, z.core.$strip>, z.ZodObject<{
226
+ kind: z.ZodLiteral<"wheel">;
227
+ x: z.ZodNumber;
228
+ y: z.ZodNumber;
229
+ dx: z.ZodNumber;
230
+ dy: z.ZodNumber;
231
+ modifiers: z.ZodOptional<z.ZodNumber>;
232
+ }, z.core.$strip>, z.ZodObject<{
233
+ kind: z.ZodLiteral<"key">;
234
+ type: z.ZodEnum<{
235
+ char: "char";
236
+ down: "down";
237
+ up: "up";
238
+ }>;
239
+ key: z.ZodString;
240
+ code: z.ZodOptional<z.ZodString>;
241
+ text: z.ZodOptional<z.ZodString>;
242
+ modifiers: z.ZodOptional<z.ZodNumber>;
243
+ }, z.core.$strip>, z.ZodObject<{
244
+ kind: z.ZodLiteral<"text">;
245
+ text: z.ZodString;
246
+ }, z.core.$strip>], "kind">;
247
+ }, z.core.$strip>;
248
+ export declare const webNavigateRequestSchema: z.ZodIntersection<z.ZodObject<{
249
+ placementId: z.ZodString;
250
+ }, z.core.$strip>, z.ZodObject<{
251
+ action: z.ZodEnum<{
252
+ back: "back";
253
+ forward: "forward";
254
+ home: "home";
255
+ reload: "reload";
256
+ url: "url";
257
+ }>;
258
+ url: z.ZodOptional<z.ZodString>;
259
+ }, z.core.$strip>>;
260
+ /**
261
+ * Sichtfeld an die Anzeigefläche des Steuernden angleichen. Ohne das rendert der Server-Browser
262
+ * immer im Kanal-Sichtfeld (Standard 1280×720) und das Bild wird im Fenster hochskaliert – die
263
+ * Seite wirkt dann herangezoomt und unscharf. Gilt nur, solange jemand steuert; danach stellt der
264
+ * Dienst das Kanal-Sichtfeld wieder her, damit der Fernseher sein gewohntes Bild behält.
265
+ */
266
+ export declare const webResizeSchema: z.ZodObject<{
267
+ placementId: z.ZodString;
268
+ w: z.ZodNumber;
269
+ h: z.ZodNumber;
270
+ }, z.core.$strip>;
271
+ export type WebResizePayload = z.infer<typeof webResizeSchema>;
272
+ export declare const webChannelInputSchema: z.ZodObject<{
273
+ name: z.ZodString;
274
+ url: z.ZodString;
275
+ viewport: z.ZodOptional<z.ZodObject<{
276
+ w: z.ZodNumber;
277
+ h: z.ZodNumber;
278
+ }, z.core.$strip>>;
279
+ mode: z.ZodOptional<z.ZodEnum<{
280
+ live: "live";
281
+ still: "still";
282
+ }>>;
283
+ refreshSeconds: z.ZodOptional<z.ZodNumber>;
284
+ zoom: z.ZodOptional<z.ZodNumber>;
285
+ scroll: z.ZodOptional<z.ZodObject<{
286
+ x: z.ZodNumber;
287
+ y: z.ZodNumber;
288
+ }, z.core.$strip>>;
289
+ css: z.ZodOptional<z.ZodString>;
290
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
291
+ basicAuth: z.ZodOptional<z.ZodNullable<z.ZodObject<{
292
+ user: z.ZodString;
293
+ password: z.ZodString;
294
+ }, z.core.$strip>>>;
295
+ }, z.core.$strip>;
296
+ export type WebChannelInput = z.infer<typeof webChannelInputSchema>;
297
+ export declare const webChannelIdSchema: z.ZodString;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Sprache zu Text und Text zu Sprache (SPA-83).
3
+ *
4
+ * Die Schnittstellen stehen bewusst vor der Umsetzung: welcher Anbieter erkennt und spricht, ist die
5
+ * Sache des Agenten-Betreibers – das Office liefert nur den Ton. Wer einen anderen Dienst nutzt,
6
+ * schreibt zwei kleine Funktionen und lässt alles andere unverändert.
7
+ *
8
+ * Mitgeliefert ist OpenAI, weil es mit einem einzigen Schlüssel beides kann und ohne zusätzliches
9
+ * Paket auskommt: Erkennung über `/v1/audio/transcriptions`, Stimme über `/v1/audio/speech` mit
10
+ * rohem PCM – das lässt sich beim Eintreffen abspielen, statt auf die fertige Datei zu warten.
11
+ */
12
+ export interface SttAdapter {
13
+ /** Eine abgeschlossene Äußerung erkennen. Leerer String = nichts Verwertbares. */
14
+ transcribe(pcm: Int16Array, sampleRate: number, signal?: AbortSignal): Promise<string>;
15
+ }
16
+ export interface TtsAdapter {
17
+ /** Abtastrate des gelieferten PCM-Stroms */
18
+ readonly sampleRate: number;
19
+ /**
20
+ * Text sprechen. Liefert 16-bit-PCM in Stücken, sobald sie da sind – wer auf die ganze Datei
21
+ * wartet, wartet eine Sekunde zu lang, und genau die entscheidet, ob es lebendig wirkt.
22
+ */
23
+ speak(text: string, signal?: AbortSignal): AsyncIterable<Int16Array>;
24
+ }
25
+ export interface OpenAiSpeechOptions {
26
+ apiKey: string;
27
+ baseUrl?: string;
28
+ /** Erkennungsmodell (`gpt-4o-mini-transcribe` ist schnell, `whisper-1` günstig) */
29
+ sttModel?: string;
30
+ /** Sprachmodell für die Stimme */
31
+ ttsModel?: string;
32
+ /** Stimme des Agenten – gehört langfristig ins Agenten-Profil (SPA-83, offene Frage 5) */
33
+ voice?: string;
34
+ /** Erwartete Sprache als ISO-Code; spart der Erkennung das Raten und damit Zeit */
35
+ language?: string;
36
+ timeoutMs?: number;
37
+ }
38
+ export declare function openAiStt(options: OpenAiSpeechOptions): SttAdapter;
39
+ export declare function openAiTts(options: OpenAiSpeechOptions): TtsAdapter;
@@ -0,0 +1,2 @@
1
+ /** Version dieses SDK – muss zu `package.json` passen (geprüft in `scripts/build.mjs` und `test/dist.test.mjs`). */
2
+ export declare const SDK_VERSION = "0.1.0";
@@ -0,0 +1,61 @@
1
+ import type { AgentVoicePayload } from './shared/index.js';
2
+ import type { SttAdapter, TtsAdapter } from './speech.js';
3
+ import type { EtazioAgent } from './index.js';
4
+ export interface VoiceReplyContext {
5
+ /** Was der Mensch gesagt hat */
6
+ text: string;
7
+ session: AgentVoicePayload;
8
+ /** Bricht ab, sobald der Mensch dazwischenredet oder das Gespräch endet – an jede Modellanfrage weiterreichen */
9
+ signal: AbortSignal;
10
+ }
11
+ export interface EtazioVoiceOptions {
12
+ stt: SttAdapter;
13
+ tts: TtsAdapter;
14
+ /**
15
+ * Antwort auf eine Äußerung. Leerer String = nichts sagen.
16
+ * Das ist die einzige Stelle, an der das eigene Modell hängt – alles davor und danach ist Mechanik.
17
+ */
18
+ reply(ctx: VoiceReplyContext): Promise<string>;
19
+ /**
20
+ * Untertitel im Chat: was der Agent sagt, soll mitlesbar sein (SPA-83). Standard: an.
21
+ * Was der Mensch sagt, wird bewusst **nicht** geschrieben – das wäre eine Aufzeichnung, und die
22
+ * gehört ausdrücklich freigegeben, nicht nebenbei angeschaltet.
23
+ */
24
+ subtitles?: boolean;
25
+ /** Stille bis zum Satzende (ms) */
26
+ silenceMs?: number;
27
+ /** Wie laut es sein muss, damit es als Rede gilt (0…1) */
28
+ threshold?: number;
29
+ onError?(err: Error): void;
30
+ onTranscript?(text: string, session: AgentVoicePayload): void;
31
+ }
32
+ export declare class EtazioVoice {
33
+ private readonly agent;
34
+ private readonly options;
35
+ private readonly live;
36
+ private started;
37
+ constructor(agent: EtazioAgent, options: EtazioVoiceOptions);
38
+ /** An die Sprachereignisse des Agenten hängen. Ab hier laufen Gespräche von selbst. */
39
+ start(): this;
40
+ /** Laufende Gespräche (für Diagnose und geordnetes Herunterfahren). */
41
+ get sessions(): AgentVoicePayload[];
42
+ stop(): Promise<void>;
43
+ private open;
44
+ /** Ton des Menschen lesen, in Äußerungen schneiden und beantworten. */
45
+ private listen;
46
+ /** Ein Gesprächszug: verstehen → antworten → sprechen. Jeder Schritt kann unterbrochen werden. */
47
+ private turn;
48
+ /** Text sprechen: Blöcke gehen raus, sobald sie da sind – wer die ganze Datei abwartet, wartet zu lang. */
49
+ private say;
50
+ /**
51
+ * Barge-in: der Mensch redet dazwischen. Der laufende Zug wird abgebrochen **und** die schon in
52
+ * LiveKit liegende Warteschlange geleert – ohne das Zweite redet der Agent noch sekundenlang weiter.
53
+ */
54
+ private bargeIn;
55
+ private state;
56
+ private close;
57
+ private closeAll;
58
+ private fail;
59
+ }
60
+ /** Kurzform: Sprachschleife an einen verbundenen Agenten hängen. */
61
+ export declare function attachVoice(agent: EtazioAgent, options: EtazioVoiceOptions): EtazioVoice;