@anuma/sdk 6.10.0 → 6.12.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.
@@ -4698,6 +4698,51 @@ interface EntityOperationsContext {
4698
4698
  allowUnscopedRows?: boolean;
4699
4699
  }
4700
4700
 
4701
+ /**
4702
+ * Class-B task types — the vocabulary of the `X-Anuma-Task-Type` header.
4703
+ *
4704
+ * A Class-B flow has ONE fixed purpose and no conversation: extract memories
4705
+ * from a snippet, pick a topic, consolidate duplicates, classify a query. Today
4706
+ * each of those builds its own system prompt on the client and sends it as a
4707
+ * `role:system` message, which makes the prompt a suggestion rather than a
4708
+ * contract — a caller can reword it into "you are a general assistant" and get
4709
+ * general chat at internal-endpoint prices.
4710
+ *
4711
+ * The fix is that the SERVER owns the text and the client only names the task.
4712
+ * This header is the name. The portal allowlists it against its own copy of this
4713
+ * enum (ai-portal `internal/systemprompt/tasks.go`) and DROPS anything it does
4714
+ * not recognise, so an unknown value degrades to "no task declared" rather than
4715
+ * failing the request.
4716
+ *
4717
+ * ⚠ These string values are a wire contract with ai-portal. Adding one here
4718
+ * before the portal knows it is harmless (it is dropped); renaming or removing
4719
+ * one silently stops that flow being counted. Keep them in step with
4720
+ * `AllTaskTypes` on the portal side, and see ai-portal
4721
+ * `docs/BACKEND_OWNED_TASK_PROMPTS.md` for the rollout.
4722
+ *
4723
+ * ⚠ RELEASE ORDER. This union is a PUBLIC type, so a split is breaking in both
4724
+ * directions and has to be sequenced:
4725
+ *
4726
+ * 1. merge + publish this package;
4727
+ * 2. bump `@anuma/sdk` in the consumer and switch its call sites to the new
4728
+ * names in the same commit.
4729
+ *
4730
+ * A consumer on the OLD version cannot compile the new names, and a consumer on
4731
+ * the NEW version cannot compile the retired ones (`app_inspiration` is gone
4732
+ * here, replaced by the three `app_inspiration_*` names). Both halves of that
4733
+ * are type-only — the wire itself always degrades safely, because the portal
4734
+ * drops any value it does not recognise. Concretely: ai-memoryless-client#5782
4735
+ * is blocked on this publish for exactly that reason.
4736
+ *
4737
+ * Deliberately ABSENT: agent personas, `reflect()` when it answers the user, and
4738
+ * the app-idea chat. Those carry an open conversation with the user, so a fixed
4739
+ * server-owned prompt would be wrong rather than merely premature — the same
4740
+ * boundary {@link INTERNAL_FLOW_MARKER} draws.
4741
+ *
4742
+ * @public
4743
+ */
4744
+ type TaskType = "title" | "commit_message" | "memory_extract" | "memory_dedup" | "memory_decay" | "memory_injection_check" | "memory_verify_support" | "memory_autosort" | "memory_topic" | "memory_consolidate" | "memory_decompose" | "memory_profile_synth" | "memory_graph" | "folder_context" | "selection_memory" | "summarize" | "style_analysis" | "classify_search" | "app_inspiration_generate" | "app_inspiration_trends" | "app_inspiration_remix" | "spotlight_image" | "slide_image" | "slide_video" | "slide_music" | "slide_sfx" | "media_video" | "media_audio" | "media_bg_removal" | "media_image_preset" | "tool_app_builder" | "tool_document_builder" | "tool_slides" | "tool_slides_image_gen" | "connector_guidance" | "cf_task" | "xbot_intent" | "xbot_council" | "scheduled_task";
4745
+
4701
4746
  /**
4702
4747
  * Shared portal LLM call for the memory pipeline.
4703
4748
  *
@@ -5641,6 +5686,17 @@ interface RecallDiagnostics {
5641
5686
  * never the cost.
5642
5687
  */
5643
5688
  vaultRowsDecrypted?: number;
5689
+ /**
5690
+ * Rows the fact lane had to re-embed through the portal because their stored
5691
+ * vector was unusable — stale `embedding_model`, wrong dimension, or
5692
+ * unparseable. Absent when the fact lane didn't run.
5693
+ *
5694
+ * Expected to be 0 on a healthy vault: rows are embedded at write time and the
5695
+ * re-embed writes the current model back, so a persistently non-zero value
5696
+ * means the writeback is not sticking and every turn is paying for it. On the
5697
+ * LEGACY read path this batch is uncapped, so it can be the whole vault.
5698
+ */
5699
+ vaultRowsEmbedded?: number;
5644
5700
  /** Facts the fact lane returned (post-dedupe, pre-fusion). */
