@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
@@ -70,6 +70,34 @@ function supportsResponseFormat(model, variant = "json_object") {
70
70
  const allow = variant === "json_schema" ? RESPONSE_SCHEMA_OK : RESPONSE_FORMAT_OK;
71
71
  return model.split("/").some((seg) => allow.has(seg));
72
72
  }
73
+ var RESPONSES_ONLY_MODEL_PREFIXES = ["gpt-5.6"];
74
+ function requiresResponsesTransport(model) {
75
+ return model.split("/").some((seg) => RESPONSES_ONLY_MODEL_PREFIXES.some((prefix) => seg.startsWith(prefix)));
76
+ }
77
+ var CHAT_TO_RESPONSES_FIELDS = {
78
+ max_completion_tokens: "max_output_tokens"
79
+ };
80
+ function responsesExtra(extra) {
81
+ if (!extra) return {};
82
+ const out = {};
83
+ for (const [key, value] of Object.entries(extra)) {
84
+ const target = CHAT_TO_RESPONSES_FIELDS[key] ?? key;
85
+ if (target !== key && target in extra) continue;
86
+ out[target] = value;
87
+ }
88
+ return out;
89
+ }
90
+ var FOREIGN_ENDPOINT_SUFFIX = {
91
+ chat: "/responses",
92
+ responses: "/chat/completions"
93
+ };
94
+ function assertTransportMatchesEndpoint(transport, endpoint) {
95
+ const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
96
+ if (!endpoint.endsWith(foreign)) return;
97
+ throw new Error(
98
+ `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.`
99
+ );
100
+ }
73
101
  var RETRYABLE_HTTP = /* @__PURE__ */ new Set([408, 409, 425, 429]);
