@ebowwa/coder 0.7.66 → 0.7.69
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.
- package/dist/index.js +209 -23
- package/dist/index.js.map +156 -0
- package/dist/interfaces/ui/terminal/cli/bootstrap.js.map +141 -0
- package/dist/interfaces/ui/terminal/cli/index.js +202 -16
- package/dist/interfaces/ui/terminal/native/index.darwin-arm64.node +0 -0
- package/dist/native/index.darwin-arm64.node +0 -0
- package/native/index.darwin-arm64.node +0 -0
- package/package.json +2 -2
- package/packages/src/core/api-client-impl.ts +166 -25
- package/packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts +31 -2
- package/packages/src/interfaces/ui/terminal/cli/interactive/scroll-handler.ts +284 -0
- package/packages/src/native/index.ts +1 -0
|
@@ -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
|
}
|
|
@@ -7664,6 +7727,7 @@ async function executeStreamAttempt(request, headers, apiEndpoint, signal, model
|
|
|
7664
7727
|
}
|
|
7665
7728
|
}
|
|
7666
7729
|
if (choice?.finish_reason) {
|
|
7730
|
+
console.log(`[API] OpenAI finish_reason: ${choice.finish_reason}, content blocks: ${currentContent.length}, hasToolUse: ${!!currentToolUseBlock}`);
|
|
7667
7731
|
if (currentTextBlock) {
|
|
7668
7732
|
currentContent.push(currentTextBlock);
|
|
7669
7733
|
currentTextBlock = null;
|
|
@@ -7771,20 +7835,6 @@ async function createMessageStream(messages, options) {
|
|
|
7771
7835
|
signal
|
|
7772
7836
|
} = options;
|
|
7773
7837
|
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
7838
|
const providerInfo = resolveProvider(model);
|
|
7789
7839
|
let apiEndpoint;
|
|
7790
7840
|
let headers;
|
|
@@ -7814,12 +7864,48 @@ async function createMessageStream(messages, options) {
|
|
|
7814
7864
|
"anthropic-version": "2023-06-01"
|
|
7815
7865
|
};
|
|
7816
7866
|
}
|
|
7867
|
+
const cachedMessages = buildCachedMessages(messages, cacheConfig);
|
|
7868
|
+
const cachedSystemPrompt = buildSystemPrompt(systemPrompt, cacheConfig);
|
|
7869
|
+
let requestMessages;
|
|
7870
|
+
if (apiFormat === "openai") {
|
|
7871
|
+
const openAIMessages = convertMessagesToOpenAIFormat(cachedMessages);
|
|
7872
|
+
if (cachedSystemPrompt) {
|
|
7873
|
+
const systemText = typeof cachedSystemPrompt === "string" ? cachedSystemPrompt : cachedSystemPrompt.map((b) => b.text).join(`
|
|
7874
|
+
|
|
7875
|
+
`);
|
|
7876
|
+
requestMessages = [
|
|
7877
|
+
{ role: "system", content: systemText },
|
|
7878
|
+
...openAIMessages
|
|
7879
|
+
];
|
|
7880
|
+
} else {
|
|
7881
|
+
requestMessages = openAIMessages;
|
|
7882
|
+
}
|
|
7883
|
+
} else {
|
|
7884
|
+
requestMessages = cachedMessages.map((m) => ({
|
|
7885
|
+
role: m.role,
|
|
7886
|
+
content: m.content
|
|
7887
|
+
}));
|
|
7888
|
+
}
|
|
7889
|
+
const request = {
|
|
7890
|
+
model,
|
|
7891
|
+
max_tokens: maxTokens,
|
|
7892
|
+
messages: requestMessages,
|
|
7893
|
+
stream: true
|
|
7894
|
+
};
|
|
7895
|
+
if (cachedSystemPrompt && apiFormat === "anthropic") {
|
|
7896
|
+
request.system = cachedSystemPrompt;
|
|
7897
|
+
}
|
|
7817
7898
|
if (tools && tools.length > 0) {
|
|
7818
7899
|
if (apiFormat === "openai") {
|
|
7819
|
-
|
|
7900
|
+
const openaiTools = convertToolsToOpenAIFormat(tools);
|
|
7901
|
+
request.tools = openaiTools;
|
|
7902
|
+
console.log(`[API] Sending ${tools.length} tools to ${apiFormat} API:`, JSON.stringify(openaiTools).substring(0, 500));
|
|
7820
7903
|
} else {
|
|
7821
7904
|
request.tools = tools;
|
|
7905
|
+
console.log(`[API] Sending ${tools.length} tools to ${apiFormat} API (Anthropic format)`);
|
|
7822
7906
|
}
|
|
7907
|
+
} else {
|
|
7908
|
+
console.log(`[API] No tools being sent to API`);
|
|
7823
7909
|
}
|
|
7824
7910
|
const shouldUseExtendedThinking = (extendedThinking?.enabled ?? false) || thinking && thinking.type !== "disabled";
|
|
7825
7911
|
if (shouldUseExtendedThinking && supportsExtendedThinking(model)) {
|
|
@@ -11249,6 +11335,84 @@ var init_spinner_frames = __esm(() => {
|
|
|
11249
11335
|
];
|
|
11250
11336
|
});
|
|
11251
11337
|
|
|
11338
|
+
// packages/src/interfaces/ui/terminal/cli/interactive/scroll-handler.ts
|
|
11339
|
+
function handleScrollEvent(event, currentOffset, totalMessages, config = {}) {
|
|
11340
|
+
const { pageScrollAmount, lineScrollAmount } = { ...DEFAULT_SCROLL_CONFIG, ...config };
|
|
11341
|
+
const maxScroll = Math.max(0, Math.max(totalMessages - 1, 10));
|
|
11342
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
11343
|
+
console.error("[ScrollHandler] Event:", {
|
|
11344
|
+
code: event.code,
|
|
11345
|
+
shift: event.shift,
|
|
11346
|
+
ctrl: event.ctrl,
|
|
11347
|
+
alt: event.alt,
|
|
11348
|
+
is_special: event.is_special
|
|
11349
|
+
});
|
|
11350
|
+
}
|
|
11351
|
+
if (KeyEvents.isPageUp(event)) {
|
|
11352
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
11353
|
+
console.error("[ScrollHandler] PageUp detected, scrolling up");
|
|
11354
|
+
}
|
|
11355
|
+
return { handled: true, newOffset: Math.min(currentOffset + pageScrollAmount, maxScroll) };
|
|
11356
|
+
}
|
|
11357
|
+
if (KeyEvents.isPageDown(event)) {
|
|
11358
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
11359
|
+
console.error("[ScrollHandler] PageDown detected, scrolling down");
|
|
11360
|
+
}
|
|
11361
|
+
return { handled: true, newOffset: Math.max(0, currentOffset - pageScrollAmount) };
|
|
11362
|
+
}
|
|
11363
|
+
if (KeyEvents.isHome(event)) {
|
|
11364
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
11365
|
+
console.error("[ScrollHandler] Home detected, resetting to bottom");
|
|
11366
|
+
}
|
|
11367
|
+
return { handled: true, newOffset: 0 };
|
|
11368
|
+
}
|
|
11369
|
+
if (KeyEvents.isUp(event) && event.alt) {
|
|
11370
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
11371
|
+
console.error("[ScrollHandler] Alt+Up detected, scrolling up");
|
|
11372
|
+
}
|
|
11373
|
+
return { handled: true, newOffset: Math.min(currentOffset + lineScrollAmount, maxScroll) };
|
|
11374
|
+
}
|
|
11375
|
+
if (KeyEvents.isDown(event) && event.alt) {
|
|
11376
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
11377
|
+
console.error("[ScrollHandler] Alt+Down detected, scrolling down");
|
|
11378
|
+
}
|
|
11379
|
+
return { handled: true, newOffset: Math.max(0, currentOffset - lineScrollAmount) };
|
|
11380
|
+
}
|
|
11381
|
+
if (KeyEvents.isUp(event) && event.ctrl) {
|
|
11382
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
11383
|
+
console.error("[ScrollHandler] Ctrl+Up detected, scrolling up");
|
|
11384
|
+
}
|
|
11385
|
+
return { handled: true, newOffset: Math.min(currentOffset + lineScrollAmount, maxScroll) };
|
|
11386
|
+
}
|
|
11387
|
+
if (KeyEvents.isDown(event) && event.ctrl) {
|
|
11388
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
11389
|
+
console.error("[ScrollHandler] Ctrl+Down detected, scrolling down");
|
|
11390
|
+
}
|
|
11391
|
+
return { handled: true, newOffset: Math.max(0, currentOffset - lineScrollAmount) };
|
|
11392
|
+
}
|
|
11393
|
+
if (KeyEvents.isUp(event) && event.shift) {
|
|
11394
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
11395
|
+
console.error("[ScrollHandler] Shift+Up detected, scrolling up");
|
|
11396
|
+
}
|
|
11397
|
+
return { handled: true, newOffset: Math.min(currentOffset + lineScrollAmount, maxScroll) };
|
|
11398
|
+
}
|
|
11399
|
+
if (KeyEvents.isDown(event) && event.shift) {
|
|
11400
|
+
if (process.env.CODER_DEBUG_SCROLL === "1") {
|
|
11401
|
+
console.error("[ScrollHandler] Shift+Down detected, scrolling down");
|
|
11402
|
+
}
|
|
11403
|
+
return { handled: true, newOffset: Math.max(0, currentOffset - lineScrollAmount) };
|
|
11404
|
+
}
|
|
11405
|
+
return { handled: false, newOffset: currentOffset };
|
|
11406
|
+
}
|
|
11407
|
+
var DEFAULT_SCROLL_CONFIG;
|
|
11408
|
+
var init_scroll_handler = __esm(() => {
|
|
11409
|
+
init_input_handler();
|
|
11410
|
+
DEFAULT_SCROLL_CONFIG = {
|
|
11411
|
+
pageScrollAmount: 3,
|
|
11412
|
+
lineScrollAmount: 1
|
|
11413
|
+
};
|
|
11414
|
+
});
|
|
11415
|
+
|
|
11252
11416
|
// packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts
|
|
11253
11417
|
import process3 from "process";
|
|
11254
11418
|
function createInitialState() {
|
|
@@ -11399,6 +11563,23 @@ class InteractiveRunner {
|
|
|
11399
11563
|
this.state = { ...this.state, inputValue: "", cursorPos: 0 };
|
|
11400
11564
|
return true;
|
|
11401
11565
|
}
|
|
11566
|
+
if (process3.env.CODER_DEBUG_SCROLL === "1") {
|
|
11567
|
+
console.error("[InteractiveRunner] Key event:", {
|
|
11568
|
+
code: event.code,
|
|
11569
|
+
shift: event.shift,
|
|
11570
|
+
ctrl: event.ctrl,
|
|
11571
|
+
alt: event.alt,
|
|
11572
|
+
is_special: event.is_special
|
|
11573
|
+
});
|
|
11574
|
+
}
|
|
11575
|
+
const scrollResult = handleScrollEvent(event, this.state.scrollOffset, this.messageStore.messages.length);
|
|
11576
|
+
if (process3.env.CODER_DEBUG_SCROLL === "1") {
|
|
11577
|
+
console.error("[InteractiveRunner] Scroll result:", scrollResult);
|
|
11578
|
+
}
|
|
11579
|
+
if (scrollResult.handled) {
|
|
11580
|
+
this.state = { ...this.state, scrollOffset: scrollResult.newOffset };
|
|
11581
|
+
return true;
|
|
11582
|
+
}
|
|
11402
11583
|
if (KeyEvents.isUp(event)) {
|
|
11403
11584
|
return this._handleHistoryUp();
|
|
11404
11585
|
}
|
|
@@ -11707,7 +11888,8 @@ class InteractiveRunner {
|
|
|
11707
11888
|
searchMode: sessionSelectMode,
|
|
11708
11889
|
searchQuery: "",
|
|
11709
11890
|
searchResults,
|
|
11710
|
-
searchSelected: 0
|
|
11891
|
+
searchSelected: 0,
|
|
11892
|
+
scrollOffset: this.state.scrollOffset
|
|
11711
11893
|
};
|
|
11712
11894
|
}
|
|
11713
11895
|
_getHelpText(section) {
|
|
@@ -11809,6 +11991,7 @@ var init_interactive_runner = __esm(() => {
|
|
|
11809
11991
|
init_native();
|
|
11810
11992
|
init_spinner_frames();
|
|
11811
11993
|
init_input_handler();
|
|
11994
|
+
init_scroll_handler();
|
|
11812
11995
|
init_types();
|
|
11813
11996
|
});
|
|
11814
11997
|
|
|
@@ -34325,3 +34508,6 @@ if (checkPtyWrapper2()) {
|
|
|
34325
34508
|
process5.exit(1);
|
|
34326
34509
|
});
|
|
34327
34510
|
}
|
|
34511
|
+
|
|
34512
|
+
//# debugId=1ADD58DCF208A7BB64756E2164756E21
|
|
34513
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ebowwa/coder",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.69",
|
|
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;
|
|
@@ -519,6 +628,9 @@ async function executeStreamAttempt(
|
|
|
519
628
|
|
|
520
629
|
// Handle finish reason
|
|
521
630
|
if (choice?.finish_reason) {
|
|
631
|
+
// DEBUG: Log finish reason
|
|
632
|
+
console.log(`[API] OpenAI finish_reason: ${choice.finish_reason}, content blocks: ${currentContent.length}, hasToolUse: ${!!currentToolUseBlock}`);
|
|
633
|
+
|
|
522
634
|
// Finalize any pending text block
|
|
523
635
|
if (currentTextBlock) {
|
|
524
636
|
currentContent.push(currentTextBlock);
|
|
@@ -647,30 +759,7 @@ export async function createMessageStream(
|
|
|
647
759
|
|
|
648
760
|
const startTime = Date.now();
|
|
649
761
|
|
|
650
|
-
//
|
|
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
|
|
762
|
+
// Resolve provider FIRST to determine API format
|
|
674
763
|
const providerInfo = resolveProvider(model);
|
|
675
764
|
|
|
676
765
|
// Determine API endpoint and headers based on provider
|
|
@@ -710,16 +799,68 @@ export async function createMessageStream(
|
|
|
710
799
|
};
|
|
711
800
|
}
|
|
712
801
|
|
|
802
|
+
// Build cached messages
|
|
803
|
+
const cachedMessages = buildCachedMessages(messages, cacheConfig);
|
|
804
|
+
|
|
805
|
+
// Build system prompt with cache control
|
|
806
|
+
const cachedSystemPrompt = buildSystemPrompt(systemPrompt, cacheConfig);
|
|
807
|
+
|
|
808
|
+
// Build request with format-appropriate message conversion
|
|
809
|
+
let requestMessages: unknown;
|
|
810
|
+
if (apiFormat === "openai") {
|
|
811
|
+
// Convert to OpenAI format (handles tool_use and tool_result properly)
|
|
812
|
+
const openAIMessages = convertMessagesToOpenAIFormat(cachedMessages);
|
|
813
|
+
|
|
814
|
+
// Add system prompt as first message for OpenAI format
|
|
815
|
+
if (cachedSystemPrompt) {
|
|
816
|
+
const systemText = typeof cachedSystemPrompt === "string"
|
|
817
|
+
? cachedSystemPrompt
|
|
818
|
+
: cachedSystemPrompt.map(b => b.text).join("\n\n");
|
|
819
|
+
requestMessages = [
|
|
820
|
+
{ role: "system", content: systemText },
|
|
821
|
+
...openAIMessages,
|
|
822
|
+
];
|
|
823
|
+
} else {
|
|
824
|
+
requestMessages = openAIMessages;
|
|
825
|
+
}
|
|
826
|
+
} else {
|
|
827
|
+
// Keep Anthropic format
|
|
828
|
+
requestMessages = cachedMessages.map((m) => ({
|
|
829
|
+
role: m.role,
|
|
830
|
+
content: m.content,
|
|
831
|
+
}));
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Build request
|
|
835
|
+
const request: APIRequest = {
|
|
836
|
+
model,
|
|
837
|
+
max_tokens: maxTokens,
|
|
838
|
+
messages: requestMessages as Message[],
|
|
839
|
+
stream: true,
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
if (cachedSystemPrompt && apiFormat === "anthropic") {
|
|
843
|
+
// Anthropic format uses separate system field
|
|
844
|
+
// OpenAI format already has system as first message
|
|
845
|
+
request.system = cachedSystemPrompt;
|
|
846
|
+
}
|
|
847
|
+
|
|
713
848
|
// Set tools with format conversion if needed
|
|
714
849
|
if (tools && tools.length > 0) {
|
|
715
850
|
if (apiFormat === "openai") {
|
|
716
851
|
// Convert Anthropic-style tools to OpenAI format
|
|
717
852
|
// Cast needed because OpenAI format differs from APITool
|
|
718
|
-
|
|
853
|
+
const openaiTools = convertToolsToOpenAIFormat(tools);
|
|
854
|
+
(request as unknown as Record<string, unknown>).tools = openaiTools;
|
|
855
|
+
// DEBUG: Log tools being sent
|
|
856
|
+
console.log(`[API] Sending ${tools.length} tools to ${apiFormat} API:`, JSON.stringify(openaiTools).substring(0, 500));
|
|
719
857
|
} else {
|
|
720
858
|
// Keep Anthropic format as-is
|
|
721
859
|
request.tools = tools;
|
|
860
|
+
console.log(`[API] Sending ${tools.length} tools to ${apiFormat} API (Anthropic format)`);
|
|
722
861
|
}
|
|
862
|
+
} else {
|
|
863
|
+
console.log(`[API] No tools being sent to API`);
|
|
723
864
|
}
|
|
724
865
|
|
|
725
866
|
const shouldUseExtendedThinking =
|
|
@@ -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
|
-
//
|
|
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,7 +710,7 @@ export class InteractiveRunner {
|
|
|
682
710
|
content: `${s.messageCount} messages`,
|
|
683
711
|
})) : [];
|
|
684
712
|
|
|
685
|
-
// NativeRenderer expects camelCase field names (NAPI-RS converts snake_case
|
|
713
|
+
// NativeRenderer expects camelCase field names (NAPI-RS auto-converts snake_case)
|
|
686
714
|
return {
|
|
687
715
|
messages: renderMessages,
|
|
688
716
|
inputValue: inputValue,
|
|
@@ -697,6 +725,7 @@ export class InteractiveRunner {
|
|
|
697
725
|
searchQuery: "",
|
|
698
726
|
searchResults: searchResults,
|
|
699
727
|
searchSelected: 0,
|
|
728
|
+
scrollOffset: this.state.scrollOffset,
|
|
700
729
|
};
|
|
701
730
|
}
|
|
702
731
|
|