@astralform/js 5.1.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/dist/index.d.cts CHANGED
@@ -1400,11 +1400,12 @@ declare class ChatSession {
1400
1400
  /**
1401
1401
  * Rename a conversation, server first.
1402
1402
  *
1403
- * Deliberately NOT optimistic, unlike the delete below. A failed delete is
1404
- * self-correcting (the row is still there on the next page fetch), but a
1405
- * failed rename that had already been written locally would leave the
1406
- * sidebar showing a title the server never accepted and nothing refetches
1407
- * a conversation that is already in the loaded list.
1403
+ * Server first, like the delete below: a failed rename written locally would
1404
+ * leave the sidebar showing a title the server never accepted, and nothing
1405
+ * refetches a conversation that is already in the loaded list. (The delete
1406
+ * used to be the counter-example hereit dropped the row whatever the
1407
+ * server said, on the theory that a failed one was self-correcting. It was
1408
+ * not: the row stayed deleted locally and alive on the server.)
1408
1409
  *
1409
1410
  * Mirrors the `title_generated` path: the entry in `conversations` is
1410
1411
  * mutated in place, which is what every consumer of the list reads.
@@ -1444,7 +1445,21 @@ declare class LLMNotConfiguredError extends AstralformError {
1444
1445
  constructor(message?: string);
1445
1446
  }
1446
1447
  declare class ServerError extends AstralformError {
1447
- constructor(message?: string);
1448
+ /**
1449
+ * The HTTP status, when this came from a response.
1450
+ *
1451
+ * Every status except 401 and 429 collapses into this one class, so without
1452
+ * it a caller cannot tell "the thing is already gone" (404) from "the server
1453
+ * broke" (500) or "this is not yours" (403) — the message is the only other
1454
+ * signal and it is prose. `deleteConversation` is the case that forced it:
1455
+ * it read EVERY failure as already-deleted and dropped the conversation
1456
+ * locally regardless, so a 500 looked exactly like success and the row came
1457
+ * back on the next device.
1458
+ *
1459
+ * Undefined when a ServerError is constructed without a response.
1460
+ */
1461
+ readonly status?: number;
1462
+ constructor(message?: string, status?: number);
1448
1463
  }
1449
1464
  declare class ConnectionError extends AstralformError {
1450
1465
  constructor(message?: string);
package/dist/index.d.ts CHANGED
@@ -1400,11 +1400,12 @@ declare class ChatSession {
1400
1400
  /**
1401
1401
  * Rename a conversation, server first.
1402
1402
  *
1403
- * Deliberately NOT optimistic, unlike the delete below. A failed delete is
1404
- * self-correcting (the row is still there on the next page fetch), but a
1405
- * failed rename that had already been written locally would leave the
1406
- * sidebar showing a title the server never accepted and nothing refetches
1407
- * a conversation that is already in the loaded list.
1403
+ * Server first, like the delete below: a failed rename written locally would
1404
+ * leave the sidebar showing a title the server never accepted, and nothing
1405
+ * refetches a conversation that is already in the loaded list. (The delete
1406
+ * used to be the counter-example hereit dropped the row whatever the
1407
+ * server said, on the theory that a failed one was self-correcting. It was
1408
+ * not: the row stayed deleted locally and alive on the server.)
1408
1409
  *
1409
1410
  * Mirrors the `title_generated` path: the entry in `conversations` is
1410
1411
  * mutated in place, which is what every consumer of the list reads.
@@ -1444,7 +1445,21 @@ declare class LLMNotConfiguredError extends AstralformError {
1444
1445
  constructor(message?: string);
1445
1446
  }
1446
1447
  declare class ServerError extends AstralformError {
1447
- constructor(message?: string);
1448
+ /**
1449
+ * The HTTP status, when this came from a response.
1450
+ *
1451
+ * Every status except 401 and 429 collapses into this one class, so without
1452
+ * it a caller cannot tell "the thing is already gone" (404) from "the server
1453
+ * broke" (500) or "this is not yours" (403) — the message is the only other
1454
+ * signal and it is prose. `deleteConversation` is the case that forced it:
1455
+ * it read EVERY failure as already-deleted and dropped the conversation
1456
+ * locally regardless, so a 500 looked exactly like success and the row came
1457
+ * back on the next device.
1458
+ *
1459
+ * Undefined when a ServerError is constructed without a response.
1460
+ */
1461
+ readonly status?: number;
1462
+ constructor(message?: string, status?: number);
1448
1463
  }
1449
1464
  declare class ConnectionError extends AstralformError {
1450
1465
  constructor(message?: string);
package/dist/index.js CHANGED
@@ -26,9 +26,10 @@ var LLMNotConfiguredError = class extends AstralformError {
26
26
  }
27
27
  };
28
28
  var ServerError = class extends AstralformError {
29
- constructor(message = "Internal server error") {
29
+ constructor(message = "Internal server error", status) {
30
30
  super(message, "server_error");
31
31
  this.name = "ServerError";
32
+ if (status !== void 0) Object.assign(this, { status });
32
33
  }
33
34
  };
34
35
  var ConnectionError = class extends AstralformError {
@@ -58,6 +59,16 @@ function generateId() {
58
59
  return v.toString(16);
59
60
  });
60
61
  }
62
+ function snakeToCamel(str) {
63
+ return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
64
+ }
65
+ function camelizeKeys(obj) {
66
+ const result = {};
67
+ for (const key of Object.keys(obj)) {
68
+ result[snakeToCamel(key)] = obj[key];
69
+ }
70
+ return result;
71
+ }
61
72
 
62
73
  // src/rate-limit.ts
63
74
  var DEFAULT_MESSAGE = "Rate limit exceeded";
@@ -208,7 +219,7 @@ async function* streamJobSSE(options) {
208
219
  case 429:
209
220
  throw createRateLimitErrorFromHttp(response, rawText);
210
221
  default:
211
- throw new ServerError(text || `HTTP ${response.status}`);
222
+ throw new ServerError(text || `HTTP ${response.status}`, response.status);
212
223
  }
213
224
  }
