heyhank 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.
Files changed (199) hide show
  1. package/README.md +40 -0
  2. package/bin/cli.ts +168 -0
  3. package/bin/ctl.ts +528 -0
  4. package/bin/generate-token.ts +28 -0
  5. package/dist/apple-touch-icon.png +0 -0
  6. package/dist/assets/AgentsPage-BPhirnCe.js +7 -0
  7. package/dist/assets/AssistantPage-DJ-cMQfb.js +1 -0
  8. package/dist/assets/CronManager-DDbz-yiT.js +1 -0
  9. package/dist/assets/HelpPage-DMfkzERp.js +1 -0
  10. package/dist/assets/IntegrationsPage-CrOitCmJ.js +1 -0
  11. package/dist/assets/MediaPage-CE5rdvkC.js +1 -0
  12. package/dist/assets/PlatformDashboard-Do6F0O2p.js +1 -0
  13. package/dist/assets/Playground-Fc5cdc5p.js +109 -0
  14. package/dist/assets/ProcessPanel-CslEiZkI.js +2 -0
  15. package/dist/assets/PromptsPage-D2EhsdNO.js +4 -0
  16. package/dist/assets/RunsPage-C5BZF5Rx.js +1 -0
  17. package/dist/assets/SandboxManager-a1AVI5q2.js +8 -0
  18. package/dist/assets/SettingsPage-DirhjQrJ.js +51 -0
  19. package/dist/assets/SocialMediaPage-DBuM28vD.js +1 -0
  20. package/dist/assets/TailscalePage-CHiFhZXF.js +1 -0
  21. package/dist/assets/TelephonyPage-x0VV0fOo.js +1 -0
  22. package/dist/assets/TerminalPage-Drwyrnfd.js +1 -0
  23. package/dist/assets/gemini-audio-t-TSU-To.js +17 -0
  24. package/dist/assets/gemini-live-client-C7rqAW7G.js +166 -0
  25. package/dist/assets/index-C8M_PUmX.css +32 -0
  26. package/dist/assets/index-CEqZnThB.js +204 -0
  27. package/dist/assets/sw-register-LSSpj6RU.js +1 -0
  28. package/dist/assets/time-ago-B6r_l9u1.js +1 -0
  29. package/dist/assets/workbox-window.prod.es5-BIl4cyR9.js +2 -0
  30. package/dist/favicon-32-original.png +0 -0
  31. package/dist/favicon-32.png +0 -0
  32. package/dist/favicon.ico +0 -0
  33. package/dist/favicon.svg +8 -0
  34. package/dist/fonts/MesloLGSNerdFontMono-Bold.woff2 +0 -0
  35. package/dist/fonts/MesloLGSNerdFontMono-Regular.woff2 +0 -0
  36. package/dist/heyhank-mascot-poster.png +0 -0
  37. package/dist/heyhank-mascot.mp4 +0 -0
  38. package/dist/heyhank-mascot.webm +0 -0
  39. package/dist/icon-192-original.png +0 -0
  40. package/dist/icon-192.png +0 -0
  41. package/dist/icon-512-original.png +0 -0
  42. package/dist/icon-512.png +0 -0
  43. package/dist/index.html +21 -0
  44. package/dist/logo-192.png +0 -0
  45. package/dist/logo-512.png +0 -0
  46. package/dist/logo-codex.svg +14 -0
  47. package/dist/logo-docker.svg +4 -0
  48. package/dist/logo-original.png +0 -0
  49. package/dist/logo.png +0 -0
  50. package/dist/logo.svg +14 -0
  51. package/dist/manifest.json +24 -0
  52. package/dist/push-sw.js +34 -0
  53. package/dist/sw.js +1 -0
  54. package/dist/workbox-d2a0910a.js +1 -0
  55. package/package.json +109 -0
  56. package/server/agent-cron-migrator.ts +85 -0
  57. package/server/agent-executor.ts +357 -0
  58. package/server/agent-store.ts +185 -0
  59. package/server/agent-timeout.ts +107 -0
  60. package/server/agent-types.ts +122 -0
  61. package/server/ai-validation-settings.ts +37 -0
  62. package/server/ai-validator.ts +181 -0
  63. package/server/anthropic-provider-migration.ts +48 -0
  64. package/server/assistant-store.ts +272 -0
  65. package/server/auth-manager.ts +150 -0
  66. package/server/auto-approve.ts +153 -0
  67. package/server/auto-namer.ts +36 -0
  68. package/server/backend-adapter.ts +54 -0
  69. package/server/cache-headers.ts +61 -0
  70. package/server/calendar-service.ts +434 -0
  71. package/server/claude-adapter.ts +889 -0
  72. package/server/claude-container-auth.ts +30 -0
  73. package/server/claude-session-discovery.ts +157 -0
  74. package/server/claude-session-history.ts +410 -0
  75. package/server/cli-launcher.ts +1303 -0
  76. package/server/codex-adapter.ts +3027 -0
  77. package/server/codex-container-auth.ts +24 -0
  78. package/server/codex-home.ts +27 -0
  79. package/server/codex-ws-proxy.cjs +226 -0
  80. package/server/commands-discovery.ts +81 -0
  81. package/server/constants.ts +7 -0
  82. package/server/container-manager.ts +1053 -0
  83. package/server/cost-tracker.ts +222 -0
  84. package/server/cron-scheduler.ts +243 -0
  85. package/server/cron-store.ts +148 -0
  86. package/server/cron-types.ts +63 -0
  87. package/server/email-service.ts +354 -0
  88. package/server/env-manager.ts +161 -0
  89. package/server/event-bus-types.ts +75 -0
  90. package/server/event-bus.ts +124 -0
  91. package/server/execution-store.ts +170 -0
  92. package/server/federation/node-connection.ts +190 -0
  93. package/server/federation/node-manager.ts +366 -0
  94. package/server/federation/node-store.ts +86 -0
  95. package/server/federation/node-types.ts +121 -0
  96. package/server/fs-utils.ts +15 -0
  97. package/server/git-utils.ts +421 -0
  98. package/server/github-pr.ts +379 -0
  99. package/server/google-media.ts +342 -0
  100. package/server/image-pull-manager.ts +279 -0
  101. package/server/index.ts +491 -0
  102. package/server/internal-ai.ts +237 -0
  103. package/server/kill-switch.ts +99 -0
  104. package/server/llm-providers.ts +342 -0
  105. package/server/logger.ts +259 -0
  106. package/server/mcp-registry.ts +401 -0
  107. package/server/message-bus.ts +271 -0
  108. package/server/message-delivery.ts +128 -0
  109. package/server/metrics-collector.ts +350 -0
  110. package/server/metrics-types.ts +108 -0
  111. package/server/middleware/managed-auth.ts +195 -0
  112. package/server/novnc-proxy.ts +99 -0
  113. package/server/path-resolver.ts +186 -0
  114. package/server/paths.ts +13 -0
  115. package/server/pr-poller.ts +162 -0
  116. package/server/prompt-manager.ts +211 -0
  117. package/server/protocol/claude-upstream/README.md +19 -0
  118. package/server/protocol/claude-upstream/sdk.d.ts.txt +1943 -0
  119. package/server/protocol/codex-upstream/ClientNotification.ts.txt +5 -0
  120. package/server/protocol/codex-upstream/ClientRequest.ts.txt +60 -0
  121. package/server/protocol/codex-upstream/README.md +18 -0
  122. package/server/protocol/codex-upstream/ServerNotification.ts.txt +41 -0
  123. package/server/protocol/codex-upstream/ServerRequest.ts.txt +16 -0
  124. package/server/protocol/codex-upstream/v2/DynamicToolCallParams.ts.txt +6 -0
  125. package/server/protocol/codex-upstream/v2/DynamicToolCallResponse.ts.txt +6 -0
  126. package/server/protocol-monitor.ts +50 -0
  127. package/server/provider-manager.ts +111 -0
  128. package/server/provider-registry.ts +393 -0
  129. package/server/push-notifications.ts +221 -0
  130. package/server/recorder.ts +374 -0
  131. package/server/recording-hub/compat-validator.ts +284 -0
  132. package/server/recording-hub/diagnostics.ts +299 -0
  133. package/server/recording-hub/hub-config.ts +19 -0
  134. package/server/recording-hub/hub-routes.ts +236 -0
  135. package/server/recording-hub/hub-store.ts +265 -0
  136. package/server/recording-hub/replay-adapter.ts +207 -0
  137. package/server/relay-client.ts +320 -0
  138. package/server/reminder-scheduler.ts +38 -0
  139. package/server/replay.ts +78 -0
  140. package/server/routes/agent-routes.ts +264 -0
  141. package/server/routes/assistant-routes.ts +90 -0
  142. package/server/routes/cron-routes.ts +103 -0
  143. package/server/routes/env-routes.ts +95 -0
  144. package/server/routes/federation-routes.ts +76 -0
  145. package/server/routes/fs-routes.ts +622 -0
  146. package/server/routes/git-routes.ts +97 -0
  147. package/server/routes/llm-routes.ts +166 -0
  148. package/server/routes/media-routes.ts +135 -0
  149. package/server/routes/metrics-routes.ts +13 -0
  150. package/server/routes/platform-routes.ts +1379 -0
  151. package/server/routes/prompt-routes.ts +67 -0
  152. package/server/routes/provider-routes.ts +109 -0
  153. package/server/routes/sandbox-routes.ts +127 -0
  154. package/server/routes/settings-routes.ts +285 -0
  155. package/server/routes/skills-routes.ts +100 -0
  156. package/server/routes/socialmedia-routes.ts +208 -0
  157. package/server/routes/system-routes.ts +228 -0
  158. package/server/routes/tailscale-routes.ts +22 -0
  159. package/server/routes/telephony-routes.ts +259 -0
  160. package/server/routes.ts +1379 -0
  161. package/server/sandbox-manager.ts +168 -0
  162. package/server/service.ts +718 -0
  163. package/server/session-creation-service.ts +457 -0
  164. package/server/session-git-info.ts +104 -0
  165. package/server/session-names.ts +67 -0
  166. package/server/session-orchestrator.ts +824 -0
  167. package/server/session-state-machine.ts +207 -0
  168. package/server/session-store.ts +146 -0
  169. package/server/session-types.ts +511 -0
  170. package/server/settings-manager.ts +149 -0
  171. package/server/shared-context.ts +157 -0
  172. package/server/socialmedia/adapter.ts +15 -0
  173. package/server/socialmedia/adapters/ayrshare-adapter.ts +169 -0
  174. package/server/socialmedia/adapters/buffer-adapter.ts +299 -0
  175. package/server/socialmedia/adapters/postiz-adapter.ts +298 -0
  176. package/server/socialmedia/manager.ts +227 -0
  177. package/server/socialmedia/store.ts +98 -0
  178. package/server/socialmedia/types.ts +89 -0
  179. package/server/tailscale-manager.ts +451 -0
  180. package/server/telephony/audio-bridge.ts +331 -0
  181. package/server/telephony/call-manager.ts +457 -0
  182. package/server/telephony/call-types.ts +108 -0
  183. package/server/telephony/telephony-store.ts +119 -0
  184. package/server/terminal-manager.ts +240 -0
  185. package/server/update-checker.ts +192 -0
  186. package/server/usage-limits.ts +225 -0
  187. package/server/web-push.d.ts +51 -0
  188. package/server/worktree-tracker.ts +84 -0
  189. package/server/ws-auth.ts +41 -0
  190. package/server/ws-bridge-browser-ingest.ts +72 -0
  191. package/server/ws-bridge-browser.ts +112 -0
  192. package/server/ws-bridge-cli-ingest.ts +81 -0
  193. package/server/ws-bridge-codex.ts +266 -0
  194. package/server/ws-bridge-controls.ts +20 -0
  195. package/server/ws-bridge-persist.ts +66 -0
  196. package/server/ws-bridge-publish.ts +79 -0
  197. package/server/ws-bridge-replay.ts +61 -0
  198. package/server/ws-bridge-types.ts +121 -0
  199. package/server/ws-bridge.ts +1240 -0
