@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.cjs CHANGED
@@ -1793,9 +1793,9 @@ var nanoid = /^[a-zA-Z0-9_-]{21}$/;
1793
1793
  var duration = /^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/;
1794
1794
  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)?)??$/;
1795
1795
  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})$/;
1796
- var uuid = /* @__PURE__ */ __name((version2) => {
1797
- 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)$/;
1798
- 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})$`);
1796
+ var uuid = /* @__PURE__ */ __name((version3) => {
1797
+ 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)$/;
1798
+ 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})$`);
1799
1799
  }, "uuid");
1800
1800
  var uuid4 = /* @__PURE__ */ uuid(4);
1801
1801
  var uuid6 = /* @__PURE__ */ uuid(6);
@@ -14979,10 +14979,10 @@ function fromJSONSchema(schema, params) {
14979
14979
  if (typeof schema === "boolean") {
14980
14980
  return schema ? z.any() : z.never();
14981
14981
  }
14982
- const version2 = detectVersion(schema, params?.defaultTarget);
14982
+ const version3 = detectVersion(schema, params?.defaultTarget);
14983
14983
  const defs = schema.$defs || schema.definitions || {};
14984
14984
  const ctx = {
14985
- version: version2,
14985
+ version: version3,
14986
14986
  defs,
14987
14987
  refs: /* @__PURE__ */ new Map(),
14988
14988
  processing: /* @__PURE__ */ new Set(),
@@ -15560,6 +15560,13 @@ external_exports.object({
15560
15560
  nextCursor: external_exports.string().nullable(),
15561
15561
  prevCursor: external_exports.string().nullable()
15562
15562
  });
15563
+ external_exports.object({
15564
+ fxtId: external_exports.uuid()
15565
+ });
15566
+ external_exports.object({
15567
+ input: jsonObject().nullable(),
15568
+ output: jsonObject().nullable()
15569
+ });
15563
15570
 
15564
15571
  // ../validators/src/lib/auth/session.ts
15565
15572
  var sessionUserSchema = external_exports.object({
@@ -16644,6 +16651,34 @@ function pickDefinedAsStrings(source) {
16644
16651
  }
16645
16652
  __name(pickDefinedAsStrings, "pickDefinedAsStrings");
16646
16653
 
16654
+ // src/lib/facade-helpers.ts
16655
+ function remapHttpNotFound(code, domainCode, message, meta3) {
16656
+ if (code !== "http_not_found") return null;
16657
+ const error48 = {
16658
+ code: domainCode,
16659
+ message
16660
+ };
16661
+ if (meta3) error48.meta = meta3;
16662
+ return err(error48);
16663
+ }
16664
+ __name(remapHttpNotFound, "remapHttpNotFound");
16665
+ function validateStringArray(values, field, errorCode) {
16666
+ if (!Array.isArray(values) || values.length === 0) {
16667
+ return err({
16668
+ code: errorCode,
16669
+ message: `\`${field}\` must be a non-empty array.`
16670
+ });
16671
+ }
16672
+ if (values.some((v) => typeof v !== "string" || !v.trim())) {
16673
+ return err({
16674
+ code: errorCode,
16675
+ message: `\`${field}[*]\` must be non-empty strings.`
16676
+ });
16677
+ }
16678
+ return null;
16679
+ }
16680
+ __name(validateStringArray, "validateStringArray");
16681
+
16647
16682
  // src/facades/config/config.ts
16648
16683
  var ConfigFacade = class {
16649
16684
  static {
@@ -16665,7 +16700,11 @@ var ConfigFacade = class {
16665
16700
  const gateResult = await this._gate();
16666
16701
  if (gateResult.error) return err(gateResult.error);
16667
16702
  const result = await this._http.get(`/api/config/${encodeURIComponent(key)}`);
16668
- if (result.error) return remapNotFound(result.error.code, key) ?? result;
16703
+ if (result.error) {
16704
+ return remapHttpNotFound(result.error.code, "config_not_found", `Config key \`${key}\` is not set for this agent.`, {
16705
+ key
16706
+ }) ?? result;
16707
+ }
16669
16708
  return ok(result.data.value);
16670
16709
  }
16671
16710
  /**
@@ -16675,18 +16714,8 @@ var ConfigFacade = class {
16675
16714
  * is `Partial<T>`).
16676
16715
  */
16677
16716
  async getMany(keys) {
16678
- if (!Array.isArray(keys) || keys.length === 0) {
16679
- return err({
16680
- code: "config_invalid_request",
16681
- message: "`keys` must be a non-empty array."
16682
- });
16683
- }
16684
- if (keys.some((k) => !k.trim())) {
16685
- return err({
16686
- code: "config_invalid_request",
16687
- message: "`keys[*]` must be non-empty strings."
16688
- });
16689
- }
16717
+ const invalid = validateStringArray(keys, "keys", "config_invalid_request");
16718
+ if (invalid) return invalid;
16690
16719
  const gateResult = await this._gate();
16691
16720
  if (gateResult.error) return err(gateResult.error);
16692
16721
  const result = await this._http.get("/api/config", {
@@ -16705,17 +16734,6 @@ var ConfigFacade = class {
16705
16734
  return ok(values);
16706
16735
  }
16707
16736
  };
16708
- function remapNotFound(code, key) {
16709
- if (code !== "http_not_found") return null;
16710
- return err({
16711
- code: "config_not_found",
16712
- message: `Config key \`${key}\` is not set for this agent.`,
16713
- meta: {
16714
- key
16715
- }
16716
- });
16717
- }
16718
- __name(remapNotFound, "remapNotFound");
16719
16737
 
16720
16738
  // src/facades/connections/connections.ts
16721
16739
  var ConnectionsFacade = class {
@@ -16801,7 +16819,11 @@ var SecretsFacade = class {
16801
16819
  const gateResult = await this._gate();
16802
16820
  if (gateResult.error) return err(gateResult.error);
16803
16821
  const result = await this._http.get(`/api/secrets/${encodeURIComponent(name)}`);
16804
- if (result.error) return remapNotFound2(result.error.code, name) ?? result;
16822
+ if (result.error) {
16823
+ return remapHttpNotFound(result.error.code, "secrets_not_found", `Secret \`${name}\` is not registered for this organization.`, {
16824
+ name
16825
+ }) ?? result;
16826
+ }
16805
16827
  if (typeof result.data.value !== "string") {
16806
16828
  return err({
16807
16829
  code: "secrets_invalid_response",
@@ -16814,23 +16836,13 @@ var SecretsFacade = class {
16814
16836
  return ok(result.data.value);
16815
16837
  }
16816
16838
  /**
16817
- * Names absent from the workspace come back as omissions in the response —
16839
+ * Names absent from the organization come back as omissions in the response —
16818
16840
  * the returned map only contains entries the control plane actually has.
16819
16841
  * Callers that need "all-or-nothing" semantics should diff the keys.
16820
16842
  */
16821
16843
  async getMany(names) {
16822
- if (!Array.isArray(names) || names.length === 0) {
16823
- return err({
16824
- code: "secrets_invalid_request",
16825
- message: "`names` must be a non-empty array."
16826
- });
16827
- }
16828
- if (names.some((n) => !n.trim())) {
16829
- return err({
16830
- code: "secrets_invalid_request",
16831
- message: "`names[*]` must be non-empty strings."
16832
- });
16833
- }
16844
+ const invalid = validateStringArray(names, "names", "secrets_invalid_request");
16845
+ if (invalid) return invalid;
16834
16846
  const gateResult = await this._gate();
16835
16847
  if (gateResult.error) return err(gateResult.error);
16836
16848
  const result = await this._http.get("/api/secrets", {
@@ -16849,17 +16861,6 @@ var SecretsFacade = class {
16849
16861
  return ok(values);
16850
16862
  }
16851
16863
  };
16852
- function remapNotFound2(code, name) {
16853
- if (code !== "http_not_found") return null;
16854
- return err({
16855
- code: "secrets_not_found",
16856
- message: `Secret \`${name}\` is not registered for this workspace.`,
16857
- meta: {
16858
- name
16859
- }
16860
- });
16861
- }
16862
- __name(remapNotFound2, "remapNotFound");
16863
16864
 
16864
16865
  // src/lib/http-client.ts
16865
16866
  var RETRYABLE_STATUS = /* @__PURE__ */ new Set([
@@ -17742,6 +17743,11 @@ async function aggregateRunCost(runId, options) {
17742
17743
  };
17743
17744
  }
17744
17745
  __name(aggregateRunCost, "aggregateRunCost");
17746
+
17747
+ // package.json
17748
+ var version2 = "0.1.0-alpha.2";
17749
+
17750
+ // src/observability/logger.ts
17745
17751
  var LOG_LEVELS = {
17746
17752
  trace: 10,
17747
17753
  debug: 20,
@@ -17955,7 +17961,7 @@ var OtlpHttpDestination = class OtlpHttpDestination2 {
17955
17961
  };
17956
17962
  var SDK_SCOPE = {
17957
17963
  name: "@stackbone/sdk",
17958
- version: "0.0.1"
17964
+ version: version2
17959
17965
  };
17960
17966
  function buildOtlpLogsBody(records, resourceAttributes) {
17961
17967
  return {
@@ -17988,11 +17994,12 @@ function toOtlpLogRecord(record2) {
17988
17994
  };
17989
17995
  }
17990
17996
  __name(toOtlpLogRecord, "toOtlpLogRecord");
17997
+ var SEVERITY_TEXT_BY_LEVEL = Object.fromEntries(Object.entries(LOG_LEVELS).map(([name, value]) => [
17998
+ value,
17999
+ name.toUpperCase()
18000
+ ]));
17991
18001
  function severityText(level) {
17992
- for (const [name, value] of Object.entries(LOG_LEVELS)) {
17993
- if (value === level) return name.toUpperCase();
17994
- }
17995
- return "INFO";
18002
+ return SEVERITY_TEXT_BY_LEVEL[level] ?? "INFO";
17996
18003
  }
17997
18004
  __name(severityText, "severityText");
17998
18005
  function toOtlpAttributes(attrs) {
@@ -18120,7 +18127,7 @@ var RunStepsSpanProcessor = class {
18120
18127
  this.intervalMs = options.flushIntervalMs ?? DEFAULT_INTERVAL_MS2;
18121
18128
  this.resolveStepType = options.resolveStepType ?? defaultStepTypeResolver;
18122
18129
  this.newId = options.newId ?? crypto$1.randomUUID;
18123
- this.connectionString = options.connectionString ?? process.env["DATABASE_URL"];
18130
+ this.connectionString = options.connectionString ?? process.env["STACKBONE_POSTGRES_URL"];
18124
18131
  if (options.sql) {
18125
18132
  this.sql = options.sql;
18126
18133
  this.ownsSql = false;
@@ -18160,11 +18167,11 @@ var RunStepsSpanProcessor = class {
18160
18167
  }
18161
18168
  }
18162
18169
  buildRow(span) {
18163
- const runId = readString(span.attributes[RUN_ID_ATTRIBUTE]);
18164
- if (!runId) return null;
18165
18170
  const spanId = span.spanContext().spanId;
18166
18171
  const stepId = this.stepIdBySpanId.get(spanId) ?? this.newId();
18167
18172
  this.stepIdBySpanId.delete(spanId);
18173
+ const runId = readString(span.attributes[RUN_ID_ATTRIBUTE]);
18174
+ if (!runId) return null;
18168
18175
  const parentSpanId = span.parentSpanContext?.spanId ?? span.parentSpanId ?? null;
18169
18176
  const parentStepId = parentSpanId ? this.stepIdBySpanId.get(parentSpanId) ?? null : null;
18170
18177
  const startedAt = formatHrTime(span.startTime);
@@ -18304,7 +18311,7 @@ var ObservabilityModule = class {
18304
18311
  if (!this._processor) {
18305
18312
  const merged = {
18306
18313
  ...options,
18307
- connectionString: options.connectionString ?? this._resolved.config.databaseUrl ?? this._resolved.env["DATABASE_URL"]
18314
+ connectionString: options.connectionString ?? this._resolved.config.databaseUrl ?? this._resolved.env["STACKBONE_POSTGRES_URL"]
18308
18315
  };
18309
18316
  this._processor = new RunStepsSpanProcessor(merged);
18310
18317
  }
@@ -18363,7 +18370,7 @@ var ObservabilityModule = class {
18363
18370
  if (!sql) {
18364
18371
  return err({
18365
18372
  code: "database_url_missing",
18366
- message: "Cannot reach the agent Postgres \u2014 set `DATABASE_URL` in the container or pass `databaseUrl` to `createClient({...})`."
18373
+ message: "Cannot reach the agent Postgres \u2014 set `STACKBONE_POSTGRES_URL` in the container or pass `databaseUrl` to `createClient({...})`."
18367
18374
  });
18368
18375
  }
18369
18376
  try {
@@ -18384,7 +18391,7 @@ var ObservabilityModule = class {
18384
18391
  // don't pay the dep cost.
18385
18392
  async resolveSql() {
18386
18393
  if (this._sqlResolved) return this._sql;
18387
- const connectionString = this._resolved.config.databaseUrl ?? this._resolved.env["DATABASE_URL"];
18394
+ const connectionString = this._resolved.config.databaseUrl ?? this._resolved.env["STACKBONE_POSTGRES_URL"];
18388
18395
  if (!connectionString) {
18389
18396
  this._sqlResolved = true;
18390
18397
  return null;
@@ -18614,6 +18621,41 @@ function isUnsupportedModelError(error48) {
18614
18621
  }
18615
18622
  __name(isUnsupportedModelError, "isUnsupportedModelError");
18616
18623
 
18624
+ // src/modules/rag/errors.ts
18625
+ var PG_UNDEFINED_TABLE = "42P01";
18626
+ var RAG_SCHEMA_MISSING_HINT = "run `stackbone db migrate add-rag` and `stackbone db migrate up`";
18627
+ function isTaggedError(cause) {
18628
+ return cause instanceof Error;
18629
+ }
18630
+ __name(isTaggedError, "isTaggedError");
18631
+ function toRagError(cause, message, fallbackCode = "rag_error") {
18632
+ if (isTaggedError(cause) && cause.code === PG_UNDEFINED_TABLE) {
18633
+ return {
18634
+ code: "rag_schema_missing",
18635
+ message: `RAG tables are not installed \u2014 ${RAG_SCHEMA_MISSING_HINT}.`,
18636
+ cause,
18637
+ meta: {
18638
+ hint: RAG_SCHEMA_MISSING_HINT
18639
+ }
18640
+ };
18641
+ }
18642
+ if (isTaggedError(cause)) {
18643
+ const error48 = {
18644
+ code: cause.code ?? fallbackCode,
18645
+ message: `${message}: ${cause.message}`,
18646
+ cause
18647
+ };
18648
+ if (cause.meta) error48.meta = cause.meta;
18649
+ return error48;
18650
+ }
18651
+ return {
18652
+ code: fallbackCode,
18653
+ message,
18654
+ cause
18655
+ };
18656
+ }
18657
+ __name(toRagError, "toRagError");
18658
+
18617
18659
  // src/modules/rag/jobs.ts
18618
18660
  function createSqlJobWriter(sql) {
18619
18661
  return {
@@ -18639,7 +18681,7 @@ function createSqlJobWriter(sql) {
18639
18681
  jobId: row.id
18640
18682
  });
18641
18683
  } catch (cause) {
18642
- return err(toJobsError(cause, "Job start failed"));
18684
+ return err(toRagError(cause, "Job start failed", "rag_jobs_error"));
18643
18685
  }
18644
18686
  },
18645
18687
  async progress({ jobId, processedChunks, totalChunks, currentDocument }) {
@@ -18678,7 +18720,7 @@ function createSqlJobWriter(sql) {
18678
18720
  },
18679
18721
  async isCancelled({ jobId }) {
18680
18722
  const rows = await sql`
18681
- SELECT id, status FROM stackbone_rag_jobs WHERE id = ${jobId}
18723
+ SELECT status FROM stackbone_rag_jobs WHERE id = ${jobId} LIMIT 1
18682
18724
  `;
18683
18725
  return rows[0]?.status === "cancelled";
18684
18726
  }
@@ -18686,10 +18728,6 @@ function createSqlJobWriter(sql) {
18686
18728
  }
18687
18729
  __name(createSqlJobWriter, "createSqlJobWriter");
18688
18730
  async function ensureCollection(sql, name) {
18689
- const existing = await sql`
18690
- SELECT id FROM stackbone_rag_collections WHERE name = ${name} LIMIT 1
18691
- `;
18692
- if (existing[0]) return existing[0].id;
18693
18731
  const inserted = await sql`
18694
18732
  INSERT INTO stackbone_rag_collections (name) VALUES (${name})
18695
18733
  ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name
@@ -18697,37 +18735,11 @@ async function ensureCollection(sql, name) {
18697
18735
  `;
18698
18736
  const row = inserted[0];
18699
18737
  if (!row) {
18700
- throw new Error(`stackbone_rag_collections insert returned no row for ${name}`);
18738
+ throw new Error(`stackbone_rag_collections upsert returned no row for ${name}`);
18701
18739
  }
18702
18740
  return row.id;
18703
18741
  }
18704
18742
  __name(ensureCollection, "ensureCollection");
18705
- function toJobsError(cause, message) {
18706
- if (cause instanceof Error) {
18707
- const tagged = cause;
18708
- if (tagged.code === "42P01") {
18709
- return {
18710
- code: "rag_schema_missing",
18711
- message: "RAG tables are not installed \u2014 run `stackbone db migrate add-rag` and `stackbone db migrate up`.",
18712
- cause,
18713
- meta: {
18714
- hint: "run `stackbone db migrate add-rag` and `stackbone db migrate up`"
18715
- }
18716
- };
18717
- }
18718
- return {
18719
- code: tagged.code ?? "rag_jobs_error",
18720
- message: `${message}: ${cause.message}`,
18721
- cause
18722
- };
18723
- }
18724
- return {
18725
- code: "rag_jobs_error",
18726
- message,
18727
- cause
18728
- };
18729
- }
18730
- __name(toJobsError, "toJobsError");
18731
18743
  var PDF_MIME_TYPES = /* @__PURE__ */ new Set([
18732
18744
  "application/pdf",
18733
18745
  "application/x-pdf"
@@ -18787,10 +18799,12 @@ __name(looksLikePdf, "looksLikePdf");
18787
18799
  var DEFAULT_NAMESPACE = "default";
18788
18800
  var META_KEY_DIMENSIONS = "dimensions";
18789
18801
  var META_KEY_DISTANCE = "distance";
18802
+ var schemaVerifiedDimensions = /* @__PURE__ */ new WeakMap();
18790
18803
  async function ensureSchema(sql, dimensions) {
18791
18804
  if (!Number.isInteger(dimensions) || dimensions <= 0 || dimensions > 8192) {
18792
18805
  throw new Error(`rag.ensureSchema: invalid dimensions ${dimensions}. Must be a positive integer <= 8192.`);
18793
18806
  }
18807
+ if (schemaVerifiedDimensions.get(sql) === dimensions) return;
18794
18808
  const existing = await sql`
18795
18809
  SELECT value::int AS value FROM _rag_meta WHERE key = ${META_KEY_DIMENSIONS}
18796
18810
  `.catch(() => []);
@@ -18805,6 +18819,7 @@ async function ensureSchema(sql, dimensions) {
18805
18819
  };
18806
18820
  throw error48;
18807
18821
  }
18822
+ schemaVerifiedDimensions.set(sql, dimensions);
18808
18823
  return;
18809
18824
  }
18810
18825
  await sql.begin(async (tx) => {
@@ -18824,6 +18839,7 @@ async function ensureSchema(sql, dimensions) {
18824
18839
  ON CONFLICT (key) DO NOTHING
18825
18840
  `;
18826
18841
  });
18842
+ schemaVerifiedDimensions.set(sql, dimensions);
18827
18843
  }
18828
18844
  __name(ensureSchema, "ensureSchema");
18829
18845
  async function reset(sql) {
@@ -18831,29 +18847,31 @@ async function reset(sql) {
18831
18847
  await tx`DROP TABLE IF EXISTS rag_chunks`;
18832
18848
  await tx`DROP TABLE IF EXISTS _rag_meta`;
18833
18849
  });
18850
+ schemaVerifiedDimensions.delete(sql);
18834
18851
  }
18835
18852
  __name(reset, "reset");
18836
- async function ingestDocument(sql, docId, namespace, rows) {
18853
+ async function clearDocument(sql, docId, namespace) {
18854
+ await sql`
18855
+ DELETE FROM rag_chunks
18856
+ WHERE namespace = ${namespace} AND doc_id = ${docId}
18857
+ `;
18858
+ }
18859
+ __name(clearDocument, "clearDocument");
18860
+ async function ingestDocument(sql, rows) {
18837
18861
  if (rows.length === 0) return 0;
18838
- await sql.begin(async (tx) => {
18839
- await tx`
18840
- DELETE FROM rag_chunks
18841
- WHERE namespace = ${namespace} AND doc_id = ${docId}
18842
- `;
18843
- const placeholders = rows.map((_, i) => {
18844
- const base = i * 6;
18845
- return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}, $${base + 5}::vector, $${base + 6}::jsonb)`;
18846
- }).join(",");
18847
- const params = rows.flatMap((row) => [
18848
- row.docId,
18849
- row.chunkIdx,
18850
- row.namespace,
18851
- row.content,
18852
- formatVector(row.embedding),
18853
- JSON.stringify(row.metadata)
18854
- ]);
18855
- await tx.unsafe(`INSERT INTO rag_chunks (doc_id, chunk_idx, namespace, content, embedding, metadata) VALUES ${placeholders}`, params);
18856
- });
18862
+ const placeholders = rows.map((_, i) => {
18863
+ const base = i * 6;
18864
+ return `($${base + 1}, $${base + 2}, $${base + 3}, $${base + 4}, $${base + 5}::vector, $${base + 6}::jsonb)`;
18865
+ }).join(",");
18866
+ const params = rows.flatMap((row) => [
18867
+ row.docId,
18868
+ row.chunkIdx,
18869
+ row.namespace,
18870
+ row.content,
18871
+ formatVector(row.embedding),
18872
+ JSON.stringify(row.metadata)
18873
+ ]);
18874
+ await sql.unsafe(`INSERT INTO rag_chunks (doc_id, chunk_idx, namespace, content, embedding, metadata) VALUES ${placeholders}`, params);
18857
18875
  return rows.length;
18858
18876
  }
18859
18877
  __name(ingestDocument, "ingestDocument");
@@ -18923,8 +18941,6 @@ var CHUNK_INDEXES_DDL = `
18923
18941
 
18924
18942
  // src/modules/rag/pipeline.ts
18925
18943
  var DEFAULT_TOP_K = 5;
18926
- var RAG_SCHEMA_MISSING_HINT = "run `stackbone db migrate add-rag` and `stackbone db migrate up`";
18927
- var PG_UNDEFINED_TABLE = "42P01";
18928
18944
  var RagPipeline = class {
18929
18945
  static {
18930
18946
  __name(this, "RagPipeline");
@@ -18989,19 +19005,16 @@ var RagPipeline = class {
18989
19005
  if (started.error) return err(started.error);
18990
19006
  jobId = started.data.jobId;
18991
19007
  }
18992
- const emit = /* @__PURE__ */ __name(async (event) => {
18993
- if (onProgress) await onProgress(event);
18994
- }, "emit");
18995
- const startEvent = {
19008
+ await onProgress?.({
18996
19009
  type: "started",
18997
19010
  jobId: jobId ?? request.id,
18998
19011
  totalChunks
18999
- };
19000
- await emit(startEvent);
19012
+ });
19001
19013
  try {
19002
19014
  await ensureSchema(sql.data, dimensions);
19003
19015
  const needsBatching = Boolean(writer || onProgress);
19004
19016
  const batchSize = this._progressBatchSize ?? (needsBatching ? 1 : rows.length || 1);
19017
+ await clearDocument(sql.data, request.id, namespace);
19005
19018
  let processed = 0;
19006
19019
  for (let offset = 0; offset < rows.length; offset += batchSize) {
19007
19020
  if (writer && jobId) {
@@ -19021,37 +19034,33 @@ var RagPipeline = class {
19021
19034
  }
19022
19035
  }
19023
19036
  const slice = rows.slice(offset, offset + batchSize);
19024
- await ingestDocument(sql.data, request.id, namespace, slice);
19037
+ await ingestDocument(sql.data, slice);
19025
19038
  processed += slice.length;
19026
- const progressEvent = {
19039
+ const currentDocument = {
19040
+ source: request.id,
19041
+ ordinal: processed - 1
19042
+ };
19043
+ await onProgress?.({
19027
19044
  type: "progress",
19028
19045
  jobId: jobId ?? request.id,
19029
19046
  processedChunks: processed,
19030
19047
  totalChunks,
19031
- currentDocument: {
19032
- source: request.id,
19033
- ordinal: processed - 1
19034
- }
19035
- };
19036
- await emit(progressEvent);
19048
+ currentDocument
19049
+ });
19037
19050
  if (writer && jobId) {
19038
19051
  await writer.progress({
19039
19052
  jobId,
19040
19053
  processedChunks: processed,
19041
19054
  totalChunks,
19042
- currentDocument: {
19043
- source: request.id,
19044
- ordinal: processed - 1
19045
- }
19055
+ currentDocument
19046
19056
  });
19047
19057
  }
19048
19058
  }
19049
- const completedEvent = {
19059
+ await onProgress?.({
19050
19060
  type: "completed",
19051
19061
  jobId: jobId ?? request.id,
19052
19062
  totalChunks
19053
- };
19054
- await emit(completedEvent);
19063
+ });
19055
19064
  if (writer && jobId) await writer.complete({
19056
19065
  jobId,
19057
19066
  totalChunks
@@ -19062,12 +19071,11 @@ var RagPipeline = class {
19062
19071
  });
19063
19072
  } catch (cause) {
19064
19073
  const mapped = toRagError(cause, "Ingest failed");
19065
- const failedEvent = {
19074
+ await onProgress?.({
19066
19075
  type: "failed",
19067
19076
  jobId: jobId ?? request.id,
19068
19077
  error: mapped.message
19069
- };
19070
- await emit(failedEvent);
19078
+ });
19071
19079
  if (writer && jobId) await writer.fail({
19072
19080
  jobId,
19073
19081
  error: mapped.message
@@ -19227,37 +19235,6 @@ function validateIngest(request, hasEmbedder) {
19227
19235
  return ok(true);
19228
19236
  }
19229
19237
  __name(validateIngest, "validateIngest");
19230
- function isTaggedError(cause) {
19231
- return cause instanceof Error;
19232
- }
19233
- __name(isTaggedError, "isTaggedError");
19234
- function toRagError(cause, message) {
19235
- if (isTaggedError(cause) && cause.code === PG_UNDEFINED_TABLE) {
19236
- return {
19237
- code: "rag_schema_missing",
19238
- message: `RAG tables are not installed \u2014 ${RAG_SCHEMA_MISSING_HINT}.`,
19239
- cause,
19240
- meta: {
19241
- hint: RAG_SCHEMA_MISSING_HINT
19242
- }
19243
- };
19244
- }
19245
- if (isTaggedError(cause)) {
19246
- const error48 = {
19247
- code: cause.code ?? "rag_error",
19248
- message: `${message}: ${cause.message}`,
19249
- cause
19250
- };
19251
- if (cause.meta) error48.meta = cause.meta;
19252
- return error48;
19253
- }
19254
- return {
19255
- code: "rag_error",
19256
- message,
19257
- cause
19258
- };
19259
- }
19260
- __name(toRagError, "toRagError");
19261
19238
 
19262
19239
  // src/modules/rag/rag.ts
19263
19240
  var DEFAULT_BATCH_SIZE4 = 128;
@@ -19269,10 +19246,14 @@ var RagModule = class {
19269
19246
  _resolved;
19270
19247
  _getAi;
19271
19248
  _gate;
19272
- constructor(_resolved, _getAi, gate) {
19249
+ _testSqlOverride;
19250
+ _testJobWriterFactory;
19251
+ constructor(_resolved, _getAi, gate, testDeps) {
19273
19252
  this._resolved = _resolved;
19274
19253
  this._getAi = _getAi;
19275
19254
  this._gate = gate ?? createModuleGate("rag", _resolved);
19255
+ if (testDeps?.sqlProvider) this._testSqlOverride = testDeps.sqlProvider;
19256
+ if (testDeps?.jobWriterFactory) this._testJobWriterFactory = testDeps.jobWriterFactory;
19276
19257
  }
19277
19258
  async ingest(request) {
19278
19259
  const gateResult = await this._gate();
@@ -19305,7 +19286,8 @@ var RagModule = class {
19305
19286
  if (gateResult.error) return err(gateResult.error);
19306
19287
  const sql = this._testSqlOverride ? this._testSqlOverride() : this.sql();
19307
19288
  if (sql.error) return err(sql.error);
19308
- const writer = this._testJobWriter ?? createSqlJobWriter(sql.data);
19289
+ const writerFactory = this._testJobWriterFactory ?? createSqlJobWriter;
19290
+ const writer = writerFactory(sql.data);
19309
19291
  const channel = createProgressChannel();
19310
19292
  const resolved = resolveIngestModel(request, this._resolved);
19311
19293
  const requestWithHook = {
@@ -19336,7 +19318,7 @@ var RagModule = class {
19336
19318
  return r;
19337
19319
  }).catch((cause) => {
19338
19320
  channel.close();
19339
- return err(toRagError2(cause, "Ingest failed"));
19321
+ return err(toRagError(cause, "Ingest failed"));
19340
19322
  });
19341
19323
  return ok({
19342
19324
  jobId,
@@ -19344,10 +19326,6 @@ var RagModule = class {
19344
19326
  result
19345
19327
  });
19346
19328
  }
19347
- /** Test-only seam — assigned by `ingest-async.spec.ts`. */
19348
- _testJobWriter;
19349
- /** Test-only seam — assigned by `ingest-async.spec.ts`. */
19350
- _testSqlOverride;
19351
19329
  async delete(ids, options) {
19352
19330
  const gateResult = await this._gate();
19353
19331
  if (gateResult.error) return err(gateResult.error);
@@ -19379,7 +19357,7 @@ var RagModule = class {
19379
19357
  await reset(sql.data);
19380
19358
  return ok(void 0);
19381
19359
  } catch (cause) {
19382
- return err(toRagError2(cause, "Reset failed"));
19360
+ return err(toRagError(cause, "Reset failed"));
19383
19361
  }
19384
19362
  }
19385
19363
  /**
@@ -19407,22 +19385,18 @@ var RagModule = class {
19407
19385
  * `Result.err` with the same `database_not_configured` shape `client.database`
19408
19386
  * raises, instead of throwing through the public surface.
19409
19387
  */
19410
- // `_resolved` is reserved for future per-config overrides (e.g. routing
19411
- // RAG queries through a logical replica). The URL itself is owned by the
19412
- // shared handle.
19413
19388
  sql() {
19414
- void this._resolved;
19415
19389
  try {
19416
19390
  return ok(getDatabaseHandle().$client);
19417
19391
  } catch (cause) {
19418
- if (isTaggedError2(cause) && cause.code === "database_not_configured") {
19392
+ if (isTaggedError(cause) && cause.code === "database_not_configured") {
19419
19393
  return err({
19420
19394
  code: "database_not_configured",
19421
19395
  message: cause.message,
19422
19396
  cause
19423
19397
  });
19424
19398
  }
19425
- return err(toRagError2(cause, "Database handle unavailable"));
19399
+ return err(toRagError(cause, "Database handle unavailable"));
19426
19400
  }
19427
19401
  }
19428
19402
  async withPipeline(embedder, run) {
@@ -19543,27 +19517,6 @@ function resolveRetrieveModel(request, resolved) {
19543
19517
  };
19544
19518
  }
19545
19519
  __name(resolveRetrieveModel, "resolveRetrieveModel");
19546
- function isTaggedError2(cause) {
19547
- return cause instanceof Error;
19548
- }
19549
- __name(isTaggedError2, "isTaggedError");
19550
- function toRagError2(cause, message) {
19551
- if (isTaggedError2(cause)) {
19552
- const error48 = {
19553
- code: cause.code ?? "rag_error",
19554
- message: `${message}: ${cause.message}`,
19555
- cause
19556
- };
19557
- if (cause.meta) error48.meta = cause.meta;
19558
- return error48;
19559
- }
19560
- return {
19561
- code: "rag_error",
19562
- message,
19563
- cause
19564
- };
19565
- }
19566
- __name(toRagError2, "toRagError");
19567
19520
  var DEFAULT_SIGNED_URL_TTL_SECONDS = 3600;
19568
19521
  var StorageModule = class {
19569
19522
  static {
@@ -19580,11 +19533,12 @@ var StorageModule = class {
19580
19533
  return new StorageBucket(bucket, () => this.settings(), this._gate);
19581
19534
  }
19582
19535
  settings() {
19583
- const accessKeyId = this._resolved.config.s3?.accessKeyId ?? this._resolved.env["AWS_ACCESS_KEY_ID"];
19584
- const secretAccessKey = this._resolved.config.s3?.secretAccessKey ?? this._resolved.env["AWS_SECRET_ACCESS_KEY"];
19585
- const endpoint = this._resolved.config.s3?.endpoint ?? this._resolved.env["S3_ENDPOINT"];
19586
- const bucket = this._resolved.config.s3?.bucket ?? this._resolved.env["S3_BUCKET"];
19536
+ const accessKeyId = this._resolved.config.s3?.accessKeyId ?? this._resolved.env["STACKBONE_S3_ACCESS_KEY"];
19537
+ const secretAccessKey = this._resolved.config.s3?.secretAccessKey ?? this._resolved.env["STACKBONE_S3_SECRET_KEY"];
19538
+ const endpoint = this._resolved.config.s3?.endpoint ?? this._resolved.env["STACKBONE_S3_ENDPOINT"];
19539
+ const bucket = this._resolved.config.s3?.bucket ?? this._resolved.env["STACKBONE_S3_BUCKET"];
19587
19540
  const agentId = this._resolved.config.agentId ?? this._resolved.env["STACKBONE_AGENT_ID"];
19541
+ const region = this._resolved.config.s3?.region ?? this._resolved.env["STACKBONE_S3_REGION"] ?? "auto";
19588
19542
  if (!accessKeyId || !secretAccessKey || !endpoint) {
19589
19543
  return err({
19590
19544
  code: "s3_credentials_missing",
@@ -19605,7 +19559,7 @@ var StorageModule = class {
19605
19559
  }
19606
19560
  this._s3 ??= new clientS3.S3Client({
19607
19561
  endpoint,
19608
- region: "auto",
19562
+ region,
19609
19563
  forcePathStyle: true,
19610
19564
  credentials: {
19611
19565
  accessKeyId,
@@ -19764,29 +19718,19 @@ var StorageBucket = class {
19764
19718
  return ok(`${base}/${bucket}/${encodeS3Key(fullKey)}`);
19765
19719
  }
19766
19720
  async getSignedUploadUrl(key, options) {
19767
- const gateResult = await this.gate();
19768
- if (gateResult.error) return err(gateResult.error);
19769
- const resolved = this.resolve(key);
19770
- if (resolved.error) return err(resolved.error);
19771
- const { client, bucket, fullKey } = resolved.data;
19772
- const expiresIn = options?.expiresIn ?? DEFAULT_SIGNED_URL_TTL_SECONDS;
19773
- try {
19774
- const url2 = await s3RequestPresigner.getSignedUrl(client, new clientS3.PutObjectCommand({
19775
- Bucket: bucket,
19776
- Key: fullKey,
19777
- ContentType: options?.contentType
19778
- }), {
19779
- expiresIn
19780
- });
19781
- return ok({
19782
- url: url2,
19783
- expiresAt: new Date(Date.now() + expiresIn * 1e3)
19784
- });
19785
- } catch (cause) {
19786
- return err(toStorageError("Signing upload URL failed", cause));
19787
- }
19721
+ return this.signUrl(key, options, "Signing upload URL failed", ({ bucket, fullKey }) => new clientS3.PutObjectCommand({
19722
+ Bucket: bucket,
19723
+ Key: fullKey,
19724
+ ContentType: options?.contentType
19725
+ }));
19788
19726
  }
19789
19727
  async getSignedDownloadUrl(key, options) {
19728
+ return this.signUrl(key, options, "Signing download URL failed", ({ bucket, fullKey }) => new clientS3.GetObjectCommand({
19729
+ Bucket: bucket,
19730
+ Key: fullKey
19731
+ }));
19732
+ }
19733
+ async signUrl(key, options, failureMessage, buildCommand) {
19790
19734
  const gateResult = await this.gate();
19791
19735
  if (gateResult.error) return err(gateResult.error);
19792
19736
  const resolved = this.resolve(key);
@@ -19794,9 +19738,9 @@ var StorageBucket = class {
19794
19738
  const { client, bucket, fullKey } = resolved.data;
19795
19739
  const expiresIn = options?.expiresIn ?? DEFAULT_SIGNED_URL_TTL_SECONDS;
19796
19740
  try {
19797
- const url2 = await s3RequestPresigner.getSignedUrl(client, new clientS3.GetObjectCommand({
19798
- Bucket: bucket,
19799
- Key: fullKey
19741
+ const url2 = await s3RequestPresigner.getSignedUrl(client, buildCommand({
19742
+ bucket,
19743
+ fullKey
19800
19744
  }), {
19801
19745
  expiresIn
19802
19746
  });
@@ -19805,7 +19749,7 @@ var StorageBucket = class {
19805
19749
  expiresAt: new Date(Date.now() + expiresIn * 1e3)
19806
19750
  });
19807
19751
  } catch (cause) {
19808
- return err(toStorageError("Signing download URL failed", cause));
19752
+ return err(toStorageError(failureMessage, cause));
19809
19753
  }
19810
19754
  }
19811
19755
  /**
@@ -19831,8 +19775,8 @@ var StorageBucket = class {
19831
19775
  });
19832
19776
  }
19833
19777
  };
19834
- 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({...})`.";
19835
- var S3_BUCKET_MISSING_MESSAGE = "No storage bucket configured \u2014 set `S3_BUCKET` in the container or pass `s3.bucket` to `createClient({...})`.";
19778
+ 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({...})`.";
19779
+ var S3_BUCKET_MISSING_MESSAGE = "No storage bucket configured \u2014 set `STACKBONE_S3_BUCKET` in the container or pass `s3.bucket` to `createClient({...})`.";
19836
19780
  var AGENT_ID_MISSING_MESSAGE = "No agent identity configured \u2014 set `STACKBONE_AGENT_ID` in the container or pass `agentId` to `createClient({...})`.";
19837
19781
  function stripEtag(etag) {
19838
19782
  return etag?.replace(/^"|"$/g, "");