@astralform/js 5.0.0 → 6.0.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/README.md CHANGED
@@ -265,8 +265,13 @@ const id = await session.createNewConversation();
265
265
  // Switch to an existing conversation (loads messages from backend)
266
266
  await session.switchConversation("conversation-id");
267
267
 
268
- // Delete a conversation
269
- await session.deleteConversation("conversation-id");
268
+ // Delete a conversation. Rejects if the server refused it — a 404 counts as
269
+ // already deleted and resolves, anything else leaves the conversation in place.
270
+ try {
271
+ await session.deleteConversation("conversation-id");
272
+ } catch (err) {
273
+ // Still there: tell the user rather than removing it from your own UI.
274
+ }
270
275
 
271
276
  // Edit and resend from a checkpoint
272
277
  await session.resendFromCheckpoint("message-id", "Updated message");
@@ -357,7 +362,7 @@ import {
357
362
  AuthenticationError, // 401 — invalid API key
358
363
  RateLimitError, // 429 — rate limit exceeded
359
364
  LLMNotConfiguredError, // LLM provider not set up
360
- ServerError, // 5xx or unexpected errors
365
+ ServerError, // 5xx or unexpected errors — carries `status` when it came from a response
361
366
  ConnectionError, // Network failures
362
367
  StreamAbortedError, // Stream cancelled via disconnect()
363
368
  } from "@astralform/js";
package/dist/index.cjs CHANGED
@@ -73,9 +73,10 @@ var LLMNotConfiguredError = class extends AstralformError {
73
73
  }
74
74
  };
75
75
  var ServerError = class extends AstralformError {
76
- constructor(message = "Internal server error") {
76
+ constructor(message = "Internal server error", status) {
77
77
  super(message, "server_error");
78
78
  this.name = "ServerError";
79
+ if (status !== void 0) Object.assign(this, { status });
79
80
  }
80
81
  };
81
82
  var ConnectionError = class extends AstralformError {
@@ -105,6 +106,16 @@ function generateId() {
105
106
  return v.toString(16);
106
107
  });
107
108
  }
109
+ function snakeToCamel(str) {
110
+ return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
111
+ }
112
+ function camelizeKeys(obj) {
113
+ const result = {};
114
+ for (const key of Object.keys(obj)) {
115
+ result[snakeToCamel(key)] = obj[key];
116
+ }
117
+ return result;
118
+ }
108
119
 
109
120
  // src/rate-limit.ts
110
121
  var DEFAULT_MESSAGE = "Rate limit exceeded";
@@ -255,7 +266,7 @@ async function* streamJobSSE(options) {
255
266
  case 429:
256
267
  throw createRateLimitErrorFromHttp(response, rawText);
257
268
  default:
258
- throw new ServerError(text || `HTTP ${response.status}`);
269
+ throw new ServerError(text || `HTTP ${response.status}`, response.status);
259
270
  }
260
271
  }
261
272
  if (!response.body) {
@@ -526,7 +537,10 @@ var AstralformClient = class {
526
537
  throw createRateLimitErrorFromHttp(response, text);
527
538
  default: {
528
539
  const safeText = text ? sanitizeErrorText(text) : "";
529
- throw new ServerError(safeText || `HTTP ${response.status}`);
540
+ throw new ServerError(
541
+ safeText || `HTTP ${response.status}`,
542
+ response.status
543
+ );
530
544
  }
531
545
  }
532
546
  }
@@ -563,13 +577,7 @@ var AstralformClient = class {
563
577
  const safeLimit = Math.max(1, Math.min(200, Math.floor(Number(limit))));
564
578
  const safeOffset = Math.max(0, Math.floor(Number(offset)));
565
579
  const raw = await this.get(`/v1/conversations?limit=${safeLimit}&offset=${safeOffset}`);
566
- return raw.map((c) => ({
567
- id: c.id,
568
- title: c.title,
569
- messageCount: c.message_count,
570
- createdAt: c.created_at,
571
- updatedAt: c.updated_at
572
- }));
580
+ return raw.map((c) => camelizeKeys(c));
573
581
  }