@@ -0,0 +1,331 @@
1
+ // ─── Audio Bridge ─────────────────────────────────────────────────────────────
2
+ // Bridges audio between FreeSWITCH (8kHz PCM via mod_audio_fork) and
3
+ // Gemini Live BidiGenerateContent API (16kHz PCM).
4
+ // This is the core of the telephony system — no STT/TTS needed,
5
+ // Gemini handles everything natively.
6
+
7
+ import type { CallState, TranscriptEntry } from "./call-types.js";
8
+
9
+ // Gemini Live WebSocket endpoint
10
+ const GEMINI_WS_BASE = "wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent";
11
+ const GEMINI_MODEL = "models/gemini-2.0-flash-live-001";
12
+
13
+ export interface AudioBridgeConfig {
14
+ geminiApiKey: string;
15
+ voice: string;
16
+ systemPrompt: string;
17
+ tools: unknown[];
18
+ onTranscript: (entry: TranscriptEntry) => void;
19
+ onStatusChange: (status: CallState["status"]) => void;
20
+ onToolCall: (calls: Array<{ id: string; name: string; args: Record<string, unknown> }>) => Promise<Array<{ id: string; name: string; response: unknown }>>;
21
+ }
22
+
23
+ /**
24
+ * AudioBridge manages a single call's audio pipeline:
25
+ * FreeSWITCH PCM (8kHz) → upsample → Gemini Live (16kHz) → downsample → FreeSWITCH
26
+ */
27
+ export class AudioBridge {
28
+ private geminiWs: WebSocket | null = null;
29
+ private config: AudioBridgeConfig;
30
+ private setupDone = false;
31
+ private callId: string;
32
+ private textBuffer = "";
33
+
34
+ constructor(callId: string, config: AudioBridgeConfig) {
35
+ this.callId = callId;
36
+ this.config = config;
37
+ }
38
+
39
+ /** Connect to Gemini Live API */
40
+ async connect(): Promise<void> {
41
+ const url = `${GEMINI_WS_BASE}?key=${this.config.geminiApiKey}`;
42
+ this.geminiWs = new WebSocket(url);
43
+
44
+ return new Promise((resolve, reject) => {
45
+ const timeout = setTimeout(() => {
46
+ reject(new Error("Gemini connection timeout"));
47
+ }, 15000);
48
+
49
+ this.geminiWs!.onopen = () => {
50
+ // Send setup with telephony-optimized config
51
+ this.geminiWs!.send(JSON.stringify({
52
+ setup: {
53
+ model: GEMINI_MODEL,
54
+ generationConfig: {
55
+ responseModalities: ["AUDIO"],
56
+ speechConfig: {
57
+ voiceConfig: {
58
+ prebuiltVoiceConfig: { voiceName: this.config.voice },
59
+ },
60
+ },
61
+ },
62
+ systemInstruction: {
63
+ parts: [{ text: this.config.systemPrompt }],
64
+ },
65
+ tools: this.config.tools,
66
+ outputAudioTranscription: {},
67
+ inputAudioTranscription: {},
68
+ },
69
+ }));
70
+ };
71
+
72
+ this.geminiWs!.onmessage = async (event: MessageEvent) => {
73
+ try {
74
+ let text: string;
75
+ if (event.data instanceof Blob) {
76
+ text = await event.data.text();
77
+ } else {
78
+ text = event.data as string;
79
+ }
80
+ const msg = JSON.parse(text);
81
+ this.handleGeminiMessage(msg, resolve, clearTimeout.bind(null, timeout));
82
+ } catch {
83
+ // ignore parse errors
84
+ }
85
+ };
86
+
87
+ this.geminiWs!.onerror = () => {
88
+ clearTimeout(timeout);
89
+ reject(new Error("Gemini WebSocket error"));
90
+ };
91
+
92
+ this.geminiWs!.onclose = () => {
93
+ this.setupDone = false;
94
+ this.flushTextBuffer();
95
+ this.config.onStatusChange("ended");
96
+ };
97
+ });
98
+ }
99
+
100
+ private flushTextBuffer(): void {
101
+ if (this.textBuffer.trim()) {
102
+ this.config.onTranscript({
103
+ speaker: "ai",
104
+ text: this.textBuffer.trim(),
105
+ isFinal: true,
106
+ ts: Date.now(),
107
+ });
108
+ }
109
+ this.textBuffer = "";
110
+ }
111
+
112
+ private handleGeminiMessage(
113
+ msg: Record<string, unknown>,
114
+ onSetupResolve?: () => void,
115
+ clearSetupTimeout?: () => void,
116
+ ): void {
117
+ // Setup complete
118
+ if ("setupComplete" in msg) {
119
+ this.setupDone = true;
120
+ clearSetupTimeout?.();
121
+ onSetupResolve?.();
122
+ this.config.onStatusChange("active");
123
+ this.config.onTranscript({
124
+ speaker: "system",
125
+ text: "AI connected to call",
126
+ isFinal: true,
127
+ ts: Date.now(),
128
+ });
129
+ return;
130
+ }
131
+
132
+ // Tool calls
133
+ if ("toolCall" in msg) {
134
+ const tc = msg.toolCall as {
135
+ functionCalls?: Array<{ id: string; name: string; args?: Record<string, unknown> }>;
136
+ };
137
+ if (tc.functionCalls?.length) {
138
+ const calls = tc.functionCalls.map((fc) => ({
139
+ id: fc.id,
140
+ name: fc.name,
141
+ args: fc.args || {},
142
+ }));
143
+
144
+ this.config.onTranscript({
145
+ speaker: "system",
146
+ text: `Tool: ${calls.map((c) => c.name).join(", ")}`,
147
+ isFinal: true,
148
+ ts: Date.now(),
149
+ });
150
+
151
+ // Execute tools and send response back
152
+ this.config.onToolCall(calls).then((responses) => {
153
+ this.sendToolResponse(responses);
154
+ }).catch(() => {});
155
+ }
156
+ return;
157
+ }
158
+
159
+ // Server content
160
+ if ("serverContent" in msg) {
161
+ const content = msg.serverContent as Record<string, unknown>;
162
+
163
+ // Output transcription (AI speech as text)
164
+ const outputT = content.outputTranscription as { text?: string } | undefined;
165
+ if (outputT?.text) {
166
+ this.textBuffer += outputT.text;
167
+ }
168
+
169
+ // Input transcription (callee speech as text)
170
+ const inputT = content.inputTranscription as { text?: string } | undefined;
171
+ if (inputT?.text?.trim()) {
172
+ this.config.onTranscript({
173
+ speaker: "callee",
174
+ text: inputT.text.trim(),
175
+ isFinal: true,
176
+ ts: Date.now(),
177
+ });
178
+ }
179
+
180
+ // Turn complete
181
+ if (content.turnComplete) {
182
+ this.flushTextBuffer();
183
+ return;
184
+ }
185
+
186
+ // Interrupted
187
+ if (content.interrupted) {
188
+ this.flushTextBuffer();
189
+ return;
190
+ }
191
+
192
+ // Model turn parts — extract audio to send back to FreeSWITCH
193
+ const modelTurn = content.modelTurn as {
194
+ parts?: Array<{ inlineData?: { data: string; mimeType: string }; text?: string }>;
195
+ } | undefined;
196
+
197
+ if (modelTurn?.parts) {
198
+ for (const part of modelTurn.parts) {
199
+ if (part.inlineData?.data) {
200
+ // This is the AI's audio response — needs to go back to FreeSWITCH
201
+ // The audio is 24kHz PCM from Gemini, needs downsampling to 8kHz for telephony
202
+ this.onGeminiAudio(part.inlineData.data);
203
+ }
204
+ }
205
+ }
206
+ }
207
+ }
208
+
209
+ /** Callback for when Gemini produces audio — override to send to FreeSWITCH */
210
+ public onGeminiAudio: (base64Pcm: string) => void = () => {};
211
+
212
+ /**
213
+ * Feed audio from FreeSWITCH into Gemini.
214
+ * Input: raw PCM 8kHz 16-bit mono from mod_audio_fork
215
+ * Gemini expects: PCM 16kHz
216
+ */
217
+ sendCallerAudio(pcm8kHz: Buffer | Uint8Array): void {
218
+ if (!this.geminiWs || this.geminiWs.readyState !== WebSocket.OPEN || !this.setupDone) return;
219
+
220
+ // Upsample 8kHz → 16kHz (simple linear interpolation)
221
+ const upsampled = upsample8to16(pcm8kHz);
222
+ const base64 = bufferToBase64(upsampled);
223
+
224
+ this.geminiWs.send(JSON.stringify({
225
+ realtimeInput: {
226
+ audio: {
227
+ mimeType: "audio/pcm;rate=16000",
228
+ data: base64,
229
+ },
230
+ },
231
+ }));
232
+ }
233
+
234
+ /** Send tool call results back to Gemini */
235
+ private sendToolResponse(responses: Array<{ id: string; name: string; response: unknown }>): void {
236
+ if (!this.geminiWs || this.geminiWs.readyState !== WebSocket.OPEN || !this.setupDone) return;
237
+
238
+ this.geminiWs.send(JSON.stringify({
239
+ toolResponse: {
240
+ functionResponses: responses.map((r) => ({
241
+ id: r.id,
242
+ name: r.name,
243
+ response: r.response,
244
+ })),
245
+ },
246
+ }));
247
+ }
248
+
249
+ /** Disconnect from Gemini */
250
+ disconnect(): void {
251
+ this.flushTextBuffer();
252
+ if (this.geminiWs) {
253
+ this.geminiWs.onclose = null;
254
+ this.geminiWs.close();
255
+ this.geminiWs = null;
256
+ }
257
+ this.setupDone = false;
258
+ }
259
+
260
+ get isReady(): boolean {
261
+ return this.setupDone && this.geminiWs?.readyState === WebSocket.OPEN;
262
+ }
263
+ }
264
+
265
+ // ─── Audio Utilities ──────────────────────────────────────────────────────────
266
+
267
+ /**
268
+ * Upsample 8kHz PCM (16-bit LE) to 16kHz using linear interpolation.
269
+ * Every sample gets doubled with an interpolated sample in between.
270
+ */
271
+ function upsample8to16(input: Buffer | Uint8Array): Uint8Array {
272
+ const inputView = new DataView(input.buffer, input.byteOffset, input.byteLength);
273
+ const sampleCount = input.byteLength / 2; // 16-bit samples
274
+ const output = new Uint8Array(sampleCount * 4); // 2x samples, 2 bytes each
275
+ const outputView = new DataView(output.buffer);
276
+
277
+ for (let i = 0; i < sampleCount; i++) {
278
+ const sample = inputView.getInt16(i * 2, true); // little-endian
279
+ const nextSample = i + 1 < sampleCount
280
+ ? inputView.getInt16((i + 1) * 2, true)
281
+ : sample;
282
+ const interpolated = Math.round((sample + nextSample) / 2);
283
+
284
+ outputView.setInt16(i * 4, sample, true);
285
+ outputView.setInt16(i * 4 + 2, interpolated, true);
286
+ }
287
+
288
+ return output;
289
+ }
290
+
291
+ /**
292
+ * Downsample 24kHz/16kHz PCM to 8kHz for FreeSWITCH.
293
+ * Takes every Nth sample (simple decimation).
294
+ */
295
+ export function downsampleTo8k(input: Uint8Array, inputRate: number): Uint8Array {
296
+ const ratio = inputRate / 8000;
297
+ const inputView = new DataView(input.buffer, input.byteOffset, input.byteLength);
298
+ const inputSamples = input.byteLength / 2;
299
+ const outputSamples = Math.floor(inputSamples / ratio);
300
+ const output = new Uint8Array(outputSamples * 2);
301
+ const outputView = new DataView(output.buffer);
302
+
303
+ for (let i = 0; i < outputSamples; i++) {
304
+ const srcIdx = Math.floor(i * ratio);
305
+ if (srcIdx * 2 + 1 < input.byteLength) {
306
+ const sample = inputView.getInt16(srcIdx * 2, true);
307
+ outputView.setInt16(i * 2, sample, true);
308
+ }
309
+ }
310
+
311
+ return output;
312
+ }
313
+
314
+ /** Convert Uint8Array/Buffer to base64 string */
315
+ function bufferToBase64(buf: Uint8Array): string {
316
+ let binary = "";
317
+ for (let i = 0; i < buf.byteLength; i++) {
318
+ binary += String.fromCharCode(buf[i]);
319
+ }
320
+ return btoa(binary);
321
+ }
322
+
323
+ /** Convert base64 string to Uint8Array */
324
+ export function base64ToBuffer(base64: string): Uint8Array {
325
+ const binary = atob(base64);
326
+ const bytes = new Uint8Array(binary.length);
327
+ for (let i = 0; i < binary.length; i++) {
328
+ bytes[i] = binary.charCodeAt(i);
329
+ }
330
+ return bytes;
331
+ }