wormclaude 1.0.91 → 1.0.93
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/api.js +12 -2
- package/dist/cli.js +8 -3
- package/dist/inlinetools.js +87 -0
- package/dist/lineeditor.js +60 -0
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Ölçüm: usage (token) bilgisini 'done' olayında döndürür (billing için).
|
|
4
4
|
import { loadStored, DEFAULT_BASE_URL } from './auth.js';
|
|
5
5
|
import { StreamingToolCallParser } from './streamparser.js';
|
|
6
|
+
import { recoverInlineToolCalls } from './inlinetools.js';
|
|
6
7
|
import { safeJsonStringify } from './safejson.js';
|
|
7
8
|
import { getTrace, getSession } from './telemetry.js';
|
|
8
9
|
export function loadConfig() {
|
|
@@ -186,6 +187,7 @@ export async function* streamChat(messages, tools, config, signal) {
|
|
|
186
187
|
let buf = '';
|
|
187
188
|
const toolParser = new StreamingToolCallParser();
|
|
188
189
|
let usage;
|
|
190
|
+
let fullText = ''; // model metne araç çağrısı gömerse sonda kurtarmak için
|
|
189
191
|
while (true) {
|
|
190
192
|
if (ac.signal.aborted && !timedOut) {
|
|
191
193
|
try {
|
|
@@ -239,8 +241,10 @@ export async function* streamChat(messages, tools, config, signal) {
|
|
|
239
241
|
const delta = j.choices?.[0]?.delta;
|
|
240
242
|
if (!delta)
|
|
241
243
|
continue;
|
|
242
|
-
if (delta.content)
|
|
244
|
+
if (delta.content) {
|
|
245
|
+
fullText += delta.content;
|
|
243
246
|
yield { type: 'text', text: delta.content };
|
|
247
|
+
}
|
|
244
248
|
if (delta.tool_calls) {
|
|
245
249
|
for (const tc of delta.tool_calls) {
|
|
246
250
|
const idx = tc.index ?? 0;
|
|
@@ -252,10 +256,16 @@ export async function* streamChat(messages, tools, config, signal) {
|
|
|
252
256
|
clearTimer(); // akış normal bitti → zamanlayıcı + dinleyici temizle
|
|
253
257
|
// Tamamlanan çağrıları sağlam biçimde topla; args'ı GEÇERLİ JSON string olarak emit et
|
|
254
258
|
// (tools.ts tarafındaki JSON.parse artık asla patlamaz — onarım burada yapıldı).
|
|
255
|
-
|
|
259
|
+
let toolCalls = toolParser.getCompleted().map((c) => ({
|
|
256
260
|
id: c.id,
|
|
257
261
|
name: c.name,
|
|
258
262
|
args: safeJsonStringify(c.args),
|
|
259
263
|
}));
|
|
264
|
+
// Yedek: upstream yapısal tool_call vermediyse modelin metne gömdüğü çağrıyı kurtar.
|
|
265
|
+
if (toolCalls.length === 0 && fullText) {
|
|
266
|
+
const recovered = recoverInlineToolCalls(fullText);
|
|
267
|
+
if (recovered.length)
|
|
268
|
+
toolCalls = recovered;
|
|
269
|
+
}
|
|
260
270
|
yield { type: 'done', toolCalls, usage };
|
|
261
271
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import React, { useState, useRef, useEffect } from 'react';
|
|
3
3
|
import { render, Box, Text, useApp, useInput } from 'ink';
|
|
4
|
-
import
|
|
4
|
+
import LineEditor from './lineeditor.js';
|
|
5
5
|
import * as path from 'node:path';
|
|
6
6
|
import { theme, VERSION } from './theme.js';
|
|
7
7
|
import { loadConfig, streamChat, fetchAccount } from './api.js';
|
|
8
|
+
import { stripInlineToolCalls } from './inlinetools.js';
|
|
8
9
|
import { newTrace, flushTelemetry } from './telemetry.js';
|
|
9
10
|
import { tier } from './program.js';
|
|
10
11
|
import { allToolSchemas, executeToolCalls, executeTool, toolLabel, setToolConfig } from './tools.js';
|
|
@@ -722,6 +723,10 @@ function App() {
|
|
|
722
723
|
setStreaming('');
|
|
723
724
|
// Sızan özel-token / tool-call markup'ını bir kez temizle (hem geçmiş hem gösterim için).
|
|
724
725
|
assistantText = cleanModelText(assistantText);
|
|
726
|
+
// Model araç çağrısını metne gömdüyse (inline kurtarma) → o JSON bloğunu gösterim+geçmişten çıkar.
|
|
727
|
+
if (toolCalls.some((t) => t.id?.startsWith('inline_'))) {
|
|
728
|
+
assistantText = stripInlineToolCalls(assistantText);
|
|
729
|
+
}
|
|
725
730
|
// Reactive compact: bağlam taştıysa bir kez özetle ve turu tekrar dene
|
|
726
731
|
if (gotCtxError && !reactiveRetried) {
|
|
727
732
|
reactiveRetried = true;
|
|
@@ -1063,7 +1068,7 @@ function App() {
|
|
|
1063
1068
|
React.createElement(Text, { color: theme.grey }, t('perm.feedbackPrompt')),
|
|
1064
1069
|
React.createElement(Box, null,
|
|
1065
1070
|
React.createElement(Text, { color: theme.redBright, bold: true }, "\u276F "),
|
|
1066
|
-
React.createElement(
|
|
1071
|
+
React.createElement(LineEditor, { value: permFeedback, onChange: setPermFeedback, onSubmit: (val) => { perm.resolve({ deny: val.trim() }); setPerm(null); setPermMode('select'); }, placeholder: t('perm.feedbackPlaceholder'), isActive: permMode === 'feedback' })),
|
|
1067
1072
|
React.createElement(Text, { color: theme.greyDim }, " Enter g\u00F6nder \u00B7 Esc iptal"))) : (React.createElement(React.Fragment, null,
|
|
1068
1073
|
React.createElement(Text, { color: permSel === 0 ? theme.redBright : theme.grey, bold: permSel === 0 },
|
|
1069
1074
|
permSel === 0 ? '❯ ' : ' ',
|
|
@@ -1097,7 +1102,7 @@ function App() {
|
|
|
1097
1102
|
(React.createElement(Box, { flexDirection: "column" },
|
|
1098
1103
|
React.createElement(Box, { borderStyle: "round", borderColor: busy ? theme.greyDim : theme.red, paddingX: 1 },
|
|
1099
1104
|
React.createElement(Text, { color: theme.redBright, bold: true }, "\u276F "),
|
|
1100
|
-
React.createElement(
|
|
1105
|
+
React.createElement(LineEditor, { value: input, onChange: (val) => { setInput(val); setCmdSel(0); }, onSubmit: onSubmit, placeholder: busy ? 'cevap üretilirken yazabilirsin…' : t('input.placeholder'), isActive: started && !perm && !ask && lang !== null })),
|
|
1101
1106
|
input.startsWith('/') ? (() => {
|
|
1102
1107
|
const filtered = cmdFilter(input);
|
|
1103
1108
|
if (!filtered.length)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Model bazen araç çağrısını yapısal `tool_calls` yerine düz METİN (content) içine gömer
|
|
2
|
+
// (yerel/abliterated modellerde sık). Tanınan biçimler:
|
|
3
|
+
// <tool_call>{"name":"Glob","arguments":{...}}</tool_call>
|
|
4
|
+
// ```json\n{"name":"Glob","arguments":{...}}\n```
|
|
5
|
+
// (tüm mesaj) {"name":"Glob","arguments":{...}}
|
|
6
|
+
// OpenAI: {"type":"function","function":{"name":...,"arguments":"{...}"}}
|
|
7
|
+
// Bu modül o gömülü çağrıları kurtarır — upstream yapısal tool_call vermediğinde YEDEK.
|
|
8
|
+
import { safeJsonParse } from './safejson.js';
|
|
9
|
+
const ARG_KEYS = ['arguments', 'parameters', 'input', 'args', 'params'];
|
|
10
|
+
function toToolCall(obj, i) {
|
|
11
|
+
if (!obj || typeof obj !== 'object')
|
|
12
|
+
return null;
|
|
13
|
+
const src = obj.function && typeof obj.function === 'object' ? obj.function : obj;
|
|
14
|
+
const name = src.name || obj.name || obj.tool || obj.tool_name;
|
|
15
|
+
if (!name || typeof name !== 'string')
|
|
16
|
+
return null;
|
|
17
|
+
let args = undefined;
|
|
18
|
+
if (src.arguments !== undefined)
|
|
19
|
+
args = src.arguments;
|
|
20
|
+
else
|
|
21
|
+
for (const k of ARG_KEYS) {
|
|
22
|
+
if (obj[k] !== undefined) {
|
|
23
|
+
args = obj[k];
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (typeof args === 'string')
|
|
28
|
+
args = safeJsonParse(args, {});
|
|
29
|
+
if (args === undefined || args === null)
|
|
30
|
+
args = {};
|
|
31
|
+
return { id: `inline_${i}`, name, args: JSON.stringify(args) };
|
|
32
|
+
}
|
|
33
|
+
function looksLikeCall(o) {
|
|
34
|
+
return !!o && typeof o === 'object' && (typeof o.name === 'string' || typeof o.tool === 'string' || (o.function && typeof o.function === 'object'));
|
|
35
|
+
}
|
|
36
|
+
/** Metindeki gömülü araç çağrılarını kurtarır. Gürültüden kaçınmak için yalnız açık sarmalları
|
|
37
|
+
* (<tool_call>, ```json) veya mesajın TAMAMI tek JSON çağrısıysa onu alır. */
|
|
38
|
+
export function recoverInlineToolCalls(text) {
|
|
39
|
+
const t = (text || '').trim();
|
|
40
|
+
if (!t || (!t.includes('"name"') && !t.includes('"tool"') && !t.includes('"function"')))
|
|
41
|
+
return [];
|
|
42
|
+
const out = [];
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
const push = (o) => {
|
|
45
|
+
const tc = toToolCall(o, out.length);
|
|
46
|
+
if (!tc)
|
|
47
|
+
return;
|
|
48
|
+
const k = tc.name + '|' + tc.args;
|
|
49
|
+
if (!seen.has(k)) {
|
|
50
|
+
seen.add(k);
|
|
51
|
+
out.push(tc);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
// 1) <tool_call>...</tool_call> (Hermes/Qwen)
|
|
55
|
+
for (const m of t.matchAll(/<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/gi)) {
|
|
56
|
+
const o = safeJsonParse(m[1].trim(), null);
|
|
57
|
+
if (looksLikeCall(o))
|
|
58
|
+
push(o);
|
|
59
|
+
}
|
|
60
|
+
// 2) ```json / ```tool_call ... ``` (kod-bloğuna gömülü)
|
|
61
|
+
if (!out.length) {
|
|
62
|
+
for (const m of t.matchAll(/```(?:json|tool_call|tool|function)?\s*([\s\S]*?)```/gi)) {
|
|
63
|
+
const o = safeJsonParse(m[1].trim(), null);
|
|
64
|
+
if (looksLikeCall(o))
|
|
65
|
+
push(o);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// 3) Mesajın TAMAMI tek JSON çağrısı (sarmasız)
|
|
69
|
+
if (!out.length && t.startsWith('{') && t.endsWith('}')) {
|
|
70
|
+
const o = safeJsonParse(t, null);
|
|
71
|
+
if (looksLikeCall(o))
|
|
72
|
+
push(o);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
/** Kurtarılan çağrı bloklarını GÖRÜNTÜ/geçmiş metninden temizler (çirkin JSON kalmasın). */
|
|
77
|
+
export function stripInlineToolCalls(text) {
|
|
78
|
+
let s = text || '';
|
|
79
|
+
s = s.replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, '');
|
|
80
|
+
s = s.replace(/```(?:json|tool_call|tool|function)?\s*\{[\s\S]*?\}\s*```/gi, (blk) => {
|
|
81
|
+
return /"(name|tool|function)"/.test(blk) ? '' : blk; // yalnız çağrı-bloklarını sil
|
|
82
|
+
});
|
|
83
|
+
const tr = s.trim();
|
|
84
|
+
if (tr.startsWith('{') && tr.endsWith('}') && /"(name|tool|function)"/.test(tr))
|
|
85
|
+
return '';
|
|
86
|
+
return s.trim();
|
|
87
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { Text, useInput } from 'ink';
|
|
3
|
+
// Tam imleç-kontrollü satır editörü (ink-text-input yerine).
|
|
4
|
+
// Destekler: yazma (imleç konumuna ekleme), Backspace/Delete, ← →, Home/End (Ctrl+A/Ctrl+E).
|
|
5
|
+
// ↑↓/PageUp/PageDown/Esc/Tab'a DOKUNMAZ → menü/scroll/dialog handler'larına geçer.
|
|
6
|
+
export default function LineEditor({ value, onChange, onSubmit, placeholder, isActive = true, color }) {
|
|
7
|
+
const [cursor, setCursor] = useState(value.length);
|
|
8
|
+
// value dışarıdan değişirse imleci sınırda tut (örn. submit'te temizlenince).
|
|
9
|
+
useEffect(() => { setCursor((c) => Math.min(c, value.length)); }, [value]);
|
|
10
|
+
useInput((input, key) => {
|
|
11
|
+
if (key.leftArrow) {
|
|
12
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (key.rightArrow) {
|
|
16
|
+
setCursor((c) => Math.min(value.length, c + 1));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (key.return) {
|
|
20
|
+
onSubmit?.(value);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (key.backspace || key.delete) {
|
|
24
|
+
if (cursor > 0) {
|
|
25
|
+
onChange(value.slice(0, cursor - 1) + value.slice(cursor));
|
|
26
|
+
setCursor(cursor - 1);
|
|
27
|
+
}
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (key.ctrl && input === 'a') {
|
|
31
|
+
setCursor(0);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (key.ctrl && input === 'e') {
|
|
35
|
+
setCursor(value.length);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Gezinme/özel tuşlar bize ait değil → metne yazma, başka handler'lara bırak.
|
|
39
|
+
if (key.ctrl || key.meta || key.tab || key.escape || key.upArrow || key.downArrow || key.pageUp || key.pageDown)
|
|
40
|
+
return;
|
|
41
|
+
if (input) {
|
|
42
|
+
onChange(value.slice(0, cursor) + input + value.slice(cursor));
|
|
43
|
+
setCursor(cursor + input.length);
|
|
44
|
+
}
|
|
45
|
+
}, { isActive });
|
|
46
|
+
// Placeholder (boş input)
|
|
47
|
+
if (!value) {
|
|
48
|
+
return React.createElement(Text, { color: color, dimColor: true },
|
|
49
|
+
isActive ? React.createElement(Text, { inverse: true }, " ") : ' ',
|
|
50
|
+
placeholder || '');
|
|
51
|
+
}
|
|
52
|
+
// İmleçli metin: imleçteki karakter ters-renkli (blok imleç)
|
|
53
|
+
const before = value.slice(0, cursor);
|
|
54
|
+
const at = value.slice(cursor, cursor + 1) || ' ';
|
|
55
|
+
const after = value.slice(cursor + 1);
|
|
56
|
+
return (React.createElement(Text, { color: color },
|
|
57
|
+
before,
|
|
58
|
+
isActive ? React.createElement(Text, { inverse: true }, at) : at,
|
|
59
|
+
after));
|
|
60
|
+
}
|