Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
429 lines
13 KiB
TypeScript
429 lines
13 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
applyOpenAIChatContinuationToBody,
|
|
extractProviderContinuationFromRawChunk,
|
|
getOpenAIChatAssistantFieldsForHistoryMessage,
|
|
isProviderContinuationForSource,
|
|
mergeProviderContinuation,
|
|
normalizeProviderContinuationOptions,
|
|
rawOpenAIChatChunkHasToolCalls,
|
|
repairOpenAIChatToolResultPairsInBody,
|
|
withProviderContinuationSource,
|
|
} from './providerContinuation';
|
|
|
|
test('extracts OpenAI-compatible reasoning deltas from raw provider chunks', () => {
|
|
const first = extractProviderContinuationFromRawChunk({
|
|
choices: [
|
|
{
|
|
delta: {
|
|
reasoning_content: 'check ',
|
|
},
|
|
},
|
|
],
|
|
});
|
|
const second = extractProviderContinuationFromRawChunk({
|
|
choices: [
|
|
{
|
|
delta: {
|
|
reasoning_content: 'tools',
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
const merged = mergeProviderContinuation(first, second);
|
|
|
|
assert.equal(merged?.openAIChatAssistantFields?.reasoning_content, 'check tools');
|
|
assert.deepEqual(merged?.reasoningParts, [{ text: 'check tools' }]);
|
|
});
|
|
|
|
test('patches OpenAI-compatible assistant tool-call messages with saved continuation fields', () => {
|
|
const body = JSON.stringify({
|
|
model: 'deepseek-v4-flash',
|
|
stream: true,
|
|
messages: [
|
|
{ role: 'system', content: 'system' },
|
|
{ role: 'user', content: 'inspect the host' },
|
|
{
|
|
role: 'assistant',
|
|
content: '',
|
|
tool_calls: [
|
|
{
|
|
id: 'call_1',
|
|
type: 'function',
|
|
function: { name: 'run_command', arguments: '{}' },
|
|
},
|
|
],
|
|
},
|
|
{ role: 'tool', tool_call_id: 'call_1', content: '{"ok":true}' },
|
|
],
|
|
});
|
|
|
|
const patched = JSON.parse(
|
|
applyOpenAIChatContinuationToBody(body, [
|
|
{ reasoning_content: 'need shell context' },
|
|
]),
|
|
);
|
|
|
|
assert.equal(patched.messages[2].reasoning_content, 'need shell context');
|
|
});
|
|
|
|
test('patches the final assistant message after a tool result with saved continuation fields', () => {
|
|
const body = JSON.stringify({
|
|
model: 'deepseek-v4-flash',
|
|
stream: true,
|
|
messages: [
|
|
{ role: 'user', content: 'inspect the host' },
|
|
{
|
|
role: 'assistant',
|
|
content: '',
|
|
tool_calls: [
|
|
{
|
|
id: 'call_1',
|
|
type: 'function',
|
|
function: { name: 'run_command', arguments: '{}' },
|
|
},
|
|
],
|
|
},
|
|
{ role: 'tool', tool_call_id: 'call_1', content: '{"ok":true}' },
|
|
{ role: 'assistant', content: 'host is healthy' },
|
|
{ role: 'user', content: 'continue' },
|
|
],
|
|
});
|
|
|
|
const patched = JSON.parse(
|
|
applyOpenAIChatContinuationToBody(body, [
|
|
{ reasoning_content: 'need shell context' },
|
|
{ reasoning_content: 'summarize result' },
|
|
]),
|
|
);
|
|
|
|
assert.equal(patched.messages[1].reasoning_content, 'need shell context');
|
|
assert.equal(patched.messages[3].reasoning_content, 'summarize result');
|
|
});
|
|
|
|
test('rebuilds OpenAI-compatible continuation fields from saved thinking for legacy history', () => {
|
|
const source = { providerConfigId: 'deepseek-custom', providerType: 'custom', modelId: 'deepseek-v4-flash' };
|
|
|
|
assert.deepEqual(
|
|
getOpenAIChatAssistantFieldsForHistoryMessage(
|
|
{
|
|
thinking: 'legacy visible reasoning',
|
|
providerId: 'custom',
|
|
model: 'deepseek-v4-flash',
|
|
},
|
|
source,
|
|
),
|
|
{ reasoning_content: 'legacy visible reasoning' },
|
|
);
|
|
});
|
|
|
|
test('does not rebuild continuation fields from thinking when provider or model differs', () => {
|
|
const source = { providerConfigId: 'deepseek-custom', providerType: 'custom', modelId: 'deepseek-v4-flash' };
|
|
|
|
assert.equal(
|
|
getOpenAIChatAssistantFieldsForHistoryMessage(
|
|
{
|
|
thinking: 'other provider reasoning',
|
|
providerId: 'openai',
|
|
model: 'deepseek-v4-flash',
|
|
},
|
|
source,
|
|
),
|
|
undefined,
|
|
);
|
|
assert.equal(
|
|
getOpenAIChatAssistantFieldsForHistoryMessage(
|
|
{
|
|
thinking: 'other model reasoning',
|
|
providerId: 'custom',
|
|
model: 'another-model',
|
|
},
|
|
source,
|
|
),
|
|
undefined,
|
|
);
|
|
assert.equal(
|
|
getOpenAIChatAssistantFieldsForHistoryMessage(
|
|
{
|
|
thinking: 'missing provider metadata',
|
|
model: 'deepseek-v4-flash',
|
|
},
|
|
source,
|
|
),
|
|
undefined,
|
|
);
|
|
assert.equal(
|
|
getOpenAIChatAssistantFieldsForHistoryMessage(
|
|
{
|
|
thinking: 'missing model metadata',
|
|
providerId: 'custom',
|
|
},
|
|
source,
|
|
),
|
|
undefined,
|
|
);
|
|
});
|
|
|
|
test('detects OpenAI-compatible tool calls in raw chunks', () => {
|
|
assert.equal(rawOpenAIChatChunkHasToolCalls({
|
|
choices: [
|
|
{
|
|
delta: {
|
|
tool_calls: [
|
|
{
|
|
id: 'call_1',
|
|
type: 'function',
|
|
function: { name: 'run_command', arguments: '{}' },
|
|
},
|
|
],
|
|
},
|
|
},
|
|
],
|
|
}), true);
|
|
|
|
assert.equal(rawOpenAIChatChunkHasToolCalls({
|
|
choices: [{ delta: { reasoning_content: 'think' } }],
|
|
}), false);
|
|
assert.equal(rawOpenAIChatChunkHasToolCalls('[DONE]'), false);
|
|
});
|
|
|
|
test('merges provider reasoning metadata into the reasoning part it belongs to', () => {
|
|
const merged = mergeProviderContinuation(
|
|
{ reasoningParts: [{ text: 'consider options' }] },
|
|
{ reasoningParts: [{ text: '', providerOptions: { anthropic: { signature: 'sig-1' } } }] },
|
|
);
|
|
|
|
assert.deepEqual(merged?.reasoningParts, [
|
|
{
|
|
text: 'consider options',
|
|
providerOptions: { anthropic: { signature: 'sig-1' } },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('normalizes provider metadata without unsafe object keys', () => {
|
|
const unsafeMetadata = JSON.parse('{"google":{"thoughtSignature":"sig-1","__proto__":{"polluted":true},"nested":{"constructor":{"bad":true},"value":"safe"}},"__proto__":{"ignored":true}}');
|
|
const normalized = normalizeProviderContinuationOptions(unsafeMetadata);
|
|
|
|
assert.deepEqual(normalized, {
|
|
google: {
|
|
thoughtSignature: 'sig-1',
|
|
nested: { value: 'safe' },
|
|
},
|
|
});
|
|
assert.equal(Object.prototype.hasOwnProperty.call(normalized?.google ?? {}, '__proto__'), false);
|
|
});
|
|
|
|
test('merges equivalent provider options without depending on key order', () => {
|
|
const merged = mergeProviderContinuation(
|
|
{ reasoningParts: [{ text: 'one ', providerOptions: { google: { b: 2, a: 1 } } }] },
|
|
{ reasoningParts: [{ text: 'two', providerOptions: { google: { a: 1, b: 2 } } }] },
|
|
);
|
|
|
|
assert.deepEqual(merged?.reasoningParts, [
|
|
{
|
|
text: 'one two',
|
|
providerOptions: { google: { b: 2, a: 1 } },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('cleans nested unsafe provider option keys when merging saved data', () => {
|
|
const unsafeOptions = JSON.parse('{"google":{"nested":{"prototype":{"bad":true},"value":"safe"}}}');
|
|
const merged = mergeProviderContinuation(
|
|
{ reasoningParts: [{ text: 'one ', providerOptions: unsafeOptions }] },
|
|
{ reasoningParts: [{ text: 'two' }] },
|
|
);
|
|
|
|
assert.deepEqual(merged?.reasoningParts, [
|
|
{
|
|
text: 'one ',
|
|
providerOptions: { google: { nested: { value: 'safe' } } },
|
|
},
|
|
{ text: 'two' },
|
|
]);
|
|
});
|
|
|
|
test('tracks continuation source so provider switches do not replay hidden context', () => {
|
|
const source = { providerConfigId: 'deepseek-custom', providerType: 'custom', modelId: 'deepseek-v4-flash' };
|
|
const continuation = withProviderContinuationSource(
|
|
{ openAIChatAssistantFields: { reasoning_content: 'think' } },
|
|
source,
|
|
);
|
|
|
|
assert.equal(isProviderContinuationForSource(continuation, source), true);
|
|
assert.equal(
|
|
isProviderContinuationForSource(continuation, {
|
|
providerConfigId: 'openai',
|
|
providerType: 'openai',
|
|
modelId: 'gpt-5',
|
|
}),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test('drops old hidden context instead of relabeling it when sources differ', () => {
|
|
const deepseek = { providerConfigId: 'deepseek-custom', providerType: 'custom', modelId: 'deepseek-v4-flash' };
|
|
const openai = { providerConfigId: 'openai', providerType: 'openai', modelId: 'gpt-5' };
|
|
const merged = mergeProviderContinuation(
|
|
{ source: deepseek, openAIChatAssistantFields: { reasoning_content: 'old' } },
|
|
{ source: openai, reasoningParts: [{ text: 'new' }] },
|
|
);
|
|
|
|
assert.deepEqual(merged, {
|
|
source: openai,
|
|
reasoningParts: [{ text: 'new' }],
|
|
});
|
|
});
|
|
|
|
test('merges tool-call provider options by tool call id', () => {
|
|
const merged = mergeProviderContinuation(
|
|
{ toolCallProviderOptionsById: { call_1: { google: { thoughtSignature: 'sig-1' } } } },
|
|
{ toolCallProviderOptionsById: { call_1: { google: { extra: true } } } },
|
|
);
|
|
|
|
assert.deepEqual(merged?.toolCallProviderOptionsById, {
|
|
call_1: {
|
|
google: {
|
|
thoughtSignature: 'sig-1',
|
|
extra: true,
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
test('skips plain assistant messages that are not part of a tool loop', () => {
|
|
const body = JSON.stringify({
|
|
stream: true,
|
|
messages: [
|
|
{ role: 'assistant', content: 'plain answer' },
|
|
{
|
|
role: 'assistant',
|
|
content: '',
|
|
tool_calls: [
|
|
{
|
|
id: 'call_1',
|
|
type: 'function',
|
|
function: { name: 'run_command', arguments: '{}' },
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
|
|
const patched = JSON.parse(
|
|
applyOpenAIChatContinuationToBody(body, [
|
|
{ reasoning_content: 'tool reasoning' },
|
|
]),
|
|
);
|
|
|
|
assert.equal(patched.messages[0].reasoning_content, undefined);
|
|
assert.equal(patched.messages[1].reasoning_content, 'tool reasoning');
|
|
});
|
|
|
|
test('keeps assistant tool-call continuation fields aligned with message order', () => {
|
|
const toolCall = (id: string) => ({
|
|
id,
|
|
type: 'function',
|
|
function: { name: 'run_command', arguments: '{}' },
|
|
});
|
|
const body = JSON.stringify({
|
|
stream: true,
|
|
messages: [
|
|
{ role: 'assistant', content: '', tool_calls: [toolCall('call_1')] },
|
|
{ role: 'tool', tool_call_id: 'call_1', content: '{"ok":true}' },
|
|
{ role: 'assistant', content: '', tool_calls: [toolCall('call_2')] },
|
|
],
|
|
});
|
|
|
|
const patched = JSON.parse(
|
|
applyOpenAIChatContinuationToBody(body, [
|
|
undefined,
|
|
{ reasoning_content: 'second reasoning' },
|
|
]),
|
|
);
|
|
|
|
assert.equal(patched.messages[0].reasoning_content, undefined);
|
|
assert.equal(patched.messages[2].reasoning_content, 'second reasoning');
|
|
});
|
|
|
|
test('leaves invalid or unchanged OpenAI-compatible request bodies alone', () => {
|
|
assert.equal(applyOpenAIChatContinuationToBody('{', []), '{');
|
|
|
|
const body = JSON.stringify({ stream: true, messages: [{ role: 'user', content: 'hi' }] });
|
|
assert.equal(applyOpenAIChatContinuationToBody(body, [{ reasoning_content: 'unused' }]), body);
|
|
});
|
|
|
|
test('leaves complete OpenAI-compatible tool result pairs unchanged', () => {
|
|
const body = JSON.stringify({
|
|
stream: true,
|
|
messages: [
|
|
{
|
|
role: 'assistant',
|
|
content: '',
|
|
tool_calls: [
|
|
{ id: 'call_1', type: 'function', function: { name: 'terminal_execute', arguments: '{}' } },
|
|
{ id: 'call_2', type: 'function', function: { name: 'terminal_execute', arguments: '{}' } },
|
|
],
|
|
},
|
|
{ role: 'tool', tool_call_id: 'call_1', content: 'one' },
|
|
{ role: 'tool', tool_call_id: 'call_2', content: 'two' },
|
|
{ role: 'user', content: 'continue' },
|
|
],
|
|
});
|
|
|
|
assert.equal(repairOpenAIChatToolResultPairsInBody(body), body);
|
|
});
|
|
|
|
test('repairs partial OpenAI-compatible tool result pairs before sending history', () => {
|
|
const body = JSON.stringify({
|
|
stream: true,
|
|
messages: [
|
|
{
|
|
role: 'assistant',
|
|
content: '',
|
|
tool_calls: [
|
|
{ id: 'call_1', type: 'function', function: { name: 'terminal_execute', arguments: '{}' } },
|
|
{ id: 'call_2', type: 'function', function: { name: 'terminal_execute', arguments: '{}' } },
|
|
],
|
|
},
|
|
{ role: 'tool', tool_call_id: 'call_2', content: 'two' },
|
|
{ role: 'user', content: 'continue' },
|
|
],
|
|
});
|
|
|
|
const repaired = JSON.parse(repairOpenAIChatToolResultPairsInBody(body));
|
|
|
|
assert.deepEqual(
|
|
repaired.messages[0].tool_calls.map((toolCall: { id: string }) => toolCall.id),
|
|
['call_2'],
|
|
);
|
|
assert.equal(repaired.messages[1].role, 'tool');
|
|
assert.equal(repaired.messages[1].tool_call_id, 'call_2');
|
|
assert.equal(repaired.messages[2].role, 'user');
|
|
});
|
|
|
|
test('drops orphaned OpenAI-compatible tool calls that have no results', () => {
|
|
const body = JSON.stringify({
|
|
stream: true,
|
|
messages: [
|
|
{
|
|
role: 'assistant',
|
|
content: '',
|
|
tool_calls: [
|
|
{ id: 'call_1', type: 'function', function: { name: 'terminal_execute', arguments: '{}' } },
|
|
],
|
|
},
|
|
{ role: 'user', content: 'are you there?' },
|
|
],
|
|
});
|
|
|
|
const repaired = JSON.parse(repairOpenAIChatToolResultPairsInBody(body));
|
|
|
|
assert.deepEqual(repaired.messages, [
|
|
{ role: 'user', content: 'are you there?' },
|
|
]);
|
|
});
|