@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 +255 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +260 -3
- package/dist/index.d.ts +260 -3
- package/dist/index.js +251 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
|
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
|