@astralform/js 7.2.0 → 7.4.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.
package/dist/index.cjs CHANGED
@@ -35,10 +35,14 @@ __export(index_exports, {
35
35
  StreamAbortedError: () => StreamAbortedError,
36
36
  StreamManager: () => StreamManager,
37
37
  ToolRegistry: () => ToolRegistry,
38
+ VOICE_POLISH_MODES: () => VOICE_POLISH_MODES,
38
39
  generateId: () => generateId,
39
40
  isEmbeddedResource: () => isEmbeddedResource,
41
+ isVoiceLLMMode: () => isVoiceLLMMode,
42
+ isVoicePolishMode: () => isVoicePolishMode,
40
43
  mapSseToChat: () => mapSseToChat,
41
44
  parseEmbeddedResource: () => parseEmbeddedResource,
45
+ parseVoicePolishFrame: () => parseVoicePolishFrame,
42
46
  replayEvents: () => replayEvents,
43
47
  streamJobSSE: () => streamJobSSE,
44
48
  translateDelta: () => translateDelta
@@ -241,12 +245,13 @@ function createRateLimitErrorFromHttp(response, rawText) {
241
245
 
242
246
  // src/streaming.ts
243
247
  async function* streamJobSSE(options) {
244
- const { url, headers, signal, fetchFn } = options;
248
+ const { url, headers, signal, fetchFn, method = "GET", body } = options;
245
249
  let response;
246
250
  try {
247
251
  response = await fetchFn(url, {
248
- method: "GET",
252
+ method,
249
253
  headers,
254
+ body,
250
255
  signal
251
256
  });
252
257
  } catch (err) {
@@ -308,6 +313,57 @@ async function* streamJobSSE(options) {
308
313
  }
309
314
  }
310
315
 
316
+ // src/types.ts
317
+ var ChatEventType = {
318
+ // Connection lifecycle (SDK-local, not wire)
319
+ Connected: "connected",
320
+ Disconnected: "disconnected",
321
+ // Turn lifecycle
322
+ MessageStart: "message_start",
323
+ MessageStop: "message_stop",
324
+ // Block lifecycle
325
+ BlockStart: "block_start",
326
+ BlockDelta: "block_delta",
327
+ BlockStop: "block_stop",
328
+ // Reliability
329
+ Stall: "stall",
330
+ Retry: "retry",
331
+ Error: "error",
332
+ Keepalive: "keepalive",
333
+ // Conversation-level (typed custom events)
334
+ UserMessage: "user_message",
335
+ TitleGenerated: "title_generated",
336
+ TodoUpdate: "todo_update",
337
+ PlanUpdate: "plan_update",
338
+ NoteUpdate: "note_update",
339
+ ContextUpdate: "context_update",
340
+ SubagentStart: "subagent_start",
341
+ SubagentStop: "subagent_stop",
342
+ ContextWarning: "context_warning",
343
+ MemoryRecall: "memory_recall",
344
+ MemoryUpdate: "memory_update",
345
+ DesktopStream: "desktop_stream",
346
+ AttachmentStaged: "attachment_staged",
347
+ WorkspaceReady: "workspace_ready",
348
+ AssetCreated: "asset_created",
349
+ ToolApprovalRequested: "tool_approval_requested",
350
+ ToolApprovalGranted: "tool_approval_granted",
351
+ ToolPermissionDenied: "tool_permission_denied",
352
+ ToolHarnessWarning: "tool_harness_warning",
353
+ UserUnavailable: "user_unavailable",
354
+ PromptSuggestion: "prompt_suggestion",
355
+ StateChanged: "state_changed",
356
+ // Generic fallthrough for unknown custom events
357
+ Custom: "custom"
358
+ };
359
+ var VOICE_POLISH_MODES = ["raw", "light", "structured", "formal"];
360
+ function isVoicePolishMode(value) {
361
+ return typeof value === "string" && VOICE_POLISH_MODES.includes(value);
362
+ }
363
+ function isVoiceLLMMode(mode) {
364
+ return mode !== "raw";
365
+ }
366
+
311
367
  // src/client.ts
312
368
  var DEFAULT_BASE_URL = "https://api.astralform.ai";
313
369
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -333,6 +389,69 @@ function isApiKeyConfig(config) {
333
389
  }
334
390
  var AstralformClient = class {
335
391
  constructor(config) {
392
+ // --- Code mode: the app user's projects ---
393
+ /**
394
+ * The projects (GitHub repositories) this app user works with, and what they
395
+ * may add.
396
+ *
397
+ * A project list is per app user within a code-mode agent: the developer
398
+ * connects the workspace's GitHub account, and each user curates their own
399
+ * list from what that connection covers. Every method 404s on a chat-mode
400
+ * agent, so the surface is invisible rather than empty there.
401
+ */
402
+ this.code = {
403
+ projects: {
404
+ /** This user's projects on the active agent, oldest first. */
405
+ list: async () => {
406
+ const raw = await this.get(
407
+ "/v1/code/projects"
408
+ );
409
+ return raw.map((p) => camelizeKeys(p));
410
+ },
411
+ /**
412
+ * What the workspace's GitHub installations cover, minus what this user
413
+ * has already added. Read `state` before the list: an empty `repositories`
414
+ * means something different in each of its three values.
415
+ */
416
+ available: async () => {
417
+ const raw = await this.get("/v1/code/projects/available");
418
+ return {
419
+ state: raw.state,
420
+ repositories: (raw.repositories ?? []).map((r) => ({
421
+ fullName: r.full_name,
422
+ private: r.private
423
+ })),
424
+ // Defaulted like its neighbours: the `unavailable` branch has nothing
425
+ // to count, and an absent field behind a `number` type prints
426
+ // "undefined" in a picker rather than a number.
427
+ totalCount: raw.total_count ?? 0,
428
+ partial: raw.partial ?? false
429
+ };
430
+ },
431
+ /**
432
+ * Add a repository. The server checks it against the workspace's own
433
+ * installations and answers a repository it cannot reach the same way it
434
+ * answers one owned by someone else — deliberately, so this call cannot be
435
+ * used to discover which organisations use Astralform.
436
+ */
437
+ add: async (repoFullName) => {
438
+ const raw = await this.post(
439
+ "/v1/code/projects",
440
+ { repo_full_name: repoFullName }
441
+ );
442
+ return camelizeKeys(raw);
443
+ },
444
+ /**
445
+ * Remove a project. Tasks already bound to that repository keep their
446
+ * binding — they simply stop grouping under it.
447
+ */
448
+ remove: async (owner, repo) => {
449
+ await this.del(
450
+ `/v1/code/projects/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`
451
+ );
452
+ }
453
+ }
454
+ };
336
455
  if (isApiKeyConfig(config)) {
337
456
  if (!config.apiKey || typeof config.apiKey !== "string") {
338
457
  throw new Error("apiKey is required and must be a non-empty string");
@@ -573,10 +692,18 @@ var AstralformClient = class {
573
692
  }
574
693
  };
575
694
  }
576
- async getConversations(limit = 50, offset = 0) {
695
+ /**
696
+ * A page of conversations, newest-updated first.
697
+ *
698
+ * `options.repository` narrows to one project's tasks (`owner/repo`) on a
699
+ * code-mode agent — the same paging applies within the filter, so a client
700
+ * showing tasks per project pages each project separately.
701
+ */
702
+ async getConversations(limit = 50, offset = 0, options) {
577
703
  const safeLimit = Math.max(1, Math.min(200, Math.floor(Number(limit))));
578
704
  const safeOffset = Math.max(0, Math.floor(Number(offset)));
579
- const raw = await this.get(`/v1/conversations?limit=${safeLimit}&offset=${safeOffset}`);
705
+ const filter = options?.repository ? `&repository=${encodeURIComponent(options.repository)}` : "";
706
+ const raw = await this.get(`/v1/conversations?limit=${safeLimit}&offset=${safeOffset}${filter}`);
580
707
  return raw.map((c) => camelizeKeys(c));
581
708
  }
582
709
  async getMessages(conversationId) {
@@ -736,6 +863,94 @@ var AstralformClient = class {
736
863
  const raw = await response.json();
737
864
  return this.mapAsset(raw);
738
865
  }
866
+ // --- Voice input ---
867
+ /** The agent's voice-input defaults (`GET /v1/voice/config`). */
868
+ async getVoiceConfig() {
869
+ const raw = await this.get("/v1/voice/config");
870
+ return {
871
+ enabled: Boolean(raw.enabled),
872
+ modes: raw.modes ?? [...VOICE_POLISH_MODES],
873
+ // A mode this SDK does not know must not reach a `switch` typed as
874
+ // `VoicePolishMode`; `structured` is the server's own default.
875
+ defaultMode: isVoicePolishMode(raw.default_mode) ? raw.default_mode : "structured",
876
+ silenceAutoStopSeconds: raw.silence_auto_stop_seconds ?? 2,
877
+ autoSend: raw.auto_send ?? true,
878
+ maxRecordingSeconds: raw.max_recording_seconds ?? 300,
879
+ supportsStreaming: Boolean(raw.supports_streaming),
880
+ hotwords: raw.hotwords ?? []
881
+ };
882
+ }
883
+ /**
884
+ * Transcribe one recording with the agent's configured speech-to-text
885
+ * provider (`POST /v1/voice/transcriptions`). 16 kHz mono 16-bit WAV is the
886
+ * reference format; anything the provider accepts works.
887
+ *
888
+ * Deliberately outside `withDeadline`: a recording can run to
889
+ * `VoiceConfig.maxRecordingSeconds`, so the 30 s default would cut real
890
+ * uploads off. Pass `options.signal` to give up on a stalled one; the
891
+ * promise then rejects with the abort reason — the runtime's `AbortError`,
892
+ * or whatever was passed to `abort(reason)`.
893
+ */
894
+ async transcribeVoice(audio, options = {}) {
895
+ const formData = new FormData();
896
+ formData.append("file", audio, options.filename ?? "recording.wav");
897
+ if (options.hotwords?.length) {
898
+ formData.append("hotwords", options.hotwords.join(", "));
899
+ }
900
+ if (options.language) {
901
+ formData.append("language", options.language);
902
+ }
903
+ const response = await this.fetchFn(`${this.baseURL}/v1/voice/transcriptions`, {
904
+ method: "POST",
905
+ headers: this.authHeaders,
906
+ body: formData,
907
+ signal: options.signal
908
+ }).catch((err) => {
909
+ if (options.signal?.aborted) {
910
+ throw err;
911
+ }
912
+ throw new ConnectionError(
913
+ err instanceof Error ? err.message : "Failed to connect"
914
+ );
915
+ });
916
+ await this.handleError(response);
917
+ const raw = await response.json();
918
+ return {
919
+ text: raw.text ?? "",
920
+ language: raw.language ?? null,
921
+ durationMs: raw.duration_ms ?? null,
922
+ asrMs: raw.asr_ms ?? 0
923
+ };
924
+ }
925
+ /**
926
+ * Stream the LLM rewrite of a transcript (`POST /v1/voice/polish`) as typed
927
+ * frames.
928
+ *
929
+ * Failures the server reports mid-stream arrive as an `error` frame, but
930
+ * the iteration itself can reject: aborting `signal` closes the connection
931
+ * (which cancels the model call upstream) and rejects with
932
+ * `StreamAbortedError`; a non-2xx open rejects with `AuthenticationError`,
933
+ * `RateLimitError` or `ServerError`; a network failure with
934
+ * `ConnectionError`. Wrap the `for await` accordingly.
935
+ */
936
+ async *streamVoicePolish(request, options = {}) {
937
+ const frames = streamJobSSE({
938
+ url: `${this.baseURL}/v1/voice/polish`,
939
+ headers: { ...this.headers, Accept: "text/event-stream" },
940
+ method: "POST",
941
+ body: JSON.stringify({
942
+ text: request.text,
943
+ mode: request.mode,
944
+ hotwords: request.hotwords ?? []
945
+ }),
946
+ signal: options.signal,
947
+ fetchFn: this.fetchFn
948
+ });
949
+ for await (const frame of frames) {
950
+ const event = parseVoicePolishFrame(frame);
951
+ if (event) yield event;
952
+ }
953
+ }
739
954
  async listUploads(conversationId) {
740
955
  const raw = await this.get(
741
956
  `/v1/conversations/${encodeURIComponent(conversationId)}/uploads`
@@ -822,6 +1037,35 @@ var AstralformClient = class {
822
1037
  }));
823
1038
  }
824
1039
  };
1040
+ function parseVoicePolishFrame(frame) {
1041
+ let payload;
1042
+ try {
1043
+ const parsed = JSON.parse(frame.data);
1044
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
1045
+ payload = parsed;
1046
+ } catch {
1047
+ return null;
1048
+ }
1049
+ switch (frame.event) {
1050
+ case "delta":
1051
+ return typeof payload.text === "string" ? { type: "delta", text: payload.text } : null;
1052
+ case "done":
1053
+ return typeof payload.text === "string" ? {
1054
+ type: "done",
1055
+ text: payload.text,
1056
+ polishMs: payload.polish_ms ?? 0
1057
+ } : null;
1058
+ case "error":
1059
+ return {
1060
+ type: "error",
1061
+ reason: payload.reason ?? "unknown",
1062
+ partial: payload.partial ?? "",
1063
+ ...typeof payload.detail === "string" ? { detail: payload.detail } : {}
1064
+ };
1065
+ default:
1066
+ return null;
1067
+ }
1068
+ }
825
1069
 
826
1070
  // src/storage.ts
827
1071
  var InMemoryStorage = class {
@@ -1554,6 +1798,9 @@ var ChatSession = class {
1554
1798
  image_mode: options?.imageMode,
1555
1799
  video_mode: options?.videoMode,
1556
1800
  goal: options?.goal,
1801
+ // The project this task belongs to, on a code-mode agent. Write-once
1802
+ // server-side: sent on every turn, honoured on the first.
1803
+ repository: options?.repository,
1557
1804
  // Per-request model choice (client-side model selection).
1558
1805
  provider: options?.provider,
1559
1806
  model: options?.model,
@@ -2306,50 +2553,6 @@ function planRestore(args) {
2306
2553
  return steps;
2307
2554
  }
2308
2555
 
2309
- // src/types.ts
2310
- var ChatEventType = {
2311
- // Connection lifecycle (SDK-local, not wire)
2312
- Connected: "connected",
2313
- Disconnected: "disconnected",
2314
- // Turn lifecycle
2315
- MessageStart: "message_start",
2316
- MessageStop: "message_stop",
2317
- // Block lifecycle
2318
- BlockStart: "block_start",
2319
- BlockDelta: "block_delta",
2320
- BlockStop: "block_stop",
2321
- // Reliability
2322
- Stall: "stall",
2323
- Retry: "retry",
2324
- Error: "error",
2325
- Keepalive: "keepalive",
2326
- // Conversation-level (typed custom events)
2327
- UserMessage: "user_message",
2328
- TitleGenerated: "title_generated",
2329
- TodoUpdate: "todo_update",
2330
- PlanUpdate: "plan_update",
2331
- NoteUpdate: "note_update",
2332
- ContextUpdate: "context_update",
2333
- SubagentStart: "subagent_start",
2334
- SubagentStop: "subagent_stop",
2335
- ContextWarning: "context_warning",
2336
- MemoryRecall: "memory_recall",
2337
- MemoryUpdate: "memory_update",
2338
- DesktopStream: "desktop_stream",
2339
- AttachmentStaged: "attachment_staged",
2340
- WorkspaceReady: "workspace_ready",
2341
- AssetCreated: "asset_created",
2342
- ToolApprovalRequested: "tool_approval_requested",
2343
- ToolApprovalGranted: "tool_approval_granted",
2344
- ToolPermissionDenied: "tool_permission_denied",
2345
- ToolHarnessWarning: "tool_harness_warning",
2346
- UserUnavailable: "user_unavailable",
2347
- PromptSuggestion: "prompt_suggestion",
2348
- StateChanged: "state_changed",
2349
- // Generic fallthrough for unknown custom events
2350
- Custom: "custom"
2351
- };
2352
-
2353
2556
  // src/stream-manager.ts
2354
2557
  var StreamManager = class {
2355
2558
  constructor(session) {
@@ -3004,10 +3207,14 @@ function parseEmbeddedResource(value) {
3004
3207
  StreamAbortedError,
3005
3208
  StreamManager,
3006
3209
  ToolRegistry,
3210
+ VOICE_POLISH_MODES,
3007
3211
  generateId,
3008
3212
  isEmbeddedResource,
3213
+ isVoiceLLMMode,
3214
+ isVoicePolishMode,
3009
3215
  mapSseToChat,
3010
3216
  parseEmbeddedResource,
3217
+ parseVoicePolishFrame,
3011
3218
  replayEvents,
3012
3219
  streamJobSSE,
3013
3220
  translateDelta