@nexrall/code-core 1.4.2 → 1.4.4
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/agent/loop.d.ts +20 -1
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +146 -2
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +13 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/agent/loop.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Message, AgentLoopOptions } from '../types';
|
|
1
|
+
import type { Message, AgentLoopOptions, EnvContext } from '../types';
|
|
2
2
|
export declare function resolveMaxIterations(optionValue: number | undefined, settingsRaw: Record<string, unknown>): number;
|
|
3
3
|
/** Approximate serialised request-body size (bytes) for the messages array. */
|
|
4
4
|
export declare function estimateBodyBytes(messages: Message[]): number;
|
|
@@ -73,5 +73,24 @@ export declare function ledgerSummary(ledger: ProgressLedger): string;
|
|
|
73
73
|
* Returns the number of bytes reclaimed (0 if nothing was prunable).
|
|
74
74
|
*/
|
|
75
75
|
export declare function pruneOldToolResults(messages: Message[]): number;
|
|
76
|
+
/** Rough token estimate for a resumed transcript — no API round-trip needed. */
|
|
77
|
+
export declare function estimateTokensRough(messages: Message[]): number;
|
|
78
|
+
/**
|
|
79
|
+
* Proactively compact `messages` in place if resuming this session would blow
|
|
80
|
+
* past the auto-compact threshold on the very first turn. Returns true if any
|
|
81
|
+
* compaction happened (so the caller can surface a one-line notice to the
|
|
82
|
+
* user). Safe to call on any message array, including empty/small ones (no-op).
|
|
83
|
+
*
|
|
84
|
+
* `model` picks the right context window (mirrors runAgentLoop's own lookup);
|
|
85
|
+
* `onNotice` is optional — pass it to show the same "auto-compacted" message
|
|
86
|
+
* the in-loop path shows, so the behaviour is visually consistent.
|
|
87
|
+
*/
|
|
88
|
+
export declare function compactMessagesForResume(messages: Message[], opts: {
|
|
89
|
+
model?: 'turbo' | 'pro' | 'ultra';
|
|
90
|
+
workDir: string;
|
|
91
|
+
clientType?: string;
|
|
92
|
+
env?: EnvContext;
|
|
93
|
+
onNotice?: (text: string) => void;
|
|
94
|
+
}): Promise<boolean>;
|
|
76
95
|
export declare function runAgentLoop(initialMessages: Message[], options: AgentLoopOptions): Promise<Message[]>;
|
|
77
96
|
//# sourceMappingURL=loop.d.ts.map
|
package/dist/agent/loop.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAMP,gBAAgB,
|
|
1
|
+
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/agent/loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAMP,gBAAgB,EAChB,UAAU,EACX,MAAM,UAAU,CAAC;AAsKlB,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,MAAM,CAWR;AA8RD,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAM7D;AAsBD,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,aAA+G,CAAC;AAC7I,gGAAgG;AAChG,eAAO,MAAM,aAAa,QAA2J,CAAC;AAEtL;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAK5E;AAUD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAwBxD;AAoBD,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,aAAa,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClE,qFAAqF;IACrF,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvD;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,YAAY,IAAI,cAAc,CAE7C;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAC1B,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC1C,EAAE,EAAE,OAAO,EACX,MAAM,CAAC,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAoDN;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CA6B5D;AAmBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAmB/D;AA+GD,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAE/D;AAED;;;;;;;;;GASG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,OAAO,EAAE,EACnB,IAAI,EAAE;IACJ,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,GACA,OAAO,CAAC,OAAO,CAAC,CA2DlB;AAID,wBAAsB,YAAY,CAChC,eAAe,EAAE,OAAO,EAAE,EAC1B,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,OAAO,EAAE,CAAC,CA+kBpB"}
|
package/dist/agent/loop.js
CHANGED
|
@@ -42,6 +42,8 @@ exports.createLedger = createLedger;
|
|
|
42
42
|
exports.ledgerRecord = ledgerRecord;
|
|
43
43
|
exports.ledgerSummary = ledgerSummary;
|
|
44
44
|
exports.pruneOldToolResults = pruneOldToolResults;
|
|
45
|
+
exports.estimateTokensRough = estimateTokensRough;
|
|
46
|
+
exports.compactMessagesForResume = compactMessagesForResume;
|
|
45
47
|
exports.runAgentLoop = runAgentLoop;
|
|
46
48
|
const client_1 = require("../api/client");
|
|
47
49
|
const executor_1 = require("../tools/executor");
|
|
@@ -772,6 +774,95 @@ async function autoCompactMessages(messages, options, ledger) {
|
|
|
772
774
|
void kept;
|
|
773
775
|
return true;
|
|
774
776
|
}
|
|
777
|
+
// ─── Resume-time proactive compaction ─────────────────────────────────────────
|
|
778
|
+
//
|
|
779
|
+
// The in-loop auto-compact above only reacts to `lastPromptTokens`, which is
|
|
780
|
+
// populated from the PREVIOUS turn's usage event. On a freshly-resumed session
|
|
781
|
+
// (opening an old chat from history and sending the first new message) there is
|
|
782
|
+
// no previous turn in this process — `lastPromptTokens` starts at 0 — so the
|
|
783
|
+
// token-pressure trigger never fires for turn 0, and the byte-pressure trigger
|
|
784
|
+
// only catches truly huge sessions (MAX_BODY_BYTES is sized to stay under the
|
|
785
|
+
// backend's 25 MB body limit, not to bound cost — 8 MB of tool-heavy JSON is
|
|
786
|
+
// already on the order of the 1M-token context window itself). The result: a
|
|
787
|
+
// resumed session comfortably under both guards, but still hundreds of
|
|
788
|
+
// thousands of tokens, gets sent to the model at FULL PRICE on the very first
|
|
789
|
+
// message after resume, silently, every time.
|
|
790
|
+
//
|
|
791
|
+
// This function closes that gap: call it once, right after loading a stored
|
|
792
|
+
// session and BEFORE the user's next message is sent, so the expensive
|
|
793
|
+
// resend is compacted proactively instead of being missed by both in-loop
|
|
794
|
+
// guards. It reuses the exact same threshold/mechanics as the in-loop guard
|
|
795
|
+
// (cheap prune first, then summarising compaction) so behaviour stays
|
|
796
|
+
// consistent whether compaction happens at resume-time or mid-run.
|
|
797
|
+
const RESUME_CHARS_PER_TOKEN = 4; // rough, conservative estimate for JSON/code-heavy transcripts
|
|
798
|
+
/** Rough token estimate for a resumed transcript — no API round-trip needed. */
|
|
799
|
+
function estimateTokensRough(messages) {
|
|
800
|
+
return Math.ceil(estimateBodyBytes(messages) / RESUME_CHARS_PER_TOKEN);
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Proactively compact `messages` in place if resuming this session would blow
|
|
804
|
+
* past the auto-compact threshold on the very first turn. Returns true if any
|
|
805
|
+
* compaction happened (so the caller can surface a one-line notice to the
|
|
806
|
+
* user). Safe to call on any message array, including empty/small ones (no-op).
|
|
807
|
+
*
|
|
808
|
+
* `model` picks the right context window (mirrors runAgentLoop's own lookup);
|
|
809
|
+
* `onNotice` is optional — pass it to show the same "auto-compacted" message
|
|
810
|
+
* the in-loop path shows, so the behaviour is visually consistent.
|
|
811
|
+
*/
|
|
812
|
+
async function compactMessagesForResume(messages, opts) {
|
|
813
|
+
if (messages.length <= COMPACT_KEEP_MIN + 2)
|
|
814
|
+
return false;
|
|
815
|
+
const settings = (0, rules_1.loadSettings)(opts.workDir);
|
|
816
|
+
if (!resolveAutoCompact(undefined, settings.raw))
|
|
817
|
+
return false;
|
|
818
|
+
const contextWindow = MODEL_CONTEXT_TOKENS[opts.model ?? 'turbo'] ?? 1000000;
|
|
819
|
+
let bodyBytes = estimateBodyBytes(messages);
|
|
820
|
+
let tokenGuess = estimateTokensRough(messages);
|
|
821
|
+
const overThreshold = () => tokenGuess > contextWindow * AUTO_COMPACT_THRESHOLD || bodyBytes > MAX_BODY_BYTES;
|
|
822
|
+
if (!overThreshold())
|
|
823
|
+
return false;
|
|
824
|
+
let compacted = false;
|
|
825
|
+
// Cheap pass first — shrinks old tool_result blocks with no model call.
|
|
826
|
+
if (messages.length > PRUNE_KEEP_RECENT + 2) {
|
|
827
|
+
const reclaimed = pruneOldToolResults(messages);
|
|
828
|
+
if (reclaimed > 0) {
|
|
829
|
+
bodyBytes = estimateBodyBytes(messages);
|
|
830
|
+
tokenGuess = estimateTokensRough(messages);
|
|
831
|
+
compacted = true;
|
|
832
|
+
opts.onNotice?.(`\n\u267b\ufe0f Trimmed ~${(reclaimed / (1024 * 1024)).toFixed(1)}MB of older tool output before resuming this chat.\n`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
// If still over threshold, fall through to summarising compaction — same
|
|
836
|
+
// mechanism the in-loop guard uses, so this can safely loop (a single
|
|
837
|
+
// summarisation pass may still leave a very long session over threshold).
|
|
838
|
+
let guard = 0;
|
|
839
|
+
while (overThreshold() && messages.length > COMPACT_KEEP_MIN + 2 && guard < 5) {
|
|
840
|
+
guard += 1;
|
|
841
|
+
// No ledger at resume time — the ledger is per-run, in-memory, and would
|
|
842
|
+
// have been created fresh anyway since this is a new process/run. The
|
|
843
|
+
// ORIGINAL TASK verbatim pin (inside autoCompactMessages) still applies.
|
|
844
|
+
const did = await autoCompactMessages(messages, {
|
|
845
|
+
workDir: opts.workDir,
|
|
846
|
+
model: opts.model,
|
|
847
|
+
clientType: opts.clientType,
|
|
848
|
+
env: opts.env,
|
|
849
|
+
onText: () => { },
|
|
850
|
+
onToolUse: () => { },
|
|
851
|
+
onToolResult: () => { },
|
|
852
|
+
onUsage: () => { },
|
|
853
|
+
requestPermission: async () => false,
|
|
854
|
+
});
|
|
855
|
+
if (!did)
|
|
856
|
+
break;
|
|
857
|
+
compacted = true;
|
|
858
|
+
bodyBytes = estimateBodyBytes(messages);
|
|
859
|
+
tokenGuess = estimateTokensRough(messages);
|
|
860
|
+
}
|
|
861
|
+
if (compacted) {
|
|
862
|
+
opts.onNotice?.(`\n\u267b\ufe0f Auto-compacted this chat's earlier history before resuming, to avoid resending it at full cost.\n`);
|
|
863
|
+
}
|
|
864
|
+
return compacted;
|
|
865
|
+
}
|
|
775
866
|
// ─── Agent Loop ───────────────────────────────────────────────────────────────
|
|
776
867
|
async function runAgentLoop(initialMessages, options) {
|
|
777
868
|
const messages = [...initialMessages];
|
|
@@ -970,7 +1061,11 @@ async function runAgentLoop(initialMessages, options) {
|
|
|
970
1061
|
completedCleanly = true;
|
|
971
1062
|
break;
|
|
972
1063
|
}
|
|
973
|
-
|
|
1064
|
+
// stopReason is transient, single-turn metadata for the truncated-tool-call guard
|
|
1065
|
+
// below — it must never be replayed to the API as part of message history (Anthropic
|
|
1066
|
+
// rejects unknown fields on a request message with a 400).
|
|
1067
|
+
const { stopReason: _stopReason, ...historyMessage } = assistantMessage;
|
|
1068
|
+
messages.push(historyMessage);
|
|
974
1069
|
// 3. Collect tool_use blocks from the completed message.
|
|
975
1070
|
// Server-side tools (e.g. web_search_20260209) are executed by Anthropic and their
|
|
976
1071
|
// tool_result blocks appear in the SAME assistant message. We must skip those —
|
|
@@ -980,6 +1075,31 @@ async function runAgentLoop(initialMessages, options) {
|
|
|
980
1075
|
.map(b => b.tool_use_id)
|
|
981
1076
|
.filter((id) => !!id));
|
|
982
1077
|
const toolUseBlocks = assistantMessage.content.filter((block) => block.type === 'tool_use' && !serverSideResultIds.has(block.id));
|
|
1078
|
+
// ─── TRUNCATED-TOOL-CALL GUARD ──────────────────────────────────────────
|
|
1079
|
+
// When Anthropic cuts a response off at `max_tokens` mid-stream, the SDK's
|
|
1080
|
+
// partial-JSON parser (used to show live tool-call args while streaming)
|
|
1081
|
+
// can still successfully parse an INCOMPLETE argument object — e.g. a
|
|
1082
|
+
// multi_edit whose `edits` array lost its last, still-in-progress element
|
|
1083
|
+
// (or came out empty/missing), or an edit_file whose `new_string` got cut
|
|
1084
|
+
// mid-string. The result LOOKS like valid JSON and often passes basic
|
|
1085
|
+
// "is edits a non-empty array" checks, so the tool executor either (a)
|
|
1086
|
+
// throws a confusing generic error ("edits must be a non-empty array")
|
|
1087
|
+
// with no indication WHY, or worse (b) silently applies a SUBSET of the
|
|
1088
|
+
// edits the model actually intended, with zero error at all — a genuine
|
|
1089
|
+
// partial-write bug, not just an unclear message.
|
|
1090
|
+
//
|
|
1091
|
+
// Only the LAST content block in the message can possibly be affected —
|
|
1092
|
+
// every earlier block already finished streaming (and was validated by
|
|
1093
|
+
// Anthropic) before the model moved on, so truncation can only land on
|
|
1094
|
+
// whatever was still being generated when the ceiling was hit. If that
|
|
1095
|
+
// last block is a tool_use AND stop_reason is 'max_tokens', refuse to
|
|
1096
|
+
// execute it blindly: return a clear, actionable error instead so the
|
|
1097
|
+
// model retries with a smaller call, rather than either failing
|
|
1098
|
+
// mysteriously or silently mutating a file incompletely.
|
|
1099
|
+
const lastBlock = assistantMessage.content[assistantMessage.content.length - 1];
|
|
1100
|
+
const truncatedToolUseId = assistantMessage.stopReason === 'max_tokens' && lastBlock?.type === 'tool_use'
|
|
1101
|
+
? lastBlock.id
|
|
1102
|
+
: undefined;
|
|
983
1103
|
// 4. If no tool use → agent produced a final text response.
|
|
984
1104
|
// But if the user queued a follow-up while we were working, fold it in as the
|
|
985
1105
|
// next user turn and keep going instead of ending (Claude-Code style).
|
|
@@ -1084,9 +1204,33 @@ async function runAgentLoop(initialMessages, options) {
|
|
|
1084
1204
|
const { id, name, input } = block;
|
|
1085
1205
|
// Notify caller about pending tool use
|
|
1086
1206
|
options.onToolUse(name, input);
|
|
1207
|
+
let result;
|
|
1208
|
+
// Checked BEFORE requesting permission: no point asking the user to
|
|
1209
|
+
// approve a diff/preview built from possibly-garbage truncated input
|
|
1210
|
+
// (see the guard comment above toolUseBlocks) — refuse immediately.
|
|
1211
|
+
if (id === truncatedToolUseId) {
|
|
1212
|
+
// This is the LAST block of a message that Anthropic cut off at
|
|
1213
|
+
// max_tokens — its `input` may be an incomplete/partial object that
|
|
1214
|
+
// happened to parse successfully (see the guard above). Refuse to
|
|
1215
|
+
// execute it blindly: a confusing generic validation error, or a
|
|
1216
|
+
// silent partial write, are both worse than a clear explanation the
|
|
1217
|
+
// model can act on immediately (retry with a smaller call).
|
|
1218
|
+
result = {
|
|
1219
|
+
error: `This tool call (${name}) was CUT OFF because the response hit the model's output-token limit ` +
|
|
1220
|
+
`mid-generation (stop_reason: max_tokens) — its arguments may be incomplete or missing fields ` +
|
|
1221
|
+
`entirely, so it was NOT executed to avoid a silent partial edit. Retry with a SMALLER call: ` +
|
|
1222
|
+
(name === 'multi_edit' || name === 'edit_file'
|
|
1223
|
+
? 'split this into fewer edits per call (or call edit_file once per change instead of one large multi_edit), '
|
|
1224
|
+
: name === 'write_file'
|
|
1225
|
+
? 'write the file in smaller chunks via write_file + edit_file follow-ups instead of one large write_file, '
|
|
1226
|
+
: '') +
|
|
1227
|
+
`so the full response fits comfortably under the per-turn output budget.`,
|
|
1228
|
+
};
|
|
1229
|
+
options.onToolResult(name, result);
|
|
1230
|
+
return { block: { ...block, id }, result };
|
|
1231
|
+
}
|
|
1087
1232
|
// Request permission
|
|
1088
1233
|
const description = humanDescription(name, input);
|
|
1089
|
-
let result;
|
|
1090
1234
|
let permitted;
|
|
1091
1235
|
try {
|
|
1092
1236
|
permitted = await options.requestPermission({ tool: name, input, description });
|
package/dist/api/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAA8B,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAKrH,eAAO,MAAM,QAAQ,4BAA4B,CAAC;AAiBlD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,aAAa,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACnC,oFAAoF;IACpF,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IACjG,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAWD,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,GAC7B,OAAO,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAA8B,UAAU,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAKrH,eAAO,MAAM,QAAQ,4BAA4B,CAAC;AAiBlD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,aAAa,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACnC,oFAAoF;IACpF,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAC;IACjG,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAWD,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,CAAC,CAAC,EAAE,QAAQ,KAAK,IAAI,GAC7B,OAAO,CAAC,OAAO,CAAC,CA4XlB;AAID,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAalD;AAID,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAgB1E;AAID,wBAAsB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAkBhF"}
|
package/dist/api/client.js
CHANGED
|
@@ -236,9 +236,22 @@ async function streamChat(messages, options, onEvent) {
|
|
|
236
236
|
contentBlocks.push({ type: 'text', text: fullText });
|
|
237
237
|
}
|
|
238
238
|
contentBlocks.push(...toolUseBlocks);
|
|
239
|
+
// Anthropic's stop_reason travels inside the nested `message` object the
|
|
240
|
+
// backend forwards verbatim (routes/code.js's `sendEvent(res, {type:
|
|
241
|
+
// 'message_complete', message})` — `message` is the raw Anthropic SDK
|
|
242
|
+
// message, which always has stop_reason). 'max_tokens' means the model's
|
|
243
|
+
// output was cut off mid-generation — if the last content block is a
|
|
244
|
+
// tool_use, its `input` may be a truncated JSON object that still happened
|
|
245
|
+
// to parse (e.g. a multi_edit whose `edits` array lost its last, still-
|
|
246
|
+
// in-progress element, or came out empty/missing entirely) without any
|
|
247
|
+
// error at all. The agent loop uses this to refuse executing that block
|
|
248
|
+
// blindly instead of silently applying a partial edit.
|
|
249
|
+
const nestedMessage = evt.message;
|
|
250
|
+
const stopReason = typeof nestedMessage?.stop_reason === 'string' ? nestedMessage.stop_reason : null;
|
|
239
251
|
completedMessage = {
|
|
240
252
|
role: 'assistant',
|
|
241
253
|
content: contentBlocks,
|
|
254
|
+
stopReason,
|
|
242
255
|
};
|
|
243
256
|
onEvent({ type: 'message_complete', message: completedMessage });
|
|
244
257
|
break;
|
package/dist/types.d.ts
CHANGED
|
@@ -18,6 +18,16 @@ export type ContentBlock = TextBlock | ToolUseBlock | ToolResultBlock;
|
|
|
18
18
|
export interface Message {
|
|
19
19
|
role: 'user' | 'assistant';
|
|
20
20
|
content: ContentBlock[];
|
|
21
|
+
/**
|
|
22
|
+
* Anthropic's stop_reason for this turn, when known (only set on a freshly
|
|
23
|
+
* streamed assistant message, never on a historical/replayed one). 'max_tokens'
|
|
24
|
+
* means the model's output was CUT OFF mid-generation — if the last block is a
|
|
25
|
+
* tool_use, its `input` may be a truncated/partial JSON object (missing required
|
|
26
|
+
* fields, or an array that looks complete but stopped early) that happened to
|
|
27
|
+
* still parse successfully. Callers MUST check this before trusting a tool_use
|
|
28
|
+
* block was fully intended by the model, not silently mangled by the ceiling.
|
|
29
|
+
*/
|
|
30
|
+
stopReason?: string | null;
|
|
21
31
|
}
|
|
22
32
|
export interface SSETextEvent {
|
|
23
33
|
type: 'text';
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,aAAa,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,YAAY,GAAG,eAAe,CAAC;AAItE,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,YAAY,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nexrall/code-core",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4",
|
|
4
4
|
"description": "Core agent loop, tools, and extension primitives for Nexrall Code — embed an AI coding agent in any Node.js application.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nexrall <support@nexrall.com> (https://nexrall.com)",
|