@ebowwa/coder 0.7.66 → 0.7.68

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.
@@ -7378,6 +7378,69 @@ function convertToolsToOpenAIFormat(tools) {
7378
7378
  }
7379
7379
  }));
7380
7380
  }
7381
+ function convertMessagesToOpenAIFormat(messages) {
7382
+ const result = [];
7383
+ for (const msg of messages) {
7384
+ if (msg.role === "assistant") {
7385
+ const toolCalls = [];
7386
+ const textParts = [];
7387
+ for (const block of msg.content) {
7388
+ if (block.type === "text") {
7389
+ textParts.push(block.text);
7390
+ } else if (block.type === "tool_use") {
7391
+ toolCalls.push({
7392
+ id: block.id,
7393
+ type: "function",
7394
+ function: {
7395
+ name: block.name,
7396
+ arguments: JSON.stringify(block.input)
7397
+ }
7398
+ });
7399
+ } else if (block.type === "thinking" || block.type === "redacted_thinking") {}
7400
+ }
7401
+ const openAIMsg = {
7402
+ role: "assistant",
7403
+ content: textParts.join(`
7404
+ `) || null
7405
+ };
7406
+ if (toolCalls.length > 0) {
7407
+ openAIMsg.tool_calls = toolCalls;
7408
+ }
7409
+ result.push(openAIMsg);
7410
+ } else if (msg.role === "user") {
7411
+ const textParts = [];
7412
+ const toolResults = [];
7413
+ for (const block of msg.content) {
7414
+ if (block.type === "text") {
7415
+ textParts.push(block.text);
7416
+ } else if (block.type === "tool_result") {
7417
+ const contentStr = typeof block.content === "string" ? block.content : block.content.map((c) => c.type === "text" ? c.text : JSON.stringify(c)).join(`
7418
+ `);
7419
+ toolResults.push({
7420
+ tool_use_id: block.tool_use_id,
7421
+ content: contentStr,
7422
+ is_error: block.is_error
7423
+ });
7424
+ }
7425
+ }
7426
+ if (textParts.length > 0) {
7427
+ result.push({
7428
+ role: "user",
7429
+ content: textParts.join(`
7430
+ `)
7431
+ });
7432
+ }
7433
+ for (const tr of toolResults) {
7434
+ result.push({
7435
+ role: "tool",
7436
+ tool_call_id: tr.tool_use_id,
7437
+ content: tr.content
7438
+ });
7439
+ }
7440
+ }
7441
+ }
7442
+ return result;
7443
+ }
7381
7444
  function calculateCost2(model, usage) {
7382
7445
  return calculateCost(model, usage);
7383
7446
  }
@@ -7771,20 +7834,6 @@ async function createMessageStream(messages, options) {
7771
7834
  signal
7772
7835
  } = options;
7773
7836
  const startTime = Date.now();
7774
- const cachedMessages = buildCachedMessages(messages, cacheConfig);
7775
- const cachedSystemPrompt = buildSystemPrompt(systemPrompt, cacheConfig);
7776
- const request = {
7777
- model,
7778
- max_tokens: maxTokens,
7779
- messages: cachedMessages.map((m) => ({
7780
- role: m.role,
7781
- content: m.content
7782
- })),
7783
- stream: true
7784
- };
7785
- if (cachedSystemPrompt) {
7786
- request.system = cachedSystemPrompt;
7787
- }
7788
7837
  const providerInfo = resolveProvider(model);
7789
7838
  let apiEndpoint;
7790
7839
  let headers;
@@ -7814,6 +7863,37 @@ async function createMessageStream(messages, options) {
7814
7863
  "anthropic-version": "2023-06-01"
7815
7864
  };
7816
7865
  }