574
582
  async getMessages(conversationId) {
575
583
  const raw = await this.get(`/v1/conversations/${encodeURIComponent(conversationId)}/messages`);
@@ -594,13 +602,7 @@ var AstralformClient = class {
594
602
  */
595
603
  async renameConversation(id, title) {
596
604
  const c = await this.patch(`/v1/conversations/${encodeURIComponent(id)}`, { title });
597
- return {
598
- id: c.id,
599
- title: c.title,
600
- messageCount: c.message_count,
601
- createdAt: c.created_at,
602
- updatedAt: c.updated_at
603
- };
605
+ return camelizeKeys(c);
604
606
  }
605
607
  async deleteConversation(id) {
606
608
  await this.del(`/v1/conversations/${encodeURIComponent(id)}`);
@@ -613,14 +615,7 @@ var AstralformClient = class {
613
615
  */
614
616
  async getAgents() {
615
617
  const raw = await this.get("/v1/agents");
616
- return raw.map((a) => ({
617
- name: a.name,
618
- displayName: a.display_name,
619
- description: a.description,
620
- isOrchestrator: a.is_orchestrator,
621
- isEnabled: a.is_enabled,
622
- avatarUrl: a.avatar_url
623
- }));
618
+ return raw.map((a) => camelizeKeys(a));
624
619
  }
625
620
  /**
626
621
  * List the models the caller may pick this turn — expanded from the curated
@@ -647,12 +642,7 @@ var AstralformClient = class {
647
642
  }
648
643
  async getSkills() {
649
644
  const raw = await this.get("/v1/skills");
650
- return raw.map((s) => ({
651
- name: s.name,
652
- displayName: s.display_name,
653
- description: s.description,
654
- isEnabled: s.is_enabled
655
- }));
645
+ return raw.map((s) => camelizeKeys(s));
656
646
  }
657
647
  async getConversationEvents(conversationId, jobId) {
658
648
  let url = `/v1/conversations/${encodeURIComponent(conversationId)}/events`;
@@ -723,6 +713,11 @@ var AstralformClient = class {
723
713
  // undefined so it matches the `url?: string` type and consumers that
724
714
  // check `!== undefined` never receive a null.
725
715
  url: raw.url ?? void 0,
716
+ // This mapping is an ALLOWLIST — a field the API returns and this
717
+ // function does not name is dropped silently, and no type error says so.
718
+ // `content_url` shipped that way and was invisible to every consumer.
719
+ contentUrl: raw.content_url ?? void 0,
720
+ posterUrl: raw.poster_url ?? void 0,
726
721
  createdAt: raw.created_at
727
722
  };
728
723
  }
@@ -764,13 +759,7 @@ var AstralformClient = class {
764
759
  // sending them in API-key mode yields 401.
765
760
  async listTeams() {
766
761
  const raw = await this.get("/v1/teams");
767
- return raw.map((t) => ({
768
- id: t.id,
769
- name: t.name,
770
- slug: t.slug,
771
- isDefault: t.is_default,
772
- role: t.role
773
- }));
762
+ return raw.map((t) => camelizeKeys(t));
774
763
  }
775
764
  /**
776
765
  * List the team-level agents (formerly "projects") the signed-in user can
@@ -779,14 +768,7 @@ var AstralformClient = class {
779
768
  */
780
769
  async listAgents(teamId) {
781
770
  const raw = await this.get(`/v1/teams/${encodeURIComponent(teamId)}/agents`);
782
- return raw.map((a) => ({
783
- id: a.id,
784
- name: a.name,
785
- teamId: a.team_id,
786
- createdAt: a.created_at,
787
- updatedAt: a.updated_at,
788
- avatarUrl: a.avatar_url ?? null
789
- }));
771
+ return raw.map((a) => camelizeKeys(a));
790
772
  }
791
773
  // --- Jobs API ---
792
774
  async createJob(request) {
@@ -823,13 +805,7 @@ var AstralformClient = class {
823
805
  };
824
806
  if (request.comment != null) body.comment = request.comment;
825
807
  const raw = await this.post(`/v1/jobs/${encodeURIComponent(jobId)}/feedback`, body);
826
- return {
827
- id: raw.id,
828
- jobId: raw.job_id,
829
- rating: raw.rating,
830
- comment: raw.comment,
831
- createdAt: raw.created_at
832
- };
808
+ return camelizeKeys(raw);
833
809
  }
834
810
  async getActiveJob(conversationId) {
835
811
  const raw = await this.get(`/v1/conversations/${encodeURIComponent(conversationId)}/active-job`);
@@ -2217,11 +2193,12 @@ var ChatSession = class {
2217
2193
  /**
2218
2194
  * Rename a conversation, server first.
2219
2195
  *
2220
- * Deliberately NOT optimistic, unlike the delete below. A failed delete is
2221
- * self-correcting (the row is still there on the next page fetch), but a
2222
- * failed rename that had already been written locally would leave the
2223
- * sidebar showing a title the server never accepted and nothing refetches
2224
- * a conversation that is already in the loaded list.
2196
+ * Server first, like the delete below: a failed rename written locally would
2197
+ * leave the sidebar showing a title the server never accepted, and nothing
2198
+ * refetches a conversation that is already in the loaded list. (The delete
2199
+ * used to be the counter-example hereit dropped the row whatever the
2200
+ * server said, on the theory that a failed one was self-correcting. It was
2201
+ * not: the row stayed deleted locally and alive on the server.)
2225
2202
  *
2226
2203
  * Mirrors the `title_generated` path: the entry in `conversations` is
2227
2204
  * mutated in place, which is what every consumer of the list reads.
@@ -2237,7 +2214,8 @@ var ChatSession = class {
2237
2214
  async deleteConversation(id) {
2238
2215
  try {
2239
2216
  await this.client.deleteConversation(id);
2240
- } catch {
2217
+ } catch (err) {
2218
+ if (!(err instanceof ServerError) || err.status !== 404) throw err;
2241
2219
  }
2242
2220
  await this.storage.deleteConversation(id);
2243
2221
  if (this.serverConversationIds.delete(id)) {