@anuma/sdk 6.14.2 → 6.15.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.
Files changed (26) hide show
  1. package/dist/client/index.cjs +13 -0
  2. package/dist/client/index.d.mts +187 -1
  3. package/dist/client/index.d.ts +187 -1
  4. package/dist/client/index.mjs +11 -0
  5. package/dist/expo/{chunk-O25LUKBK.mjs → chunk-4Z2X4UYQ.mjs} +73 -9
  6. package/dist/expo/{chunk-M4JSLFTQ.mjs → chunk-A2G6TRUK.mjs} +1 -1
  7. package/dist/expo/{chunk-JA7CDJCA.mjs → chunk-TNMPAE3V.mjs} +2 -2
  8. package/dist/{react/consolidate-LSDBMQDW.mjs → expo/consolidate-ZM5JNULQ.mjs} +2 -2
  9. package/dist/expo/index.cjs +183 -82
  10. package/dist/expo/index.mjs +118 -77
  11. package/dist/expo/{recall-JYVHWU6N.mjs → recall-FJQTIMYT.mjs} +2 -2
  12. package/dist/react/{chunk-O25LUKBK.mjs → chunk-4Z2X4UYQ.mjs} +73 -9
  13. package/dist/react/{chunk-2XLHV2XW.mjs → chunk-7X7BNMGS.mjs} +1 -1
  14. package/dist/react/{chunk-DNSPMVBY.mjs → chunk-YSL3J4K4.mjs} +2 -2
  15. package/dist/{expo/consolidate-S3KFA2WT.mjs → react/consolidate-7NMKF7UC.mjs} +2 -2
  16. package/dist/react/index.cjs +183 -82
  17. package/dist/react/index.mjs +118 -77
  18. package/dist/react/{recall-2BWBJWGG.mjs → recall-UVVZGRVF.mjs} +2 -2
  19. package/dist/server/{chunk-GP6YY6H5.mjs → chunk-4LPSEROP.mjs} +1 -1
  20. package/dist/server/{chunk-O7TB36P3.mjs → chunk-ARYSRI4Z.mjs} +2 -2
  21. package/dist/server/{chunk-7D3RI25K.mjs → chunk-BSCEZOIW.mjs} +73 -9
  22. package/dist/server/{consolidate-Z2JJP7LW.mjs → consolidate-MSAXASHL.mjs} +2 -2
  23. package/dist/server/index.cjs +183 -82
  24. package/dist/server/index.mjs +118 -77
  25. package/dist/server/{recall-VHU372MK.mjs → recall-OVSSDRYU.mjs} +2 -2
  26. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getLogger
3
- } from "./chunk-7D3RI25K.mjs";
3
+ } from "./chunk-BSCEZOIW.mjs";
4
4
 
5
5
  // src/lib/pii/patterns.ts
6
6
  function luhnCheck(digits) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  callPortalJsonCompletion,
3
3
  getLogger
4
- } from "./chunk-7D3RI25K.mjs";
4
+ } from "./chunk-BSCEZOIW.mjs";
5
5
  import {
6
6
  BASE_URL,
7
7
  __commonJS,
@@ -20923,7 +20923,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
20923
20923
  subQueries = decomp.subQueries;
20924
20924
  }
20925
20925
  }
20926
- const { recall: recall2 } = await import("./recall-VHU372MK.mjs");
20926
+ const { recall: recall2 } = await import("./recall-OVSSDRYU.mjs");
20927
20927
  let recallDegraded = [];