7866
+ const cachedMessages = buildCachedMessages(messages, cacheConfig);
7867
+ const cachedSystemPrompt = buildSystemPrompt(systemPrompt, cacheConfig);
7868
+ let requestMessages;
7869
+ if (apiFormat === "openai") {
7870
+ const openAIMessages = convertMessagesToOpenAIFormat(cachedMessages);
7871
+ if (cachedSystemPrompt) {
7872
+ const systemText = typeof cachedSystemPrompt === "string" ? cachedSystemPrompt : cachedSystemPrompt.map((b) => b.text).join(`
7873
+
7874
+ `);
7875
+ requestMessages = [
7876
+ { role: "system", content: systemText },
7877
+ ...openAIMessages
7878
+ ];
7879
+ } else {
7880
+ requestMessages = openAIMessages;
7881
+ }
7882
+ } else {
7883
+ requestMessages = cachedMessages.map((m) => ({
7884
+ role: m.role,
7885
+ content: m.content
7886
+ }));
7887
+ }
7888
+ const request = {
7889
+ model,
7890
+ max_tokens: maxTokens,
7891
+ messages: requestMessages,
7892
+ stream: true
7893
+ };
7894
+ if (cachedSystemPrompt && apiFormat === "anthropic") {
7895
+ request.system = cachedSystemPrompt;
7896
+ }
7817
7897
  if (tools && tools.length > 0) {
7818
7898
  if (apiFormat === "openai") {
7819
7899
  request.tools = convertToolsToOpenAIFormat(tools);
@@ -11249,6 +11329,84 @@ var init_spinner_frames = __esm(() => {
11249
11329
  ];
11250
11330
  });
11251
11331
 
11332
+ // packages/src/interfaces/ui/terminal/cli/interactive/scroll-handler.ts
11333
+ function handleScrollEvent(event, currentOffset, totalMessages, config = {}) {
11334
+ const { pageScrollAmount, lineScrollAmount } = { ...DEFAULT_SCROLL_CONFIG, ...config };
11335
+ const maxScroll = Math.max(0, Math.max(totalMessages - 1, 10));
11336
+ if (process.env.CODER_DEBUG_SCROLL === "1") {
11337
+ console.error("[ScrollHandler] Event:", {
11338
+ code: event.code,
11339
+ shift: event.shift,
11340
+ ctrl: event.ctrl,
11341
+ alt: event.alt,
11342
+ is_special: event.is_special
11343
+ });
11344
+ }
11345
+ if (KeyEvents.isPageUp(event)) {
11346
+ if (process.env.CODER_DEBUG_SCROLL === "1") {
11347
+ console.error("[ScrollHandler] PageUp detected, scrolling up");
11348
+ }
11349
+ return { handled: true, newOffset: Math.min(currentOffset + pageScrollAmount, maxScroll) };
11350
+ }
11351
+ if (KeyEvents.isPageDown(event)) {
11352
+ if (process.env.CODER_DEBUG_SCROLL === "1") {
11353
+ console.error("[ScrollHandler] PageDown detected, scrolling down");
11354
+ }
11355
+ return { handled: true, newOffset: Math.max(0, currentOffset - pageScrollAmount) };
11356
+ }
11357
+ if (KeyEvents.isHome(event)) {
11358
+ if (process.env.CODER_DEBUG_SCROLL === "1") {
11359
+ console.error("[ScrollHandler] Home detected, resetting to bottom");
11360
+ }
11361
+ return { handled: true, newOffset: 0 };
11362
+ }
11363
+ if (KeyEvents.isUp(event) && event.alt) {
11364
+ if (process.env.CODER_DEBUG_SCROLL === "1") {
11365
+ console.error("[ScrollHandler] Alt+Up detected, scrolling up");
11366
+ }
11367
+ return { handled: true, newOffset: Math.min(currentOffset + lineScrollAmount, maxScroll) };
11368
+ }
11369
+ if (KeyEvents.isDown(event) && event.alt) {
11370
+ if (process.env.CODER_DEBUG_SCROLL === "1") {
11371
+ console.error("[ScrollHandler] Alt+Down detected, scrolling down");
11372
+ }
11373
+ return { handled: true, newOffset: Math.max(0, currentOffset - lineScrollAmount) };
11374
+ }
11375
+ if (KeyEvents.isUp(event) && event.ctrl) {
11376
+ if (process.env.CODER_DEBUG_SCROLL === "1") {
11377
+ console.error("[ScrollHandler] Ctrl+Up detected, scrolling up");
11378
+ }
11379
+ return { handled: true, newOffset: Math.min(currentOffset + lineScrollAmount, maxScroll) };
11380
+ }
11381
+ if (KeyEvents.isDown(event) && event.ctrl) {
11382
+ if (process.env.CODER_DEBUG_SCROLL === "1") {
11383
+ console.error("[ScrollHandler] Ctrl+Down detected, scrolling down");
11384
+ }
11385
+ return { handled: true, newOffset: Math.max(0, currentOffset - lineScrollAmount) };
11386
+ }
11387
+ if (KeyEvents.isUp(event) && event.shift) {
11388
+ if (process.env.CODER_DEBUG_SCROLL === "1") {
11389
+ console.error("[ScrollHandler] Shift+Up detected, scrolling up");
11390
+ }
11391
+ return { handled: true, newOffset: Math.min(currentOffset + lineScrollAmount, maxScroll) };
11392
+ }
11393
+ if (KeyEvents.isDown(event) && event.shift) {
11394
+ if (process.env.CODER_DEBUG_SCROLL === "1") {
11395
+ console.error("[ScrollHandler] Shift+Down detected, scrolling down");
11396
+ }
11397
+ return { handled: true, newOffset: Math.max(0, currentOffset - lineScrollAmount) };
11398
+ }
11399
+ return { handled: false, newOffset: currentOffset };
11400
+ }
11401
+ var DEFAULT_SCROLL_CONFIG;
11402
+ var init_scroll_handler = __esm(() => {
11403
+ init_input_handler();
11404
+ DEFAULT_SCROLL_CONFIG = {
11405
+ pageScrollAmount: 3,
11406
+ lineScrollAmount: 1
11407
+ };
11408
+ });
11409
+
11252
11410
  // packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts
11253
11411
  import process3 from "process";
11254
11412
  function createInitialState() {
@@ -11399,6 +11557,23 @@ class InteractiveRunner {
11399
11557
  this.state = { ...this.state, inputValue: "", cursorPos: 0 };
11400
11558
  return true;
11401
11559
  }
11560
+ if (process3.env.CODER_DEBUG_SCROLL === "1") {
11561
+ console.error("[InteractiveRunner] Key event:", {
11562
+ code: event.code,
11563
+ shift: event.shift,
11564
+ ctrl: event.ctrl,
11565
+ alt: event.alt,
11566
+ is_special: event.is_special
11567
+ });
11568
+ }
11569
+ const scrollResult = handleScrollEvent(event, this.state.scrollOffset, this.messageStore.messages.length);
11570
+ if (process3.env.CODER_DEBUG_SCROLL === "1") {
11571
+ console.error("[InteractiveRunner] Scroll result:", scrollResult);
11572
+ }
11573
+ if (scrollResult.handled) {
11574
+ this.state = { ...this.state, scrollOffset: scrollResult.newOffset };
11575
+ return true;
11576
+ }
11402
11577
  if (KeyEvents.isUp(event)) {
11403
11578
  return this._handleHistoryUp();
11404
11579
  }
@@ -11696,18 +11871,19 @@ class InteractiveRunner {
11696
11871
  })) : [];
