@cortexkit/pi-magic-context 0.49.4 → 0.49.5

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 (2) hide show
  1. package/dist/index.js +370 -71
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3421,7 +3421,7 @@ function startHostStallProfilerSwitch() {
3421
3421
  // package.json
3422
3422
  var package_default = {
3423
3423
  name: "@cortexkit/pi-magic-context",
3424
- version: "0.49.4",
3424
+ version: "0.49.5",
3425
3425
  type: "module",
3426
3426
  description: "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
3427
3427
  main: "dist/index.js",
@@ -32850,6 +32850,14 @@ var PI_CONTEXT_BUDGET = {
32850
32850
  class PiContextDeadlineError extends Error {
32851
32851
  }
32852
32852
 
32853
+ class PiContextSupersededError extends Error {
32854
+ origin;
32855
+ constructor(message, origin = "context ownership") {
32856
+ super(message);
32857
+ this.origin = origin;
32858
+ }
32859
+ }
32860
+
32853
32861
  class PiContextBudget {
32854
32862
  startedAt;
32855
32863
  now;
@@ -33512,9 +33520,151 @@ function piMatchesChatImages(content, urls) {
33512
33520
  return true;
33513
33521
  }
33514
33522
 
33523
+ // src/pi-dispatch-image-budget.ts
33524
+ function object3(value) {
33525
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
33526
+ }
33527
+ function piImageBudgetPlan(model) {
33528
+ if (!model?.input?.includes("image") || object3(model.compat)?.stripImageInput === true)
33529
+ return;
33530
+ return {
33531
+ count: 0,
33532
+ api: model.api,
33533
+ rows: new Map,
33534
+ hoistToolImages: model.api?.startsWith("google-") === true && object3(model.compat)?.multimodalFunctionResponse !== true || model.api === "bedrock-converse-stream" && model.requiresToolResultImageHoisting === true,
33535
+ hoistErrorImages: model.api === "anthropic-messages" || model.api === "bedrock-converse-stream"
33536
+ };
33537
+ }
33538
+ function inputImage(block, api) {
33539
+ const file = object3(block.providerFile);
33540
+ if (api === "ollama-chat")
33541
+ return { kind: "bytes", value: String(block.data ?? "") };
33542
+ if ((api?.endsWith("responses") && file?.provider === "openai" || api === "anthropic-messages" && file?.provider === "anthropic") && typeof file?.id === "string")
33543
+ return { kind: "file", value: file.id };
33544
+ if (api?.startsWith("google-") && file?.provider === "google" && typeof file.uri === "string")
33545
+ return { kind: "url", value: file.uri };
33546
+ if (api !== "bedrock-converse-stream" && typeof block.url === "string")
33547
+ return { kind: "url", value: block.url };
33548
+ return {
33549
+ kind: "data",
33550
+ value: typeof block.data === "string" ? block.data : "",
33551
+ mime: typeof block.mimeType === "string" ? block.mimeType : ""
33552
+ };
33553
+ }
33554
+ function piRecordImages(plan, row, source) {
33555
+ if (!plan || !source || !["user", "developer", "toolResult"].includes(String(source.role)) || !Array.isArray(source.content))
33556
+ return;
33557
+ const images = [];
33558
+ for (const value of source.content) {
33559
+ const block = object3(value);
33560
+ if (block?.type === "image")
33561
+ images.push(inputImage(block, plan.api));
33562
+ }
33563
+ if (!images.length)
33564
+ return;
33565
+ plan.rows.set(row, {
33566
+ first: plan.count,
33567
+ images,
33568
+ hoisted: source.role === "toolResult" && (plan.hoistToolImages || source.isError === true && plan.hoistErrorImages)
33569
+ });
33570
+ plan.count += images.length;
33571
+ }
33572
+ function piRemainingImages(run, drops) {
33573
+ return run.images.slice(Math.max(0, Math.min(run.images.length, drops - run.first)));
33574
+ }
33575
+ function piWireImages(content, ollamaImages) {
33576
+ if (Array.isArray(ollamaImages))
33577
+ return ollamaImages.map((value) => ({
33578
+ kind: "bytes",
33579
+ value: String(value)
33580
+ }));
33581
+ if (!Array.isArray(content))
33582
+ return [];
33583
+ const images = [];
33584
+ for (const value of content) {
33585
+ const block = object3(value);
33586
+ if (!block)
33587
+ continue;
33588
+ const source = object3(block.source);
33589
+ const inline = object3(block.inlineData);
33590
+ const file = object3(block.fileData);
33591
+ const bedrock = object3(block.image);
33592
+ if (block.type === "image_url" || block.type === "input_image") {
33593
+ if (typeof block.file_id === "string")
33594
+ images.push({ kind: "file", value: block.file_id });
33595
+ else
33596
+ images.push({
33597
+ kind: "url",
33598
+ value: String(typeof block.image_url === "string" ? block.image_url : object3(block.image_url)?.url)
33599
+ });
33600
+ } else if (block.type === "image") {
33601
+ if (source?.type === "file")
33602
+ images.push({ kind: "file", value: String(source.file_id) });
33603
+ else if (source?.type === "url")
33604
+ images.push({ kind: "url", value: String(source.url) });
33605
+ else
33606
+ images.push({
33607
+ kind: "data",
33608
+ value: String(source?.data),
33609
+ mime: String(source?.media_type)
33610
+ });
33611
+ } else if (inline)
33612
+ images.push({
33613
+ kind: "data",
33614
+ value: String(inline.data),
33615
+ mime: String(inline.mimeType)
33616
+ });
33617
+ else if (file)
33618
+ images.push({ kind: "url", value: String(file.fileUri) });
33619
+ else if (bedrock)
33620
+ images.push({
33621
+ kind: "data",
33622
+ value: String(object3(bedrock.source)?.bytes),
33623
+ mime: `image/${bedrock.format}`
33624
+ });
33625
+ }
33626
+ return images;
33627
+ }
33628
+ function piMatchesImages(actual, expected) {
33629
+ if (actual.length !== expected.length)
33630
+ return false;
33631
+ for (let index = 0;index < expected.length; index++) {
33632
+ const a = actual[index];
33633
+ const e = expected[index];
33634
+ if (e.kind === "data" && a.kind === "url") {
33635
+ const prefix = `data:${e.mime};base64,`;
33636
+ if (a.value.length !== prefix.length + e.value.length || !a.value.startsWith(prefix) || !a.value.endsWith(e.value))
33637
+ return false;
33638
+ } else if (a.kind !== e.kind || a.value !== e.value || a.mime?.replace("image/jpg", "image/jpeg") !== e.mime?.replace("image/jpg", "image/jpeg"))
33639
+ return false;
33640
+ }
33641
+ return true;
33642
+ }
33643
+ function piWireImageCount(payload) {
33644
+ const body = object3(payload);
33645
+ const visit = (value) => {
33646
+ if (Array.isArray(value))
33647
+ return value.reduce((n, item) => n + visit(item), 0);
33648
+ const block = object3(value);
33649
+ if (!block)
33650
+ return 0;
33651
+ if (["image", "image_url", "input_image"].includes(String(block.type)) || block.inlineData || block.fileData || block.image)
33652
+ return 1;
33653
+ return [
33654
+ block.content,
33655
+ block.output,
33656
+ block.parts,
33657
+ block.toolResult,
33658
+ block.functionResponse,
33659
+ block.response
33660
+ ].reduce((n, item) => n + visit(item), Array.isArray(block.images) ? block.images.length : 0);
33661
+ };
33662
+ return visit(body?.messages ?? body?.input ?? body?.contents ?? object3(body?.request)?.contents);
33663
+ }
33664
+
33515
33665
  // src/pi-dispatch-responses-history.ts
33516
33666
  var MAX_TEXT_UNITS = 32 * 1024 * 1024;
33517
- function object3(value) {
33667
+ function object4(value) {
33518
33668
  return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
33519
33669
  }
33520
33670
  function wellFormed(text) {
@@ -33529,7 +33679,7 @@ function textIdentity(text, phase) {
33529
33679
  function signaturePhase(block) {
33530
33680
  try {
33531
33681
  if (typeof block.textSignature === "string") {
33532
- const signature = object3(JSON.parse(block.textSignature));
33682
+ const signature = object4(JSON.parse(block.textSignature));
33533
33683
  if (signature?.v === 1 && typeof signature.id === "string")
33534
33684
  return signature.phase;
33535
33685
  }
@@ -33549,17 +33699,17 @@ ${wellFormed(text)}
33549
33699
  return;
33550
33700
  }
33551
33701
  function piResponsesAssistantTexts(value, model) {
33552
- const message = object3(value);
33702
+ const message = object4(value);
33553
33703
  if (!message || !Array.isArray(message.content))
33554
33704
  return;
33555
- if (["length", "error", "aborted"].includes(String(message.stopReason)) && !message.content.some((value) => ["text", "toolCall"].includes(String(object3(value)?.type))))
33705
+ if (["length", "error", "aborted"].includes(String(message.stopReason)) && !message.content.some((value) => ["text", "toolCall"].includes(String(object4(value)?.type))))
33556
33706
  return [];
33557
33707
  const texts = [];
33558
33708
  const blocks = [];
33559
33709
  const sameModel = model !== undefined && typeof model.api === "string" && typeof model.id === "string" && typeof model.provider === "string" && message.api === model.api && message.model === model.id && message.provider === model.provider;
33560
33710
  let units = 0;
33561
33711
  for (const value of message.content) {
33562
- const block = object3(value);
33712
+ const block = object4(value);
33563
33713
  if (block?.type === "toolCall")
33564
33714
  continue;
33565
33715
  if (block?.type === "thinking") {
@@ -33569,7 +33719,7 @@ function piResponsesAssistantTexts(value, model) {
33569
33719
  return;
33570
33720
  if (!block.thinking.trim())
33571
33721
  continue;
33572
- const compat = object3(model.compat);
33722
+ const compat = object4(model.compat);
33573
33723
  if (block.summary === false && (compat?.requiresReasoningContentForToolCalls === true || compat?.requiresReasoningContentForAllAssistantTurns === true))
33574
33724
  return;
33575
33725
  const text = demotedThinking(block.thinking, model);
@@ -33589,23 +33739,23 @@ function piResponsesAssistantTexts(value, model) {
33589
33739
  texts.push(textIdentity(wellFormed(block.text), sameModel ? signaturePhase(block) : undefined));
33590
33740
  blocks.push(block);
33591
33741
  }
33592
- const payload = object3(message.providerPayload);
33742
+ const payload = object4(message.providerPayload);
33593
33743
  if (message.api !== model?.api || typeof model?.id !== "string" || typeof model?.provider !== "string" || message.model !== model?.id || (payload?.provider ?? message.provider) !== model?.provider || payload?.type !== "openaiResponsesHistory" || payload.dt !== true || !Array.isArray(payload.items) || message.content.length !== blocks.length)
33594
33744
  return texts;
33595
33745
  const replay = [];
33596
33746
  for (const value of payload.items) {
33597
- const item = object3(value);
33747
+ const item = object4(value);
33598
33748
  if (item?.type !== "message" || item.role !== "assistant" || !Array.isArray(item.content))
33599
33749
  return texts;
33600
33750
  for (const value of item.content) {
33601
- const part = object3(value);
33751
+ const part = object4(value);
33602
33752
  const block = blocks[replay.length];
33603
33753
  if (!block || part?.type !== "output_text" || typeof part.text !== "string")
33604
33754
  return texts;
33605
33755
  let signature;
33606
33756
  try {
33607
33757
  if (typeof block.textSignature === "string")
33608
- signature = object3(JSON.parse(block.textSignature));
33758
+ signature = object4(JSON.parse(block.textSignature));
33609
33759
  } catch {
33610
33760
  return texts;
33611
33761
  }
@@ -33626,7 +33776,7 @@ function piResponsesWireTexts(value, phase) {
33626
33776
  const texts = [];
33627
33777
  let units = 0;
33628
33778
  for (const partValue of value) {
33629
- const part = object3(partValue);
33779
+ const part = object4(partValue);
33630
33780
  if (part?.type !== "output_text" || typeof part.text !== "string")
33631
33781
  return;
33632
33782
  units += part.text.length;
@@ -33709,11 +33859,28 @@ function piToolCallWireId(id, model, source) {
33709
33859
 
33710
33860
  // src/pi-dispatch-structure.ts
33711
33861
  var MAX_MESSAGES = 1e5;
33712
- function object4(value) {
33862
+ var ORPHAN_NOTE = Symbol("prepared orphan tool result");
33863
+ var TRANSFORM_MESSAGES_APIS = new Set([
33864
+ "openai-completions",
33865
+ "openai-responses",
33866
+ "openrouter",
33867
+ "openai-codex-responses",
33868
+ "azure-openai-responses",
33869
+ "anthropic-messages",
33870
+ "bedrock-converse-stream",
33871
+ "google-generative-ai",
33872
+ "google-gemini-cli",
33873
+ "google-vertex",
33874
+ "ollama-chat",
33875
+ "factory-droid-agent",
33876
+ "devin-agent",
33877
+ "apple-foundation-models"
33878
+ ]);
33879
+ function object5(value) {
33713
33880
  return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
33714
33881
  }
33715
33882
  function messageStructure(value) {
33716
- const message = object4(value);
33883
+ const message = object5(value);
33717
33884
  if (!message)
33718
33885
  return;
33719
33886
  const role = [
@@ -33728,6 +33895,11 @@ function messageStructure(value) {
33728
33895
  if (role !== "user" && role !== "assistant" && role !== "toolResult")
33729
33896
  return;
33730
33897
  const row = { role, calls: [], results: [] };
33898
+ if (ORPHAN_NOTE in message && typeof message.content === "string") {
33899
+ if (message.content.length > 32 * 1024 * 1024)
33900
+ return;
33901
+ row.exactText = message.content;
33902
+ }
33731
33903
  if (typeof message.timestamp === "number")
33732
33904
  row.timestamp = message.timestamp;
33733
33905
  if (role === "toolResult") {
@@ -33737,7 +33909,7 @@ function messageStructure(value) {
33737
33909
  }
33738
33910
  if (role === "assistant" && Array.isArray(message.content)) {
33739
33911
  for (const value of message.content) {
33740
- const block = object4(value);
33912
+ const block = object5(value);
33741
33913
  if (block?.type !== "toolCall" && block?.type !== "tool_use")
33742
33914
  continue;
33743
33915
  if (typeof block.id !== "string")
@@ -33749,35 +33921,41 @@ function messageStructure(value) {
33749
33921
  }
33750
33922
  return row;
33751
33923
  }
33752
- function projectMissingToolResults(messages) {
33924
+ function projectMissingToolResults(messages, api) {
33925
+ const projectOrphans = TRANSFORM_MESSAGES_APIS.has(api ?? "");
33753
33926
  const responsesComponents = new Set;
33754
33927
  const opaqueIds = new Set;
33928
+ const validToolUseIds = projectOrphans ? new Set : undefined;
33755
33929
  const component = (id) => {
33756
33930
  const pipe = id.indexOf("|");
33757
33931
  return pipe > 0 ? id.slice(0, pipe) : id;
33758
33932
  };
33759
33933
  for (const value of messages) {
33760
- const message = object4(value);
33934
+ const message = object5(value);
33761
33935
  if (message?.role !== "assistant" || !Array.isArray(message.content))
33762
33936
  continue;
33763
33937
  for (const value of message.content) {
33764
- const block = object4(value);
33938
+ const block = object5(value);
33765
33939
  if (block?.type !== "toolCall" || typeof block.id !== "string")
33766
33940
  continue;
33767
33941
  if ([
33768
33942
  "openai-responses",
33769
33943
  "azure-openai-responses",
33770
33944
  "openai-codex-responses"
33771
- ].includes(String(message.api)))
33945
+ ].includes(String(message.api))) {
33772
33946
  responsesComponents.add(component(block.id));
33773
- else if (block.id.includes("|"))
33774
- opaqueIds.add(block.id);
33947
+ validToolUseIds?.add(component(block.id));
33948
+ } else {
33949
+ if (block.id.includes("|"))
33950
+ opaqueIds.add(block.id);
33951
+ validToolUseIds?.add(block.id);
33952
+ }
33775
33953
  }
33776
33954
  }
33777
33955
  const pairingId = (id) => !opaqueIds.has(id) && responsesComponents.has(component(id)) ? component(id) : id;
33778
33956
  const lastResult = new Map;
33779
33957
  messages.forEach((value, index) => {
33780
- const message = object4(value);
33958
+ const message = object5(value);
33781
33959
  if (message?.role === "toolResult" && typeof message.toolCallId === "string")
33782
33960
  lastResult.set(pairingId(message.toolCallId), index);
33783
33961
  });
@@ -33808,13 +33986,35 @@ function projectMissingToolResults(messages) {
33808
33986
  };
33809
33987
  for (let index = 0;index < messages.length; index++) {
33810
33988
  const value = messages[index];
33811
- const message = object4(value);
33989
+ const message = object5(value);
33812
33990
  if ((message?.role === "bashExecution" || message?.role === "pythonExecution") && message.excludeFromContext === true)
33813
33991
  continue;
33814
33992
  if (message?.role !== "toolResult")
33815
33993
  flush(message?.timestamp);
33816
33994
  else if (typeof message.toolCallId === "string" && pending.some((call) => pairingId(call.id) === pairingId(String(message.toolCallId))))
33817
33995
  resolved.add(pairingId(message.toolCallId));
33996
+ else if (validToolUseIds && message?.role === "toolResult" && typeof message.toolCallId === "string" && typeof message.toolName === "string" && !validToolUseIds.has(pairingId(message.toolCallId)) && Array.isArray(message.content)) {
33997
+ if (pending.some((call) => !resolved.has(pairingId(call.id))))
33998
+ continue;
33999
+ const parts = message.content.flatMap((value) => {
34000
+ const part = object5(value);
34001
+ return part?.type === "text" && typeof part.text === "string" && part.text.trim() ? [part.text] : [];
34002
+ });
34003
+ if (parts.length) {
34004
+ const error = message.isError ? ' is-error="true"' : "";
34005
+ const note = `<stale-tool-result tool="${message.toolName}" id="${message.toolCallId}"${error}>
34006
+ ${parts.join(`
34007
+ `)}
34008
+ </stale-tool-result>`;
34009
+ projected.push({
34010
+ role: "user",
34011
+ content: api === "ollama-chat" ? note : new TextDecoder().decode(new TextEncoder().encode(note)),
34012
+ timestamp: message.timestamp,
34013
+ [ORPHAN_NOTE]: true
34014
+ });
34015
+ }
34016
+ continue;
34017
+ }
33818
34018
  projected.push(value);
33819
34019
  if (message?.role !== "assistant" || !Array.isArray(message.content))
33820
34020
  continue;
@@ -33824,7 +34024,7 @@ function projectMissingToolResults(messages) {
33824
34024
  if (aborted && typeof timestamp !== "number")
33825
34025
  continue;
33826
34026
  for (const value of message.content) {
33827
- const block = object4(value);
34027
+ const block = object5(value);
33828
34028
  if (block?.type === "toolCall" && typeof block.id === "string")
33829
34029
  pending.push({
33830
34030
  id: block.id,
@@ -33838,7 +34038,7 @@ function projectMissingToolResults(messages) {
33838
34038
  function preparePiDispatch(messages, model, omp) {
33839
34039
  let output = messages;
33840
34040
  const structure = buildPiPreparedStructure(messages, model, omp ? (index, source) => {
33841
- if (object4(source.providerPayload)?.type !== "openaiResponsesHistory")
34041
+ if (object5(source.providerPayload)?.type !== "openaiResponsesHistory")
33842
34042
  return;
33843
34043
  if (output === messages)
33844
34044
  output = messages.slice();
@@ -33851,11 +34051,11 @@ function buildPiPreparedStructure(messages, model, prepareUser) {
33851
34051
  if (!messages.length || messages.length > MAX_MESSAGES)
33852
34052
  return;
33853
34053
  for (let index = 0;index < messages.length; index++) {
33854
- const source = object4(messages[index]);
34054
+ const source = object5(messages[index]);
33855
34055
  if (source?.role === "user" || source?.role === "compactionSummary")
33856
34056
  prepareUser?.(index, source);
33857
34057
  }
33858
- const projectedMessages = projectMissingToolResults(model?.api ? piAgentHistory(messages, model.api) : messages);
34058
+ const projectedMessages = projectMissingToolResults(model?.api ? piAgentHistory(messages, model.api) : messages, model?.api);
33859
34059
  if (projectedMessages.length > MAX_MESSAGES)
33860
34060
  return;
33861
34061
  const rows = [];
@@ -33863,10 +34063,11 @@ function buildPiPreparedStructure(messages, model, prepareUser) {
33863
34063
  const toolIds = new Map;
33864
34064
  const responsesToolIds = new Map;
33865
34065
  const byIndex = new Map;
34066
+ const imageBudget = piImageBudgetPlan(model);
33866
34067
  let valid = true;
33867
34068
  for (let index = 0;index < projectedMessages.length; index++) {
33868
34069
  const message = projectedMessages[index];
33869
- const source = object4(message);
34070
+ const source = object5(message);
33870
34071
  if (rows.length === 0 && (source?.role === "system" || source?.role === "developer"))
33871
34072
  continue;
33872
34073
  if ((source?.role === "bashExecution" || source?.role === "pythonExecution") && source.excludeFromContext === true)
@@ -33890,7 +34091,7 @@ function buildPiPreparedStructure(messages, model, prepareUser) {
33890
34091
  "azure-openai-responses",
33891
34092
  "openai-codex-responses"
33892
34093
  ].includes(model?.api ?? "") && (source?.role === "user" || source?.role === "developer") && (typeof source.content === "string" ? !source.content.trim() : Array.isArray(source.content) && !source.content.some((value) => {
33893
- const block = object4(value);
34094
+ const block = object5(value);
33894
34095
  return block?.type === "image" || block?.type === "text" && typeof block.text === "string" && block.text.trim();
33895
34096
  })))
33896
34097
  continue;
@@ -33916,6 +34117,7 @@ function buildPiPreparedStructure(messages, model, prepareUser) {
33916
34117
  }
33917
34118
  rows.push(row);
33918
34119
  byIndex.set(index, row);
34120
+ piRecordImages(imageBudget, row, source);
33919
34121
  }
33920
34122
  if (!valid)
33921
34123
  return;
@@ -33949,28 +34151,40 @@ function buildPiPreparedStructure(messages, model, prepareUser) {
33949
34151
  }
33950
34152
  }
33951
34153
  const plan = piChatHistoryPlan(projectedMessages, model);
34154
+ let pendingToolImages = [];
33952
34155
  const chatMessages = plan?.flatMap((step) => {
33953
34156
  if (step.kind === "source") {
33954
34157
  const row = byIndex.get(step.index);
34158
+ const images = row && imageBudget?.rows.get(row);
34159
+ if (row?.role === "toolResult" && images)
34160
+ pendingToolImages.push(images);
33955
34161
  return row ? [row] : [];
33956
34162
  }
33957
- return [
33958
- {
33959
- role: "user",
33960
- calls: [],
33961
- results: [],
33962
- ...step.kind === "note" ? { exactText: step.text } : { exactImages: step.urls }
33963
- }
33964
- ];
34163
+ const row = {
34164
+ role: "user",
34165
+ calls: [],
34166
+ results: [],
34167
+ ...step.kind === "note" ? { exactText: step.text } : { exactImages: step.urls }
34168
+ };
34169
+ if (step.kind === "images" && imageBudget && pendingToolImages.length) {
34170
+ imageBudget.rows.set(row, {
34171
+ first: pendingToolImages[0].first,
34172
+ images: pendingToolImages.flatMap((run) => run.images),
34173
+ synthesized: true
34174
+ });
34175
+ pendingToolImages = [];
34176
+ }
34177
+ return [row];
33965
34178
  });
33966
34179
  return {
33967
34180
  messages: rows,
34181
+ ...imageBudget?.count ? { imageBudget } : {},
33968
34182
  ...responsesApi && !responsesMessages ? { structuralSkipReason: "Responses history transform is not modeled" } : model?.api === "openai-completions" && !chatMessages ? { structuralSkipReason: "Chat history transform is not modeled" } : {},
33969
34183
  ...chatMessages ? { chatMessages } : {},
33970
34184
  ...responsesMessages ? { responsesMessages } : {},
33971
34185
  ...model?.api ? { api: model.api } : {},
33972
34186
  ...model?.api?.startsWith("google-") ? {
33973
- googleFunctionIds: object4(model.compat)?.supportsFunctionPartId === true
34187
+ googleFunctionIds: object5(model.compat)?.supportsFunctionPartId === true
33974
34188
  } : {}
33975
34189
  };
33976
34190
  }
@@ -33981,13 +34195,13 @@ function responsesLane(api) {
33981
34195
  "openai-codex-responses"
33982
34196
  ].includes(api ?? "");
33983
34197
  }
33984
- function payloadStructure(payload, ollama = false, responsesItems = false) {
33985
- const body = object4(payload);
34198
+ function payloadStructure(payload, ollama = false, responsesItems = false, imageProjection = false) {
34199
+ const body = object5(payload);
33986
34200
  if (!body)
33987
34201
  return;
33988
34202
  const google = piGoogleConversation(payload);
33989
34203
  if (google)
33990
- return googleStructure(google.contents);
34204
+ return googleStructure(google.contents, imageProjection);
33991
34205
  const bedrock = body.inferenceConfig !== undefined;
33992
34206
  if (body.messages === undefined && typeof body.input === "string")
33993
34207
  return [{ role: "user", calls: [], results: [] }];
@@ -33997,7 +34211,7 @@ function payloadStructure(payload, ollama = false, responsesItems = false) {
33997
34211
  return;
33998
34212
  const rows = [];
33999
34213
  for (const value of messages) {
34000
- const message = object4(value);
34214
+ const message = object5(value);
34001
34215
  if (!message)
34002
34216
  return;
34003
34217
  if (rows.length === 0 && (message.role === "system" || message.role === "developer"))
@@ -34028,6 +34242,8 @@ function payloadStructure(payload, ollama = false, responsesItems = false) {
34028
34242
  if (typeof message.call_id !== "string")
34029
34243
  return;
34030
34244
  rows.push({ role: "toolResult", calls: [], results: [message.call_id] });
34245
+ if (imageProjection)
34246
+ rows[rows.length - 1].images = piWireImages(message.output);
34031
34247
  continue;
34032
34248
  }
34033
34249
  if (message.role === "tool") {
@@ -34037,7 +34253,8 @@ function payloadStructure(payload, ollama = false, responsesItems = false) {
34037
34253
  calls: [],
34038
34254
  results: [""],
34039
34255
  resultNames: [message.tool_name],
34040
- idlessResults: [true]
34256
+ idlessResults: [true],
34257
+ ...imageProjection ? { images: piWireImages(message.content, message.images) } : {}
34041
34258
  });
34042
34259
  continue;
34043
34260
  }
@@ -34046,12 +34263,15 @@ function payloadStructure(payload, ollama = false, responsesItems = false) {
34046
34263
  rows.push({
34047
34264
  role: "toolResult",
34048
34265
  calls: [],
34049
- results: [message.tool_call_id]
34266
+ results: [message.tool_call_id],
34267
+ ...imageProjection ? {
34268
+ images: piWireImages(message.content, ollama ? message.images : undefined)
34269
+ } : {}
34050
34270
  });
34051
34271
  continue;
34052
34272
  }
34053
34273
  if (message.role === "user" && Array.isArray(message.content)) {
34054
- const results = message.content.filter((block) => object4(block)?.type === "tool_result" || bedrock && object4(block)?.toolResult);
34274
+ const results = message.content.filter((block) => object5(block)?.type === "tool_result" || bedrock && object5(block)?.toolResult);
34055
34275
  if (results.length) {
34056
34276
  for (const block of results) {
34057
34277
  const id = bedrock ? block.toolResult?.toolUseId : block.tool_use_id;
@@ -34060,9 +34280,17 @@ function payloadStructure(payload, ollama = false, responsesItems = false) {
34060
34280
  rows.push({
34061
34281
  role: "toolResult",
34062
34282
  calls: [],
34063
- results: [id]
34283
+ results: [id],
34284
+ ...imageProjection ? {
34285
+ images: piWireImages(bedrock ? block.toolResult?.content : block.content)
34286
+ } : {}
34064
34287
  });
34065
34288
  }
34289
+ if (imageProjection) {
34290
+ const images = piWireImages(message.content.filter((block) => !results.includes(block)));
34291
+ if (images.length)
34292
+ rows.push({ role: "user", calls: [], results: [], images });
34293
+ }
34066
34294
  continue;
34067
34295
  }
34068
34296
  }
@@ -34070,6 +34298,8 @@ function payloadStructure(payload, ollama = false, responsesItems = false) {
34070
34298
  if (!row)
34071
34299
  return;
34072
34300
  row.wireContent = message.content;
34301
+ if (imageProjection)
34302
+ row.images = piWireImages(message.content, ollama ? message.images : undefined);
34073
34303
  if (responses && responsesItems && row.role === "assistant") {
34074
34304
  const texts = piResponsesWireTexts(message.content, message.phase);
34075
34305
  if (!texts)
@@ -34088,11 +34318,11 @@ function payloadStructure(payload, ollama = false, responsesItems = false) {
34088
34318
  }
34089
34319
  if (Array.isArray(message.tool_calls)) {
34090
34320
  for (const call of message.tool_calls) {
34091
- const source = object4(call);
34321
+ const source = object5(call);
34092
34322
  if (!source)
34093
34323
  return;
34094
34324
  if (source.id === undefined && ollama) {
34095
- const fn = object4(source.function);
34325
+ const fn = object5(source.function);
34096
34326
  if (typeof fn?.name !== "string")
34097
34327
  return;
34098
34328
  row.calls.push("");
@@ -34123,12 +34353,12 @@ function payloadStructure(payload, ollama = false, responsesItems = false) {
34123
34353
  }
34124
34354
  return rows;
34125
34355
  }
34126
- function googleStructure(contents) {
34356
+ function googleStructure(contents, imageProjection = false) {
34127
34357
  if (contents.length > MAX_MESSAGES)
34128
34358
  return;
34129
34359
  const rows = [];
34130
34360
  for (const value of contents) {
34131
- const message = object4(value);
34361
+ const message = object5(value);
34132
34362
  if (!message || !Array.isArray(message.parts))
34133
34363
  return;
34134
34364
  if (message.role !== "user" && message.role !== "model")
@@ -34136,15 +34366,18 @@ function googleStructure(contents) {
34136
34366
  const row = {
34137
34367
  role: message.role === "model" ? "assistant" : "user",
34138
34368
  calls: [],
34139
- results: []
34369
+ results: [],
34370
+ ...imageProjection ? { images: piWireImages(message.parts) } : {},
34371
+ wireRole: message.role,
34372
+ wireContent: message.parts
34140
34373
  };
34141
34374
  const results = [];
34142
34375
  for (const value of message.parts) {
34143
- const part = object4(value);
34376
+ const part = object5(value);
34144
34377
  if (!part)
34145
34378
  return;
34146
34379
  if (part.functionCall) {
34147
- const call = object4(part.functionCall);
34380
+ const call = object5(part.functionCall);
34148
34381
  if (row.role !== "assistant" || typeof call?.name !== "string" || call.id !== undefined && typeof call.id !== "string")
34149
34382
  return;
34150
34383
  row.calls.push(call.id ?? "");
@@ -34152,20 +34385,23 @@ function googleStructure(contents) {
34152
34385
  row.callNames.push(call.name);
34153
34386
  }
34154
34387
  if (part.functionResponse) {
34155
- const result = object4(part.functionResponse);
34388
+ const result = object5(part.functionResponse);
34156
34389
  if (row.role !== "user" || typeof result?.name !== "string" || result.id !== undefined && typeof result.id !== "string")
34157
34390
  return;
34158
34391
  results.push({
34159
34392
  role: "toolResult",
34160
34393
  calls: [],
34161
34394
  results: [result.id ?? ""],
34162
- resultNames: [result.name]
34395
+ resultNames: [result.name],
34396
+ ...imageProjection ? { images: piWireImages(result.parts) } : {}
34163
34397
  });
34164
34398
  }
34165
34399
  }
34166
- if (results.length)
34400
+ if (results.length) {
34167
34401
  rows.push(...results);
34168
- else
34402
+ if (imageProjection && row.images?.length)
34403
+ rows.push({ role: "user", calls: [], results: [], images: row.images });
34404
+ } else
34169
34405
  rows.push(row);
34170
34406
  }
34171
34407
  return rows;
@@ -34179,6 +34415,19 @@ function wireId(id, anthropic, responses) {
34179
34415
  }
34180
34416
  return id;
34181
34417
  }
34418
+ function matchesUserText(content, text, api, responses, googleOrBedrock) {
34419
+ if (responses || googleOrBedrock) {
34420
+ if (!Array.isArray(content) || content.length !== 1)
34421
+ return false;
34422
+ const block = object5(content[0]);
34423
+ return !!block && block.text === text && (responses ? block.type === "input_text" : Object.keys(block).length === 1);
34424
+ }
34425
+ if ((api === "anthropic-messages" || api === "factory-droid-agent") && Array.isArray(content)) {
34426
+ const block = content.length === 1 ? object5(content[0]) : undefined;
34427
+ return block?.type === "text" && block.text === text;
34428
+ }
34429
+ return content === text;
34430
+ }
34182
34431
  function safeRole(row, readable) {
34183
34432
  if (!readable)
34184
34433
  return "unreadable";
@@ -34194,16 +34443,57 @@ function structureDetail(index, expected, actual, comparison) {
34194
34443
  const actualRow = actual?.[index];
34195
34444
  return `row=${index} expectedRows=${safeCount(expected?.length)} actualRows=${safeCount(actual?.length)} expectedRole=${safeRole(expectedRow, !!expected)} actualRole=${safeRole(actualRow, !!actual)} expectedCalls=${safeCount(expected ? expectedRow?.calls.length ?? 0 : undefined)} actualCalls=${safeCount(actual ? actualRow?.calls.length ?? 0 : undefined)} expectedResults=${safeCount(expected ? expectedRow?.results.length ?? 0 : undefined)} actualResults=${safeCount(actual ? actualRow?.results.length ?? 0 : undefined)} comparison=${comparison}`;
34196
34445
  }
34197
- function piPreparedStructureMismatch(payload, prepared) {
34446
+ function trimPreparedImageRows(rows, budget, drops) {
34447
+ const projected = [];
34448
+ let hoisted = [];
34449
+ const flush = () => {
34450
+ if (hoisted.length)
34451
+ projected.push({ role: "user", calls: [], results: [], images: hoisted });
34452
+ hoisted = [];
34453
+ };
34454
+ for (const row of rows) {
34455
+ if (row.role !== "toolResult")
34456
+ flush();
34457
+ const run = budget.rows.get(row);
34458
+ if (!run || budget.api === "openai-completions" && row.role === "toolResult") {
34459
+ projected.push(row);
34460
+ continue;
34461
+ }
34462
+ const images = piRemainingImages(run, drops);
34463
+ if (run.synthesized) {
34464
+ if (images.length)
34465
+ projected.push({
34466
+ ...row,
34467
+ images,
34468
+ exactImages: row.exactImages?.slice(run.images.length - images.length)
34469
+ });
34470
+ } else if (run.hoisted) {
34471
+ projected.push(row);
34472
+ hoisted.push(...images);
34473
+ } else
34474
+ projected.push({ ...row, images });
34475
+ }
34476
+ flush();
34477
+ return projected;
34478
+ }
34479
+ function piPreparedStructureMismatch(payload, prepared, onImageTrim) {
34198
34480
  try {
34199
34481
  if (prepared?.structuralSkipReason)
34200
34482
  return;
34201
- const body = object4(payload);
34483
+ const imageBudget = prepared?.imageBudget;
34484
+ const actualImages = imageBudget ? piWireImageCount(payload) : 0;
34485
+ const trimmed = imageBudget ? imageBudget.count - actualImages : 0;
34486
+ if (trimmed < 0)
34487
+ return "comparison=image count";
34488
+ const imageProjection = trimmed > 0;
34489
+ const body = object5(payload);
34202
34490
  const ollama = prepared?.api === "ollama-chat" && !!body && Array.isArray(body.messages) && body.input === undefined && body.system === undefined && body.inferenceConfig === undefined;
34203
34491
  const responses = body?.messages === undefined && Array.isArray(body?.input);
34204
- const rows = payloadStructure(payload, ollama, responses && !!prepared?.responsesMessages);
34492
+ const rows = payloadStructure(payload, ollama, responses && !!prepared?.responsesMessages, imageProjection);
34205
34493
  const chat = !!body && Array.isArray(body.messages) && (prepared?.api === "openai-completions" || body.system === undefined) && body.input === undefined && body.inferenceConfig === undefined;
34206
- const expectedRows = responses && prepared?.responsesMessages ? prepared.responsesMessages : chat && prepared?.chatMessages ? prepared.chatMessages : prepared?.messages;
34494
+ let expectedRows = responses && prepared?.responsesMessages ? prepared.responsesMessages : chat && prepared?.chatMessages ? prepared.chatMessages : prepared?.messages;
34495
+ if (expectedRows && imageProjection && imageBudget)
34496
+ expectedRows = trimPreparedImageRows(expectedRows, imageBudget, trimmed);
34207
34497
  if (!rows || !expectedRows)
34208
34498
  return structureDetail(0, expectedRows, rows, "count");
34209
34499
  if (!body)
@@ -34215,12 +34505,14 @@ function piPreparedStructureMismatch(payload, prepared) {
34215
34505
  const actual = rows[i];
34216
34506
  if (actual.role !== expected.role)
34217
34507
  return structureDetail(i, expectedRows, rows, "role");
34508
+ if (imageProjection && !piMatchesImages(actual.images ?? [], expected.images ?? []))
34509
+ return structureDetail(i, expectedRows, rows, "content");
34218
34510
  if (expected.responseTexts !== undefined && (expected.responseTexts.length !== actual.responseTexts?.length || expected.responseTexts.some((text, index) => text !== actual.responseTexts?.[index])))
34219
34511
  return structureDetail(i, expectedRows, rows, "content");
34220
34512
  if (expected.exactText !== undefined || expected.exactImages !== undefined) {
34221
34513
  if (actual.wireRole !== "user")
34222
34514
  return structureDetail(i, expectedRows, rows, "role");
34223
- if (expected.exactText !== undefined && actual.wireContent !== expected.exactText || expected.exactImages !== undefined && !piMatchesChatImages(actual.wireContent, expected.exactImages))
34515
+ if (expected.exactText !== undefined && !matchesUserText(actual.wireContent, expected.exactText, prepared?.api, responses, google || body.inferenceConfig !== undefined) || expected.exactImages !== undefined && !piMatchesChatImages(actual.wireContent, expected.exactImages))
34224
34516
  return structureDetail(i, expectedRows, rows, "content");
34225
34517
  }
34226
34518
  if (actual.calls.length !== expected.calls.length || actual.results.length !== expected.results.length)
@@ -34236,6 +34528,12 @@ function piPreparedStructureMismatch(payload, prepared) {
34236
34528
  }
34237
34529
  if (rows.length !== expectedRows.length)
34238
34530
  return structureDetail(Math.min(rows.length, expectedRows.length), expectedRows, rows, "count");
34531
+ if (imageProjection && imageBudget)
34532
+ onImageTrim?.({
34533
+ planned: imageBudget.count,
34534
+ actual: actualImages,
34535
+ trimmed
34536
+ });
34239
34537
  return;
34240
34538
  } catch {
34241
34539
  return structureDetail(0, undefined, undefined, "count");
@@ -34246,14 +34544,6 @@ function piPreparedStructureMismatch(payload, prepared) {
34246
34544
  var ENTRY_TYPE = "magic-context-turn-refused";
34247
34545
  var RETRY_MESSAGE = "Magic Context could not safely prepare this turn; send your message again.";
34248
34546
  var MAX_UNSENDABLE_NONCES_PER_SESSION = 32;
34249
-
34250
- class PiContextSupersededError extends Error {
34251
- origin;
34252
- constructor(message, origin = "context ownership") {
34253
- super(message);
34254
- this.origin = origin;
34255
- }
34256
- }
34257
34547
  var receiptMaps = new Set;
34258
34548
  function clearPiContextReceipt(sessionId) {
34259
34549
  for (const receipts of receiptMaps)
@@ -34412,6 +34702,8 @@ function registerPiGuardedContext(pi, handler, options = {}) {
34412
34702
  receiptMaps.add(unsendableNonces);
34413
34703
  const structureWarnings = new Map;
34414
34704
  receiptMaps.add(structureWarnings);
34705
+ const imageTrimNotices = new Map;
34706
+ receiptMaps.add(imageTrimNotices);
34415
34707
  const refuse = (ctx, budget, reason, isCurrent) => {
34416
34708
  if (!isCurrent()) {
34417
34709
  log2(`[magic-context][pi] DISCARDED CONTEXT RESULT ${budget.diagnostic()}`, reason);
@@ -34537,7 +34829,12 @@ function registerPiGuardedContext(pi, handler, options = {}) {
34537
34829
  structureWarnings.set(key, true);
34538
34830
  log2(`[magic-context][pi] OMP structural comparison skipped: ${skipReason}; stale prepared requests are not detected on this pass; session=${String(key)}`);
34539
34831
  }
34540
- const structureMismatch = skipReason ? undefined : piPreparedStructureMismatch(payload, active.preparedStructure);
34832
+ const structureMismatch = skipReason ? undefined : piPreparedStructureMismatch(payload, active.preparedStructure, (counts) => {
34833
+ if (imageTrimNotices.has(key))
34834
+ return;
34835
+ imageTrimNotices.set(key, true);
34836
+ log2(`[magic-context][pi] OMP provider images trimmed: planned=${counts.planned} actual=${counts.actual} trimmed=${counts.trimmed}`);
34837
+ });
34541
34838
  if (structureMismatch)
34542
34839
  throw new Error(`provider request does not match this pass's managed dispatch (prepared turn structure); ${structureMismatch}`);
34543
34840
  if (active.dispatched) {
@@ -36293,6 +36590,8 @@ function flushPiServedArrayLedger() {
36293
36590
  item.assertCurrentPass?.();
36294
36591
  return true;
36295
36592
  } catch (error) {
36593
+ if (error instanceof PiContextSupersededError)
36594
+ return false;
36296
36595
  log2("[magic-context][pi] DISCARDED CONTEXT RESULT: queued served capture", error);
36297
36596
  return false;
36298
36597
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/pi-magic-context",
3
- "version": "0.49.4",
3
+ "version": "0.49.5",
4
4
  "type": "module",
5
5
  "description": "Pi and OMP coding agent extension for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",