5645
5701
  factCount: number;
5646
5702
  /** Chunks the chunk lane returned (post-dedupe, pre-fusion). */
@@ -5653,6 +5709,37 @@ interface RecallDiagnostics {
5653
5709
  prep: number;
5654
5710
  /** Vault fact-lane search (`searchVaultMemoriesWithSize`). */
5655
5711
  factLane: number;
5712
+ /**
5713
+ * The cross-encoder's share of {@link factLane} — wall-clock spent inside
5714
+ * `rerankPairs`. Billed even when the CE threw partway through, because a
5715
+ * rerank that burned three seconds and then failed still cost them.
5716
+ *
5717
+ * A SUBSET of `factLane`, not a sibling: read `factLane - rerank` for
5718
+ * everything else the lane did (query embed, vault read, fused ranking).
5719
+ * 0 when the CE did not run; `reranked` is what distinguishes that from a
5720
+ * rerank that cost nothing.
5721
+ *
5722
+ * Exists because #845 spent three rounds arguing about which stage inside
5723
+ * the fact lane dominated — whole-vault read, then admission window, then
5724
+ * the CE — with one aggregate number for all of them. Every hypothesis was
5725
+ * an inference; this makes the question a query.
5726
+ */
5727
+ rerank: number;
5728
+ /**
5729
+ * The query embed's share of {@link factLane} — one portal round trip.
5730
+ *
5731
+ * Also a SUBSET of `factLane`. Together with `rerank` this makes the lane
5732
+ * decomposable: `factLane - queryEmbed - rerank` is the local work (vault
5733
+ * read, decrypt, BM25/cosine fusion), and each of the three implies a
5734
+ * completely different fix.
5735
+ *
5736
+ * 0 when the lane returned before embedding, which includes every empty
5737
+ * vault — `prepareVaultCandidates` short-circuits first. That is why the
5738
+ * fast `vault_size = 0` population never established a baseline for this
5739
+ * cost, and why the ~850ms floor on the smallest NON-empty vaults had no
5740
+ * attributable owner.
5741
+ */
5742
+ queryEmbed: number;
5656
5743
  /** Chunk-lane search (`searchChunksOp`). */
5657
5744
  chunkLane: number;
5658
5745
  /** Cross-lane RRF fusion + provenance dedup after both lanes. */
@@ -8757,6 +8844,28 @@ interface ReflectOptions extends RecallOptions, PortalLlmAuth {
8757
8844
  maxTokens?: number;
8758
8845
  /** Override the grounding system prompt. */
8759
8846
  systemPrompt?: string;
8847
+ /**
8848
+ * Extra caller instruction to carry on the USER turn, between the question and
8849
+ * the evidence block (see the `userMessage` assembly below). This is the slot a
8850
+ * background caller uses to keep its per-request data OUT of the system message
8851
+ * without colliding with the numbered evidence list — profile-facet synthesis
8852
+ * puts its section label, guidance and response-field hint here so its system
8853
+ * half can stay fixed and server-ownable.
8854
+ */
8855
+ userInstructions?: string;
8856
+ /**
8857
+ * Class-B task name for the `X-Anuma-Task-Type` header, or nothing.
8858
+ *
8859
+ * Deliberately OPTIONAL and unset by default. reflect() also answers the user's
8860
+ * OWN question, and that traffic is chat, not an internal flow — declaring a
8861
+ * task type unconditionally here would put an internal-flow name on real
8862
+ * conversation, which is the same boundary `INTERNAL_FLOW_MARKER` draws
8863
+ * (reflect is deliberately unmarked; its background caller marks its own
8864
+ * prompt — see ../internalFlowMarker.ts). So the name is per call: only a
8865
+ * caller with ONE fixed purpose passes
8866
+ * one, and today that is profile-facet synthesis (`memory_profile_synth`).
8867
+ */
8868
+ taskType?: TaskType;
8760
8869
  /** Endpoint for the answer LLM. */
8761
8870
  baseUrl?: string;
8762
8871
  /** Override fetch (for tests). */
@@ -141,7 +141,7 @@ import {
141
141
  updateVaultMemoryOp,
142
142
  upsertMessageOp,
143
143
  vaultMemoryToStored
144
- } from "./chunk-I56324FR.mjs";
144
+ } from "./chunk-7YTEE6JT.mjs";
145
145
  import {
146
146
  createStreamingDeAnonymizer,
147
147
  notifyConsolidationFallback,
@@ -6963,11 +6963,13 @@ Respond with ONLY a single JSON object conforming to this JSON Schema, with no p
6963
6963
  ${JSON.stringify(
6964
6964
  options.responseSchema
6965
6965
  )}` : basePrompt;
6966
- const userMessage = `Question:
6967
- ${trimmed}
6968
-
6969
- Memories (use only these as evidence):
6970
- ${evidence}`;
6966
+ const userMessage = [
6967
+ `Question:
6968
+ ${trimmed}`,
6969
+ ...options.userInstructions ? [options.userInstructions] : [],
6970
+ `Memories (use only these as evidence):
6971
+ ${evidence}`
6972
+ ].join("\n\n");
6971
6973
  const maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
6972
6974
  const fetchImpl = options.fetchFn ?? fetch;
6973
6975
  const log = getLogger();
@@ -6981,7 +6983,13 @@ ${evidence}`;
6981
6983
  try {
6982
6984
  response = await fetchImpl(`${baseUrl}/api/v1/chat/completions`, {
6983
6985
  method: "POST",
6984
- headers: { ...authHeaders, "Content-Type": "application/json" },
6986
+ // No task type unless the caller named one — an undeclared task adds no
6987
+ // header at all, which is what keeps user-facing reflect() unlabelled.
6988
+ headers: {
6989
+ ...authHeaders,
6990
+ ...taskTypeHeader(options.taskType),
6991
+ "Content-Type": "application/json"
6992
+ },
6985
6993
  body: JSON.stringify({
6986
6994
  model,
6987
6995
  // Modern OpenAI field; the portal reads only `max_completion_tokens`
@@ -7421,10 +7429,15 @@ async function synthesizeFacet(facet, ctx, options, prior) {
7421
7429
  limit,
7422
7430
  types: ["fact"],
7423
7431
  maxTokens: DEFAULT_FACET_MAX_TOKENS,
7424
- // Profile-facet synthesis is a background op, so it marks its own prompt.
7425
- // reflect() itself must NOT mark — it also serves the user's own questions,
7426
- // and that traffic is chat, not an internal flow. See ../internalFlowMarker.
7427
- systemPrompt: withInternalFlowMarker(buildFacetSystemPrompt(facet)),
7432
+ // Profile-facet synthesis is a background op, so it marks its own prompt AND
7433
+ // names its own task. reflect() itself must do neither — it also serves the
7434
+ // user's own questions, and that traffic is chat, not an internal flow. See
7435
+ // ../internalFlowMarker and ReflectOptions.taskType.
7436
+ taskType: "memory_profile_synth",
7437
+ systemPrompt: withInternalFlowMarker(FACET_SYSTEM_PROMPT),
7438
+ // The facet's label/guidance/response-fields, on the user turn — what keeps
7439
+ // the system half fixed and therefore server-ownable.
7440
+ userInstructions: buildFacetUserInstructions(facet),
7428
7441
  responseSchema: facetResponseSchema(facet.key),
7429
7442
  memories
7430
7443
  });
@@ -7482,19 +7495,22 @@ function fallbackSection(facet, prior) {
7482
7495
  stale: true
7483
7496
  };
7484
7497
  }
7485
- function buildFacetSystemPrompt(facet) {
7486
- const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
7487
- return `You are writing the "${facet.label}" section of a person's shareable profile, using their private memories (supplied as evidence) as the only source of truth.
7488
-
7489
- Task for this section:
7490
- ${facet.guidance}
7498
+ var FACET_SYSTEM_PROMPT = `You are writing one section of a person's shareable profile, using their private memories (supplied as evidence) as the only source of truth. The user turn names the section, states the task for it, and gives the exact JSON shape to respond in.
7491
7499
 
7492
7500
  Rules:
7493
7501
  - Ground every claim in the supplied memories \u2014 never invent, infer beyond, or embellish what they support.
7494
7502
  - If the memories don't cover this section, return an empty summary with hasEvidence=false. Do not pad or guess.
7495
7503
  - Write in third person about the person, in a natural voice suitable for a public profile (no "I"/"you", no name repetition).
7496
7504
  - Be concise and specific; no preamble, hedging, or meta-commentary.
7497
- - Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
7505
+ - Respond as JSON in exactly the shape the user turn states, with no extra fields.`;
7506
+ function buildFacetUserInstructions(facet) {
7507
+ const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
7508
+ return `Section: "${facet.label}"
7509
+
7510
+ Task for this section:
7511
+ ${facet.guidance}
7512
+
7513
+ Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
7498
7514
  }
7499
7515
  function extractFacetText(structured) {
7500
7516
  if (structured && typeof structured === "object") {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  recall
3
- } from "./chunk-I56324FR.mjs";
3
+ } from "./chunk-7YTEE6JT.mjs";
4
4
  import "./chunk-KF5OU4IN.mjs";
5
5
  import "./chunk-3RPGXXPZ.mjs";
6
6
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anuma/sdk",
3
- "version": "6.10.0",
3
+ "version": "6.12.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",