11697
11872
  return {
11698
11873
  messages: renderMessages,
11699
- inputValue,
11700
- cursorPos,
11701
- statusText,
11702
- isLoading,
11703
- streamingText,
11874
+ input_value: inputValue,
11875
+ cursor_pos: cursorPos,
11876
+ status_text: statusText,
11877
+ is_loading: isLoading,
11878
+ streaming_text: streamingText,
11704
11879
  model: this.props.model,
11705
- showHelp: helpMode,
11706
- helpText,
11707
- searchMode: sessionSelectMode,
11708
- searchQuery: "",
11709
- searchResults,
11710
- searchSelected: 0
11880
+ show_help: helpMode,
11881
+ help_text: helpText,
11882
+ search_mode: sessionSelectMode,
11883
+ search_query: "",
11884
+ search_results: searchResults,
11885
+ search_selected: 0,
11886
+ scroll_offset: this.state.scrollOffset
11711
11887
  };
11712
11888
  }
11713
11889
  _getHelpText(section) {
@@ -11809,6 +11985,7 @@ var init_interactive_runner = __esm(() => {
11809
11985
  init_native();
11810
11986
  init_spinner_frames();
11811
11987
  init_input_handler();
11988
+ init_scroll_handler();
11812
11989
  init_types();
11813
11990
  });
11814
11991
 
@@ -34325,3 +34502,6 @@ if (checkPtyWrapper2()) {
34325
34502
  process5.exit(1);
34326
34503
  });