214
225
  if (!response.body) {
@@ -479,7 +490,10 @@ var AstralformClient = class {
479
490
  throw createRateLimitErrorFromHttp(response, text);
480
491
  default: {
481
492
  const safeText = text ? sanitizeErrorText(text) : "";
482
- throw new ServerError(safeText || `HTTP ${response.status}`);
493
+ throw new ServerError(
494
+ safeText || `HTTP ${response.status}`,
495
+ response.status
496
+ );
483
497
  }
484
498
  }
485
499
  }
@@ -516,13 +530,7 @@ var AstralformClient = class {
516
530
  const safeLimit = Math.max(1, Math.min(200, Math.floor(Number(limit))));
517
531
  const safeOffset = Math.max(0, Math.floor(Number(offset)));
518
532
  const raw = await this.get(`/v1/conversations?limit=${safeLimit}&offset=${safeOffset}`);
519
- return raw.map((c) => ({
520
- id: c.id,
521
- title: c.title,
522
- messageCount: c.message_count,
523
- createdAt: c.created_at,
524
- updatedAt: c.updated_at
525
- }));
533
+ return raw.map((c) => camelizeKeys(c));
526
534
  }
527
535
  async getMessages(conversationId) {
528
536
  const raw = await this.get(`/v1/conversations/${encodeURIComponent(conversationId)}/messages`);
@@ -547,13 +555,7 @@ var AstralformClient = class {
547
555
  */
548
556
  async renameConversation(id, title) {
549
557
  const c = await this.patch(`/v1/conversations/${encodeURIComponent(id)}`, { title });
550
- return {
551
- id: c.id,
552
- title: c.title,
553
- messageCount: c.message_count,
554
- createdAt: c.created_at,
555
- updatedAt: c.updated_at
556
- };
558
+ return camelizeKeys(c);
557
559
  }
558
560
  async deleteConversation(id) {
559
561
  await this.del(`/v1/conversations/${encodeURIComponent(id)}`);
@@ -566,14 +568,7 @@ var AstralformClient = class {
566
568
  */
567
569
  async getAgents() {
568
570
  const raw = await this.get("/v1/agents");
569
- return raw.map((a) => ({
570
- name: a.name,
571
- displayName: a.display_name,
572
- description: a.description,
573
- isOrchestrator: a.is_orchestrator,
574
- isEnabled: a.is_enabled,
575
- avatarUrl: a.avatar_url
576
- }));
571
+ return raw.map((a) => camelizeKeys(a));
577
572
  }
578
573
  /**
579
574
  * List the models the caller may pick this turn — expanded from the curated
@@ -600,12 +595,7 @@ var AstralformClient = class {
600
595
  }
601
596
  async getSkills() {
602
597
  const raw = await this.get("/v1/skills");
603
- return raw.map((s) => ({
604
- name: s.name,
605
- displayName: s.display_name,
606
- description: s.description,
607
- isEnabled: s.is_enabled
608
- }));
598
+ return raw.map((s) => camelizeKeys(s));
609
599
  }
610
600
  async getConversationEvents(conversationId, jobId) {
611
601
  let url = `/v1/conversations/${encodeURIComponent(conversationId)}/events`;
@@ -722,13 +712,7 @@ var AstralformClient = class {
722
712
  // sending them in API-key mode yields 401.
723
713
  async listTeams() {
724
714
  const raw = await this.get("/v1/teams");
725
- return raw.map((t) => ({
726
- id: t.id,
727
- name: t.name,
728
- slug: t.slug,
729
- isDefault: t.is_default,
730
- role: t.role
731
- }));
715
+ return raw.map((t) => camelizeKeys(t));
732
716
  }
733
717
  /**
734
718
  * List the team-level agents (formerly "projects") the signed-in user can
@@ -737,14 +721,7 @@ var AstralformClient = class {
737
721
  */
738
722
  async listAgents(teamId) {
739
723
  const raw = await this.get(`/v1/teams/${encodeURIComponent(teamId)}/agents`);
740
- return raw.map((a) => ({
741
- id: a.id,
742
- name: a.name,
743
- teamId: a.team_id,
744
- createdAt: a.created_at,
745
- updatedAt: a.updated_at,
746
- avatarUrl: a.avatar_url ?? null
747
- }));
724
+ return raw.map((a) => camelizeKeys(a));
748
725
  }
749
726
  // --- Jobs API ---
750
727
  async createJob(request) {
@@ -781,13 +758,7 @@ var AstralformClient = class {
781
758
  };
782
759
  if (request.comment != null) body.comment = request.comment;
783
760
  const raw = await this.post(`/v1/jobs/${encodeURIComponent(jobId)}/feedback`, body);
784
- return {
785
- id: raw.id,
786
- jobId: raw.job_id,
787
- rating: raw.rating,
788
- comment: raw.comment,
789
- createdAt: raw.created_at
790
- };
761
+ return camelizeKeys(raw);
791
762
  }
792
763
  async getActiveJob(conversationId) {
793
764
  const raw = await this.get(`/v1/conversations/${encodeURIComponent(conversationId)}/active-job`);
@@ -2175,11 +2146,12 @@ var ChatSession = class {
2175
2146
  /**
2176
2147
  * Rename a conversation, server first.
2177
2148
  *
2178
- * Deliberately NOT optimistic, unlike the delete below. A failed delete is
2179
- * self-correcting (the row is still there on the next page fetch), but a
2180
- * failed rename that had already been written locally would leave the
2181
- * sidebar showing a title the server never accepted and nothing refetches
2182
- * a conversation that is already in the loaded list.
2149
+ * Server first, like the delete below: a failed rename written locally would
2150
+ * leave the sidebar showing a title the server never accepted, and nothing
2151
+ * refetches a conversation that is already in the loaded list. (The delete
2152
+ * used to be the counter-example hereit dropped the row whatever the
2153
+ * server said, on the theory that a failed one was self-correcting. It was
2154
+ * not: the row stayed deleted locally and alive on the server.)
2183
2155
  *
2184
2156
  * Mirrors the `title_generated` path: the entry in `conversations` is
2185
2157
  * mutated in place, which is what every consumer of the list reads.
@@ -2195,7 +2167,8 @@ var ChatSession = class {
2195
2167
  async deleteConversation(id) {
2196
2168
  try {
2197
2169
  await this.client.deleteConversation(id);
2198
- } catch {
2170
+ } catch (err) {
2171
+ if (!(err instanceof ServerError) || err.status !== 404) throw err;
2199
2172
  }
2200
2173
  await this.storage.deleteConversation(id);
2201
2174
  if (this.serverConversationIds.delete(id)) {