sam-coder-cli 1.0.62 → 1.0.64

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/bin/agi-cli.js +163 -4
  2. package/package.json +1 -1
package/bin/agi-cli.js CHANGED
@@ -752,6 +752,133 @@ function normalizeToolCallsFromMessage(message) {
752
752
  return message;
753
753
  }
754
754
 
755
+ // Parse segmented format like <|start|>channel<|message|>...<|end|>
756
+ function parseSegmentedTranscript(text) {
757
+ if (!text || typeof text !== 'string') {
758
+ return { content: text || '', thought: '', recoveredToolCalls: null, segmented: false };
759
+ }
760
+
761
+ const blockRegex = /\<\|start\|>([^<|]+)\<\|message\|>([\s\S]*?)\<\|end\|>/gi;
762
+ let match;
763
+ let visibleParts = [];
764
+ let thoughts = [];
765
+ let commentaryParts = [];
766
+ let segmentedFound = false;
767
+
768
+ while ((match = blockRegex.exec(text)) !== null) {
769
+ const rawRole = (match[1] || '').trim().toLowerCase();
770
+ const body = (match[2] || '').trim();
771
+ if (!rawRole) continue;
772
+ segmentedFound = true;
773
+
774
+ if (rawRole === 'analysis') {
775
+ thoughts.push(body);
776
+ } else if (rawRole === 'commentary') {
777
+ commentaryParts.push(body);
778
+ } else if (rawRole === 'final' || rawRole === 'assistant' || rawRole === 'user' || rawRole === 'system' || rawRole === 'developer') {
779
+ // Prefer 'final' or 'assistant' as visible, but include others to preserve content order
780
+ visibleParts.push(body);
781
+ } else {
782
+ // Unknown channel: treat as visible content
783
+ visibleParts.push(body);
784
+ }
785
+ }
786
+
787
+ // If no blocks matched, return original
788
+ if (visibleParts.length === 0 && thoughts.length === 0 && commentaryParts.length === 0) {
789
+ // Try channel-only segments: <|channel|>X [to=Y] [<|constrain|>Z] <|message|>... (<|end|>|<|call|>|<|return|>)
790
+ const chanRegex = /\<\|channel\|>\s*([a-zA-Z]+)\s*(?:to=([^\s<]+))?\s*(?:\<\|constrain\|>(\w+))?\s*\<\|message\|>([\s\S]*?)(?:\<\|end\|>|\<\|call\|>|\<\|return\|>)/gi;
791
+ let anyChannel = false;
792
+ let commsWithRecipients = [];
793
+ while ((match = chanRegex.exec(text)) !== null) {
794
+ anyChannel = true;
795
+ segmentedFound = true;
796
+ const channel = (match[1] || '').trim().toLowerCase();
797
+ const recipient = (match[2] || '').trim();
798
+ const constraint = (match[3] || '').trim().toLowerCase();
799
+ const body = (match[4] || '').trim();
800
+ if (channel === 'analysis') {
801
+ thoughts.push(body);
802
+ } else if (channel === 'commentary') {
803
+ if (recipient) {
804
+ commsWithRecipients.push({ recipient, constraint, body });
805
+ } else {
806
+ // preamble visible to user per spec
807
+ visibleParts.push(body);
808
+ }
809
+ } else if (channel === 'final') {
810
+ visibleParts.push(body);
811
+ } else {
812
+ visibleParts.push(body);
813
+ }
814
+ }
815
+ // Build recovered tool calls from commentary with recipients
816
+ let recoveredToolCalls = null;
817
+ if (commsWithRecipients.length) {
818
+ recoveredToolCalls = [];
819
+ for (const item of commsWithRecipients) {
820
+ // recipient format like functions.get_weather
821
+ let funcName = item.recipient;
822
+ if (funcName.startsWith('functions.')) {
823
+ funcName = funcName.slice('functions.'.length);
824
+ }
825
+ // parse args
826
+ let args = {};
827
+ if (item.constraint === 'json' || item.body.startsWith('{') || item.body.startsWith('[')) {
828
+ try {
829
+ const parsed = JSON.parse(item.body);
830
+ args = parsed;
831
+ } catch (_) {
832
+ // ignore parse error; leave empty
833
+ }
834
+ }
835
+ recoveredToolCalls.push({
836
+ id: `inline-${recoveredToolCalls.length + 1}`,
837
+ type: 'function',
838
+ function: {
839
+ name: funcName,
840
+ arguments: typeof args === 'string' ? args : JSON.stringify(args)
841
+ }
842
+ });
843
+ }
844
+ }
845
+
846
+ if (!anyChannel) {
847
+ return { content: text, thought: '', recoveredToolCalls: null, segmented: false };
848
+ }
849
+ return {
850
+ content: visibleParts.join('\n\n').trim(),
851
+ thought: thoughts.join('\n\n').trim(),
852
+ recoveredToolCalls: recoveredToolCalls && recoveredToolCalls.length ? recoveredToolCalls : null,
853
+ segmented: segmentedFound
854
+ };
855
+ }
856
+
857
+ // Look for a Reasoning: level outside blocks as a hint
858
+ const reasoningMatch = text.match(/Reasoning:\s*(high|medium|low)/i);
859
+ if (reasoningMatch) {
860
+ thoughts.unshift(`Reasoning level: ${reasoningMatch[1]}`);
861
+ }
862
+
863
+ // Recover tool calls from commentary channels
864
+ let recoveredToolCalls = null;
865
+ if (commentaryParts.length) {
866
+ for (const part of commentaryParts) {
867
+ const found = parseInlineToolCalls(part);
868
+ if (found && found.length) {
869
+ recoveredToolCalls = (recoveredToolCalls || []).concat(found);
870
+ }
871
+ }
872
+ }
873
+
874
+ return {
875
+ content: visibleParts.join('\n\n').trim(),
876
+ thought: thoughts.join('\n\n').trim(),
877
+ recoveredToolCalls: recoveredToolCalls && recoveredToolCalls.length ? recoveredToolCalls : null,
878
+ segmented: segmentedFound
879
+ };
880
+ }
881
+
755
882
  // Call OpenRouter API with tool calling