20928
20928
  const result = await recall2(
20929
20929
  query,
@@ -57,6 +57,34 @@ function supportsResponseFormat(model, variant = "json_object") {
57
57
  const allow = variant === "json_schema" ? RESPONSE_SCHEMA_OK : RESPONSE_FORMAT_OK;
58
58
  return model.split("/").some((seg) => allow.has(seg));
59
59
  }
60
+ var RESPONSES_ONLY_MODEL_PREFIXES = ["gpt-5.6"];
61
+ function requiresResponsesTransport(model) {
62
+ return model.split("/").some((seg) => RESPONSES_ONLY_MODEL_PREFIXES.some((prefix) => seg.startsWith(prefix)));
63
+ }
64
+ var CHAT_TO_RESPONSES_FIELDS = {
65
+ max_completion_tokens: "max_output_tokens"
66
+ };
67
+ function responsesExtra(extra) {
68
+ if (!extra) return {};
69
+ const out = {};
70
+ for (const [key, value] of Object.entries(extra)) {
71
+ const target = CHAT_TO_RESPONSES_FIELDS[key] ?? key;
72
+ if (target !== key && target in extra) continue;
73
+ out[target] = value;
74
+ }
75
+ return out;
76
+ }
77
+ var FOREIGN_ENDPOINT_SUFFIX = {
78
+ chat: "/responses",
79
+ responses: "/chat/completions"
80
+ };
81
+ function assertTransportMatchesEndpoint(transport, endpoint) {
82
+ const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
83
+ if (!endpoint.endsWith(foreign)) return;
84
+ throw new Error(
85
+ `endpointOverride "${endpoint}" is the other transport's endpoint, but transport is "${transport}". The request body is built for the transport, so this sends the wrong shape and 400s without retry.`
86
+ );
87
+ }
60
88
  var RETRYABLE_HTTP = /* @__PURE__ */ new Set([408, 409, 425, 429]);
61
89
  function isRetryableStatus(status) {
62
90
  return status >= 500 || RETRYABLE_HTTP.has(status);
@@ -98,13 +126,14 @@ async function resolvePortalAuthHeaders(auth, tag) {
98
126
  }
99
127
  async function callPortalJsonCompletion(req) {
100
128
  const log = getLogger();
101
- let endpoint = "/api/v1/chat/completions";
129
+ let endpoint = req.transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
102
130
  if (req.endpointOverride !== void 0) {
103
131
  const overrideValidation = validateEndpointOverride(req.endpointOverride);
104
132
  if (!overrideValidation.valid) {
105
133
  throw new Error(overrideValidation.message);
106
134
  }
107
135
  endpoint = overrideValidation.endpoint;
136
+ assertTransportMatchesEndpoint(req.transport ?? "chat", endpoint);
108
137
  }
109
138
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
110
139
  const startedAt = Date.now();
@@ -166,8 +195,8 @@ async function attemptPortalJson(req, endpoint) {
166
195
  const authHeaders = await resolvePortalAuthHeaders(req, req.tag);
167
196
  if (authHeaders === null)
168
197
  return { kind: "terminal", code: "auth-unavailable", reason: "auth unavailable (no token)" };
169
- const isAnthropic = req.model.startsWith("anthropic/");
170
- const prefill = isAnthropic ? "{" : "";
198
+ const usesPrefill = req.transport !== "responses" && req.model.startsWith("anthropic/");
199
+ const prefill = usesPrefill ? "{" : "";
171
200
  const messages = [
172
201
  // Every caller of this helper is a first-party BACKGROUND op (fact extraction,
173
202
  // topic sweep, consolidation, classifiers, graph traversal, query
@@ -186,13 +215,34 @@ async function attemptPortalJson(req, endpoint) {
186
215
  const supportsJsonObjectFormat = supportsResponseFormat(req.model);
187
216
  const controller = new AbortController();
188
217
  const timer = setTimeout(() => controller.abort(), timeoutMs);
189
- const requestBody = {
218
+ const requestBody = req.transport === "responses" ? {
219
+ model: req.model,
220
+ // Same system+user pair under the Responses-API field name. No prefill
221
+ // entry to strip: `usesPrefill` is false on this transport, so it was
222
+ // never pushed and the response-side restore is disarmed with it. An
223
+ // earlier version pushed it and sliced it back off here, which left
224
+ // `prefill` truthy and the restore still armed — the fix belonged at
225
+ // the decision, not at the two symptoms.
226
+ input: messages,
227
+ ...req.reasoning && { reasoning: req.reasoning },
228
+ // NO response_format. The Responses API spells structured output
229
+ // differently (`text.format`), and it is unverified against this
230
+ // portal — so this transport relies on the same strict-JSON system
231
+ // prompt plus `extractJsonCandidate` that every response_format-
232
+ // rejecting model already relies on. Verified end to end on dev:
233
+ // luna returns clean parseable JSON here without it. Add the field
234
+ // only with a per-provider check, exactly as RESPONSE_FORMAT_OK was
235
+ // earned.
236
+ ...responsesExtra(req.extra)
237
+ } : {
190
238
  model: req.model,
191
239
  messages,
192
240
  ...supportsJsonObjectFormat && { response_format: { type: "json_object" } },
193
241
  ...req.extra
194
242
  };
195
- if (!supportsJsonObjectFormat) delete requestBody.response_format;
243
+ if (req.transport === "responses" || !supportsJsonObjectFormat) {
244
+ delete requestBody.response_format;
245
+ }
196
246
  let response;
197
247
  try {
198
248
  response = await fetchImpl(`${baseUrl}${endpoint}`, {
@@ -322,13 +372,15 @@ function logPortalUsage(body, tag) {
322
372
  const root = asRecord(body);
323
373
  if (!root) return;
324
374
  const usage = asRecord(root.usage);
325
- const promptTokens = asCount(usage?.prompt_tokens);
326
- const completionTokens = asCount(usage?.completion_tokens);
375
+ const promptTokens = asCount(usage?.prompt_tokens) ?? asCount(usage?.input_tokens);
376
+ const completionTokens = asCount(usage?.completion_tokens) ?? asCount(usage?.output_tokens);
327
377
  if (promptTokens === void 0 && completionTokens === void 0) return;
328
- const cachedTokens = asCount(asRecord(root.portal)?.cached_tokens) ?? asCount(asRecord(usage?.prompt_tokens_details)?.cached_tokens) ?? 0;
378
+ const cachedTokens = asCount(asRecord(root.portal)?.cached_tokens) ?? asCount(asRecord(usage?.prompt_tokens_details)?.cached_tokens) ?? asCount(asRecord(usage?.input_tokens_details)?.cached_tokens) ?? 0;
379
+ const reasoningTokens = asCount(asRecord(usage?.output_tokens_details)?.reasoning_tokens) ?? asCount(asRecord(usage?.completion_tokens_details)?.reasoning_tokens);
329
380
  const parts = [];
330
381
  if (promptTokens !== void 0) parts.push(`prompt=${promptTokens}`);
331
382
  if (completionTokens !== void 0) parts.push(`completion=${completionTokens}`);
383
+ if (reasoningTokens !== void 0) parts.push(`reasoning=${reasoningTokens}`);
332
384
  parts.push(`cached=${cachedTokens}`);
333
385
  getLogger().debug(`[${tag}] usage ${parts.join(" ")}`);
334
386
  } catch {
@@ -336,12 +388,22 @@ function logPortalUsage(body, tag) {
336
388
  }
337
389
  function extractCompletionContent(body) {
338
390
  if (typeof body !== "object" || body === null) return null;
391
+ const responsesText = extractResponsesContent(body);
392
+ if (responsesText !== null) return responsesText;
339
393
  const choices = body.choices;
340
394
  if (!Array.isArray(choices) || choices.length === 0) return null;
341
395
  const first = choices[0];
342
396
  const content = first?.message?.content;
343
397
  return typeof content === "string" ? content : null;
344
398
  }
399
+ function extractResponsesContent(body) {
400
+ const root = asRecord(body);
401
+ if (!root) return null;
402
+ if (typeof root.output_text === "string" && root.output_text !== "") return root.output_text;
403
+ if (!Array.isArray(root.output)) return null;
404
+ const text = 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("");
405
+ return text;
406
+ }
345
407
 
346
408
  export {
347
409
  getLogger,
@@ -350,7 +412,9 @@ export {
350
412
  taskTypeHeader,
351
413
  validateEndpointOverride,
352
414
  supportsResponseFormat,
415
+ requiresResponsesTransport,
353
416
  resolvePortalAuthHeaders,
354
417
  callPortalJsonCompletion,
355
- extractJsonCandidate
418
+ extractJsonCandidate,
419
+ extractCompletionContent
356
420
  };
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  notifyConsolidationFallback,
3
3
  resolvePiiRedactor
4
- } from "./chunk-GP6YY6H5.mjs";
4
+ } from "./chunk-4LPSEROP.mjs";
5
5
  import {
6
6
  callPortalJsonCompletion
7
- } from "./chunk-7D3RI25K.mjs";
7
+ } from "./chunk-BSCEZOIW.mjs";
8
8
  import "./chunk-3RPGXXPZ.mjs";
9
9
 
10
10
  // src/lib/memory/consolidate.ts
@@ -20611,6 +20611,26 @@ function supportsResponseFormat(model, variant = "json_object") {
20611
20611
  const allow = variant === "json_schema" ? RESPONSE_SCHEMA_OK : RESPONSE_FORMAT_OK;
20612
20612
  return model.split("/").some((seg) => allow.has(seg));
20613
20613
  }
20614
+ function requiresResponsesTransport(model) {
20615
+ return model.split("/").some((seg) => RESPONSES_ONLY_MODEL_PREFIXES.some((prefix) => seg.startsWith(prefix)));
20616
+ }
20617
+ function responsesExtra(extra) {
20618
+ if (!extra) return {};
20619
+ const out = {};
20620
+ for (const [key, value] of Object.entries(extra)) {
20621
+ const target = CHAT_TO_RESPONSES_FIELDS[key] ?? key;
20622
+ if (target !== key && target in extra) continue;
20623
+ out[target] = value;
20624
+ }
20625
+ return out;
20626
+ }
20627
+ function assertTransportMatchesEndpoint(transport, endpoint) {
20628
+ const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
20629
+ if (!endpoint.endsWith(foreign)) return;
20630
+ throw new Error(
20631
+ `endpointOverride "${endpoint}" is the other transport's endpoint, but transport is "${transport}". The request body is built for the transport, so this sends the wrong shape and 400s without retry.`
20632
+ );
20633
+ }
20614
20634
  function isRetryableStatus(status) {
20615
20635
  return status >= 500 || RETRYABLE_HTTP.has(status);
20616
20636
  }
@@ -20651,13 +20671,14 @@ async function resolvePortalAuthHeaders(auth, tag) {
20651
20671
  }
20652
20672
  async function callPortalJsonCompletion(req) {
20653
20673
  const log = getLogger();
20654
- let endpoint = "/api/v1/chat/completions";
20674
+ let endpoint = req.transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
20655
20675
  if (req.endpointOverride !== void 0) {
20656
20676
  const overrideValidation = validateEndpointOverride(req.endpointOverride);
20657
20677
  if (!overrideValidation.valid) {
20658
20678
  throw new Error(overrideValidation.message);
20659
20679
  }
20660
20680
  endpoint = overrideValidation.endpoint;
20681
+ assertTransportMatchesEndpoint(req.transport ?? "chat", endpoint);
20661
20682
  }
20662
20683
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
20663
20684
  const startedAt = Date.now();
@@ -20719,8 +20740,8 @@ async function attemptPortalJson(req, endpoint) {
20719
20740
  const authHeaders = await resolvePortalAuthHeaders(req, req.tag);
20720
20741
  if (authHeaders === null)
20721
20742
  return { kind: "terminal", code: "auth-unavailable", reason: "auth unavailable (no token)" };
20722
- const isAnthropic = req.model.startsWith("anthropic/");
20723
- const prefill = isAnthropic ? "{" : "";
20743
+ const usesPrefill = req.transport !== "responses" && req.model.startsWith("anthropic/");
20744
+ const prefill = usesPrefill ? "{" : "";
20724
20745
  const messages = [
20725
20746
  // Every caller of this helper is a first-party BACKGROUND op (fact extraction,
20726
20747
  // topic sweep, consolidation, classifiers, graph traversal, query
@@ -20739,13 +20760,34 @@ async function attemptPortalJson(req, endpoint) {
20739
20760
  const supportsJsonObjectFormat = supportsResponseFormat(req.model);
20740
20761
  const controller = new AbortController();
20741
20762
  const timer = setTimeout(() => controller.abort(), timeoutMs);
20742
- const requestBody = {
20763
+ const requestBody = req.transport === "responses" ? {
20764
+ model: req.model,
20765
+ // Same system+user pair under the Responses-API field name. No prefill
20766
+ // entry to strip: `usesPrefill` is false on this transport, so it was
20767
+ // never pushed and the response-side restore is disarmed with it. An
20768
+ // earlier version pushed it and sliced it back off here, which left
20769
+ // `prefill` truthy and the restore still armed — the fix belonged at
20770
+ // the decision, not at the two symptoms.
20771
+ input: messages,
20772
+ ...req.reasoning && { reasoning: req.reasoning },
20773
+ // NO response_format. The Responses API spells structured output
20774
+ // differently (`text.format`), and it is unverified against this
20775
+ // portal — so this transport relies on the same strict-JSON system
20776
+ // prompt plus `extractJsonCandidate` that every response_format-
20777
+ // rejecting model already relies on. Verified end to end on dev:
20778
+ // luna returns clean parseable JSON here without it. Add the field
20779
+ // only with a per-provider check, exactly as RESPONSE_FORMAT_OK was
20780
+ // earned.
20781
+ ...responsesExtra(req.extra)
20782
+ } : {
20743
20783
  model: req.model,
20744
20784
  messages,
20745
20785
  ...supportsJsonObjectFormat && { response_format: { type: "json_object" } },
20746
20786
  ...req.extra
20747
20787
  };
20748
- if (!supportsJsonObjectFormat) delete requestBody.response_format;
20788
+ if (req.transport === "responses" || !supportsJsonObjectFormat) {
20789
+ delete requestBody.response_format;
20790
+ }
20749
20791
  let response;
20750
20792
  try {
20751
20793
  response = await fetchImpl(`${baseUrl}${endpoint}`, {
@@ -20875,13 +20917,15 @@ function logPortalUsage(body, tag) {
20875
20917
  const root = asRecord(body);
20876
20918
  if (!root) return;
20877
20919
  const usage = asRecord(root.usage);
20878
- const promptTokens = asCount(usage?.prompt_tokens);
20879
- const completionTokens = asCount(usage?.completion_tokens);
20920
+ const promptTokens = asCount(usage?.prompt_tokens) ?? asCount(usage?.input_tokens);
20921
+ const completionTokens = asCount(usage?.completion_tokens) ?? asCount(usage?.output_tokens);
20880
20922
  if (promptTokens === void 0 && completionTokens === void 0) return;
20881
- const cachedTokens = asCount(asRecord(root.portal)?.cached_tokens) ?? asCount(asRecord(usage?.prompt_tokens_details)?.cached_tokens) ?? 0;
20923
+ const cachedTokens = asCount(asRecord(root.portal)?.cached_tokens) ?? asCount(asRecord(usage?.prompt_tokens_details)?.cached_tokens) ?? asCount(asRecord(usage?.input_tokens_details)?.cached_tokens) ?? 0;
20924
+ const reasoningTokens = asCount(asRecord(usage?.output_tokens_details)?.reasoning_tokens) ?? asCount(asRecord(usage?.completion_tokens_details)?.reasoning_tokens);
20882
20925
  const parts = [];
20883
20926
  if (promptTokens !== void 0) parts.push(`prompt=${promptTokens}`);
20884
20927
  if (completionTokens !== void 0) parts.push(`completion=${completionTokens}`);
20928
+ if (reasoningTokens !== void 0) parts.push(`reasoning=${reasoningTokens}`);
20885
20929
  parts.push(`cached=${cachedTokens}`);
20886
20930
  getLogger().debug(`[${tag}] usage ${parts.join(" ")}`);
20887
20931
  } catch {
@@ -20889,13 +20933,23 @@ function logPortalUsage(body, tag) {
20889
20933
  }
20890
20934
  function extractCompletionContent(body) {
20891
20935
  if (typeof body !== "object" || body === null) return null;
20936
+ const responsesText = extractResponsesContent(body);
20937
+ if (responsesText !== null) return responsesText;
20892
20938
  const choices = body.choices;
20893
20939
  if (!Array.isArray(choices) || choices.length === 0) return null;
20894
20940
  const first = choices[0];
20895
20941
  const content = first?.message?.content;
20896
20942
  return typeof content === "string" ? content : null;
20897
20943
  }
20898
- var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, JSON_CONTRACT_REMINDER, RETRYABLE_HTTP;
20944
+ function extractResponsesContent(body) {
20945
+ const root = asRecord(body);
20946
+ if (!root) return null;
20947
+ if (typeof root.output_text === "string" && root.output_text !== "") return root.output_text;
20948
+ if (!Array.isArray(root.output)) return null;
20949
+ 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("");
20950
+ return text12;
20951
+ }
20952
+ var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, JSON_CONTRACT_REMINDER, RESPONSES_ONLY_MODEL_PREFIXES, CHAT_TO_RESPONSES_FIELDS, FOREIGN_ENDPOINT_SUFFIX, RETRYABLE_HTTP;
20899
20953
  var init_portalLlm = __esm({
20900
20954
  "src/lib/memory/portalLlm.ts"() {
20901
20955
  "use strict";
@@ -20907,6 +20961,14 @@ var init_portalLlm = __esm({
20907
20961
  RESPONSE_FORMAT_OK = /* @__PURE__ */ new Set(["openai", "inclusionai", "deepseek"]);
20908
20962
  RESPONSE_SCHEMA_OK = /* @__PURE__ */ new Set(["openai"]);
20909
20963
  JSON_CONTRACT_REMINDER = "Output ONLY the strict JSON object requested. No prose, no explanation, no markdown.";
20964
+ RESPONSES_ONLY_MODEL_PREFIXES = ["gpt-5.6"];
20965
+ CHAT_TO_RESPONSES_FIELDS = {
20966
+ max_completion_tokens: "max_output_tokens"
20967
+ };
20968
+ FOREIGN_ENDPOINT_SUFFIX = {
20969
+ chat: "/responses",
20970
+ responses: "/chat/completions"
20971
+ };
20910
20972
  RETRYABLE_HTTP = /* @__PURE__ */ new Set([408, 409, 425, 429]);
20911
20973
  }
20912
20974
  });
@@ -30675,6 +30737,9 @@ var DEFAULT_BASE_URL2 = BASE_URL;
30675
30737
  var DEFAULT_MODEL4 = "anthropic/claude-sonnet-4-6";
30676
30738
  var DEFAULT_MAX_TOKENS = 4096;
30677
30739
  var REQUEST_TIMEOUT_MS = 6e4;
30740
+ var MIN_RETRY_BUDGET_MS = 2e3;
30741
+ var MIN_RESPONSES_OUTPUT_TOKENS = 2048;
30742
+ var SCHEMA_FALLBACK_SKIP_STATUSES = /* @__PURE__ */ new Set([401, 403, 404, 408, 409, 413, 425, 429]);
30678
30743
  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.
30679
30744
 
30680
30745
  Rules:
@@ -30706,14 +30771,9 @@ ${m.content}`).join("\n\n");
30706
30771
  const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL2;
30707
30772
  const model = options.llmModel ?? DEFAULT_MODEL4;
30708
30773
  const wantsStructured = !!options.responseSchema;
30709
- const sendResponseFormat = wantsStructured && supportsResponseFormat(model, "json_schema");
30774
+ const useResponsesTransport = requiresResponsesTransport(model);
30775
+ const sendResponseFormat = wantsStructured && !useResponsesTransport && supportsResponseFormat(model, "json_schema");
30710
30776
  const basePrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
30711
- const systemPrompt = wantsStructured && !sendResponseFormat ? `${basePrompt}
30712
-
30713
- Respond with ONLY a single JSON object conforming to this JSON Schema, with no prose, comments, or code fences:
30714
- ${JSON.stringify(
30715
- options.responseSchema
30716
- )}` : basePrompt;
30717
30777
  const userMessage = [
30718
30778
  `Question:
30719
30779
  ${trimmed}`,
@@ -30723,85 +30783,121 @@ ${evidence}`
30723
30783
  ].join("\n\n");
30724
30784
  const maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
30725
30785
  const fetchImpl = options.fetchFn ?? fetch;
30786
+ const buildBody = (useResponseFormat) => {
30787
+ const systemPrompt = wantsStructured && !useResponseFormat ? `${basePrompt}
30788
+
30789
+ Respond with ONLY a single JSON object conforming to this JSON Schema, with no prose, comments, or code fences:
30790
+ ${JSON.stringify(
30791
+ options.responseSchema
30792
+ )}` : basePrompt;
30793
+ const messages = [
30794
+ { role: "system", content: systemPrompt },
30795
+ { role: "user", content: userMessage }
30796
+ ];
30797
+ if (useResponsesTransport) {
30798
+ return JSON.stringify({
30799
+ model,
30800
+ // Same system+user pair under the Responses-API field names: `input`
30801
+ // for the turns, `max_output_tokens` for the cap. Sending the
30802
+ // chat-completions spelling here is silently ignored, which caps the
30803
+ // answer at the portal default.
30804
+ input: messages,
30805
+ max_output_tokens: Math.max(maxTokens, MIN_RESPONSES_OUTPUT_TOKENS)
30806
+ });
30807
+ }
30808
+ return JSON.stringify({
30809
+ model,
30810
+ // Modern OpenAI field; the portal reads only `max_completion_tokens`
30811
+ // (the deprecated `max_tokens` is silently ignored → falls back to the
30812
+ // portal's default output cap and truncates the answer).
30813
+ max_completion_tokens: maxTokens,
30814
+ messages,
30815
+ ...useResponseFormat && {
30816
+ response_format: {
30817
+ type: "json_schema",
30818
+ json_schema: { name: "reflect_output", schema: options.responseSchema }
30819
+ }
30820
+ }
30821
+ });
30822
+ };
30726
30823
  const log = getLogger();
30727
30824
  const authHeaders = await resolvePortalAuthHeaders(options, "memory/reflect");
30728
30825
  if (authHeaders === null) return baseResult;
30729
- const controller = new AbortController();
30730
30826
  const deadline = Date.now() + REQUEST_TIMEOUT_MS;
30731
30827
  const remaining = () => Math.max(0, deadline - Date.now());
30732
- const timer = setTimeout(() => controller.abort(), remaining());
30733
- let response;
30734
- try {
30735
- response = await fetchImpl(`${baseUrl}/api/v1/chat/completions`, {
30736
- method: "POST",
30737
- // No task type unless the caller named one — an undeclared task adds no
30738
- // header at all, which is what keeps user-facing reflect() unlabelled.
30739
- headers: {
30740
- ...authHeaders,
30741
- ...taskTypeHeader(options.taskType),
30742
- "Content-Type": "application/json"
30743
- },
30744
- body: JSON.stringify({
30745
- model,
30746
- // Modern OpenAI field; the portal reads only `max_completion_tokens`
30747
- // (the deprecated `max_tokens` is silently ignored → falls back to the
30748
- // portal's default output cap and truncates the answer).
30749
- max_completion_tokens: maxTokens,
30750
- messages: [
30751
- { role: "system", content: systemPrompt },
30752
- { role: "user", content: userMessage }
30753
- ],
30754
- ...sendResponseFormat && {
30755
- response_format: {
30756
- type: "json_schema",
30757
- json_schema: { name: "reflect_output", schema: options.responseSchema }
30758
- }
30759
- }
30760
- }),
30761
- signal: controller.signal
30762
- });
30763
- } catch (err) {
30828
+ const endpoint = useResponsesTransport ? "/api/v1/responses" : "/api/v1/chat/completions";
30829
+ const sendOnce = async (useResponseFormat) => {
30830
+ const controller = new AbortController();
30831
+ const timer = setTimeout(() => controller.abort(), remaining());
30832
+ let response;
30833
+ try {
30834
+ response = await fetchImpl(`${baseUrl}${endpoint}`, {
30835
+ method: "POST",
30836
+ // No task type unless the caller named one — an undeclared task adds no
30837
+ // header at all, which is what keeps user-facing reflect() unlabelled.
30838
+ headers: {
30839
+ ...authHeaders,
30840
+ ...taskTypeHeader(options.taskType),
30841
+ "Content-Type": "application/json"
30842
+ },
30843
+ body: buildBody(useResponseFormat),
30844
+ signal: controller.signal
30845
+ });
30846
+ } catch (err) {
30847
+ clearTimeout(timer);
30848
+ log.warn("[memory/reflect] portal request failed", {
30849
+ err: err instanceof Error ? err.message : String(err),
30850
+ baseUrl
30851
+ });
30852
+ return { kind: "error" };
30853
+ }
30764
30854
  clearTimeout(timer);
30765
- log.warn("[memory/reflect] portal request failed", {
30766
- err: err instanceof Error ? err.message : String(err),
30767
- baseUrl
30855
+ if (!response.ok) {
30856
+ return { kind: "http", status: response.status, statusText: response.statusText };
30857
+ }
30858
+ const bodyTimer = setTimeout(() => controller.abort(), remaining());
30859
+ try {
30860
+ const body = await response.json();
30861
+ clearTimeout(bodyTimer);
30862
+ return { kind: "ok", body };
30863
+ } catch (err) {
30864
+ clearTimeout(bodyTimer);
30865
+ log.warn("[memory/reflect] failed to parse portal response body", {
30866
+ err: err instanceof Error ? err.message : String(err)
30867
+ });
30868
+ return { kind: "error" };
30869
+ }
30870
+ };
30871
+ let attempt = await sendOnce(sendResponseFormat);
30872
+ let schemaFallbackUsed = false;
30873
+ const budgetLeftMs = remaining();
30874
+ if (attempt.kind === "http" && sendResponseFormat && !SCHEMA_FALLBACK_SKIP_STATUSES.has(attempt.status) && budgetLeftMs >= MIN_RETRY_BUDGET_MS) {
30875
+ log.warn("[memory/reflect] response_format json_schema rejected; retrying schema-in-prompt", {
30876
+ status: attempt.status,
30877
+ statusText: attempt.statusText,
30878
+ model,
30879
+ taskType: options.taskType,
30880
+ budgetLeftMs
30768
30881
  });
30769
- return baseResult;
30882
+ schemaFallbackUsed = true;
30883
+ attempt = await sendOnce(false);
30770
30884
  }
30771
- clearTimeout(timer);
30772
- if (!response.ok) {
30885
+ if (attempt.kind === "error") return baseResult;
30886
+ if (attempt.kind === "http") {
30773
30887
  log.warn("[memory/reflect] portal returned non-OK", {
30774
- status: response.status,
30775
- statusText: response.statusText
30888
+ status: attempt.status,
30889
+ statusText: attempt.statusText,
30890
+ // Distinguishes "the fallback ran and still failed" from "never tried".
30891
+ schemaFallbackUsed
30776
30892
  });
30777
30893
  return baseResult;
30778
30894
  }
30779
- const bodyTimer = setTimeout(() => controller.abort(), remaining());
30780
- let body;
30781
- try {
30782
- body = await response.json();
30783
- } catch (err) {
30784
- clearTimeout(bodyTimer);
30785
- log.warn("[memory/reflect] failed to parse portal response body", {
30786
- err: err instanceof Error ? err.message : String(err)
30787
- });
30788
- return baseResult;
30789
- }
30790
- clearTimeout(bodyTimer);
30791
- return parseAnswer(body, baseResult, !!options.responseSchema);
30895
+ return parseAnswer(attempt.body, baseResult, !!options.responseSchema);
30792
30896
  }
30793
30897
  function parseAnswer(body, base, parseSchema) {
30794
30898
  if (typeof body !== "object" || body === null) return base;
30795
30899
  const obj = body;
30796
- const choices = obj.choices;
30797
- let text12 = "";
30798
- if (Array.isArray(choices) && choices.length > 0) {
30799
- const message = choices[0].message;
30800
- if (typeof message === "object" && message !== null) {
30801
- const content = message.content;
30802
- if (typeof content === "string") text12 = content;
30803
- }
30804
- }
30900
+ const text12 = extractCompletionContent(obj) ?? "";
30805
30901
  const usage = obj.usage;
30806
30902
  let structuredOutput;
30807
30903
  if (parseSchema && text12) {
@@ -30813,14 +30909,19 @@ function parseAnswer(body, base, parseSchema) {
30813
30909
  });
30814
30910
  }
30815
30911
  }
30912
+ const promptTokens = usage?.prompt_tokens ?? usage?.input_tokens ?? 0;
30913
+ const completionTokens = usage?.completion_tokens ?? usage?.output_tokens ?? 0;
30816
30914
  return {
30817
30915
  text: text12,
30818
30916
  ...structuredOutput !== void 0 && { structuredOutput },
30819
30917
  basedOn: base.basedOn,
30820
30918
  usage: {
30821
- promptTokens: usage?.prompt_tokens ?? 0,
30822
- completionTokens: usage?.completion_tokens ?? 0,
30823
- totalTokens: usage?.total_tokens ?? 0
30919
+ promptTokens,
30920
+ completionTokens,
30921
+ // Derived when absent: the Responses API reports the two component counts
30922
+ // and does not always send a total, which would otherwise report zero
30923
+ // spend for a call that plainly cost something.
30924
+ totalTokens: usage?.total_tokens ?? promptTokens + completionTokens
30824
30925
  }
30825
30926
  };
30826
30927
  }