@anuma/sdk 6.15.0 → 6.15.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.
@@ -2220,6 +2220,29 @@ async function getMessageSkeletonsOp(ctx, convId) {
2220
2220
  }
2221
2221
  return skeletons;
2222
2222
  }
2223
+ async function getToolCallEventIdsOp(ctx, convId) {
2224
+ const results = await ctx.messagesCollection.query(import_watermelondb.Q.where("conversation_id", convId)).unsafeFetchRaw();
2225
+ const ids = /* @__PURE__ */ new Set();
2226
+ for (const raw of results) {
2227
+ const value = raw.tool_call_events;
2228
+ if (!value) continue;
2229
+ let events;
2230
+ if (typeof value === "string") {
2231
+ try {
2232
+ events = JSON.parse(value);
2233
+ } catch {
2234
+ continue;
2235
+ }
2236
+ } else if (Array.isArray(value)) {
2237
+ events = value;
2238
+ }
2239
+ if (!Array.isArray(events)) continue;
2240
+ for (const evt of events) {
2241
+ if (evt && evt.id) ids.add(evt.id);
2242
+ }
2243
+ }
2244
+ return ids;
2245
+ }
2223
2246
  async function clearMessagesOp(ctx, convId) {
2224
2247
  const messages = await ctx.messagesCollection.query(import_watermelondb.Q.where("conversation_id", convId)).fetch();
2225
2248
  await ctx.database.write(async () => {
@@ -2449,14 +2472,14 @@ function resolveChunkText(content, chunk, chunksInRow) {
2449
2472
  if (covered !== content.length) return content;
2450
2473
  return content.slice(start, end);
2451
2474
  }
2452
- async function readJsonField(model, column, walletAddress) {
2453
- const raw = model._getRaw(column);
2454
- if (!raw) return void 0;
2455
- if (walletAddress && isEncrypted(raw)) {
2456
- return await decryptJsonField(raw, walletAddress);
2475
+ async function readRawJsonField(raw, column, walletAddress) {
2476
+ const value = raw[column];
2477
+ if (!value) return void 0;
2478
+ if (walletAddress && isEncrypted(value)) {
2479
+ return await decryptJsonField(value, walletAddress);
2457
2480
  }
2458
2481
  try {
2459
- return JSON.parse(raw);
2482
+ return JSON.parse(value);
2460
2483
  } catch {
2461
2484
  return void 0;
2462
2485
  }
@@ -2469,12 +2492,12 @@ async function searchMessagesOp(ctx, queryVector, options) {
2469
2492
  activeConversations.map((c) => String(c._getRaw("conversation_id")))
2470
2493
  );
2471
2494
  const queryConditions = conversationId ? [import_watermelondb.Q.where("conversation_id", conversationId)] : [];
2472
- const messages = await ctx.messagesCollection.query(...queryConditions).fetch();
2495
+ const messages = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
2473
2496
  const candidates = [];
2474
2497
  for (const message of messages) {
2475
- const msgConvId = String(message._getRaw("conversation_id"));
2498
+ const msgConvId = String(message.conversation_id);
2476
2499
  if (!activeConversationIds.has(msgConvId)) continue;
2477
- const messageVector = await readJsonField(message, "vector", ctx.walletAddress);
2500
+ const messageVector = await readRawJsonField(message, "vector", ctx.walletAddress);
2478
2501
  if (!messageVector || messageVector.length === 0) continue;
2479
2502
  const similarity = cosineSimilarity(queryVector, messageVector);
2480
2503
  if (similarity >= minSimilarity) {
@@ -2484,7 +2507,7 @@ async function searchMessagesOp(ctx, queryVector, options) {
2484
2507
  const topK = candidates.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
2485
2508
  return Promise.all(
2486
2509
  topK.map(async ({ message, similarity }) => {
2487
- const stored = await messageToStored(
2510
+ const stored = await messageRawToStored(
2488
2511
  message,
2489
2512
  ctx.walletAddress,
2490
2513
  ctx.signMessage,
@@ -2508,21 +2531,21 @@ async function searchChunksOp(ctx, queryVector, options) {
2508
2531
  activeConversations.map((c) => String(c._getRaw("conversation_id")))
2509
2532
  );
2510
2533
  const queryConditions = conversationId ? [import_watermelondb.Q.where("conversation_id", conversationId)] : [];
2511
- const messages = await ctx.messagesCollection.query(...queryConditions).fetch();
2534
+ const messages = await ctx.messagesCollection.query(...queryConditions).unsafeFetchRaw();
2512
2535
  const candidates = [];
2513
2536
  let staleSkipped = 0;
2514
2537
  let unreadableVectors = 0;
2515
2538
  for (const message of messages) {
2516
- const msgConvId = String(message._getRaw("conversation_id"));
2539
+ const msgConvId = String(message.conversation_id);
2517
2540
  if (!activeConversationIds.has(msgConvId)) continue;
2518
2541
  if (embeddingModel) {
2519
- const storedModel = message._getRaw("embedding_model");
2542
+ const storedModel = message.embedding_model;
2520
2543
  if (storedModel !== null && storedModel !== void 0 && storedModel !== embeddingModel) {
2521
2544
  staleSkipped++;
2522
2545
  continue;
2523
2546
  }
2524
2547
  }
2525
- const version = Number(message._getRaw("updated_at")) || 0;
2548
+ const version = Number(message.updated_at) || 0;
2526
2549
  const cached = chunkCache?.get(message.id);
2527
2550
  if (cached && cached.version === version) {
2528
2551
  if (cached.chunks.length > 0) {
@@ -2546,7 +2569,7 @@ async function searchChunksOp(ctx, queryVector, options) {
2546
2569
  }
2547
2570
  continue;
2548
2571
  }
2549
- const chunks = await readJsonField(message, "chunks", ctx.walletAddress);
2572
+ const chunks = await readRawJsonField(message, "chunks", ctx.walletAddress);
2550
2573
  if (chunks && chunks.length > 0) {
2551
2574
  const vectors = chunks.map(
2552
2575
  (chunk) => decodeChunkVector(chunk.vector, () => {
@@ -2567,7 +2590,7 @@ async function searchChunksOp(ctx, queryVector, options) {
2567
2590
  }
2568
2591
  }
2569
2592
  } else {
2570
- const messageVector = await readJsonField(message, "vector", ctx.walletAddress);
2593
+ const messageVector = await readRawJsonField(message, "vector", ctx.walletAddress);
2571
2594
  if (!messageVector || messageVector.length === 0) continue;
2572
2595
  const fallback = Float32Array.from(messageVector);
2573
2596
  chunkCache?.set(message.id, { version, chunks: [], fallback });
@@ -2593,7 +2616,7 @@ async function searchChunksOp(ctx, queryVector, options) {
2593
2616
  const storedById = new Map(
2594
2617
  await Promise.all(
2595
2618
  Array.from(uniqueMessages, async ([id, m]) => {
2596
- const stored = await messageToStored(
2619
+ const stored = await messageRawToStored(
2597
2620
  m,
2598
2621
  ctx.walletAddress,
2599
2622
  ctx.signMessage,
@@ -2611,8 +2634,8 @@ async function searchChunksOp(ctx, queryVector, options) {
2611
2634
  await Promise.all(
2612
2635
  Array.from(needsChunkText, async (id) => {
2613
2636
  const m = uniqueMessages.get(id);
2614
- const parsed = await readJsonField(m, "chunks", ctx.walletAddress);
2615
- const version = Number(m._getRaw("updated_at")) || 0;
2637
+ const parsed = await readRawJsonField(m, "chunks", ctx.walletAddress);
2638
+ const version = Number(m.updated_at) || 0;
2616
2639
  if (parsed) chunkStateByMsgId.set(id, { version, chunks: parsed });
2617
2640
  })
2618
2641
  );
@@ -6429,6 +6452,9 @@ function supportsResponseFormat(model, variant = "json_object") {
6429
6452
  const allow = variant === "json_schema" ? RESPONSE_SCHEMA_OK : RESPONSE_FORMAT_OK;
6430
6453
  return model.split("/").some((seg) => allow.has(seg));
6431
6454
  }
6455
+ function requiresResponsesTransport(model) {
6456
+ return model.split("/").some((seg) => RESPONSES_ONLY_MODEL_PREFIXES.some((prefix) => seg.startsWith(prefix)));
6457
+ }
6432
6458
  function responsesExtra(extra) {
6433
6459
  if (!extra) return {};
6434
6460
  const out = {};
@@ -6764,7 +6790,7 @@ function extractResponsesContent(body) {
6764
6790
  const text12 = root.output.filter((item) => asRecord(item)?.type === "message").flatMap((item) => Array.isArray(item.content) ? item.content : []).map((part) => asRecord(part)?.text).filter((t) => typeof t === "string").join("");
6765
6791
  return text12;
6766
6792
  }
6767
- var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, JSON_CONTRACT_REMINDER, CHAT_TO_RESPONSES_FIELDS, FOREIGN_ENDPOINT_SUFFIX, RETRYABLE_HTTP;
6793
+ var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, JSON_CONTRACT_REMINDER, RESPONSES_ONLY_MODEL_PREFIXES, CHAT_TO_RESPONSES_FIELDS, FOREIGN_ENDPOINT_SUFFIX, RETRYABLE_HTTP;
6768
6794
  var init_portalLlm = __esm({
6769
6795
  "src/lib/memory/portalLlm.ts"() {
6770
6796
  "use strict";
@@ -6776,6 +6802,7 @@ var init_portalLlm = __esm({
6776
6802
  RESPONSE_FORMAT_OK = /* @__PURE__ */ new Set(["openai", "inclusionai", "deepseek"]);
6777
6803
  RESPONSE_SCHEMA_OK = /* @__PURE__ */ new Set(["openai"]);
6778
6804
  JSON_CONTRACT_REMINDER = "Output ONLY the strict JSON object requested. No prose, no explanation, no markdown.";
6805
+ RESPONSES_ONLY_MODEL_PREFIXES = ["gpt-5.6"];
6779
6806
  CHAT_TO_RESPONSES_FIELDS = {
6780
6807
  max_completion_tokens: "max_output_tokens"
6781
6808
  };
@@ -19115,6 +19142,9 @@ var DEFAULT_BASE_URL2 = BASE_URL;
19115
19142
  var DEFAULT_MODEL4 = "anthropic/claude-sonnet-4-6";
19116
19143
  var DEFAULT_MAX_TOKENS = 4096;
19117
19144
  var REQUEST_TIMEOUT_MS = 6e4;
19145
+ var MIN_RETRY_BUDGET_MS = 2e3;
19146
+ var MIN_RESPONSES_OUTPUT_TOKENS = 2048;
19147
+ var SCHEMA_FALLBACK_SKIP_STATUSES = /* @__PURE__ */ new Set([401, 403, 404, 408, 409, 413, 425, 429]);
19118
19148
  var DEFAULT_SYSTEM_PROMPT = `You are a personal assistant with access to the user's memory. Answer the user's question using the supplied memories as evidence.
19119
19149
 
19120
19150
  Rules:
@@ -19146,14 +19176,9 @@ ${m.content}`).join("\n\n");
19146
19176
  const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL2;
19147
19177
  const model = options.llmModel ?? DEFAULT_MODEL4;
19148
19178
  const wantsStructured = !!options.responseSchema;
19149
- const sendResponseFormat = wantsStructured && supportsResponseFormat(model, "json_schema");
19179
+ const useResponsesTransport = requiresResponsesTransport(model);
19180
+ const sendResponseFormat = wantsStructured && !useResponsesTransport && supportsResponseFormat(model, "json_schema");
19150
19181
  const basePrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
19151
- const systemPrompt = wantsStructured && !sendResponseFormat ? `${basePrompt}
19152
-
19153
- Respond with ONLY a single JSON object conforming to this JSON Schema, with no prose, comments, or code fences:
19154
- ${JSON.stringify(
19155
- options.responseSchema
19156
- )}` : basePrompt;
19157
19182
  const userMessage = [
19158
19183
  `Question:
19159
19184
  ${trimmed}`,
@@ -19163,85 +19188,121 @@ ${evidence}`
19163
19188
  ].join("\n\n");
19164
19189
  const maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
19165
19190
  const fetchImpl = options.fetchFn ?? fetch;
19191
+ const buildBody = (useResponseFormat) => {
19192
+ const systemPrompt = wantsStructured && !useResponseFormat ? `${basePrompt}
19193
+
19194
+ Respond with ONLY a single JSON object conforming to this JSON Schema, with no prose, comments, or code fences:
19195
+ ${JSON.stringify(
19196
+ options.responseSchema
19197
+ )}` : basePrompt;
19198
+ const messages = [
19199
+ { role: "system", content: systemPrompt },
19200
+ { role: "user", content: userMessage }
19201
+ ];
19202
+ if (useResponsesTransport) {
19203
+ return JSON.stringify({
19204
+ model,
19205
+ // Same system+user pair under the Responses-API field names: `input`
19206
+ // for the turns, `max_output_tokens` for the cap. Sending the
19207
+ // chat-completions spelling here is silently ignored, which caps the
19208
+ // answer at the portal default.
19209
+ input: messages,
19210
+ max_output_tokens: Math.max(maxTokens, MIN_RESPONSES_OUTPUT_TOKENS)
19211
+ });
19212
+ }
19213
+ return JSON.stringify({
19214
+ model,
19215
+ // Modern OpenAI field; the portal reads only `max_completion_tokens`
19216
+ // (the deprecated `max_tokens` is silently ignored → falls back to the
19217
+ // portal's default output cap and truncates the answer).
19218
+ max_completion_tokens: maxTokens,
19219
+ messages,
19220
+ ...useResponseFormat && {
19221
+ response_format: {
19222
+ type: "json_schema",
19223
+ json_schema: { name: "reflect_output", schema: options.responseSchema }
19224
+ }
19225
+ }
19226
+ });
19227
+ };
19166
19228
  const log = getLogger();
19167
19229
  const authHeaders = await resolvePortalAuthHeaders(options, "memory/reflect");
19168
19230
  if (authHeaders === null) return baseResult;
19169
- const controller = new AbortController();
19170
19231
  const deadline = Date.now() + REQUEST_TIMEOUT_MS;
19171
19232
  const remaining = () => Math.max(0, deadline - Date.now());
19172
- const timer = setTimeout(() => controller.abort(), remaining());
19173
- let response;
19174
- try {
19175
- response = await fetchImpl(`${baseUrl}/api/v1/chat/completions`, {
19176
- method: "POST",
19177
- // No task type unless the caller named one — an undeclared task adds no
19178
- // header at all, which is what keeps user-facing reflect() unlabelled.
19179
- headers: {
19180
- ...authHeaders,
19181
- ...taskTypeHeader(options.taskType),
19182
- "Content-Type": "application/json"
19183
- },
19184
- body: JSON.stringify({
19185
- model,
19186
- // Modern OpenAI field; the portal reads only `max_completion_tokens`
19187
- // (the deprecated `max_tokens` is silently ignored → falls back to the
19188
- // portal's default output cap and truncates the answer).
19189
- max_completion_tokens: maxTokens,
19190
- messages: [
19191
- { role: "system", content: systemPrompt },
19192
- { role: "user", content: userMessage }
19193
- ],
19194
- ...sendResponseFormat && {
19195
- response_format: {
19196
- type: "json_schema",
19197
- json_schema: { name: "reflect_output", schema: options.responseSchema }
19198
- }
19199
- }
19200
- }),
19201
- signal: controller.signal
19202
- });
19203
- } catch (err) {
19233
+ const endpoint = useResponsesTransport ? "/api/v1/responses" : "/api/v1/chat/completions";
19234
+ const sendOnce = async (useResponseFormat) => {
19235
+ const controller = new AbortController();
19236
+ const timer = setTimeout(() => controller.abort(), remaining());
19237
+ let response;
19238
+ try {
19239
+ response = await fetchImpl(`${baseUrl}${endpoint}`, {
19240
+ method: "POST",
19241
+ // No task type unless the caller named one — an undeclared task adds no
19242
+ // header at all, which is what keeps user-facing reflect() unlabelled.
19243
+ headers: {
19244
+ ...authHeaders,
19245
+ ...taskTypeHeader(options.taskType),
19246
+ "Content-Type": "application/json"
19247
+ },
19248
+ body: buildBody(useResponseFormat),
19249
+ signal: controller.signal
19250
+ });
19251
+ } catch (err) {
19252
+ clearTimeout(timer);
19253
+ log.warn("[memory/reflect] portal request failed", {
19254
+ err: err instanceof Error ? err.message : String(err),
19255
+ baseUrl
19256
+ });
19257
+ return { kind: "error" };
19258
+ }
19204
19259
  clearTimeout(timer);
19205
- log.warn("[memory/reflect] portal request failed", {
19206
- err: err instanceof Error ? err.message : String(err),
19207
- baseUrl
19260
+ if (!response.ok) {
19261
+ return { kind: "http", status: response.status, statusText: response.statusText };
19262
+ }
19263
+ const bodyTimer = setTimeout(() => controller.abort(), remaining());
19264
+ try {
19265
+ const body = await response.json();
19266
+ clearTimeout(bodyTimer);
19267
+ return { kind: "ok", body };
19268
+ } catch (err) {
19269
+ clearTimeout(bodyTimer);
19270
+ log.warn("[memory/reflect] failed to parse portal response body", {
19271
+ err: err instanceof Error ? err.message : String(err)
19272
+ });
19273
+ return { kind: "error" };
19274
+ }
19275
+ };
19276
+ let attempt = await sendOnce(sendResponseFormat);
19277
+ let schemaFallbackUsed = false;
19278
+ const budgetLeftMs = remaining();
19279
+ if (attempt.kind === "http" && sendResponseFormat && !SCHEMA_FALLBACK_SKIP_STATUSES.has(attempt.status) && budgetLeftMs >= MIN_RETRY_BUDGET_MS) {
19280
+ log.warn("[memory/reflect] response_format json_schema rejected; retrying schema-in-prompt", {
19281
+ status: attempt.status,
19282
+ statusText: attempt.statusText,
19283
+ model,
19284
+ taskType: options.taskType,
19285
+ budgetLeftMs
19208
19286
  });
19209
- return baseResult;
19287
+ schemaFallbackUsed = true;
19288
+ attempt = await sendOnce(false);
19210
19289
  }
19211
- clearTimeout(timer);
19212
- if (!response.ok) {
19290
+ if (attempt.kind === "error") return baseResult;
19291
+ if (attempt.kind === "http") {
19213
19292
  log.warn("[memory/reflect] portal returned non-OK", {
19214
- status: response.status,
19215
- statusText: response.statusText
19293
+ status: attempt.status,
19294
+ statusText: attempt.statusText,
19295
+ // Distinguishes "the fallback ran and still failed" from "never tried".
19296
+ schemaFallbackUsed
19216
19297
  });
19217
19298
  return baseResult;
19218
19299
  }
19219
- const bodyTimer = setTimeout(() => controller.abort(), remaining());
19220
- let body;
19221
- try {
19222
- body = await response.json();
19223
- } catch (err) {
19224
- clearTimeout(bodyTimer);
19225
- log.warn("[memory/reflect] failed to parse portal response body", {
19226
- err: err instanceof Error ? err.message : String(err)
19227
- });
19228
- return baseResult;
19229
- }
19230
- clearTimeout(bodyTimer);
19231
- return parseAnswer(body, baseResult, !!options.responseSchema);
19300
+ return parseAnswer(attempt.body, baseResult, !!options.responseSchema);
19232
19301
  }
19233
19302
  function parseAnswer(body, base, parseSchema) {
19234
19303
  if (typeof body !== "object" || body === null) return base;
19235
19304
  const obj = body;
19236
- const choices = obj.choices;
19237
- let text12 = "";
19238
- if (Array.isArray(choices) && choices.length > 0) {
19239
- const message = choices[0].message;
19240
- if (typeof message === "object" && message !== null) {
19241
- const content = message.content;
19242
- if (typeof content === "string") text12 = content;
19243
- }
19244
- }
19305
+ const text12 = extractCompletionContent(obj) ?? "";
19245
19306
  const usage = obj.usage;
19246
19307
  let structuredOutput;
19247
19308
  if (parseSchema && text12) {
@@ -19253,14 +19314,19 @@ function parseAnswer(body, base, parseSchema) {
19253
19314
  });
19254
19315
  }
19255
19316
  }
19317
+ const promptTokens = usage?.prompt_tokens ?? usage?.input_tokens ?? 0;
19318
+ const completionTokens = usage?.completion_tokens ?? usage?.output_tokens ?? 0;
19256
19319
  return {
19257
19320
  text: text12,
19258
19321
  ...structuredOutput !== void 0 && { structuredOutput },
19259
19322
  basedOn: base.basedOn,
19260
19323
  usage: {
19261
- promptTokens: usage?.prompt_tokens ?? 0,
19262
- completionTokens: usage?.completion_tokens ?? 0,
19263
- totalTokens: usage?.total_tokens ?? 0
19324
+ promptTokens,
19325
+ completionTokens,
19326
+ // Derived when absent: the Responses API reports the two component counts
19327
+ // and does not always send a total, which would otherwise report zero
19328
+ // spend for a call that plainly cost something.
19329
+ totalTokens: usage?.total_tokens ?? promptTokens + completionTokens
19264
19330
  }
19265
19331
  };
19266
19332
  }
@@ -23922,22 +23988,31 @@ function useChatStorage(options) {
23922
23988
  }
23923
23989
  })() : null;
23924
23990
  let messagesToSend;
23925
- const storedMessages = await getMessagesOp(storageCtx, convId);
23926
- const knownToolCallEventIds = /* @__PURE__ */ new Set();
23927
- for (const msg of storedMessages) {
23928
- if (msg.toolCallEvents) {
23929
- for (const evt of msg.toolCallEvents) {
23930
- if (evt.id) knownToolCallEventIds.add(evt.id);
23931
- }
23932
- }
23933
- }
23991
+ const knownToolCallEventIds = await getToolCallEventIdsOp(storageCtx, convId);
23934
23992
  if (includeHistory) {
23935
- const validMessages = storedMessages.filter((msg) => !msg.error);
23936
- const replayableMessages = prepareToolResultsForReplay(validMessages, {
23937
- fold: foldToolResultsInHistoryRef.current === true,
23938
- exclude: toolResultsHistoryExcludeRef.current,
23939
- placeholder: DISPLAY_CARD_PLACEHOLDER
23940
- });
23993
+ let tail = [];
23994
+ let replayableMessages = [];
23995
+ let beforeMessageId;
23996
+ let boundaryExcludeUniqueIds;
23997
+ for (; ; ) {
23998
+ const page = await getMessagesPageOp(storageCtx, convId, {
23999
+ limit: maxHistoryMessages,
24000
+ beforeMessageId,
24001
+ boundaryExcludeUniqueIds
24002
+ });
24003
+ if (page.length === 0) break;
24004
+ tail = [...page, ...tail];
24005
+ const validMessages = tail.filter((msg) => !msg.error);
24006
+ replayableMessages = prepareToolResultsForReplay(validMessages, {
24007
+ fold: foldToolResultsInHistoryRef.current === true,
24008
+ exclude: toolResultsHistoryExcludeRef.current,
24009
+ placeholder: DISPLAY_CARD_PLACEHOLDER
24010
+ });
24011
+ if (replayableMessages.length >= maxHistoryMessages) break;
24012
+ if (page.length < maxHistoryMessages) break;
24013
+ beforeMessageId = page[0].messageId;
24014
+ boundaryExcludeUniqueIds = tail.filter((msg) => msg.messageId === beforeMessageId).map((msg) => msg.uniqueId);
24015
+ }
23941
24016
  const limitedMessages = replayableMessages.slice(-maxHistoryMessages);
23942
24017
  if (!fileContextForRequest) {
23943
24018
  for (let i = limitedMessages.length - 1; i >= 0; i--) {