@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.js
CHANGED
|
@@ -194,12 +194,13 @@ function createRateLimitErrorFromHttp(response, rawText) {
|
|
|
194
194
|
|
|
195
195
|
// src/streaming.ts
|
|
196
196
|
async function* streamJobSSE(options) {
|
|
197
|
-
const { url, headers, signal, fetchFn } = options;
|
|
197
|
+
const { url, headers, signal, fetchFn, method = "GET", body } = options;
|
|
198
198
|
let response;
|
|
199
199
|
try {
|
|
200
200
|
response = await fetchFn(url, {
|
|
201
|
-
method
|
|
201
|
+
method,
|
|
202
202
|
headers,
|
|
203
|
+
body,
|
|
203
204
|
signal
|
|
204
205
|
});
|
|
205
206
|
} catch (err) {
|
|
@@ -261,6 +262,57 @@ async function* streamJobSSE(options) {
|
|
|
261
262
|
}
|
|
262
263
|
}
|
|
263
264
|
|
|
265
|
+
// src/types.ts
|
|
266
|
+
var ChatEventType = {
|
|
267
|
+
// Connection lifecycle (SDK-local, not wire)
|
|
268
|
+
Connected: "connected",
|
|
269
|
+
Disconnected: "disconnected",
|
|
270
|
+
// Turn lifecycle
|
|
271
|
+
MessageStart: "message_start",
|
|
272
|
+
MessageStop: "message_stop",
|
|
273
|
+
// Block lifecycle
|
|
274
|
+
BlockStart: "block_start",
|
|
275
|
+
BlockDelta: "block_delta",
|
|
276
|
+
BlockStop: "block_stop",
|
|
277
|
+
// Reliability
|
|
278
|
+
Stall: "stall",
|
|
279
|
+
Retry: "retry",
|
|
280
|
+
Error: "error",
|
|
281
|
+
Keepalive: "keepalive",
|
|
282
|
+
// Conversation-level (typed custom events)
|
|
283
|
+
UserMessage: "user_message",
|
|
284
|
+
TitleGenerated: "title_generated",
|
|
285
|
+
TodoUpdate: "todo_update",
|
|
286
|
+
PlanUpdate: "plan_update",
|
|
287
|
+
NoteUpdate: "note_update",
|
|
288
|
+
ContextUpdate: "context_update",
|
|
289
|
+
SubagentStart: "subagent_start",
|
|
290
|
+
SubagentStop: "subagent_stop",
|
|
291
|
+
ContextWarning: "context_warning",
|
|
292
|
+
MemoryRecall: "memory_recall",
|
|
293
|
+
MemoryUpdate: "memory_update",
|
|
294
|
+
DesktopStream: "desktop_stream",
|
|
295
|
+
AttachmentStaged: "attachment_staged",
|
|
296
|
+
WorkspaceReady: "workspace_ready",
|
|
297
|
+
AssetCreated: "asset_created",
|
|
298
|
+
ToolApprovalRequested: "tool_approval_requested",
|
|
299
|
+
ToolApprovalGranted: "tool_approval_granted",
|
|
300
|
+
ToolPermissionDenied: "tool_permission_denied",
|
|
301
|
+
ToolHarnessWarning: "tool_harness_warning",
|
|
302
|
+
UserUnavailable: "user_unavailable",
|
|
303
|
+
PromptSuggestion: "prompt_suggestion",
|
|
304
|
+
StateChanged: "state_changed",
|
|
305
|
+
// Generic fallthrough for unknown custom events
|
|
306
|
+
Custom: "custom"
|
|
307
|
+
};
|
|
308
|
+
var VOICE_POLISH_MODES = ["raw", "light", "structured", "formal"];
|
|
309
|
+
function isVoicePolishMode(value) {
|
|
310
|
+
return typeof value === "string" && VOICE_POLISH_MODES.includes(value);
|
|
311
|
+
}
|
|
312
|
+
function isVoiceLLMMode(mode) {
|
|
313
|
+
return mode !== "raw";
|
|
314
|
+
}
|
|
315
|
+
|
|
264
316
|
// src/client.ts
|
|
265
317
|
var DEFAULT_BASE_URL = "https://api.astralform.ai";
|
|
266
318
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -286,6 +338,69 @@ function isApiKeyConfig(config) {
|
|
|
286
338
|
}
|
|
287
339
|
var AstralformClient = class {
|
|
288
340
|
constructor(config) {
|
|
341
|
+
// --- Code mode: the app user's projects ---
|
|
342
|
+
/**
|
|
343
|
+
* The projects (GitHub repositories) this app user works with, and what they
|
|
344
|
+
* may add.
|
|
345
|
+
*
|
|
346
|
+
* A project list is per app user within a code-mode agent: the developer
|
|
347
|
+
* connects the workspace's GitHub account, and each user curates their own
|
|
348
|
+
* list from what that connection covers. Every method 404s on a chat-mode
|
|
349
|
+
* agent, so the surface is invisible rather than empty there.
|
|
350
|
+
*/
|
|
351
|
+
this.code = {
|
|
352
|
+
projects: {
|
|
353
|
+
/** This user's projects on the active agent, oldest first. */
|
|
354
|
+
list: async () => {
|
|
355
|
+
const raw = await this.get(
|
|
356
|
+
"/v1/code/projects"
|
|
357
|
+
);
|
|
358
|
+
return raw.map((p) => camelizeKeys(p));
|
|
359
|
+
},
|
|
360
|
+
/**
|
|
361
|
+
* What the workspace's GitHub installations cover, minus what this user
|
|
362
|
+
* has already added. Read `state` before the list: an empty `repositories`
|
|
363
|
+
* means something different in each of its three values.
|
|
364
|
+
*/
|
|
365
|
+
available: async () => {
|
|
366
|
+
const raw = await this.get("/v1/code/projects/available");
|
|
367
|
+
return {
|
|
368
|
+
state: raw.state,
|
|
369
|
+
repositories: (raw.repositories ?? []).map((r) => ({
|
|
370
|
+
fullName: r.full_name,
|
|
371
|
+
private: r.private
|
|
372
|
+
})),
|
|
373
|
+
// Defaulted like its neighbours: the `unavailable` branch has nothing
|
|
374
|
+
// to count, and an absent field behind a `number` type prints
|
|
375
|
+
// "undefined" in a picker rather than a number.
|
|
376
|
+
totalCount: raw.total_count ?? 0,
|
|
377
|
+
partial: raw.partial ?? false
|
|
378
|
+
};
|
|
379
|
+
},
|
|
380
|
+
/**
|
|
381
|
+
* Add a repository. The server checks it against the workspace's own
|
|
382
|
+
* installations and answers a repository it cannot reach the same way it
|
|
383
|
+
* answers one owned by someone else — deliberately, so this call cannot be
|
|
384
|
+
* used to discover which organisations use Astralform.
|
|
385
|
+
*/
|
|
386
|
+
add: async (repoFullName) => {
|
|
387
|
+
const raw = await this.post(
|
|
388
|
+
"/v1/code/projects",
|
|
389
|
+
{ repo_full_name: repoFullName }
|
|
390
|
+
);
|
|
391
|
+
return camelizeKeys(raw);
|
|
392
|
+
},
|
|
393
|
+
/**
|
|
394
|
+
* Remove a project. Tasks already bound to that repository keep their
|
|
395
|
+
* binding — they simply stop grouping under it.
|
|
396
|
+
*/
|
|
397
|
+
remove: async (owner, repo) => {
|
|
398
|
+
await this.del(
|
|
399
|
+
`/v1/code/projects/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
};
|
|
289
404
|
if (isApiKeyConfig(config)) {
|
|
290
405
|
if (!config.apiKey || typeof config.apiKey !== "string") {
|
|
291
406
|
throw new Error("apiKey is required and must be a non-empty string");
|
|
@@ -526,10 +641,18 @@ var AstralformClient = class {
|
|
|
526
641
|
}
|
|
527
642
|
};
|
|
528
643
|
}
|
|
529
|
-
|
|
644
|
+
/**
|
|
645
|
+
* A page of conversations, newest-updated first.
|
|
646
|
+
*
|
|
647
|
+
* `options.repository` narrows to one project's tasks (`owner/repo`) on a
|
|
648
|
+
* code-mode agent — the same paging applies within the filter, so a client
|
|
649
|
+
* showing tasks per project pages each project separately.
|
|
650
|
+
*/
|
|
651
|
+
async getConversations(limit = 50, offset = 0, options) {
|
|
530
652
|
const safeLimit = Math.max(1, Math.min(200, Math.floor(Number(limit))));
|
|
531
653
|
const safeOffset = Math.max(0, Math.floor(Number(offset)));
|
|
532
|
-
const
|
|
654
|
+
const filter = options?.repository ? `&repository=${encodeURIComponent(options.repository)}` : "";
|
|
655
|
+
const raw = await this.get(`/v1/conversations?limit=${safeLimit}&offset=${safeOffset}${filter}`);
|
|
533
656
|
return raw.map((c) => camelizeKeys(c));
|
|
534
657
|
}
|
|
535
658
|
async getMessages(conversationId) {
|
|
@@ -689,6 +812,94 @@ var AstralformClient = class {
|
|
|
689
812
|
const raw = await response.json();
|
|
690
813
|
return this.mapAsset(raw);
|
|
691
814
|
}
|
|
815
|
+
// --- Voice input ---
|
|
816
|
+
/** The agent's voice-input defaults (`GET /v1/voice/config`). */
|
|
817
|
+
async getVoiceConfig() {
|
|
818
|
+
const raw = await this.get("/v1/voice/config");
|
|
819
|
+
return {
|
|
820
|
+
enabled: Boolean(raw.enabled),
|
|
821
|
+
modes: raw.modes ?? [...VOICE_POLISH_MODES],
|
|
822
|
+
// A mode this SDK does not know must not reach a `switch` typed as
|
|
823
|
+
// `VoicePolishMode`; `structured` is the server's own default.
|
|
824
|
+
defaultMode: isVoicePolishMode(raw.default_mode) ? raw.default_mode : "structured",
|
|
825
|
+
silenceAutoStopSeconds: raw.silence_auto_stop_seconds ?? 2,
|
|
826
|
+
autoSend: raw.auto_send ?? true,
|
|
827
|
+
maxRecordingSeconds: raw.max_recording_seconds ?? 300,
|
|
828
|
+
supportsStreaming: Boolean(raw.supports_streaming),
|
|
829
|
+
hotwords: raw.hotwords ?? []
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Transcribe one recording with the agent's configured speech-to-text
|
|
834
|
+
* provider (`POST /v1/voice/transcriptions`). 16 kHz mono 16-bit WAV is the
|
|
835
|
+
* reference format; anything the provider accepts works.
|
|
836
|
+
*
|
|
837
|
+
* Deliberately outside `withDeadline`: a recording can run to
|
|
838
|
+
* `VoiceConfig.maxRecordingSeconds`, so the 30 s default would cut real
|
|
839
|
+
* uploads off. Pass `options.signal` to give up on a stalled one; the
|
|
840
|
+
* promise then rejects with the abort reason — the runtime's `AbortError`,
|
|
841
|
+
* or whatever was passed to `abort(reason)`.
|
|
842
|
+
*/
|
|
843
|
+
async transcribeVoice(audio, options = {}) {
|
|
844
|
+
const formData = new FormData();
|
|
845
|
+
formData.append("file", audio, options.filename ?? "recording.wav");
|
|
846
|
+
if (options.hotwords?.length) {
|
|
847
|
+
formData.append("hotwords", options.hotwords.join(", "));
|
|
848
|
+
}
|
|
849
|
+
if (options.language) {
|
|
850
|
+
formData.append("language", options.language);
|
|
851
|
+
}
|
|
852
|
+
const response = await this.fetchFn(`${this.baseURL}/v1/voice/transcriptions`, {
|
|
853
|
+
method: "POST",
|
|
854
|
+
headers: this.authHeaders,
|
|
855
|
+
body: formData,
|
|
856
|
+
signal: options.signal
|
|
857
|
+
}).catch((err) => {
|
|
858
|
+
if (options.signal?.aborted) {
|
|
859
|
+
throw err;
|
|
860
|
+
}
|
|
861
|
+
throw new ConnectionError(
|
|
862
|
+
err instanceof Error ? err.message : "Failed to connect"
|
|
863
|
+
);
|
|
864
|
+
});
|
|
865
|
+
await this.handleError(response);
|
|
866
|
+
const raw = await response.json();
|
|
867
|
+
return {
|
|
868
|
+
text: raw.text ?? "",
|
|
869
|
+
language: raw.language ?? null,
|
|
870
|
+
durationMs: raw.duration_ms ?? null,
|
|
871
|
+
asrMs: raw.asr_ms ?? 0
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Stream the LLM rewrite of a transcript (`POST /v1/voice/polish`) as typed
|
|
876
|
+
* frames.
|
|
877
|
+
*
|
|
878
|
+
* Failures the server reports mid-stream arrive as an `error` frame, but
|
|
879
|
+
* the iteration itself can reject: aborting `signal` closes the connection
|
|
880
|
+
* (which cancels the model call upstream) and rejects with
|
|
881
|
+
* `StreamAbortedError`; a non-2xx open rejects with `AuthenticationError`,
|
|
882
|
+
* `RateLimitError` or `ServerError`; a network failure with
|
|
883
|
+
* `ConnectionError`. Wrap the `for await` accordingly.
|
|
884
|
+
*/
|
|
885
|
+
async *streamVoicePolish(request, options = {}) {
|
|
886
|
+
const frames = streamJobSSE({
|
|
887
|
+
url: `${this.baseURL}/v1/voice/polish`,
|
|
888
|
+
headers: { ...this.headers, Accept: "text/event-stream" },
|
|
889
|
+
method: "POST",
|
|
890
|
+
body: JSON.stringify({
|
|
891
|
+
text: request.text,
|
|
892
|
+
mode: request.mode,
|
|
893
|
+
hotwords: request.hotwords ?? []
|
|
894
|
+
}),
|
|
895
|
+
signal: options.signal,
|
|
896
|
+
fetchFn: this.fetchFn
|
|
897
|
+
});
|
|
898
|
+
for await (const frame of frames) {
|
|
899
|
+
const event = parseVoicePolishFrame(frame);
|
|
900
|
+
if (event) yield event;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
692
903
|
async listUploads(conversationId) {
|
|
693
904
|
const raw = await this.get(
|
|
694
905
|
`/v1/conversations/${encodeURIComponent(conversationId)}/uploads`
|
|
@@ -775,6 +986,35 @@ var AstralformClient = class {
|
|
|
775
986
|
}));
|
|
776
987
|
}
|
|
777
988
|
};
|
|
989
|
+
function parseVoicePolishFrame(frame) {
|
|
990
|
+
let payload;
|
|
991
|
+
try {
|
|
992
|
+
const parsed = JSON.parse(frame.data);
|
|
993
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
994
|
+
payload = parsed;
|
|
995
|
+
} catch {
|
|
996
|
+
return null;
|
|
997
|
+
}
|
|
998
|
+
switch (frame.event) {
|
|
999
|
+
case "delta":
|
|
1000
|
+
return typeof payload.text === "string" ? { type: "delta", text: payload.text } : null;
|
|
1001
|
+
case "done":
|
|
1002
|
+
return typeof payload.text === "string" ? {
|
|
1003
|
+
type: "done",
|
|
1004
|
+
text: payload.text,
|
|
1005
|
+
polishMs: payload.polish_ms ?? 0
|
|
1006
|
+
} : null;
|
|
1007
|
+
case "error":
|
|
1008
|
+
return {
|
|
1009
|
+
type: "error",
|
|
1010
|
+
reason: payload.reason ?? "unknown",
|
|
1011
|
+
partial: payload.partial ?? "",
|
|
1012
|
+
...typeof payload.detail === "string" ? { detail: payload.detail } : {}
|
|
1013
|
+
};
|
|
1014
|
+
default:
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
778
1018
|
|
|
779
1019
|
// src/storage.ts
|
|
780
1020
|
var InMemoryStorage = class {
|
|
@@ -1507,6 +1747,9 @@ var ChatSession = class {
|
|
|
1507
1747
|
image_mode: options?.imageMode,
|
|
1508
1748
|
video_mode: options?.videoMode,
|
|
1509
1749
|
goal: options?.goal,
|
|
1750
|
+
// The project this task belongs to, on a code-mode agent. Write-once
|
|
1751
|
+
// server-side: sent on every turn, honoured on the first.
|
|
1752
|
+
repository: options?.repository,
|
|
1510
1753
|
// Per-request model choice (client-side model selection).
|
|
1511
1754
|
provider: options?.provider,
|
|
1512
1755
|
model: options?.model,
|
|
@@ -2259,50 +2502,6 @@ function planRestore(args) {
|
|
|
2259
2502
|
return steps;
|
|
2260
2503
|
}
|
|
2261
2504
|
|
|
2262
|
-
// src/types.ts
|
|
2263
|
-
var ChatEventType = {
|
|
2264
|
-
// Connection lifecycle (SDK-local, not wire)
|
|
2265
|
-
Connected: "connected",
|
|
2266
|
-
Disconnected: "disconnected",
|
|
2267
|
-
// Turn lifecycle
|
|
2268
|
-
MessageStart: "message_start",
|
|
2269
|
-
MessageStop: "message_stop",
|
|
2270
|
-
// Block lifecycle
|
|
2271
|
-
BlockStart: "block_start",
|
|
2272
|
-
BlockDelta: "block_delta",
|
|
2273
|
-
BlockStop: "block_stop",
|
|
2274
|
-
// Reliability
|
|
2275
|
-
Stall: "stall",
|
|
2276
|
-
Retry: "retry",
|
|
2277
|
-
Error: "error",
|
|
2278
|
-
Keepalive: "keepalive",
|
|
2279
|
-
// Conversation-level (typed custom events)
|
|
2280
|
-
UserMessage: "user_message",
|
|
2281
|
-
TitleGenerated: "title_generated",
|
|
2282
|
-
TodoUpdate: "todo_update",
|
|
2283
|
-
PlanUpdate: "plan_update",
|
|
2284
|
-
NoteUpdate: "note_update",
|
|
2285
|
-
ContextUpdate: "context_update",
|
|
2286
|
-
SubagentStart: "subagent_start",
|
|
2287
|
-
SubagentStop: "subagent_stop",
|
|
2288
|
-
ContextWarning: "context_warning",
|
|
2289
|
-
MemoryRecall: "memory_recall",
|
|
2290
|
-
MemoryUpdate: "memory_update",
|
|
2291
|
-
DesktopStream: "desktop_stream",
|
|
2292
|
-
AttachmentStaged: "attachment_staged",
|
|
2293
|
-
WorkspaceReady: "workspace_ready",
|
|
2294
|
-
AssetCreated: "asset_created",
|
|
2295
|
-
ToolApprovalRequested: "tool_approval_requested",
|
|
2296
|
-
ToolApprovalGranted: "tool_approval_granted",
|
|
2297
|
-
ToolPermissionDenied: "tool_permission_denied",
|
|
2298
|
-
ToolHarnessWarning: "tool_harness_warning",
|
|
2299
|
-
UserUnavailable: "user_unavailable",
|
|
2300
|
-
PromptSuggestion: "prompt_suggestion",
|
|
2301
|
-
StateChanged: "state_changed",
|
|
2302
|
-
// Generic fallthrough for unknown custom events
|
|
2303
|
-
Custom: "custom"
|
|
2304
|
-
};
|
|
2305
|
-
|
|
2306
2505
|
// src/stream-manager.ts
|
|
2307
2506
|
var StreamManager = class {
|
|
2308
2507
|
constructor(session) {
|
|
@@ -2956,10 +3155,14 @@ export {
|
|
|
2956
3155
|
StreamAbortedError,
|
|
2957
3156
|
StreamManager,
|
|
2958
3157
|
ToolRegistry,
|
|
3158
|
+
VOICE_POLISH_MODES,
|
|
2959
3159
|
generateId,
|
|
2960
3160
|
isEmbeddedResource,
|
|
3161
|
+
isVoiceLLMMode,
|
|
3162
|
+
isVoicePolishMode,
|
|
2961
3163
|
mapSseToChat,
|
|
2962
3164
|
parseEmbeddedResource,
|
|
3165
|
+
parseVoicePolishFrame,
|
|
2963
3166
|
replayEvents,
|
|
2964
3167
|
streamJobSSE,
|
|
2965
3168
|
translateDelta
|