34327
34504
  }
34505
+
34506
+ //# debugId=7897AF5E6C87CE9064756E2164756E21
34507
+ //# sourceMappingURL=bootstrap.js.map
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ebowwa/coder",
3
- "version": "0.7.66",
3
+ "version": "0.7.68",
4
4
  "description": "AI-powered terminal coding assistant",
5
5
  "author": "ebowwa",
6
6
  "license": "MIT",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "scripts": {
26
26
  "build": "bun run build:native && bun run build:ts && bun run link",
27
- "build:ts": "bun build ./packages/src/index.ts ./packages/src/interfaces/ui/terminal/cli/bootstrap.ts --outdir ./dist --target bun --external @ebowwa/coder-native && cp -r native dist/ && mkdir -p dist/interfaces/ui/terminal && cp -r native dist/interfaces/ui/terminal/ && mv dist/interfaces/ui/terminal/cli/bootstrap.js dist/interfaces/ui/terminal/cli/index.js",
27
+ "build:ts": "bun build ./packages/src/index.ts ./packages/src/interfaces/ui/terminal/cli/bootstrap.ts --outdir ./dist --target bun --external @ebowwa/coder-native --sourcemap && cp -r native dist/ && mkdir -p dist/interfaces/ui/terminal && cp -r native dist/interfaces/ui/terminal/ && mv dist/interfaces/ui/terminal/cli/bootstrap.js dist/interfaces/ui/terminal/cli/index.js",
28
28
  "build:native": "cd packages/rust && cargo build --release --target-dir ./target && cd ../.. && bun run build:napi-artifacts",
29
29
  "build:napi-artifacts": "napi build --platform --release --target-dir packages/rust/target --manifest-path packages/rust/Cargo.toml --output-dir native --js-package-name @ebowwa/coder-native --const-enum --strip --dts index.d.ts",
30
30
  "link": "bun link || npm link",
@@ -63,6 +63,115 @@ function convertToolsToOpenAIFormat(tools: APITool[]): unknown[] {
63
63
  }));
64
64
  }
65
65
 