756
883
  async function callOpenRouter(messages, currentModel, useJson = false) {
757
884
  const apiKey = OPENROUTER_API_KEY;
@@ -810,7 +937,18 @@ async function processQueryWithTools(query, conversation = [], currentModel) {
810
937
  const assistantMessage = response.choices[0].message;
811
938
  // Handle thinking tags and optionally display them
812
939
  if (assistantMessage && typeof assistantMessage.content === 'string') {
813
- const { thought, content } = splitThinking(assistantMessage.content);
940
+ // First handle segmented transcripts, then fallback to <think>
941
+ const segmented = parseSegmentedTranscript(assistantMessage.content);
942
+ let thought = segmented.thought;
943
+ let content = segmented.content;
944
+ if (!segmented.thought && !segmented.recoveredToolCalls) {
945
+ const thinkSplit = splitThinking(assistantMessage.content);
946
+ thought = thought || thinkSplit.thought;
947
+ content = content || thinkSplit.content;
948
+ }
949
+ if (segmented.recoveredToolCalls && (!assistantMessage.tool_calls)) {
950
+ assistantMessage.tool_calls = segmented.recoveredToolCalls;
951
+ }
814
952
  if (thought && SHOW_THOUGHTS) {
815
953
  ui.showThought(thought);
816
954
  }
@@ -834,7 +972,14 @@ async function processQueryWithTools(query, conversation = [], currentModel) {
834
972
  const finalResponseObj = await callOpenRouter(messages, currentModel);
835
973
  const finalAssistantMessage = finalResponseObj.choices[0].message;
836
974
  if (finalAssistantMessage && typeof finalAssistantMessage.content === 'string') {
837
- const { thought, content } = splitThinking(finalAssistantMessage.content);
975
+ const segmented = parseSegmentedTranscript(finalAssistantMessage.content);
976
+ let thought = segmented.thought;
977
+ let content = segmented.content;
978
+ if (!segmented.thought && !segmented.recoveredToolCalls) {
979
+ const thinkSplit = splitThinking(finalAssistantMessage.content);
980
+ thought = thought || thinkSplit.thought;
981
+ content = content || thinkSplit.content;
982
+ }
838
983
  if (thought && SHOW_THOUGHTS) {
839
984
  ui.showThought(thought);
840
985
  }
@@ -859,7 +1004,14 @@ async function processQueryWithTools(query, conversation = [], currentModel) {
859
1004
  const finalResponseObj = await callOpenRouter(messages, currentModel);
860
1005
  const finalAssistantMessage = finalResponseObj.choices[0].message;
861
1006
  if (finalAssistantMessage && typeof finalAssistantMessage.content === 'string') {
862
- const { thought, content } = splitThinking(finalAssistantMessage.content);
1007
+ const segmented = parseSegmentedTranscript(finalAssistantMessage.content);
1008
+ let thought = segmented.thought;
1009
+ let content = segmented.content;
1010
+ if (!segmented.thought && !segmented.recoveredToolCalls) {
1011
+ const thinkSplit = splitThinking(finalAssistantMessage.content);
1012
+ thought = thought || thinkSplit.thought;
1013
+ content = content || thinkSplit.content;
1014
+ }
863
1015
  if (thought && SHOW_THOUGHTS) {
864
1016
  ui.showThought(thought);
865
1017
  }
@@ -1001,7 +1153,14 @@ async function processQuery(query, conversation = [], currentModel) {
1001
1153
  const responseObj = await callOpenRouter(messages, currentModel, true);
1002
1154
  const assistantMessage = responseObj.choices[0].message;
1003
1155
  if (assistantMessage && typeof assistantMessage.content === 'string') {
1004
- const { thought, content } = splitThinking(assistantMessage.content);
1156
+ const segmented = parseSegmentedTranscript(assistantMessage.content);
1157
+ let thought = segmented.thought;
1158
+ let content = segmented.content;
1159
+ if (!segmented.thought && !segmented.recoveredToolCalls) {
1160
+ const thinkSplit = splitThinking(assistantMessage.content);
1161
+ thought = thought || thinkSplit.thought;
1162
+ content = content || thinkSplit.content;
1163
+ }
1005
1164
  if (thought && SHOW_THOUGHTS) {
1006
1165
  ui.showThought(thought);
1007
1166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sam-coder-cli",
3
- "version": "1.0.62",
3
+ "version": "1.0.64",
4
4
  "description": "SAM-CODER: An animated command-line AI assistant with agency capabilities.",
5
5
  "main": "bin/agi-cli.js",
6
6
  "bin": {