@stackbone/sdk 0.1.0-alpha.0 → 0.1.0-alpha.2

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/index.js CHANGED
@@ -1786,9 +1786,9 @@ var nanoid = /^[a-zA-Z0-9_-]{21}$/;
1786
1786
  var duration = /^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/;
1787
1787
  var extendedDuration = /^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/;
1788
1788
  var guid = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;
1789
- var uuid = /* @__PURE__ */ __name((version2) => {
1790
- if (!version2) return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;
1791
- return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version2}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);
1789
+ var uuid = /* @__PURE__ */ __name((version3) => {
1790
+ if (!version3) return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;
1791
+ return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version3}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);
1792
1792
  }, "uuid");
1793
1793
  var uuid4 = /* @__PURE__ */ uuid(4);
1794
1794
  var uuid6 = /* @__PURE__ */ uuid(6);
@@ -14972,10 +14972,10 @@ function fromJSONSchema(schema, params) {
14972
14972
  if (typeof schema === "boolean") {
14973
14973
  return schema ? z.any() : z.never();
14974
14974
  }
14975
- const version2 = detectVersion(schema, params?.defaultTarget);
14975
+ const version3 = detectVersion(schema, params?.defaultTarget);
14976
14976
  const defs = schema.$defs || schema.definitions || {};
14977
14977
  const ctx = {
14978
- version: version2,
14978
+ version: version3,
14979
14979
  defs,
14980
14980
  refs: /* @__PURE__ */ new Map(),
14981
14981
  processing: /* @__PURE__ */ new Set(),
@@ -15553,6 +15553,13 @@ external_exports.object({
15553
15553
  nextCursor: external_exports.string().nullable(),
15554
15554
  prevCursor: external_exports.string().nullable()
15555
15555
  });
15556
+ external_exports.object({
15557
+ fxtId: external_exports.uuid()
15558
+ });
15559
+ external_exports.object({
15560
+ input: jsonObject().nullable(),
15561
+ output: jsonObject().nullable()
15562
+ });
15556
15563
 
15557
15564
  // ../validators/src/lib/auth/session.ts
15558
15565
  var sessionUserSchema = external_exports.object({
@@ -16637,6 +16644,34 @@ function pickDefinedAsStrings(source) {
16637
16644
  }
16638
16645
  __name(pickDefinedAsStrings, "pickDefinedAsStrings");
16639
16646
 
16647
+ // src/lib/facade-helpers.ts
16648
+ function remapHttpNotFound(code, domainCode, message, meta3) {
16649
+ if (code !== "http_not_found") return null;
16650
+ const error48 = {
16651
+ code: domainCode,
16652
+ message
16653
+ };
16654
+ if (meta3) error48.meta = meta3;
16655
+ return err(error48);
16656
+ }
16657
+ __name(remapHttpNotFound, "remapHttpNotFound");
16658
+ function validateStringArray(values, field, errorCode) {
16659
+ if (!Array.isArray(values) || values.length === 0) {
16660
+ return err({
16661
+ code: errorCode,
16662
+ message: `\`${field}\` must be a non-empty array.`
16663
+ });
16664
+ }
16665
+ if (values.some((v) => typeof v !== "string" || !v.trim())) {
16666
+ return err({
16667
+ code: errorCode,
16668
+ message: `\`${field}[*]\` must be non-empty strings.`
16669
+ });
16670
+ }
16671
+ return null;
16672
+ }
16673
+ __name(validateStringArray, "validateStringArray");
16674
+
16640
16675
  // src/facades/config/config.ts
16641
16676
  var ConfigFacade = class {
16642
16677
  static {
@@ -16658,7 +16693,11 @@ var ConfigFacade = class {
16658
16693
  const gateResult = await this._gate();
16659
16694
  if (gateResult.error) return err(gateResult.error);
16660
16695
  const result = await this._http.get(`/api/config/${encodeURIComponent(key)}`);
16661
- if (result.error) return remapNotFound(result.error.code, key) ?? result;
16696
+ if (result.error) {
16697
+ return remapHttpNotFound(result.error.code, "config_not_found", `Config key \`${key}\` is not set for this agent.`, {
16698
+ key
16699
+ }) ?? result;
16700
+ }
16662
16701
  return ok(result.data.value);
16663
16702
  }
16664
16703
  /**
@@ -16668,18 +16707,8 @@ var ConfigFacade = class {
16668
16707
  * is `Partial<T>`).
16669
16708
  */
16670
16709
  async getMany(keys) {
16671
- if (!Array.isArray(keys) || keys.length === 0) {
16672
- return err({
16673
- code: "config_invalid_request",
16674
- message: "`keys` must be a non-empty array."
16675
- });
16676
- }
16677
- if (keys.some((k) => !k.trim())) {
16678
- return err({
16679
- code: "config_invalid_request",
16680
- message: "`keys[*]` must be non-empty strings."
16681
- });
16682
- }
16710
+ const invalid = validateStringArray(keys, "keys", "config_invalid_request");
16711
+ if (invalid) return invalid;
16683
16712
  const gateResult = await this._gate();
16684
16713
  if (gateResult.error) return err(gateResult.error);
16685
16714
  const result = await this._http.get("/api/config", {
@@ -16698,17 +16727,6 @@ var ConfigFacade = class {
16698
16727
  return ok(values);
16699
16728
  }
16700
16729
  };
16701
- function remapNotFound(code, key) {
16702
- if (code !== "http_not_found") return null;
16703
- return err({
16704
- code: "config_not_found",
16705
- message: `Config key \`${key}\` is not set for this agent.`,
16706
- meta: {
16707
- key
16708
- }
16709
- });
16710
- }
16711
- __name(remapNotFound, "remapNotFound");
16712
16730
 
16713
16731
  // src/facades/connections/connections.ts
16714
16732
  var ConnectionsFacade = class {
@@ -16794,7 +16812,11 @@ var SecretsFacade = class {
16794
16812
  const gateResult = await this._gate();
16795
16813
  if (gateResult.error) return err(gateResult.error);
16796
16814
  const result = await this._http.get(`/api/secrets/${encodeURIComponent(name)}`);
16797
- if (result.error) return remapNotFound2(result.error.code, name) ?? result;
16815
+ if (result.error) {
16816
+ return remapHttpNotFound(result.error.code, "secrets_not_found", `Secret \`${name}\` is not registered for this organization.`, {
16817
+ name
16818
+ }) ?? result;
16819
+ }
16798
16820
  if (typeof result.data.value !== "string") {
16799
16821
  return err({
16800
16822
  code: "secrets_invalid_response",
@@ -16807,23 +16829,13 @@ var SecretsFacade = class {
16807
16829
  return ok(result.data.value);
16808
16830
  }
16809
16831
  /**
16810
- * Names absent from the workspace come back as omissions in the response —
16832
+ * Names absent from the organization come back as omissions in the response —
16811
16833
  * the returned map only contains entries the control plane actually has.
16812
16834
  * Callers that need "all-or-nothing" semantics should diff the keys.
16813
16835
  */
16814
16836
  async getMany(names) {
16815
- if (!Array.isArray(names) || names.length === 0) {
16816
- return err({
16817
- code: "secrets_invalid_request",
16818
- message: "`names` must be a non-empty array."
16819
- });
16820
- }
16821
- if (names.some((n) => !n.trim())) {
16822
- return err({
16823
- code: "secrets_invalid_request",
16824
- message: "`names[*]` must be non-empty strings."
16825
- });
16826
- }
16837
+ const invalid = validateStringArray(names, "names", "secrets_invalid_request");
16838
+ if (invalid) return invalid;
16827
16839
  const gateResult = await this._gate();
16828
16840
  if (gateResult.error) return err(gateResult.error);
16829
16841
  const result = await this._http.get("/api/secrets", {
@@ -16842,17 +16854,6 @@ var SecretsFacade = class {
16842
16854
  return ok(values);
16843
16855
  }
16844
16856
  };
16845
- function remapNotFound2(code, name) {
16846
- if (code !== "http_not_found") return null;
16847
- return err({
16848
- code: "secrets_not_found",
16849
- message: `Secret \`${name}\` is not registered for this workspace.`,
16850
- meta: {
16851
- name
16852
- }
16853
- });
16854
- }
16855
- __name(remapNotFound2, "remapNotFound");
16856
16857
 
16857
16858
  // src/lib/http-client.ts
16858
16859
  var RETRYABLE_STATUS = /* @__PURE__ */ new Set([
@@ -17735,6 +17736,11 @@ async function aggregateRunCost(runId, options) {
17735
17736
  };
17736
17737
  }
17737
17738
  __name(aggregateRunCost, "aggregateRunCost");
17739
+
17740
+ // package.json
17741
+ var version2 = "0.1.0-alpha.2";
17742
+
17743
+ // src/observability/logger.ts
17738
17744
  var LOG_LEVELS = {
17739
17745
  trace: 10,
17740
17746
  debug: 20,
@@ -17948,7 +17954,7 @@ var OtlpHttpDestination = class OtlpHttpDestination2 {
17948
17954
  };
17949
17955
  var SDK_SCOPE = {
17950
17956
  name: "@stackbone/sdk",
17951
- version: "0.0.1"
17957
+ version: version2
17952
17958
  };
17953
17959
  function buildOtlpLogsBody(records, resourceAttributes) {
17954
17960
  return {
@@ -17981,11 +17987,12 @@ function toOtlpLogRecord(record2) {
17981
17987
  };
17982
17988
  }
17983
17989
  __name(toOtlpLogRecord, "toOtlpLogRecord");
17990
+ var SEVERITY_TEXT_BY_LEVEL = Object.fromEntries(Object.entries(LOG_LEVELS).map(([name, value]) => [
17991
+ value,
17992
+ name.toUpperCase()
17993
+ ]));
17984
17994
  function severityText(level) {
17985
- for (const [name, value] of Object.entries(LOG_LEVELS)) {
17986
- if (value === level) return name.toUpperCase();
17987
- }
17988
- return "INFO";
17995
+ return SEVERITY_TEXT_BY_LEVEL[level] ?? "INFO";
17989
17996
  }
17990
17997
  __name(severityText, "severityText");
17991
17998
  function toOtlpAttributes(attrs) {
@@ -18113,7 +18120,7 @@ var RunStepsSpanProcessor = class {
18113
18120
  this.intervalMs = options.flushIntervalMs ?? DEFAULT_INTERVAL_MS2;
18114
18121
  this.resolveStepType = options.resolveStepType ?? defaultStepTypeResolver;
18115
18122
  this.newId = options.newId ?? randomUUID;
18116
- this.connectionString = options.connectionString ?? process.env["DATABASE_URL"];
18123
+ this.connectionString = options.connectionString ?? process.env["STACKBONE_POSTGRES_URL"];
18117
18124
  if (options.sql) {
18118
18125
  this.sql = options.sql;
18119
18126
  this.ownsSql = false;
@@ -18153,11 +18160,11 @@ var RunStepsSpanProcessor = class {
18153
18160
  }
18154
18161
  }
18155
18162
  buildRow(span) {
18156
- const runId = readString(span.attributes[RUN_ID_ATTRIBUTE]);
18157
- if (!runId) return null;
18158
18163
  const spanId = span.spanContext().spanId;
18159
18164
  const stepId = this.stepIdBySpanId.get(spanId) ?? this.newId();
18160
18165
  this.stepIdBySpanId.delete(spanId);
18166
+ const runId = readString(span.attributes[RUN_ID_ATTRIBUTE]);
18167
+ if (!runId) return null;
18161
18168
  const parentSpanId = span.parentSpanContext?.spanId ?? span.parentSpanId ?? null;
18162
18169
  const parentStepId = parentSpanId ? this.stepIdBySpanId.get(parentSpanId) ?? null : null;
18163
18170
  const startedAt = formatHrTime(span.startTime);
@@ -18297,7 +18304,7 @@ var ObservabilityModule = class {
18297
18304
  if (!this._processor) {
18298
18305
  const merged = {
18299
18306
  ...options,
18300
- connectionString: options.connectionString ?? this._resolved.config.databaseUrl ?? this._resolved.env["DATABASE_URL"]
18307
+ connectionString: options.connectionString ?? this._resolved.config.databaseUrl ?? this._resolved.env["STACKBONE_POSTGRES_URL"]
18301
18308
  };
18302
18309
  this._processor = new RunStepsSpanProcessor(merged);
18303
18310
  }
@@ -18356,7 +18363,7 @@ var ObservabilityModule = class {
18356
18363
  if (!sql) {
18357
18364
  return err({
18358
18365
  code: "database_url_missing",
18359
- message: "Cannot reach the agent Postgres \u2014 set `DATABASE_URL` in the container or pass `databaseUrl` to `createClient({...})`."
18366
+ message: "Cannot reach the agent Postgres \u2014 set `STACKBONE_POSTGRES_URL` in the container or pass `databaseUrl` to `createClient({...})`."
18360
18367
  });
18361
18368
  }
18362
18369
  try {
@@ -18377,7 +18384,7 @@ var ObservabilityModule = class {
18377
18384
  // don't pay the dep cost.
18378
18385
  async resolveSql() {
18379
18386
  if (this._sqlResolved) return this._sql;
18380
- const connectionString = this._resolved.config.databaseUrl ?? this._resolved.env["DATABASE_URL"];
18387
+ const connectionString = this._resolved.config.databaseUrl ?? this._resolved.env["STACKBONE_POSTGRES_URL"];
18381
18388
  if (!connectionString) {
18382
18389
  this._sqlResolved = true;
18383
18390
  return null;
@@ -18607,6 +18614,41 @@ function isUnsupportedModelError(error48) {
18607
18614
  }
18608
18615
  __name(isUnsupportedModelError, "isUnsupportedModelError");
18609
18616
 
18617
+ // src/modules/rag/errors.ts
18618
+ var PG_UNDEFINED_TABLE = "42P01";
18619
+ var RAG_SCHEMA_MISSING_HINT = "run `stackbone db migrate add-rag` and `stackbone db migrate up`";
18620
+ function isTaggedError(cause) {
18621
+ return cause instanceof Error;
18622
+ }
18623
+ __name(isTaggedError, "isTaggedError");
18624
+ function toRagError(cause, message, fallbackCode = "rag_error") {
18625
+ if (isTaggedError(cause) && cause.code === PG_UNDEFINED_TABLE) {
18626
+ return {
18627
+ code: "rag_schema_missing",
18628
+ message: `RAG tables are not installed \u2014 ${RAG_SCHEMA_MISSING_HINT}.`,
18629
+ cause,
18630
+ meta: {
18631
+ hint: RAG_SCHEMA_MISSING_HINT
18632
+ }
18633
+ };
18634
+ }
18635
+ if (isTaggedError(cause)) {
18636
+ const error48 = {
18637
+ code: cause.code ?? fallbackCode,
18638
+ message: `${message}: ${cause.message}`,
18639
+ cause
18640
+ };
18641
+ if (cause.meta) error48.meta = cause.meta;
18642
+ return error48;
18643
+ }
18644
+ return {
18645
+ code: fallbackCode,
18646
+ message,
18647
+ cause
18648
+ };
18649
+ }
18650
+ __name(toRagError, "toRagError");
18651
+
18610
18652
  // src/modules/rag/jobs.ts
18611
18653
  function createSqlJobWriter(sql) {
18612
18654
  return {
@@ -18632,7 +18674,7 @@ function createSqlJobWriter(sql) {
18632
18674
  jobId: row.id
18633
18675
  });
18634
18676
  } catch (cause) {
18635
- return err(toJobsError(cause, "Job start failed"));
18677
+ return err(toRagError(cause, "Job start failed", "rag_jobs_error"));
18636
18678
  }
18637
18679
  },
18638
18680
  async progress({ jobId, processedChunks, totalChunks, currentDocument }) {
@@ -18671,7 +18713,7 @@ function createSqlJobWriter(sql) {
18671
18713
  },
18672
18714
  async isCancelled({ jobId }) {
18673
18715
  const rows = await sql`
18674
- SELECT id, status FROM stackbone_rag_jobs WHERE id = ${jobId}
18716
+ SELECT status FROM stackbone_rag_jobs WHERE id = ${jobId} LIMIT 1
18675
18717
  `;
18676
18718
  return rows[0]?.status === "cancelled";
18677
18719
  }
@@ -18679,10 +18721,6 @@ function createSqlJobWriter(sql) {
18679
18721
  }
18680
18722
  __name(createSqlJobWriter, "createSqlJobWriter");
18681
18723
  async function ensureCollection(sql, name) {
18682
- const existing = await sql`
18683
- SELECT id FROM stackbone_rag_collections WHERE name = ${name} LIMIT 1
18684
- `;
18685
- if (existing[0]) return existing[0].id;
18686
18724
  const inserted = await sql`
18687
18725
  INSERT INTO stackbone_rag_collections (name) VALUES (${name})
18688
18726
  ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name
@@ -18690,37 +18728,11 @@ async function ensureCollection(sql, name) {
18690
18728
  `;
18691
18729
  const row = inserted[0];
18692
18730
  if (!row) {
18693
- throw new Error(`stackbone_rag_collections insert returned no row for ${name}`);
18731
+ throw new Error(`stackbone_rag_collections upsert returned no row for ${name}`);
18694
18732
  }
18695
18733
  return row.id;
18696
18734
  }
18697
18735
  __name(ensureCollection, "ensureCollection");
18698
- function toJobsError(cause, message) {
18699
- if (cause instanceof Error) {
18700
- const tagged = cause;
18701
- if (tagged.code === "42P01") {
18702
- return {
18703
- code: "rag_schema_missing",
18704
- message: "RAG tables are not installed \u2014 run `stackbone db migrate add-rag` and `stackbone db migrate up`.",
18705
- cause,
18706
- meta: {
18707
- hint: "run `stackbone db migrate add-rag` and `stackbone db migrate up`"
18708
- }
18709
- };
18710
- }
18711
- return {
18712
- code: tagged.code ?? "rag_jobs_error",
18713
- message: `${message}: ${cause.message}`,
18714
- cause
18715
- };
18716
- }
18717
- return {
18718
- code: "rag_jobs_error",
18719
- message,
18720
- cause
18721
- };
18722
- }
18723
- __name(toJobsError, "toJobsError");
18724
18736
  var PDF_MIME_TYPES = /* @__PURE__ */ new Set([
18725
18737
  "application/pdf",
18726
18738
  "application/x-pdf"
@@ -18780,10 +18792,12 @@ __name(looksLikePdf, "looksLikePdf");
18780
18792
  var DEFAULT_NAMESPACE = "default";
18781
18793
  var META_KEY_DIMENSIONS = "dimensions";
18782
18794
  var META_KEY_DISTANCE = "distance";
18795
+ var schemaVerifiedDimensions = /* @__PURE__ */ new WeakMap();
18783
18796
  async function ensureSchema(sql, dimensions) {
18784
18797
  if (!Number.isInteger(dimensions) || dimensions <= 0 || dimensions > 8192) {
18785
18798
  throw new Error(`rag.ensureSchema: invalid dimensions ${dimensions}. Must be a positive integer <= 8192.`);
18786
18799
  }
18800
+ if (schemaVerifiedDimensions.get(sql) === dimensions) return;
18787
18801
  const existing = await sql`
18788
18802
  SELECT value::int AS value FROM _rag_meta WHERE key = ${META_KEY_DIMENSIONS}
18789
18803
  `.catch(() => []);
@@ -18798,6 +18812,7 @@ async function ensureSchema(sql, dimensions) {
18798
18812
  };
18799
18813
  throw error48;
18800
18814
  }
18815
+ schemaVerifiedDimensions.set(sql, dimensions);
18801
18816
  return;
18802
18817
  }
18803
18818
  await sql.begin(async (tx) => {
@@ -18817,6 +18832,7 @@ async function ensureSchema(sql, dimensions) {
18817
18832
  ON CONFLICT (key) DO NOTHING
18818
18833
  `;
18819
18834
  });
18835
+ schemaVerifiedDimensions.set(sql, dimensions);
18820
18836
  }
18821
18837
  __name(ensureSchema, "ensureSchema");
18822
18838
  async function reset(sql) {
@@ -18824,29 +18840,31 @@ async function reset(sql) {
18824
18840
  await tx`DROP TABLE IF EXISTS rag_chunks`;
18825
18841
  await tx`DROP TABLE IF EXISTS _rag_meta`;
18826
18842
  });
18843
+ schemaVerifiedDimensions.delete(sql);
18827
18844
  }
18828
18845
  __name(reset, "reset");
18829
- async function ingestDocument(sql, docId, namespace, rows) {
18846
+ async function clearDocument(sql, docId, namespace) {
18847
+ await sql`
18848
+ DELETE FROM rag_chunks
18849
+ WHERE namespace = ${namespace} AND doc_id = ${docId}
18850
+ `;
18851
+ }
18852
+ __name(clearDocument, "clearDocument");
18853
+ async function ingestDocument(sql, rows) {
18830
18854
  if (rows.length === 0) return 0;
18831
- await sql.begin(async (tx) => {
18832
- await tx`
18833
- DELETE FROM rag_chunks
18834
- WHERE namespace = ${namespace} AND doc_id = ${docId}
18835
- `;
18836
- const placeholders = rows.map((_, i) => {
18837
- const base = i * 6;
18838
- return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}, $${base + 5}::vector, $${base + 6}::jsonb)`;
18839
- }).join(",");
18840
- const params = rows.flatMap((row) => [
18841
- row.docId,
18842
- row.chunkIdx,
18843
- row.namespace,
18844
- row.content,
18845
- formatVector(row.embedding),
18846
- JSON.stringify(row.metadata)
18847
- ]);
18848
- await tx.unsafe(`INSERT INTO rag_chunks (doc_id, chunk_idx, namespace, content, embedding, metadata) VALUES ${placeholders}`, params);
18849
- });
18855
+ const placeholders = rows.map((_, i) => {
18856
+ const base = i * 6;
18857
+ return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}, $${base + 5}::vector, $${base + 6}::jsonb)`;
18858
+ }).join(",");
18859
+ const params = rows.flatMap((row) => [
18860
+ row.docId,
18861
+ row.chunkIdx,
18862
+ row.namespace,
18863
+ row.content,
18864
+ formatVector(row.embedding),
18865
+ JSON.stringify(row.metadata)
18866
+ ]);
18867
+ await sql.unsafe(`INSERT INTO rag_chunks (doc_id, chunk_idx, namespace, content, embedding, metadata) VALUES ${placeholders}`, params);
18850
18868
  return rows.length;
18851
18869
  }
18852
18870
  __name(ingestDocument, "ingestDocument");
@@ -18916,8 +18934,6 @@ var CHUNK_INDEXES_DDL = `
18916
18934
 
18917
18935
  // src/modules/rag/pipeline.ts
18918
18936
  var DEFAULT_TOP_K = 5;
18919
- var RAG_SCHEMA_MISSING_HINT = "run `stackbone db migrate add-rag` and `stackbone db migrate up`";
18920
- var PG_UNDEFINED_TABLE = "42P01";
18921
18937
  var RagPipeline = class {
18922
18938
  static {
18923
18939
  __name(this, "RagPipeline");
@@ -18982,19 +18998,16 @@ var RagPipeline = class {
18982
18998
  if (started.error) return err(started.error);
18983
18999
  jobId = started.data.jobId;
18984
19000
  }
18985
- const emit = /* @__PURE__ */ __name(async (event) => {
18986
- if (onProgress) await onProgress(event);
18987
- }, "emit");
18988
- const startEvent = {
19001
+ await onProgress?.({
18989
19002
  type: "started",
18990
19003
  jobId: jobId ?? request.id,
18991
19004
  totalChunks
18992
- };
18993
- await emit(startEvent);
19005
+ });
18994
19006
  try {
18995
19007
  await ensureSchema(sql.data, dimensions);
18996
19008
  const needsBatching = Boolean(writer || onProgress);
18997
19009
  const batchSize = this._progressBatchSize ?? (needsBatching ? 1 : rows.length || 1);
19010
+ await clearDocument(sql.data, request.id, namespace);
18998
19011
  let processed = 0;
18999
19012
  for (let offset = 0; offset < rows.length; offset += batchSize) {
19000
19013
  if (writer && jobId) {
@@ -19014,37 +19027,33 @@ var RagPipeline = class {
19014
19027
  }
19015
19028
  }
19016
19029
  const slice = rows.slice(offset, offset + batchSize);
19017
- await ingestDocument(sql.data, request.id, namespace, slice);
19030
+ await ingestDocument(sql.data, slice);
19018
19031
  processed += slice.length;
19019
- const progressEvent = {
19032
+ const currentDocument = {
19033
+ source: request.id,
19034
+ ordinal: processed - 1
19035
+ };
19036
+ await onProgress?.({
19020
19037
  type: "progress",
19021
19038
  jobId: jobId ?? request.id,
19022
19039
  processedChunks: processed,
19023
19040
  totalChunks,
19024
- currentDocument: {
19025
- source: request.id,
19026
- ordinal: processed - 1
19027
- }
19028
- };
19029
- await emit(progressEvent);
19041
+ currentDocument
19042
+ });
19030
19043
  if (writer && jobId) {
19031
19044
  await writer.progress({
19032
19045
  jobId,
19033
19046
  processedChunks: processed,
19034
19047
  totalChunks,
19035
- currentDocument: {
19036
- source: request.id,
19037
- ordinal: processed - 1
19038
- }
19048
+ currentDocument
19039
19049
  });
19040
19050
  }
19041
19051
  }
19042
- const completedEvent = {
19052
+ await onProgress?.({
19043
19053
  type: "completed",
19044
19054
  jobId: jobId ?? request.id,
19045
19055
  totalChunks
19046
- };
19047
- await emit(completedEvent);
19056
+ });
19048
19057
  if (writer && jobId) await writer.complete({
19049
19058
  jobId,
19050
19059
  totalChunks
@@ -19055,12 +19064,11 @@ var RagPipeline = class {
19055
19064
  });
19056
19065
  } catch (cause) {
19057
19066
  const mapped = toRagError(cause, "Ingest failed");
19058
- const failedEvent = {
19067
+ await onProgress?.({
19059
19068
  type: "failed",
19060
19069
  jobId: jobId ?? request.id,
19061
19070
  error: mapped.message
19062
- };
19063
- await emit(failedEvent);
19071
+ });
19064
19072
  if (writer && jobId) await writer.fail({
19065
19073
  jobId,
19066
19074
  error: mapped.message
@@ -19220,37 +19228,6 @@ function validateIngest(request, hasEmbedder) {
19220
19228
  return ok(true);
19221
19229
  }
19222
19230
  __name(validateIngest, "validateIngest");
19223
- function isTaggedError(cause) {
19224
- return cause instanceof Error;
19225
- }
19226
- __name(isTaggedError, "isTaggedError");
19227
- function toRagError(cause, message) {
19228
- if (isTaggedError(cause) && cause.code === PG_UNDEFINED_TABLE) {
19229
- return {
19230
- code: "rag_schema_missing",
19231
- message: `RAG tables are not installed \u2014 ${RAG_SCHEMA_MISSING_HINT}.`,
19232
- cause,
19233
- meta: {
19234
- hint: RAG_SCHEMA_MISSING_HINT
19235
- }
19236
- };
19237
- }
19238
- if (isTaggedError(cause)) {
19239
- const error48 = {
19240
- code: cause.code ?? "rag_error",
19241
- message: `${message}: ${cause.message}`,
19242
- cause
19243
- };
19244
- if (cause.meta) error48.meta = cause.meta;
19245
- return error48;
19246
- }
19247
- return {
19248
- code: "rag_error",
19249
- message,
19250
- cause
19251
- };
19252
- }
19253
- __name(toRagError, "toRagError");
19254
19231
 
19255
19232
  // src/modules/rag/rag.ts
19256
19233
  var DEFAULT_BATCH_SIZE4 = 128;
@@ -19262,10 +19239,14 @@ var RagModule = class {
19262
19239
  _resolved;
19263
19240
  _getAi;
19264
19241
  _gate;
19265
- constructor(_resolved, _getAi, gate) {
19242
+ _testSqlOverride;
19243
+ _testJobWriterFactory;
19244
+ constructor(_resolved, _getAi, gate, testDeps) {
19266
19245
  this._resolved = _resolved;
19267
19246
  this._getAi = _getAi;
19268
19247
  this._gate = gate ?? createModuleGate("rag", _resolved);
19248
+ if (testDeps?.sqlProvider) this._testSqlOverride = testDeps.sqlProvider;
19249
+ if (testDeps?.jobWriterFactory) this._testJobWriterFactory = testDeps.jobWriterFactory;
19269
19250
  }
19270
19251
  async ingest(request) {
19271
19252
  const gateResult = await this._gate();
@@ -19298,7 +19279,8 @@ var RagModule = class {
19298
19279
  if (gateResult.error) return err(gateResult.error);
19299
19280
  const sql = this._testSqlOverride ? this._testSqlOverride() : this.sql();
19300
19281
  if (sql.error) return err(sql.error);
19301
- const writer = this._testJobWriter ?? createSqlJobWriter(sql.data);
19282
+ const writerFactory = this._testJobWriterFactory ?? createSqlJobWriter;
19283
+ const writer = writerFactory(sql.data);
19302
19284
  const channel = createProgressChannel();
19303
19285
  const resolved = resolveIngestModel(request, this._resolved);
19304
19286
  const requestWithHook = {
@@ -19329,7 +19311,7 @@ var RagModule = class {
19329
19311
  return r;
19330
19312
  }).catch((cause) => {
19331
19313
  channel.close();
19332
- return err(toRagError2(cause, "Ingest failed"));
19314
+ return err(toRagError(cause, "Ingest failed"));
19333
19315
  });
19334
19316
  return ok({
19335
19317
  jobId,
@@ -19337,10 +19319,6 @@ var RagModule = class {
19337
19319
  result
19338
19320
  });
19339
19321
  }
19340
- /** Test-only seam — assigned by `ingest-async.spec.ts`. */
19341
- _testJobWriter;
19342
- /** Test-only seam — assigned by `ingest-async.spec.ts`. */
19343
- _testSqlOverride;
19344
19322
  async delete(ids, options) {
19345
19323
  const gateResult = await this._gate();
19346
19324
  if (gateResult.error) return err(gateResult.error);
@@ -19372,7 +19350,7 @@ var RagModule = class {
19372
19350
  await reset(sql.data);
19373
19351
  return ok(void 0);
19374
19352
  } catch (cause) {
19375
- return err(toRagError2(cause, "Reset failed"));
19353
+ return err(toRagError(cause, "Reset failed"));
19376
19354
  }
19377
19355
  }
19378
19356
  /**
@@ -19400,22 +19378,18 @@ var RagModule = class {
19400
19378
  * `Result.err` with the same `database_not_configured` shape `client.database`
19401
19379
  * raises, instead of throwing through the public surface.
19402
19380
  */
19403
- // `_resolved` is reserved for future per-config overrides (e.g. routing
19404
- // RAG queries through a logical replica). The URL itself is owned by the
19405
- // shared handle.
19406
19381
  sql() {
19407
- void this._resolved;
19408
19382
  try {
19409
19383
  return ok(getDatabaseHandle().$client);
19410
19384
  } catch (cause) {
19411
- if (isTaggedError2(cause) && cause.code === "database_not_configured") {
19385
+ if (isTaggedError(cause) && cause.code === "database_not_configured") {
19412
19386
  return err({
19413
19387
  code: "database_not_configured",
19414
19388
  message: cause.message,
19415
19389
  cause
19416
19390
  });
19417
19391
  }
19418
- return err(toRagError2(cause, "Database handle unavailable"));
19392
+ return err(toRagError(cause, "Database handle unavailable"));
19419
19393
  }
19420
19394
  }
19421
19395
  async withPipeline(embedder, run) {
@@ -19536,27 +19510,6 @@ function resolveRetrieveModel(request, resolved) {
19536
19510
  };
19537
19511
  }
19538
19512
  __name(resolveRetrieveModel, "resolveRetrieveModel");
19539
- function isTaggedError2(cause) {
19540
- return cause instanceof Error;
19541
- }
19542
- __name(isTaggedError2, "isTaggedError");
19543
- function toRagError2(cause, message) {
19544
- if (isTaggedError2(cause)) {
19545
- const error48 = {
19546
- code: cause.code ?? "rag_error",
19547
- message: `${message}: ${cause.message}`,
19548
- cause
19549
- };
19550
- if (cause.meta) error48.meta = cause.meta;
19551
- return error48;
19552
- }
19553
- return {
19554
- code: "rag_error",
19555
- message,
19556
- cause
19557
- };
19558
- }
19559
- __name(toRagError2, "toRagError");
19560
19513
  var DEFAULT_SIGNED_URL_TTL_SECONDS = 3600;
19561
19514
  var StorageModule = class {
19562
19515
  static {
@@ -19573,11 +19526,12 @@ var StorageModule = class {
19573
19526
  return new StorageBucket(bucket, () => this.settings(), this._gate);
19574
19527
  }
19575
19528
  settings() {
19576
- const accessKeyId = this._resolved.config.s3?.accessKeyId ?? this._resolved.env["AWS_ACCESS_KEY_ID"];
19577
- const secretAccessKey = this._resolved.config.s3?.secretAccessKey ?? this._resolved.env["AWS_SECRET_ACCESS_KEY"];
19578
- const endpoint = this._resolved.config.s3?.endpoint ?? this._resolved.env["S3_ENDPOINT"];
19579
- const bucket = this._resolved.config.s3?.bucket ?? this._resolved.env["S3_BUCKET"];
19529
+ const accessKeyId = this._resolved.config.s3?.accessKeyId ?? this._resolved.env["STACKBONE_S3_ACCESS_KEY"];
19530
+ const secretAccessKey = this._resolved.config.s3?.secretAccessKey ?? this._resolved.env["STACKBONE_S3_SECRET_KEY"];
19531
+ const endpoint = this._resolved.config.s3?.endpoint ?? this._resolved.env["STACKBONE_S3_ENDPOINT"];
19532
+ const bucket = this._resolved.config.s3?.bucket ?? this._resolved.env["STACKBONE_S3_BUCKET"];
19580
19533
  const agentId = this._resolved.config.agentId ?? this._resolved.env["STACKBONE_AGENT_ID"];
19534
+ const region = this._resolved.config.s3?.region ?? this._resolved.env["STACKBONE_S3_REGION"] ?? "auto";
19581
19535
  if (!accessKeyId || !secretAccessKey || !endpoint) {
19582
19536
  return err({
19583
19537
  code: "s3_credentials_missing",
@@ -19598,7 +19552,7 @@ var StorageModule = class {
19598
19552
  }
19599
19553
  this._s3 ??= new S3Client({
19600
19554
  endpoint,
19601
- region: "auto",
19555
+ region,
19602
19556
  forcePathStyle: true,
19603
19557
  credentials: {
19604
19558
  accessKeyId,
@@ -19757,29 +19711,19 @@ var StorageBucket = class {
19757
19711
  return ok(`${base}/${bucket}/${encodeS3Key(fullKey)}`);
19758
19712
  }
19759
19713
  async getSignedUploadUrl(key, options) {
19760
- const gateResult = await this.gate();
19761
- if (gateResult.error) return err(gateResult.error);
19762
- const resolved = this.resolve(key);
19763
- if (resolved.error) return err(resolved.error);
19764
- const { client, bucket, fullKey } = resolved.data;
19765
- const expiresIn = options?.expiresIn ?? DEFAULT_SIGNED_URL_TTL_SECONDS;
19766
- try {
19767
- const url2 = await getSignedUrl(client, new PutObjectCommand({
19768
- Bucket: bucket,
19769
- Key: fullKey,
19770
- ContentType: options?.contentType
19771
- }), {
19772
- expiresIn
19773
- });
19774
- return ok({
19775
- url: url2,
19776
- expiresAt: new Date(Date.now() + expiresIn * 1e3)
19777
- });
19778
- } catch (cause) {
19779
- return err(toStorageError("Signing upload URL failed", cause));
19780
- }
19714
+ return this.signUrl(key, options, "Signing upload URL failed", ({ bucket, fullKey }) => new PutObjectCommand({
19715
+ Bucket: bucket,
19716
+ Key: fullKey,
19717
+ ContentType: options?.contentType
19718
+ }));
19781
19719
  }
19782
19720
  async getSignedDownloadUrl(key, options) {
19721
+ return this.signUrl(key, options, "Signing download URL failed", ({ bucket, fullKey }) => new GetObjectCommand({
19722
+ Bucket: bucket,
19723
+ Key: fullKey
19724
+ }));
19725
+ }
19726
+ async signUrl(key, options, failureMessage, buildCommand) {
19783
19727
  const gateResult = await this.gate();
19784
19728
  if (gateResult.error) return err(gateResult.error);
19785
19729
  const resolved = this.resolve(key);
@@ -19787,9 +19731,9 @@ var StorageBucket = class {
19787
19731
  const { client, bucket, fullKey } = resolved.data;
19788
19732
  const expiresIn = options?.expiresIn ?? DEFAULT_SIGNED_URL_TTL_SECONDS;
19789
19733
  try {
19790
- const url2 = await getSignedUrl(client, new GetObjectCommand({
19791
- Bucket: bucket,
19792
- Key: fullKey
19734
+ const url2 = await getSignedUrl(client, buildCommand({
19735
+ bucket,
19736
+ fullKey
19793
19737
  }), {
19794
19738
  expiresIn
19795
19739
  });
@@ -19798,7 +19742,7 @@ var StorageBucket = class {
19798
19742
  expiresAt: new Date(Date.now() + expiresIn * 1e3)
19799
19743
  });
19800
19744
  } catch (cause) {
19801
- return err(toStorageError("Signing download URL failed", cause));
19745
+ return err(toStorageError(failureMessage, cause));
19802
19746
  }
19803
19747
  }
19804
19748
  /**
@@ -19824,8 +19768,8 @@ var StorageBucket = class {
19824
19768
  });
19825
19769
  }
19826
19770
  };
19827
- var S3_CREDENTIALS_MISSING_MESSAGE = "Cannot reach object storage \u2014 set `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `S3_ENDPOINT` in the container or pass `s3` to `createClient({...})`.";
19828
- var S3_BUCKET_MISSING_MESSAGE = "No storage bucket configured \u2014 set `S3_BUCKET` in the container or pass `s3.bucket` to `createClient({...})`.";
19771
+ var S3_CREDENTIALS_MISSING_MESSAGE = "Cannot reach object storage \u2014 set `STACKBONE_S3_ACCESS_KEY`, `STACKBONE_S3_SECRET_KEY`, and `STACKBONE_S3_ENDPOINT` in the container or pass `s3` to `createClient({...})`.";
19772
+ var S3_BUCKET_MISSING_MESSAGE = "No storage bucket configured \u2014 set `STACKBONE_S3_BUCKET` in the container or pass `s3.bucket` to `createClient({...})`.";
19829
19773
  var AGENT_ID_MISSING_MESSAGE = "No agent identity configured \u2014 set `STACKBONE_AGENT_ID` in the container or pass `agentId` to `createClient({...})`.";
19830
19774
  function stripEtag(etag) {
19831
19775
  return etag?.replace(/^"|"$/g, "");