66
+ /**
67
+ * OpenAI-format message types
68
+ */
69
+ interface OpenAIToolCall {
70
+ id: string;
71
+ type: "function";
72
+ function: {
73
+ name: string;
74
+ arguments: string;
75
+ };
76
+ }
77
+
78
+ interface OpenAIMessage {
79
+ role: "user" | "assistant" | "tool" | "system";
80
+ content: string | null;
81
+ tool_calls?: OpenAIToolCall[];
82
+ tool_call_id?: string;
83
+ name?: string;
84
+ }
85
+
86
+ /**
87
+ * Convert Anthropic-style messages to OpenAI-style messages
88
+ *
89
+ * Key conversions:
90
+ * 1. Assistant messages with tool_use → add tool_calls array
91
+ * 2. User messages with tool_result → separate role: "tool" messages
92
+ *
93
+ * This is required because OpenAI-format APIs (Zhipu, etc.) don't understand
94
+ * Anthropic's tool_result content block type.
95
+ */
96
+ function convertMessagesToOpenAIFormat(messages: Message[]): OpenAIMessage[] {
97
+ const result: OpenAIMessage[] = [];
98
+
99
+ for (const msg of messages) {
100
+ if (msg.role === "assistant") {
101
+ // Assistant message - check for tool_use blocks
102
+ const toolCalls: OpenAIToolCall[] = [];
103
+ const textParts: string[] = [];
104
+
105
+ for (const block of msg.content) {
106
+ if (block.type === "text") {
107
+ textParts.push(block.text);
108
+ } else if (block.type === "tool_use") {
109
+ toolCalls.push({
110
+ id: block.id,
111
+ type: "function",
112
+ function: {
113
+ name: block.name,
114
+ arguments: JSON.stringify(block.input),
115
+ },
116
+ });
117
+ } else if (block.type === "thinking" || block.type === "redacted_thinking") {
118
+ // Skip thinking blocks in OpenAI format (not supported)
119
+ }
120
+ }
121
+
122
+ const openAIMsg: OpenAIMessage = {
123
+ role: "assistant",
124
+ content: textParts.join("\n") || null,
125
+ };
126
+
127
+ if (toolCalls.length > 0) {
128
+ openAIMsg.tool_calls = toolCalls;
129
+ }
130
+
131
+ result.push(openAIMsg);
132
+ } else if (msg.role === "user") {
133
+ // User message - check for tool_result blocks
134
+ const textParts: string[] = [];
135
+ const toolResults: { tool_use_id: string; content: string; is_error?: boolean }[] = [];
136
+
137
+ for (const block of msg.content) {
138
+ if (block.type === "text") {
139
+ textParts.push(block.text);
140
+ } else if (block.type === "tool_result") {
141
+ // Extract content as string
142
+ const contentStr = typeof block.content === "string"
143
+ ? block.content
144
+ : block.content.map(c => c.type === "text" ? c.text : JSON.stringify(c)).join("\n");
145
+ toolResults.push({
146
+ tool_use_id: block.tool_use_id,
147
+ content: contentStr,
148
+ is_error: block.is_error,
149
+ });
150
+ }
151
+ }
152
+
153
+ // Add text content as user message if present
154
+ if (textParts.length > 0) {
155
+ result.push({
156
+ role: "user",
157
+ content: textParts.join("\n"),
158
+ });
159
+ }
160
+
161
+ // Add each tool result as a separate "tool" role message
162
+ for (const tr of toolResults) {
163
+ result.push({
164
+ role: "tool",
165
+ tool_call_id: tr.tool_use_id,
166
+ content: tr.content,
167
+ });
168
+ }
169
+ }
170
+ }
171
+
172
+ return result;
173
+ }
174
+
66
175
  export interface StreamOptions {
67
176
  apiKey: string;
68
177
  model?: string;
@@ -647,30 +756,7 @@ export async function createMessageStream(
647
756
 
648
757
  const startTime = Date.now();
649
758
 
650
- // Build cached messages
651
- const cachedMessages = buildCachedMessages(messages, cacheConfig);
652
-
653
- // Build system prompt with cache control
654
- const cachedSystemPrompt = buildSystemPrompt(systemPrompt, cacheConfig);
655
-
656
- // Build request
657
- const request: APIRequest = {
658
- model,
659
- max_tokens: maxTokens,
660
- messages: cachedMessages.map((m) => ({
661
- role: m.role,
662
- content: m.content,
663
- })),
664
- stream: true,
665
- };
666
-
667
- if (cachedSystemPrompt) {
668
- request.system = cachedSystemPrompt;
669
- }
670
-
671
- // Tools will be set after determining API format (for format conversion)
672
-
673
- // Resolve provider based on model name
759
+ // Resolve provider FIRST to determine API format
674
760
  const providerInfo = resolveProvider(model);
675
761
 
676
762
  // Determine API endpoint and headers based on provider
@@ -710,6 +796,52 @@ export async function createMessageStream(
710
796
  };
711
797
  }
712
798
 
799
+ // Build cached messages
800
+ const cachedMessages = buildCachedMessages(messages, cacheConfig);
801
+
802
+ // Build system prompt with cache control
803
+ const cachedSystemPrompt = buildSystemPrompt(systemPrompt, cacheConfig);
804
+
805
+ // Build request with format-appropriate message conversion
806
+ let requestMessages: unknown;
807
+ if (apiFormat === "openai") {
808
+ // Convert to OpenAI format (handles tool_use and tool_result properly)
809
+ const openAIMessages = convertMessagesToOpenAIFormat(cachedMessages);
810
+
811
+ // Add system prompt as first message for OpenAI format
812
+ if (cachedSystemPrompt) {
813
+ const systemText = typeof cachedSystemPrompt === "string"
814
+ ? cachedSystemPrompt
815
+ : cachedSystemPrompt.map(b => b.text).join("\n\n");
816
+ requestMessages = [
817
+ { role: "system", content: systemText },
818
+ ...openAIMessages,
819
+ ];
820
+ } else {
821
+ requestMessages = openAIMessages;
822
+ }
823
+ } else {
824
+ // Keep Anthropic format
825
+ requestMessages = cachedMessages.map((m) => ({
826
+ role: m.role,
827
+ content: m.content,
828
+ }));
829
+ }
830
+
831
+ // Build request
832
+ const request: APIRequest = {
833
+ model,
834
+ max_tokens: maxTokens,
835
+ messages: requestMessages as Message[],
836
+ stream: true,
837
+ };
838
+
839
+ if (cachedSystemPrompt && apiFormat === "anthropic") {
840
+ // Anthropic format uses separate system field
841
+ // OpenAI format already has system as first message
842
+ request.system = cachedSystemPrompt;
843
+ }
844
+
713
845
  // Set tools with format conversion if needed
714
846
  if (tools && tools.length > 0) {
715
847
  if (apiFormat === "openai") {
@@ -23,6 +23,7 @@ import type { InputEvent, NativeRendererType } from "../../../../../native/index
23
23
  import { spinnerFrames } from "../../shared/spinner-frames.js";
24
24
  import { MessageStoreImpl } from "./message-store.js";
25
25
  import { InputManagerImpl, KeyEvents, inputEventToNativeKeyEvent } from "./input-handler.js";
26
+ import { handleScrollEvent } from "./scroll-handler.js";
26
27
  import type {
27
28
  InteractiveRunnerProps,
28
29
  InteractiveState,
@@ -269,7 +270,34 @@ export class InteractiveRunner {
269
270
  return true;
270
271
  }
271
272
 
272
- // History navigation
273
+ // Chat history scroll - handle all scroll keys via scroll handler
274
+ // Debug: log event details
275
+ if (process.env.CODER_DEBUG_SCROLL === "1") {
276
+ console.error("[InteractiveRunner] Key event:", {
277
+ code: event.code,
278
+ shift: event.shift,
279
+ ctrl: event.ctrl,
280
+ alt: event.alt,
281
+ is_special: event.is_special,
282
+ });
283
+ }
284
+
285
+ const scrollResult = handleScrollEvent(
286
+ event,
287
+ this.state.scrollOffset,
288
+ this.messageStore.messages.length
289
+ );
290
+
291
+ if (process.env.CODER_DEBUG_SCROLL === "1") {
292
+ console.error("[InteractiveRunner] Scroll result:", scrollResult);
293
+ }
294
+
295
+ if (scrollResult.handled) {
296
+ this.state = { ...this.state, scrollOffset: scrollResult.newOffset };
297
+ return true;
298
+ }
299
+
300
+ // History navigation (plain Up/Down without shift)
273
301
  if (KeyEvents.isUp(event)) {
274
302
  return this._handleHistoryUp();
275
303
  }
@@ -682,21 +710,22 @@ export class InteractiveRunner {
682
710
  content: `${s.messageCount} messages`,
683
711
  })) : [];
684
712
 
685
- // NativeRenderer expects camelCase field names (NAPI-RS converts snake_case to camelCase)
713
+ // NativeRenderer expects snake_case field names (NAPI-RS keeps Rust naming)
686
714
  return {
687
715
  messages: renderMessages,
688
- inputValue: inputValue,
689
- cursorPos: cursorPos,
690
- statusText: statusText,
691
- isLoading: isLoading,
692
- streamingText: streamingText,
716
+ input_value: inputValue,
717
+ cursor_pos: cursorPos,
718
+ status_text: statusText,
719
+ is_loading: isLoading,
720
+ streaming_text: streamingText,
693
721
  model: this.props.model,
694
- showHelp: helpMode,
695
- helpText: helpText,
696
- searchMode: sessionSelectMode,
697
- searchQuery: "",
698
- searchResults: searchResults,
699
- searchSelected: 0,
722
+ show_help: helpMode,
723
+ help_text: helpText,
724
+ search_mode: sessionSelectMode,
725
+ search_query: "",
726
+ search_results: searchResults,
727
+ search_selected: 0,
728
+ scroll_offset: this.state.scrollOffset,
700
729
  };
701
730
  }
702
731