74
102
  function isRetryableStatus(status) {
75
103
  return status >= 500 || RETRYABLE_HTTP.has(status);
@@ -111,13 +139,14 @@ async function resolvePortalAuthHeaders(auth, tag) {
111
139
  }
112
140
  async function callPortalJsonCompletion(req) {
113
141
  const log = getLogger();
114
- let endpoint = "/api/v1/chat/completions";
142
+ let endpoint = req.transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
115
143
  if (req.endpointOverride !== void 0) {
116
144
  const overrideValidation = validateEndpointOverride(req.endpointOverride);
117
145
  if (!overrideValidation.valid) {
118
146
  throw new Error(overrideValidation.message);
119
147
  }
120
148
  endpoint = overrideValidation.endpoint;
149
+ assertTransportMatchesEndpoint(req.transport ?? "chat", endpoint);
121
150
  }
122
151
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
123
152
  const startedAt = Date.now();
@@ -179,8 +208,8 @@ async function attemptPortalJson(req, endpoint) {
179
208
  const authHeaders = await resolvePortalAuthHeaders(req, req.tag);
180
209
  if (authHeaders === null)
181
210
  return { kind: "terminal", code: "auth-unavailable", reason: "auth unavailable (no token)" };
182
- const isAnthropic = req.model.startsWith("anthropic/");
183
- const prefill = isAnthropic ? "{" : "";
211
+ const usesPrefill = req.transport !== "responses" && req.model.startsWith("anthropic/");
212
+ const prefill = usesPrefill ? "{" : "";
184
213
  const messages = [
185
214
  // Every caller of this helper is a first-party BACKGROUND op (fact extraction,
186
215
  // topic sweep, consolidation, classifiers, graph traversal, query
@@ -199,13 +228,34 @@ async function attemptPortalJson(req, endpoint) {
199
228
  const supportsJsonObjectFormat = supportsResponseFormat(req.model);
200
229
  const controller = new AbortController();
201
230
  const timer = setTimeout(() => controller.abort(), timeoutMs);
202
- const requestBody = {
231
+ const requestBody = req.transport === "responses" ? {
232
+ model: req.model,
233
+ // Same system+user pair under the Responses-API field name. No prefill
234
+ // entry to strip: `usesPrefill` is false on this transport, so it was
235
+ // never pushed and the response-side restore is disarmed with it. An
236
+ // earlier version pushed it and sliced it back off here, which left
237
+ // `prefill` truthy and the restore still armed — the fix belonged at
238
+ // the decision, not at the two symptoms.
239
+ input: messages,
240
+ ...req.reasoning && { reasoning: req.reasoning },
241
+ // NO response_format. The Responses API spells structured output
242
+ // differently (`text.format`), and it is unverified against this
243
+ // portal — so this transport relies on the same strict-JSON system
244
+ // prompt plus `extractJsonCandidate` that every response_format-
245
+ // rejecting model already relies on. Verified end to end on dev:
246
+ // luna returns clean parseable JSON here without it. Add the field
247
+ // only with a per-provider check, exactly as RESPONSE_FORMAT_OK was
248
+ // earned.
249
+ ...responsesExtra(req.extra)
250
+ } : {
203
251
  model: req.model,
204
252
  messages,
205
253
  ...supportsJsonObjectFormat && { response_format: { type: "json_object" } },
206
254
  ...req.extra
207
255
  };
208
- if (!supportsJsonObjectFormat) delete requestBody.response_format;
256
+ if (req.transport === "responses" || !supportsJsonObjectFormat) {
257
+ delete requestBody.response_format;
258
+ }
209
259
  let response;
210
260
  try {
211
261
  response = await fetchImpl(`${baseUrl}${endpoint}`, {
@@ -335,13 +385,15 @@ function logPortalUsage(body, tag) {
335
385
  const root = asRecord(body);
336
386
  if (!root) return;
337
387
  const usage = asRecord(root.usage);
338
- const promptTokens = asCount(usage?.prompt_tokens);
339
- const completionTokens = asCount(usage?.completion_tokens);
388
+ const promptTokens = asCount(usage?.prompt_tokens) ?? asCount(usage?.input_tokens);
389
+ const completionTokens = asCount(usage?.completion_tokens) ?? asCount(usage?.output_tokens);
340
390
  if (promptTokens === void 0 && completionTokens === void 0) return;
341
- const cachedTokens = asCount(asRecord(root.portal)?.cached_tokens) ?? asCount(asRecord(usage?.prompt_tokens_details)?.cached_tokens) ?? 0;
391
+ 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;
392
+ const reasoningTokens = asCount(asRecord(usage?.output_tokens_details)?.reasoning_tokens) ?? asCount(asRecord(usage?.completion_tokens_details)?.reasoning_tokens);
342
393
  const parts = [];
343
394
  if (promptTokens !== void 0) parts.push(`prompt=${promptTokens}`);
344
395
  if (completionTokens !== void 0) parts.push(`completion=${completionTokens}`);
396
+ if (reasoningTokens !== void 0) parts.push(`reasoning=${reasoningTokens}`);
345
397
  parts.push(`cached=${cachedTokens}`);
346
398
  getLogger().debug(`[${tag}] usage ${parts.join(" ")}`);
347
399
  } catch {
@@ -349,12 +401,22 @@ function logPortalUsage(body, tag) {
349
401
  }
350
402
  function extractCompletionContent(body) {
351
403
  if (typeof body !== "object" || body === null) return null;
404
+ const responsesText = extractResponsesContent(body);
405
+ if (responsesText !== null) return responsesText;
352
406
  const choices = body.choices;
353
407
  if (!Array.isArray(choices) || choices.length === 0) return null;
354
408
  const first = choices[0];
355
409
  const content = first?.message?.content;
356
410
  return typeof content === "string" ? content : null;
357
411
  }
412
+ function extractResponsesContent(body) {
413
+ const root = asRecord(body);
414
+ if (!root) return null;
415
+ if (typeof root.output_text === "string" && root.output_text !== "") return root.output_text;
416
+ if (!Array.isArray(root.output)) return null;
417
+ 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("");
418
+ return text;
419
+ }
358
420
 
359
421
  export {
360
422
  consoleLogger,
@@ -366,7 +428,9 @@ export {
366
428
  withInternalFlowMarker,
367
429
  taskTypeHeader,
368
430
  supportsResponseFormat,
431
+ requiresResponsesTransport,
369
432
  resolvePortalAuthHeaders,
370
433
  callPortalJsonCompletion,
371
- extractJsonCandidate
434
+ extractJsonCandidate,
435
+ extractCompletionContent
372
436
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getLogger
3
- } from "./chunk-O25LUKBK.mjs";
3
+ } from "./chunk-4Z2X4UYQ.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-O25LUKBK.mjs";
4
+ } from "./chunk-4Z2X4UYQ.mjs";
5
5
  import {
6
6
  BASE_URL,
7
7
  createClientConfig
@@ -6885,7 +6885,7 @@ function createMemoryVaultSearchTool(vaultCtx, embeddingOptions, cache, searchOp
6885
6885
  subQueries = decomp.subQueries;
6886
6886
  }
6887
6887
  }
6888
- const { recall: recall2 } = await import("./recall-JYVHWU6N.mjs");
6888
+ const { recall: recall2 } = await import("./recall-FJQTIMYT.mjs");
6889
6889
  let recallDegraded = [];
6890
6890
  const result = await recall2(
6891
6891
  query,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  notifyConsolidationFallback,
3
3
  resolvePiiRedactor
4
- } from "./chunk-2XLHV2XW.mjs";
4
+ } from "./chunk-A2G6TRUK.mjs";
5
5
  import {
6
6
  callPortalJsonCompletion
7
- } from "./chunk-O25LUKBK.mjs";
7
+ } from "./chunk-4Z2X4UYQ.mjs";
8
8
  import "./chunk-GYKJOWEO.mjs";
9
9
 
10
10
  // src/lib/memory/consolidate.ts
@@ -6145,6 +6145,26 @@ function supportsResponseFormat(model, variant = "json_object") {
6145
6145
  const allow = variant === "json_schema" ? RESPONSE_SCHEMA_OK : RESPONSE_FORMAT_OK;
6146
6146
  return model.split("/").some((seg) => allow.has(seg));
6147
6147
  }
6148
+ function requiresResponsesTransport(model) {
6149
+ return model.split("/").some((seg) => RESPONSES_ONLY_MODEL_PREFIXES.some((prefix) => seg.startsWith(prefix)));
6150
+ }
6151
+ function responsesExtra(extra) {
6152
+ if (!extra) return {};
6153
+ const out = {};
6154
+ for (const [key, value] of Object.entries(extra)) {
6155
+ const target = CHAT_TO_RESPONSES_FIELDS[key] ?? key;
6156
+ if (target !== key && target in extra) continue;
6157
+ out[target] = value;
6158
+ }
6159
+ return out;
6160
+ }
6161
+ function assertTransportMatchesEndpoint(transport, endpoint) {
6162
+ const foreign = FOREIGN_ENDPOINT_SUFFIX[transport];
6163
+ if (!endpoint.endsWith(foreign)) return;
6164
+ throw new Error(
6165
+ `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.`
6166
+ );
6167
+ }
6148
6168
  function isRetryableStatus(status) {
6149
6169
  return status >= 500 || RETRYABLE_HTTP.has(status);
6150
6170
  }
@@ -6185,13 +6205,14 @@ async function resolvePortalAuthHeaders(auth, tag) {
6185
6205
  }
6186
6206
  async function callPortalJsonCompletion(req) {
6187
6207
  const log = getLogger();
6188
- let endpoint = "/api/v1/chat/completions";
6208
+ let endpoint = req.transport === "responses" ? "/api/v1/responses" : "/api/v1/chat/completions";
6189
6209
  if (req.endpointOverride !== void 0) {
6190
6210
  const overrideValidation = validateEndpointOverride(req.endpointOverride);
6191
6211
  if (!overrideValidation.valid) {
6192
6212
  throw new Error(overrideValidation.message);
6193
6213
  }
6194
6214
  endpoint = overrideValidation.endpoint;
6215
+ assertTransportMatchesEndpoint(req.transport ?? "chat", endpoint);
6195
6216
  }
6196
6217
  const maxAttempts = Math.max(1, req.maxAttempts ?? 3);
6197
6218
  const startedAt = Date.now();
@@ -6253,8 +6274,8 @@ async function attemptPortalJson(req, endpoint) {
6253
6274
  const authHeaders = await resolvePortalAuthHeaders(req, req.tag);
6254
6275
  if (authHeaders === null)
6255
6276
  return { kind: "terminal", code: "auth-unavailable", reason: "auth unavailable (no token)" };
6256
- const isAnthropic = req.model.startsWith("anthropic/");
6257
- const prefill = isAnthropic ? "{" : "";
6277
+ const usesPrefill = req.transport !== "responses" && req.model.startsWith("anthropic/");
6278
+ const prefill = usesPrefill ? "{" : "";
6258
6279
  const messages = [
6259
6280
  // Every caller of this helper is a first-party BACKGROUND op (fact extraction,
6260
6281
  // topic sweep, consolidation, classifiers, graph traversal, query
@@ -6273,13 +6294,34 @@ async function attemptPortalJson(req, endpoint) {
6273
6294
  const supportsJsonObjectFormat = supportsResponseFormat(req.model);
6274
6295
  const controller = new AbortController();
6275
6296
  const timer = setTimeout(() => controller.abort(), timeoutMs);
6276
- const requestBody = {
6297
+ const requestBody = req.transport === "responses" ? {
6298
+ model: req.model,
6299
+ // Same system+user pair under the Responses-API field name. No prefill
6300
+ // entry to strip: `usesPrefill` is false on this transport, so it was
6301
+ // never pushed and the response-side restore is disarmed with it. An
6302
+ // earlier version pushed it and sliced it back off here, which left
6303
+ // `prefill` truthy and the restore still armed — the fix belonged at
6304
+ // the decision, not at the two symptoms.
6305
+ input: messages,
6306
+ ...req.reasoning && { reasoning: req.reasoning },
6307
+ // NO response_format. The Responses API spells structured output
6308
+ // differently (`text.format`), and it is unverified against this
6309
+ // portal — so this transport relies on the same strict-JSON system
6310
+ // prompt plus `extractJsonCandidate` that every response_format-
6311
+ // rejecting model already relies on. Verified end to end on dev:
6312
+ // luna returns clean parseable JSON here without it. Add the field
6313
+ // only with a per-provider check, exactly as RESPONSE_FORMAT_OK was
6314
+ // earned.
6315
+ ...responsesExtra(req.extra)
6316
+ } : {
6277
6317
  model: req.model,
6278
6318
  messages,
6279
6319
  ...supportsJsonObjectFormat && { response_format: { type: "json_object" } },
6280
6320
  ...req.extra
6281
6321
  };
6282
- if (!supportsJsonObjectFormat) delete requestBody.response_format;
6322
+ if (req.transport === "responses" || !supportsJsonObjectFormat) {
6323
+ delete requestBody.response_format;
6324
+ }
6283
6325
  let response;
6284
6326
  try {
6285
6327
  response = await fetchImpl(`${baseUrl}${endpoint}`, {
@@ -6409,13 +6451,15 @@ function logPortalUsage(body, tag) {
6409
6451
  const root = asRecord(body);
6410
6452
  if (!root) return;
6411
6453
  const usage = asRecord(root.usage);
6412
- const promptTokens = asCount(usage?.prompt_tokens);
6413
- const completionTokens = asCount(usage?.completion_tokens);
6454
+ const promptTokens = asCount(usage?.prompt_tokens) ?? asCount(usage?.input_tokens);
6455
+ const completionTokens = asCount(usage?.completion_tokens) ?? asCount(usage?.output_tokens);
6414
6456
  if (promptTokens === void 0 && completionTokens === void 0) return;
6415
- const cachedTokens = asCount(asRecord(root.portal)?.cached_tokens) ?? asCount(asRecord(usage?.prompt_tokens_details)?.cached_tokens) ?? 0;
6457
+ 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;
6458
+ const reasoningTokens = asCount(asRecord(usage?.output_tokens_details)?.reasoning_tokens) ?? asCount(asRecord(usage?.completion_tokens_details)?.reasoning_tokens);
6416
6459
  const parts = [];
6417
6460
  if (promptTokens !== void 0) parts.push(`prompt=${promptTokens}`);
6418
6461
  if (completionTokens !== void 0) parts.push(`completion=${completionTokens}`);
6462
+ if (reasoningTokens !== void 0) parts.push(`reasoning=${reasoningTokens}`);
6419
6463
  parts.push(`cached=${cachedTokens}`);
6420
6464
  getLogger().debug(`[${tag}] usage ${parts.join(" ")}`);
6421
6465
  } catch {
@@ -6423,13 +6467,23 @@ function logPortalUsage(body, tag) {
6423
6467
  }
6424
6468
  function extractCompletionContent(body) {
6425
6469
  if (typeof body !== "object" || body === null) return null;
6470
+ const responsesText = extractResponsesContent(body);
6471
+ if (responsesText !== null) return responsesText;
6426
6472
  const choices = body.choices;
6427
6473
  if (!Array.isArray(choices) || choices.length === 0) return null;
6428
6474
  const first = choices[0];
6429
6475
  const content = first?.message?.content;
6430
6476
  return typeof content === "string" ? content : null;
6431
6477
  }
6432
- var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, JSON_CONTRACT_REMINDER, RETRYABLE_HTTP;
6478
+ function extractResponsesContent(body) {
6479
+ const root = asRecord(body);
6480
+ if (!root) return null;
6481
+ if (typeof root.output_text === "string" && root.output_text !== "") return root.output_text;
6482
+ if (!Array.isArray(root.output)) return null;
6483
+ 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("");
6484
+ return text12;
6485
+ }
6486
+ var RESPONSE_FORMAT_OK, RESPONSE_SCHEMA_OK, JSON_CONTRACT_REMINDER, RESPONSES_ONLY_MODEL_PREFIXES, CHAT_TO_RESPONSES_FIELDS, FOREIGN_ENDPOINT_SUFFIX, RETRYABLE_HTTP;
6433
6487
  var init_portalLlm = __esm({
6434
6488
  "src/lib/memory/portalLlm.ts"() {
6435
6489
  "use strict";
@@ -6441,6 +6495,14 @@ var init_portalLlm = __esm({
6441
6495
  RESPONSE_FORMAT_OK = /* @__PURE__ */ new Set(["openai", "inclusionai", "deepseek"]);
6442
6496
  RESPONSE_SCHEMA_OK = /* @__PURE__ */ new Set(["openai"]);
6443
6497
  JSON_CONTRACT_REMINDER = "Output ONLY the strict JSON object requested. No prose, no explanation, no markdown.";
6498
+ RESPONSES_ONLY_MODEL_PREFIXES = ["gpt-5.6"];
6499
+ CHAT_TO_RESPONSES_FIELDS = {
6500
+ max_completion_tokens: "max_output_tokens"
6501
+ };
6502
+ FOREIGN_ENDPOINT_SUFFIX = {
6503
+ chat: "/responses",
6504
+ responses: "/chat/completions"
6505
+ };
6444
6506
  RETRYABLE_HTTP = /* @__PURE__ */ new Set([408, 409, 425, 429]);
6445
6507
  }
6446
6508
  });
@@ -17759,6 +17821,9 @@ var DEFAULT_BASE_URL2 = BASE_URL;
17759
17821
  var DEFAULT_MODEL4 = "anthropic/claude-sonnet-4-6";
17760
17822
  var DEFAULT_MAX_TOKENS = 4096;
17761
17823
  var REQUEST_TIMEOUT_MS = 6e4;
17824
+ var MIN_RETRY_BUDGET_MS = 2e3;
17825
+ var MIN_RESPONSES_OUTPUT_TOKENS = 2048;
17826
+ var SCHEMA_FALLBACK_SKIP_STATUSES = /* @__PURE__ */ new Set([401, 403, 404, 408, 409, 413, 425, 429]);
17762
17827
  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.
17763
17828
 
17764
17829
  Rules:
@@ -17790,14 +17855,9 @@ ${m.content}`).join("\n\n");
17790
17855
  const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL2;
17791
17856
  const model = options.llmModel ?? DEFAULT_MODEL4;
17792
17857
  const wantsStructured = !!options.responseSchema;
17793
- const sendResponseFormat = wantsStructured && supportsResponseFormat(model, "json_schema");
17858
+ const useResponsesTransport = requiresResponsesTransport(model);
17859
+ const sendResponseFormat = wantsStructured && !useResponsesTransport && supportsResponseFormat(model, "json_schema");
17794
17860
  const basePrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
17795
- const systemPrompt = wantsStructured && !sendResponseFormat ? `${basePrompt}
17796
-
17797
- Respond with ONLY a single JSON object conforming to this JSON Schema, with no prose, comments, or code fences:
17798
- ${JSON.stringify(
17799
- options.responseSchema
17800
- )}` : basePrompt;
17801
17861
  const userMessage = [
17802
17862
  `Question:
17803
17863
  ${trimmed}`,
@@ -17807,85 +17867,121 @@ ${evidence}`
17807
17867
  ].join("\n\n");
17808
17868
  const maxTokens = options.maxTokens ?? DEFAULT_MAX_TOKENS;
17809
17869
  const fetchImpl = options.fetchFn ?? fetch;
17870
+ const buildBody = (useResponseFormat) => {
17871
+ const systemPrompt = wantsStructured && !useResponseFormat ? `${basePrompt}
17872
+
17873
+ Respond with ONLY a single JSON object conforming to this JSON Schema, with no prose, comments, or code fences:
17874
+ ${JSON.stringify(
17875
+ options.responseSchema
17876
+ )}` : basePrompt;
17877
+ const messages = [
17878
+ { role: "system", content: systemPrompt },
17879
+ { role: "user", content: userMessage }
17880
+ ];
17881
+ if (useResponsesTransport) {
17882
+ return JSON.stringify({
17883
+ model,
17884
+ // Same system+user pair under the Responses-API field names: `input`
17885
+ // for the turns, `max_output_tokens` for the cap. Sending the
17886
+ // chat-completions spelling here is silently ignored, which caps the
17887
+ // answer at the portal default.
17888
+ input: messages,
17889
+ max_output_tokens: Math.max(maxTokens, MIN_RESPONSES_OUTPUT_TOKENS)
17890
+ });
17891
+ }
17892
+ return JSON.stringify({
17893
+ model,
17894
+ // Modern OpenAI field; the portal reads only `max_completion_tokens`
17895
+ // (the deprecated `max_tokens` is silently ignored → falls back to the
17896
+ // portal's default output cap and truncates the answer).
17897
+ max_completion_tokens: maxTokens,
17898
+ messages,
17899
+ ...useResponseFormat && {
17900
+ response_format: {
17901
+ type: "json_schema",
17902
+ json_schema: { name: "reflect_output", schema: options.responseSchema }
17903
+ }
17904
+ }
17905
+ });
17906
+ };
17810
17907
  const log = getLogger();
17811
17908
  const authHeaders = await resolvePortalAuthHeaders(options, "memory/reflect");
17812
17909
  if (authHeaders === null) return baseResult;
17813
- const controller = new AbortController();
17814
17910
  const deadline = Date.now() + REQUEST_TIMEOUT_MS;
17815
17911
  const remaining = () => Math.max(0, deadline - Date.now());
17816
- const timer = setTimeout(() => controller.abort(), remaining());
17817
- let response;
17818
- try {
17819
- response = await fetchImpl(`${baseUrl}/api/v1/chat/completions`, {
17820
- method: "POST",
17821
- // No task type unless the caller named one — an undeclared task adds no
17822
- // header at all, which is what keeps user-facing reflect() unlabelled.
17823
- headers: {
17824
- ...authHeaders,
17825
- ...taskTypeHeader(options.taskType),
17826
- "Content-Type": "application/json"
17827
- },
17828
- body: JSON.stringify({
17829
- model,
17830
- // Modern OpenAI field; the portal reads only `max_completion_tokens`
17831
- // (the deprecated `max_tokens` is silently ignored → falls back to the
17832
- // portal's default output cap and truncates the answer).
17833
- max_completion_tokens: maxTokens,
17834
- messages: [
17835
- { role: "system", content: systemPrompt },
17836
- { role: "user", content: userMessage }
17837
- ],
17838
- ...sendResponseFormat && {
17839
- response_format: {
17840
- type: "json_schema",
17841
- json_schema: { name: "reflect_output", schema: options.responseSchema }
17842
- }
17843
- }
17844
- }),
17845
- signal: controller.signal
17846
- });
17847
- } catch (err) {
17912
+ const endpoint = useResponsesTransport ? "/api/v1/responses" : "/api/v1/chat/completions";
17913
+ const sendOnce = async (useResponseFormat) => {
17914
+ const controller = new AbortController();
17915
+ const timer = setTimeout(() => controller.abort(), remaining());
17916
+ let response;
17917
+ try {
17918
+ response = await fetchImpl(`${baseUrl}${endpoint}`, {
17919
+ method: "POST",
17920
+ // No task type unless the caller named one — an undeclared task adds no
17921
+ // header at all, which is what keeps user-facing reflect() unlabelled.
17922
+ headers: {
17923
+ ...authHeaders,
17924
+ ...taskTypeHeader(options.taskType),
17925
+ "Content-Type": "application/json"
17926
+ },
17927
+ body: buildBody(useResponseFormat),
17928
+ signal: controller.signal
17929
+ });
17930
+ } catch (err) {
17931
+ clearTimeout(timer);
17932
+ log.warn("[memory/reflect] portal request failed", {
17933
+ err: err instanceof Error ? err.message : String(err),
17934
+ baseUrl
17935
+ });
17936
+ return { kind: "error" };
17937
+ }
17848
17938
  clearTimeout(timer);
17849
- log.warn("[memory/reflect] portal request failed", {
17850
- err: err instanceof Error ? err.message : String(err),
17851
- baseUrl
17939
+ if (!response.ok) {
17940
+ return { kind: "http", status: response.status, statusText: response.statusText };
17941
+ }
17942
+ const bodyTimer = setTimeout(() => controller.abort(), remaining());
17943
+ try {
17944
+ const body = await response.json();
17945
+ clearTimeout(bodyTimer);
17946
+ return { kind: "ok", body };
17947
+ } catch (err) {
17948
+ clearTimeout(bodyTimer);
17949
+ log.warn("[memory/reflect] failed to parse portal response body", {
17950
+ err: err instanceof Error ? err.message : String(err)
17951
+ });
17952
+ return { kind: "error" };
17953
+ }
17954
+ };
17955
+ let attempt = await sendOnce(sendResponseFormat);
17956
+ let schemaFallbackUsed = false;
17957
+ const budgetLeftMs = remaining();
17958
+ if (attempt.kind === "http" && sendResponseFormat && !SCHEMA_FALLBACK_SKIP_STATUSES.has(attempt.status) && budgetLeftMs >= MIN_RETRY_BUDGET_MS) {
17959
+ log.warn("[memory/reflect] response_format json_schema rejected; retrying schema-in-prompt", {
17960
+ status: attempt.status,
17961
+ statusText: attempt.statusText,
17962
+ model,
17963
+ taskType: options.taskType,
17964
+ budgetLeftMs
17852
17965
  });
17853
- return baseResult;
17966
+ schemaFallbackUsed = true;
17967
+ attempt = await sendOnce(false);
17854
17968
  }
17855
- clearTimeout(timer);
17856
- if (!response.ok) {
17969
+ if (attempt.kind === "error") return baseResult;
17970
+ if (attempt.kind === "http") {
17857
17971
  log.warn("[memory/reflect] portal returned non-OK", {
17858
- status: response.status,
17859
- statusText: response.statusText
17972
+ status: attempt.status,
17973
+ statusText: attempt.statusText,
17974
+ // Distinguishes "the fallback ran and still failed" from "never tried".
17975
+ schemaFallbackUsed
17860
17976
  });
17861
17977
  return baseResult;
17862
17978
  }
17863
- const bodyTimer = setTimeout(() => controller.abort(), remaining());
17864
- let body;
17865
- try {
17866
- body = await response.json();
17867
- } catch (err) {
17868
- clearTimeout(bodyTimer);
17869
- log.warn("[memory/reflect] failed to parse portal response body", {
17870
- err: err instanceof Error ? err.message : String(err)
17871
- });
17872
- return baseResult;
17873
- }
17874
- clearTimeout(bodyTimer);
17875
- return parseAnswer(body, baseResult, !!options.responseSchema);
17979
+ return parseAnswer(attempt.body, baseResult, !!options.responseSchema);
17876
17980
  }
17877
17981
  function parseAnswer(body, base, parseSchema) {
17878
17982
  if (typeof body !== "object" || body === null) return base;
17879
17983
  const obj = body;
17880
- const choices = obj.choices;
17881
- let text12 = "";
17882
- if (Array.isArray(choices) && choices.length > 0) {
17883
- const message = choices[0].message;
17884
- if (typeof message === "object" && message !== null) {
17885
- const content = message.content;
17886
- if (typeof content === "string") text12 = content;
17887
- }
17888
- }
17984
+ const text12 = extractCompletionContent(obj) ?? "";
17889
17985
  const usage = obj.usage;
17890
17986
  let structuredOutput;
17891
17987
  if (parseSchema && text12) {
@@ -17897,14 +17993,19 @@ function parseAnswer(body, base, parseSchema) {
17897
17993
  });
17898
17994
  }
17899
17995
  }
17996
+ const promptTokens = usage?.prompt_tokens ?? usage?.input_tokens ?? 0;
17997
+ const completionTokens = usage?.completion_tokens ?? usage?.output_tokens ?? 0;
17900
17998
  return {
17901
17999
  text: text12,
17902
18000
  ...structuredOutput !== void 0 && { structuredOutput },
17903
18001
  basedOn: base.basedOn,
17904
18002
  usage: {
17905
- promptTokens: usage?.prompt_tokens ?? 0,
17906
- completionTokens: usage?.completion_tokens ?? 0,
17907
- totalTokens: usage?.total_tokens ?? 0
18003
+ promptTokens,
18004
+ completionTokens,
18005
+ // Derived when absent: the Responses API reports the two component counts
18006
+ // and does not always send a total, which would otherwise report zero
18007
+ // spend for a call that plainly cost something.
18008
+ totalTokens: usage?.total_tokens ?? promptTokens + completionTokens
17908
18009
  }
17909
18010
  };
17910
18011
  }