@astra-code/astra-ai 0.1.8 → 0.1.9

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/lib/voice.ts +54 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astra-code/astra-ai",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Astra Code terminal app built by Sean Donovan",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/lib/voice.ts CHANGED
@@ -5,7 +5,8 @@ import {basename, join} from "path";
5
5
  import {tmpdir} from "os";
6
6
  import {readFile, rm} from "fs/promises";
7
7
  import {getBackendUrl} from "./config.js";
8
- import {loadSession} from "./sessionStore.js";
8
+ import {loadSession, saveSession} from "./sessionStore.js";
9
+ import type {AuthSession} from "../types/events.js";
9
10
 
10
11
  const VOICE_TEXT_LIMIT = 600;
11
12
  const DEFAULT_STT_MODEL = process.env.ASTRA_STT_MODEL?.trim() || "whisper-1";
@@ -169,6 +170,31 @@ const captureAudioChunk = async (seconds: number): Promise<string> => {
169
170
  return outPath;
170
171
  };
171
172
 
173
+ const tryRefreshToken = async (): Promise<string | null> => {
174
+ const stored = loadSession();
175
+ const refreshToken = stored?.refresh_token?.trim();
176
+ if (!refreshToken) return null;
177
+ try {
178
+ const res = await fetch(`${getBackendUrl()}/api/auth/refresh`, {
179
+ method: "POST",
180
+ headers: {"content-type": "application/json"},
181
+ body: JSON.stringify({refresh_token: refreshToken})
182
+ });
183
+ if (!res.ok) return null;
184
+ const data = (await res.json()) as Record<string, unknown>;
185
+ const newAccess = typeof data.access_token === "string" ? data.access_token.trim() : null;
186
+ if (!newAccess) return null;
187
+ const newRefresh = typeof data.refresh_token === "string" ? data.refresh_token.trim() : refreshToken;
188
+ if (stored) {
189
+ const refreshed: AuthSession = {...stored, access_token: newAccess, refresh_token: newRefresh};
190
+ saveSession(refreshed);
191
+ }
192
+ return newAccess;
193
+ } catch {
194
+ return null;
195
+ }
196
+ };
197
+
172
198
  const transcribeAudioFile = async (filePath: string): Promise<string> => {
173
199
  const bytes = await readFile(filePath);
174
200
  // #region agent log
@@ -176,25 +202,45 @@ const transcribeAudioFile = async (filePath: string): Promise<string> => {
176
202
  // #endregion
177
203
  const file = new File([bytes], basename(filePath), {type: "audio/wav"});
178
204
 
179
- // Backend proxy only: backend holds provider secrets.
180
- const token = loadSession()?.access_token;
181
- if (!token) {
182
- throw new Error("Missing auth session for backend STT. Please sign in again.");
183
- }
184
205
  const form = new FormData();
185
206
  form.append("file", file);
186
207
  form.append("model", DEFAULT_STT_MODEL);
187
- const response = await fetch(`${getBackendUrl()}/api/agent/transcribe`, {
208
+
209
+ let token = loadSession()?.access_token?.trim();
210
+ if (!token) {
211
+ throw new Error("Missing auth session for backend STT. Please sign in again.");
212
+ }
213
+
214
+ let response = await fetch(`${getBackendUrl()}/api/agent/transcribe`, {
188
215
  method: "POST",
189
216
  headers: {Authorization: `Bearer ${token}`},
190
217
  body: form
191
218
  });
219
+
220
+ if (response.status === 401) {
221
+ const newToken = await tryRefreshToken();
222
+ if (newToken) {
223
+ const formRetry = new FormData();
224
+ formRetry.append("file", new File([bytes], basename(filePath), {type: "audio/wav"}));
225
+ formRetry.append("model", DEFAULT_STT_MODEL);
226
+ response = await fetch(`${getBackendUrl()}/api/agent/transcribe`, {
227
+ method: "POST",
228
+ headers: {Authorization: `Bearer ${newToken}`},
229
+ body: formRetry
230
+ });
231
+ }
232
+ }
233
+
192
234
  // #region agent log
193
235
  fetch('http://127.0.0.1:7573/ingest/fdd4f018-1ba3-4303-b1bb-375443267476',{method:'POST',headers:{'Content-Type':'application/json','X-Debug-Session-Id':'17f1ea'},body:JSON.stringify({sessionId:'17f1ea',runId:'voice_run_2',hypothesisId:'H7',location:'voice.ts:transcribeAudioFile',message:'backend transcribe response',data:{status:response.status,ok:response.ok},timestamp:Date.now()})}).catch(()=>{});
194
236
  // #endregion
195
237
  if (!response.ok) {
196
238
  const detail = (await response.text()).slice(0, 400);
197
- throw new Error(`Backend transcription failed ${response.status}: ${detail}`);
239
+ const hint =
240
+ response.status === 401
241
+ ? " Token expired and refresh failed. Run /logout and sign in again."
242
+ : "";
243
+ throw new Error(`Backend transcription failed ${response.status}: ${detail}${hint}`);
198
244
  }
199
245
  const data = (await response.json()) as {text?: string};
200
246
  const out = String(data.text ?? "").trim();