@anuma/sdk 6.11.0 → 6.12.1

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.
@@ -5155,6 +5155,9 @@ async function decomposeQuery(query, options) {
5155
5155
  ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
5156
5156
  model: options.model ?? DEFAULT_MODEL,
5157
5157
  systemPrompt: SYSTEM_PROMPT,
5158
+ // The query itself rides the user turn below, so SYSTEM_PROMPT is the whole
5159
+ // fixed instruction — which is what lets the portal own it outright.
5160
+ taskType: "memory_decompose",
5158
5161
  // Wrap the bare query in a task framing so the model treats it as
5159
5162
  // input to classify, not as a question to answer conversationally.
5160
5163
  // Bare-query inputs caused Anthropic models to respond with prose
@@ -6882,7 +6885,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6882
6885
  subQueries = decomp.subQueries;
6883
6886
  }
6884
6887
  }
6885
- const { recall: recall2 } = await import("./recall-OALPGL3H.mjs");
6888
+ const { recall: recall2 } = await import("./recall-PIATJ336.mjs");
6886
6889
  let recallDegraded = [];
6887
6890
  const result = await recall2(
6888
6891
  query,
@@ -7135,29 +7138,29 @@ async function traverseGraphLane(query, entityCtx, options = {}) {
7135
7138
  var DEFAULT_REFINER_MODEL = "inclusionai/ling-2.6-flash";
7136
7139
  var DEFAULT_REFINER_ATTEMPTS = 1;
7137
7140
  var DEFAULT_REFINER_TOTAL_TIMEOUT_MS = 8e3;
7141
+ var REFINER_SYSTEM_PROMPT = `You help a memory-retrieval system decide which related topics to explore.
7142
+
7143
+ Given a user's question and a numbered list of candidate topics/entities linked to memories found so far, pick the ones most likely to lead to memories that help ANSWER the question. Never return more than the maximum the user turn asks for. Prefer topics semantically related to the question; ignore incidental ones.
7144
+
7145
+ Output strict JSON, no prose: { "expand": [<entity names to expand, verbatim from the list>] }`;
7138
7146
  function createLlmNeighborRefiner(options) {
7139
7147
  return {
7140
7148
  async refine(query, candidates, limit) {
7141
7149
  if (candidates.length === 0) return [];
7142
7150
  const numbered = candidates.map((name, i) => `[${i + 1}] ${name}`).join("\n");
7143
- const systemPrompt = `You help a memory-retrieval system decide which related topics to explore.
7144
-
7145
- Given a user's question and a numbered list of candidate topics/entities linked to memories found so far, pick the ones most likely to lead to memories that help ANSWER the question. Choose at most ${limit}. Prefer topics semantically related to the question; ignore incidental ones.
7146
-
7147
- Output strict JSON, no prose: { "expand": [<entity names to expand, verbatim from the list>] }`;
7148
7151
  const parsed = await callPortalJsonCompletion({
7149
7152
  ...options.apiKey !== void 0 && { apiKey: options.apiKey },
7150
7153
  ...options.getToken !== void 0 && { getToken: options.getToken },
7151
7154
  ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
7152
7155
  taskType: "memory_graph",
7153
7156
  model: options.model ?? DEFAULT_REFINER_MODEL,
7154
- systemPrompt,
7157
+ systemPrompt: REFINER_SYSTEM_PROMPT,
7155
7158
  userMessage: `Question: ${query}
7156
7159
 
7157
7160
  Candidate topics:
7158
7161
  ${numbered}
7159
7162
 
7160
- Which should be expanded?`,
7163
+ Which should be expanded? Choose at most ${limit}.`,
7161
7164
  tag: "memory/graph-refine",
7162
7165
  maxAttempts: options.maxAttempts ?? DEFAULT_REFINER_ATTEMPTS,
7163
7166
  totalTimeoutMs: options.totalTimeoutMs ?? DEFAULT_REFINER_TOTAL_TIMEOUT_MS,
@@ -6864,6 +6864,9 @@ async function decomposeQuery(query, options) {
6864
6864
  ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
6865
6865
  model: options.model ?? DEFAULT_MODEL2,
6866
6866
  systemPrompt: SYSTEM_PROMPT2,
6867
+ // The query itself rides the user turn below, so SYSTEM_PROMPT is the whole
6868
+ // fixed instruction — which is what lets the portal own it outright.
6869
+ taskType: "memory_decompose",
6867
6870
  // Wrap the bare query in a task framing so the model treats it as
6868
6871
  // input to classify, not as a question to answer conversationally.
6869
6872
  // Bare-query inputs caused Anthropic models to respond with prose
@@ -7207,24 +7210,19 @@ function createLlmNeighborRefiner(options) {
7207
7210
  async refine(query, candidates, limit) {
7208
7211
  if (candidates.length === 0) return [];
7209
7212
  const numbered = candidates.map((name, i) => `[${i + 1}] ${name}`).join("\n");
7210
- const systemPrompt = `You help a memory-retrieval system decide which related topics to explore.
7211
-
7212
- Given a user's question and a numbered list of candidate topics/entities linked to memories found so far, pick the ones most likely to lead to memories that help ANSWER the question. Choose at most ${limit}. Prefer topics semantically related to the question; ignore incidental ones.
7213
-
7214
- Output strict JSON, no prose: { "expand": [<entity names to expand, verbatim from the list>] }`;
7215
7213
  const parsed = await callPortalJsonCompletion({
7216
7214
  ...options.apiKey !== void 0 && { apiKey: options.apiKey },
7217
7215
  ...options.getToken !== void 0 && { getToken: options.getToken },
7218
7216
  ...options.baseUrl !== void 0 && { baseUrl: options.baseUrl },
7219
7217
  taskType: "memory_graph",
7220
7218
  model: options.model ?? DEFAULT_REFINER_MODEL,
7221
- systemPrompt,
7219
+ systemPrompt: REFINER_SYSTEM_PROMPT,
7222
7220
  userMessage: `Question: ${query}
7223
7221
 
7224
7222
  Candidate topics:
7225
7223
  ${numbered}
7226
7224
 
7227
- Which should be expanded?`,
7225
+ Which should be expanded? Choose at most ${limit}.`,
7228
7226
  tag: "memory/graph-refine",
7229
7227
  maxAttempts: options.maxAttempts ?? DEFAULT_REFINER_ATTEMPTS,
7230
7228
  totalTimeoutMs: options.totalTimeoutMs ?? DEFAULT_REFINER_TOTAL_TIMEOUT_MS,
@@ -7245,7 +7243,7 @@ Which should be expanded?`,
7245
7243
  }
7246
7244
  };
7247
7245
  }
7248
- var MAX_HOPS, ENTITY_FANOUT, MIN_REFINER_CANDIDATES, MAX_REFINER_CANDIDATES, NODE_BUDGET, VAULT_SIZE_HOP_CAP, DEFAULT_REFINER_MODEL, DEFAULT_REFINER_ATTEMPTS, DEFAULT_REFINER_TOTAL_TIMEOUT_MS;
7246
+ var MAX_HOPS, ENTITY_FANOUT, MIN_REFINER_CANDIDATES, MAX_REFINER_CANDIDATES, NODE_BUDGET, VAULT_SIZE_HOP_CAP, DEFAULT_REFINER_MODEL, DEFAULT_REFINER_ATTEMPTS, DEFAULT_REFINER_TOTAL_TIMEOUT_MS, REFINER_SYSTEM_PROMPT;
7249
7247
  var init_graphTraversal = __esm({
7250
7248
  "src/lib/memory/graphTraversal.ts"() {
7251
7249
  "use strict";
@@ -7264,6 +7262,11 @@ var init_graphTraversal = __esm({
7264
7262
  DEFAULT_REFINER_MODEL = "inclusionai/ling-2.6-flash";
7265
7263
  DEFAULT_REFINER_ATTEMPTS = 1;
7266
7264
  DEFAULT_REFINER_TOTAL_TIMEOUT_MS = 8e3;
7265
+ REFINER_SYSTEM_PROMPT = `You help a memory-retrieval system decide which related topics to explore.
7266
+
7267
+ Given a user's question and a numbered list of candidate topics/entities linked to memories found so far, pick the ones most likely to lead to memories that help ANSWER the question. Never return more than the maximum the user turn asks for. Prefer topics semantically related to the question; ignore incidental ones.
7268
+
7269
+ Output strict JSON, no prose: { "expand": [<entity names to expand, verbatim from the list>] }`;
7267
7270
  }
7268
7271
  });
7269
7272
 
@@ -17740,6 +17743,7 @@ ${TRUNCATION_NOTICE}` : formatted;
17740
17743
  // src/lib/memory/reflect.ts
17741
17744
  init_clientConfig();
17742
17745
  init_logger();
17746
+ init_taskType();
17743
17747
  init_portalLlm();
17744
17748
  init_recall();
17745
17749
  var DEFAULT_BASE_URL2 = BASE_URL;
@@ -17785,11 +17789,13 @@ Respond with ONLY a single JSON object conforming to this JSON Schema, with no p
17785
17789
  ${JSON.stringify(
17786
17790
  options.responseSchema
17787
17791
  )}` : basePrompt;
17788
- const userMessage = `Question:
17789
- ${trimmed}
17790
-
17791
- Memories (use only these as evidence):
17792
- ${evidence}`;
17792
+ const userMessage = [
17793
+ `Question:
17794
+ ${trimmed}`,
17795
+ ...options.userInstructions ? [options.userInstructions] : [],
17796
+ `Memories (use only these as evidence):
17797
+ ${evidence}`
17798
+ ].join("\n\n");
17793
17799
  const maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
17794
17800
  const fetchImpl = options.fetchFn ?? fetch;
17795
17801
  const log = getLogger();
@@ -17803,7 +17809,13 @@ ${evidence}`;
17803
17809
  try {
17804
17810
  response = await fetchImpl(`${baseUrl}/api/v1/chat/completions`, {
17805
17811
  method: "POST",
17806
- headers: { ...authHeaders, "Content-Type": "application/json" },
17812
+ // No task type unless the caller named one — an undeclared task adds no
17813
+ // header at all, which is what keeps user-facing reflect() unlabelled.
17814
+ headers: {
17815
+ ...authHeaders,
17816
+ ...taskTypeHeader(options.taskType),
17817
+ "Content-Type": "application/json"
17818
+ },
17807
17819
  body: JSON.stringify({
17808
17820
  model,
17809
17821
  // Modern OpenAI field; the portal reads only `max_completion_tokens`
@@ -18255,10 +18267,15 @@ async function synthesizeFacet(facet, ctx, options, prior) {
18255
18267
  limit,
18256
18268
  types: ["fact"],
18257
18269
  maxTokens: DEFAULT_FACET_MAX_TOKENS,
18258
- // Profile-facet synthesis is a background op, so it marks its own prompt.
18259
- // reflect() itself must NOT mark — it also serves the user's own questions,
18260
- // and that traffic is chat, not an internal flow. See ../internalFlowMarker.
18261
- systemPrompt: withInternalFlowMarker(buildFacetSystemPrompt(facet)),
18270
+ // Profile-facet synthesis is a background op, so it marks its own prompt AND
18271
+ // names its own task. reflect() itself must do neither — it also serves the
18272
+ // user's own questions, and that traffic is chat, not an internal flow. See
18273
+ // ../internalFlowMarker and ReflectOptions.taskType.
18274
+ taskType: "memory_profile_synth",
18275
+ systemPrompt: withInternalFlowMarker(FACET_SYSTEM_PROMPT),
18276
+ // The facet's label/guidance/response-fields, on the user turn — what keeps
18277
+ // the system half fixed and therefore server-ownable.
18278
+ userInstructions: buildFacetUserInstructions(facet),
18262
18279
  responseSchema: facetResponseSchema(facet.key),
18263
18280
  memories
18264
18281
  });
@@ -18316,19 +18333,22 @@ function fallbackSection(facet, prior) {
18316
18333
  stale: true
18317
18334
  };
18318
18335
  }
18319
- function buildFacetSystemPrompt(facet) {
18320
- const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
18321
- 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.
18322
-
18323
- Task for this section:
18324
- ${facet.guidance}
18336
+ 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.
18325
18337
 
18326
18338
  Rules:
18327
18339
  - Ground every claim in the supplied memories \u2014 never invent, infer beyond, or embellish what they support.
18328
18340
  - If the memories don't cover this section, return an empty summary with hasEvidence=false. Do not pad or guess.
18329
18341
  - Write in third person about the person, in a natural voice suitable for a public profile (no "I"/"you", no name repetition).
18330
18342
  - Be concise and specific; no preamble, hedging, or meta-commentary.
18331
- - Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
18343
+ - Respond as JSON in exactly the shape the user turn states, with no extra fields.`;
18344
+ function buildFacetUserInstructions(facet) {
18345
+ const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
18346
+ return `Section: "${facet.label}"
18347
+
18348
+ Task for this section:
18349
+ ${facet.guidance}
18350
+
18351
+ Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
18332
18352
  }
18333
18353
  function extractFacetText(structured) {
18334
18354
  if (structured && typeof structured === "object") {
@@ -19650,6 +19670,20 @@ function clientToolToCompletionsFormat(tool) {
19650
19670
  }
19651
19671
  var TOOL_SEARCH_TOOL_TYPE = "tool_search_tool_regex_20251119";
19652
19672
  var TOOL_SEARCH_TOOL_NAME = "tool_search_tool_regex";
19673
+ function resolveDeferredServerTools(allServerTools, serverToolsFilter, config) {
19674
+ const allowed = serverToolsFilter === void 0 || typeof serverToolsFilter === "function" ? allServerTools : filterServerTools(allServerTools, [...serverToolsFilter]);
19675
+ const excluded = new Set(config.excludeTools ?? []);
19676
+ if (typeof serverToolsFilter === "function") {
19677
+ for (const name of serverToolsFilter.excludeTools ?? []) excluded.add(name);
19678
+ }
19679
+ if (excluded.size === 0) return allowed;
19680
+ return allowed.filter((tool) => !excluded.has(tool.name));
19681
+ }
19682
+ function deferFormattingConfig(serverToolsFilter, config) {
19683
+ if (!config?.enabled) return config;
19684
+ const isExplicitList = serverToolsFilter !== void 0 && typeof serverToolsFilter !== "function";
19685
+ return isExplicitList ? void 0 : config;
19686
+ }
19653
19687
  function byNameAscending(a, b) {
19654
19688
  if (a.name < b.name) return -1;
19655
19689
  if (a.name > b.name) return 1;
@@ -19976,7 +20010,7 @@ function createServerToolsFilter(options = {}) {
19976
20010
  const exclude = new Set(options.excludeTools ?? []);
19977
20011
  const sets = options.toolSets ?? [];
19978
20012
  const matchOpts = options.matchOptions;
19979
- return (embeddings, tools) => {
20013
+ const filter = (embeddings, tools) => {
19980
20014
  const matches = findMatchingTools(embeddings, tools, matchOpts);
19981
20015
  if (matches.length === 0) return [];
19982
20016
  const matchedNames = new Set(matches.map((m) => m.tool.name));
@@ -19994,6 +20028,10 @@ function createServerToolsFilter(options = {}) {
19994
20028
  for (const name of exclude) finalNames.delete(name);
19995
20029
  return [...finalNames];
19996
20030
  };
20031
+ return Object.defineProperty(filter, "excludeTools", {
20032
+ value: Object.freeze([...exclude]),
20033
+ enumerable: false
20034
+ });
19997
20035
  }
19998
20036
  var DEFAULT_EXCLUDED_SERVER_TOOLS = [
19999
20037
  "AnumaVisionMCP-anuma_analyze_image",
@@ -20994,7 +21032,11 @@ function useChatStorage(options) {
20994
21032
  cache: serverToolsConfig?.cache
20995
21033
  });
20996
21034
  if (serverToolsConfig?.deferLoading?.enabled) {
20997
- filteredServerTools2 = allServerTools;
21035
+ filteredServerTools2 = resolveDeferredServerTools(
21036
+ allServerTools,
21037
+ serverToolsFilter,
21038
+ serverToolsConfig.deferLoading
21039
+ );
20998
21040
  } else if (isServerToolsFunction2) {
20999
21041
  if (messageContent.length >= MIN_CONTENT_LENGTH_FOR_TOOLS) {
21000
21042
  skipUserEmbedding = await embedToolText(
@@ -21053,7 +21095,7 @@ function useChatStorage(options) {
21053
21095
  filteredServerTools2,
21054
21096
  narrowedClientTools2,
21055
21097
  effectiveApiType2,
21056
- serverToolsConfig?.deferLoading
21098
+ deferFormattingConfig(serverToolsFilter, serverToolsConfig?.deferLoading)
21057
21099
  );
21058
21100
  }
21059
21101
  if (onToolSelection) {
@@ -21266,7 +21308,11 @@ function useChatStorage(options) {
21266
21308
  if ("error" in settledTools) throw settledTools.error;
21267
21309
  const allServerTools = settledTools.tools;
21268
21310
  if (serverToolsConfig?.deferLoading?.enabled && effectiveApiType === "responses") {
21269
- filteredServerTools = allServerTools;
21311
+ filteredServerTools = resolveDeferredServerTools(
21312
+ allServerTools,
21313
+ serverToolsFilter,
21314
+ serverToolsConfig.deferLoading
21315
+ );
21270
21316
  } else if (isServerToolsFunction) {
21271
21317
  if (contentForStorage.length >= MIN_CONTENT_LENGTH_FOR_TOOLS) {
21272
21318
  const settledEmbedding = await (userMessageEmbeddingPromise ?? embedForToolsSettled(getTokenRef.current));
@@ -21286,7 +21332,7 @@ function useChatStorage(options) {
21286
21332
  filteredServerTools,
21287
21333
  clientTools,
21288
21334
  effectiveApiType,
21289
- serverToolsConfig?.deferLoading
21335
+ deferFormattingConfig(serverToolsFilter, serverToolsConfig?.deferLoading)
21290
21336
  );
21291
21337
  }
21292
21338
  } catch (error) {
@@ -21327,7 +21373,7 @@ function useChatStorage(options) {
21327
21373
  filteredServerTools,
21328
21374
  narrowedClientTools,
21329
21375
  effectiveApiType,
21330
- serverToolsConfig?.deferLoading
21376
+ deferFormattingConfig(serverToolsFilter, serverToolsConfig?.deferLoading)
21331
21377
  ) : void 0;
21332
21378
  } catch (error) {
21333
21379
  getLogger().warn("[useChatStorage] client tool filtering failed:", error);
@@ -3308,6 +3308,22 @@ interface DeferLoadingConfig {
3308
3308
  enabled: boolean;
3309
3309
  /** Server-tool names to keep non-deferred (besides the tool-search tool), in priority order. */
3310
3310
  hotToolNames: readonly string[];
3311
+ /**
3312
+ * Server-tool names to drop entirely while defer-loading is on.
3313
+ *
3314
+ * Defer widens the per-turn selection to the whole catalog, which also bypasses the `excludeTools`
3315
+ * baked into a caller's semantic filter. A filter built by {@link createServerToolsFilter} tags itself
3316
+ * with its own exclusions and those are honoured automatically — so most callers need nothing here.
3317
+ *
3318
+ * Set this when the filter's tag can't be seen: a hand-written filter, a filter wrapped in a plain
3319
+ * closure (the wrapper drops the tag), or a static-array `serverToolsFilter`. Anything excluded
3320
+ * UNCONDITIONALLY (a tool the app replaces with its own UI, a capability the model already has
3321
+ * natively) belongs in one of the two places, or defer quietly hands it back to the model.
3322
+ *
3323
+ * This list is UNIONED with the filter's own tag, never a replacement for it — so adding one name
3324
+ * here cannot re-admit the tools the filter already excludes.
3325
+ */
3326
+ excludeTools?: readonly string[];
3311
3327
  }
3312
3328
  /**
3313
3329
  * A tool set defines a group of tools that work together. When any "anchor"
@@ -4880,6 +4896,51 @@ interface EntityOperationsContext {
4880
4896
  allowUnscopedRows?: boolean;
4881
4897
  }
4882
4898
 
4899
+ /**
4900
+ * Class-B task types — the vocabulary of the `X-Anuma-Task-Type` header.
4901
+ *
4902
+ * A Class-B flow has ONE fixed purpose and no conversation: extract memories
4903
+ * from a snippet, pick a topic, consolidate duplicates, classify a query. Today
4904
+ * each of those builds its own system prompt on the client and sends it as a
4905
+ * `role:system` message, which makes the prompt a suggestion rather than a
4906
+ * contract — a caller can reword it into "you are a general assistant" and get
4907
+ * general chat at internal-endpoint prices.
4908
+ *
4909
+ * The fix is that the SERVER owns the text and the client only names the task.
4910
+ * This header is the name. The portal allowlists it against its own copy of this
4911
+ * enum (ai-portal `internal/systemprompt/tasks.go`) and DROPS anything it does
4912
+ * not recognise, so an unknown value degrades to "no task declared" rather than
4913
+ * failing the request.
4914
+ *
4915
+ * ⚠ These string values are a wire contract with ai-portal. Adding one here
4916
+ * before the portal knows it is harmless (it is dropped); renaming or removing
4917
+ * one silently stops that flow being counted. Keep them in step with
4918
+ * `AllTaskTypes` on the portal side, and see ai-portal
4919
+ * `docs/BACKEND_OWNED_TASK_PROMPTS.md` for the rollout.
4920
+ *
4921
+ * ⚠ RELEASE ORDER. This union is a PUBLIC type, so a split is breaking in both
4922
+ * directions and has to be sequenced:
4923
+ *
4924
+ * 1. merge + publish this package;
4925
+ * 2. bump `@anuma/sdk` in the consumer and switch its call sites to the new
4926
+ * names in the same commit.
4927
+ *
4928
+ * A consumer on the OLD version cannot compile the new names, and a consumer on
4929
+ * the NEW version cannot compile the retired ones (`app_inspiration` is gone
4930
+ * here, replaced by the three `app_inspiration_*` names). Both halves of that
4931
+ * are type-only — the wire itself always degrades safely, because the portal
4932
+ * drops any value it does not recognise. Concretely: ai-memoryless-client#5782
4933
+ * is blocked on this publish for exactly that reason.
4934
+ *
4935
+ * Deliberately ABSENT: agent personas, `reflect()` when it answers the user, and
4936
+ * the app-idea chat. Those carry an open conversation with the user, so a fixed
4937
+ * server-owned prompt would be wrong rather than merely premature — the same
4938
+ * boundary {@link INTERNAL_FLOW_MARKER} draws.
4939
+ *
4940
+ * @public
4941
+ */
4942
+ 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";
4943
+
4883
4944
  /**
4884
4945
  * Shared portal LLM call for the memory pipeline.
4885
4946
  *
@@ -8222,6 +8283,28 @@ interface ReflectOptions extends RecallOptions, PortalLlmAuth {
8222
8283
  maxTokens?: number;
8223
8284
  /** Override the grounding system prompt. */
8224
8285
  systemPrompt?: string;
8286
+ /**
8287
+ * Extra caller instruction to carry on the USER turn, between the question and
8288
+ * the evidence block (see the `userMessage` assembly below). This is the slot a
8289
+ * background caller uses to keep its per-request data OUT of the system message
8290
+ * without colliding with the numbered evidence list — profile-facet synthesis
8291
+ * puts its section label, guidance and response-field hint here so its system
8292
+ * half can stay fixed and server-ownable.
8293
+ */
8294
+ userInstructions?: string;
8295
+ /**
8296
+ * Class-B task name for the `X-Anuma-Task-Type` header, or nothing.
8297
+ *
8298
+ * Deliberately OPTIONAL and unset by default. reflect() also answers the user's
8299
+ * OWN question, and that traffic is chat, not an internal flow — declaring a
8300
+ * task type unconditionally here would put an internal-flow name on real
8301
+ * conversation, which is the same boundary `INTERNAL_FLOW_MARKER` draws
8302
+ * (reflect is deliberately unmarked; its background caller marks its own
8303
+ * prompt — see ../internalFlowMarker.ts). So the name is per call: only a
8304
+ * caller with ONE fixed purpose passes
8305
+ * one, and today that is profile-facet synthesis (`memory_profile_synth`).
8306
+ */
8307
+ taskType?: TaskType;
8225
8308
  /** Endpoint for the answer LLM. */
8226
8309
  baseUrl?: string;
8227
8310
  /** Override fetch (for tests). */
@@ -3308,6 +3308,22 @@ interface DeferLoadingConfig {
3308
3308
  enabled: boolean;
3309
3309
  /** Server-tool names to keep non-deferred (besides the tool-search tool), in priority order. */
3310
3310
  hotToolNames: readonly string[];
3311
+ /**
3312
+ * Server-tool names to drop entirely while defer-loading is on.
3313
+ *
3314
+ * Defer widens the per-turn selection to the whole catalog, which also bypasses the `excludeTools`
3315
+ * baked into a caller's semantic filter. A filter built by {@link createServerToolsFilter} tags itself
3316
+ * with its own exclusions and those are honoured automatically — so most callers need nothing here.
3317
+ *
3318
+ * Set this when the filter's tag can't be seen: a hand-written filter, a filter wrapped in a plain
3319
+ * closure (the wrapper drops the tag), or a static-array `serverToolsFilter`. Anything excluded
3320
+ * UNCONDITIONALLY (a tool the app replaces with its own UI, a capability the model already has
3321
+ * natively) belongs in one of the two places, or defer quietly hands it back to the model.
3322
+ *
3323
+ * This list is UNIONED with the filter's own tag, never a replacement for it — so adding one name
3324
+ * here cannot re-admit the tools the filter already excludes.
3325
+ */
3326
+ excludeTools?: readonly string[];
3311
3327
  }
3312
3328
  /**
3313
3329
  * A tool set defines a group of tools that work together. When any "anchor"
@@ -4880,6 +4896,51 @@ interface EntityOperationsContext {
4880
4896
  allowUnscopedRows?: boolean;
4881
4897
  }
4882
4898
 
4899
+ /**
4900
+ * Class-B task types — the vocabulary of the `X-Anuma-Task-Type` header.
4901
+ *
4902
+ * A Class-B flow has ONE fixed purpose and no conversation: extract memories
4903
+ * from a snippet, pick a topic, consolidate duplicates, classify a query. Today
4904
+ * each of those builds its own system prompt on the client and sends it as a
4905
+ * `role:system` message, which makes the prompt a suggestion rather than a
4906
+ * contract — a caller can reword it into "you are a general assistant" and get
4907
+ * general chat at internal-endpoint prices.
4908
+ *
4909
+ * The fix is that the SERVER owns the text and the client only names the task.
4910
+ * This header is the name. The portal allowlists it against its own copy of this
4911
+ * enum (ai-portal `internal/systemprompt/tasks.go`) and DROPS anything it does
4912
+ * not recognise, so an unknown value degrades to "no task declared" rather than
4913
+ * failing the request.
4914
+ *
4915
+ * ⚠ These string values are a wire contract with ai-portal. Adding one here
4916
+ * before the portal knows it is harmless (it is dropped); renaming or removing
4917
+ * one silently stops that flow being counted. Keep them in step with
4918
+ * `AllTaskTypes` on the portal side, and see ai-portal
4919
+ * `docs/BACKEND_OWNED_TASK_PROMPTS.md` for the rollout.
4920
+ *
4921
+ * ⚠ RELEASE ORDER. This union is a PUBLIC type, so a split is breaking in both
4922
+ * directions and has to be sequenced:
4923
+ *
4924
+ * 1. merge + publish this package;
4925
+ * 2. bump `@anuma/sdk` in the consumer and switch its call sites to the new
4926
+ * names in the same commit.
4927
+ *
4928
+ * A consumer on the OLD version cannot compile the new names, and a consumer on
4929
+ * the NEW version cannot compile the retired ones (`app_inspiration` is gone
4930
+ * here, replaced by the three `app_inspiration_*` names). Both halves of that
4931
+ * are type-only — the wire itself always degrades safely, because the portal
4932
+ * drops any value it does not recognise. Concretely: ai-memoryless-client#5782
4933
+ * is blocked on this publish for exactly that reason.
4934
+ *
4935
+ * Deliberately ABSENT: agent personas, `reflect()` when it answers the user, and
4936
+ * the app-idea chat. Those carry an open conversation with the user, so a fixed
4937
+ * server-owned prompt would be wrong rather than merely premature — the same
4938
+ * boundary {@link INTERNAL_FLOW_MARKER} draws.
4939
+ *
4940
+ * @public
4941
+ */
4942
+ 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";
4943
+
4883
4944
  /**
4884
4945
  * Shared portal LLM call for the memory pipeline.
4885
4946
  *
@@ -8222,6 +8283,28 @@ interface ReflectOptions extends RecallOptions, PortalLlmAuth {
8222
8283
  maxTokens?: number;
8223
8284
  /** Override the grounding system prompt. */
8224
8285
  systemPrompt?: string;
8286
+ /**
8287
+ * Extra caller instruction to carry on the USER turn, between the question and
8288
+ * the evidence block (see the `userMessage` assembly below). This is the slot a
8289
+ * background caller uses to keep its per-request data OUT of the system message
8290
+ * without colliding with the numbered evidence list — profile-facet synthesis
8291
+ * puts its section label, guidance and response-field hint here so its system
8292
+ * half can stay fixed and server-ownable.
8293
+ */
8294
+ userInstructions?: string;
8295
+ /**
8296
+ * Class-B task name for the `X-Anuma-Task-Type` header, or nothing.
8297
+ *
8298
+ * Deliberately OPTIONAL and unset by default. reflect() also answers the user's
8299
+ * OWN question, and that traffic is chat, not an internal flow — declaring a
8300
+ * task type unconditionally here would put an internal-flow name on real
8301
+ * conversation, which is the same boundary `INTERNAL_FLOW_MARKER` draws
8302
+ * (reflect is deliberately unmarked; its background caller marks its own
8303
+ * prompt — see ../internalFlowMarker.ts). So the name is per call: only a
8304
+ * caller with ONE fixed purpose passes
8305
+ * one, and today that is profile-facet synthesis (`memory_profile_synth`).
8306
+ */
8307
+ taskType?: TaskType;
8225
8308
  /** Endpoint for the answer LLM. */
8226
8309
  baseUrl?: string;
8227
8310
  /** Override fetch (for tests). */