blob: e8074484fe995f9b3340c2dcad8e95c790ce54b6 [file]
#!/usr/bin/env node
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { createInterface } from 'node:readline';
if (process.argv.slice(2).join(' ') !== 'app-server --stdio') process.exit(64);
const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
const send = (value) => process.stdout.write(`${JSON.stringify(value)}\n`);
let threadStarts = 0;
let turnCount = 0;
let pendingApproval;
let interrupted;
let threadModel;
let turnModel;
let turnEffort;
function completeTurn(input, approvalDecision = 'none') {
const turnId = `turn-${turnCount}`;
send({
method: 'item/completed',
params: {
threadId: 'thread-1',
turnId,
completedAtMs: Date.now(),
item: {
id: `command-${turnCount}`,
type: 'commandExecution',
command: 'printf fake',
cwd: process.cwd(),
status: approvalDecision === 'decline' ? 'declined' : 'completed',
commandActions: [],
aggregatedOutput: approvalDecision === 'decline' ? null : 'fake output',
exitCode: approvalDecision === 'decline' ? null : 0,
durationMs: 1,
},
},
});
send({
method: 'item/reasoning/summaryTextDelta',
params: {
threadId: 'thread-1',
turnId,
itemId: `reasoning-${turnCount}`,
delta: 'Checked by fake Codex. ',
summaryIndex: 0,
},
});
const text = `Codex handled: ${input} (threadStarts=${threadStarts}, threadModel=${String(threadModel)}, turnModel=${String(turnModel)}, turnEffort=${String(turnEffort)}, approval=${approvalDecision})`;
send({
method: 'item/agentMessage/delta',
params: {
threadId: 'thread-1',
turnId,
itemId: `message-${turnCount}`,
delta: text,
},
});
send({
method: 'item/completed',
params: {
threadId: 'thread-1',
turnId,
completedAtMs: Date.now(),
item: {
id: `message-${turnCount}`,
type: 'agentMessage',
text,
phase: 'final_answer',
},
},
});
send({
method: 'turn/completed',
params: {
threadId: 'thread-1',
turn: { id: turnId, status: 'completed', items: [], error: null },
},
});
}
for await (const line of lines) {
const message = JSON.parse(line);
if (message.method === 'initialize') {
send({ id: message.id, result: { userAgent: 'fake-codex' } });
} else if (message.method === 'model/list') {
send({
id: message.id,
result: {
data: [
{
model: 'gpt-fake',
displayName: 'GPT Fake',
description: 'Fixture model',
isDefault: true,
defaultReasoningEffort: 'medium',
supportedReasoningEfforts: [
{ reasoningEffort: 'low', description: 'Fast' },
{ reasoningEffort: 'medium', description: 'Balanced' },
],
},
],
nextCursor: null,
},
});
} else if (message.method === 'thread/start') {
threadStarts += 1;
threadModel = message.params.model;
send({
id: message.id,
result: { thread: { id: 'thread-1', ephemeral: true } },
});
} else if (message.method === 'turn/start') {
turnCount += 1;
turnModel = message.params.model;
turnEffort = message.params.effort;
const turnId = `turn-${turnCount}`;
send({
id: message.id,
result: { turn: { id: turnId, status: 'inProgress', items: [] } },
});
send({
method: 'item/started',
params: {
threadId: 'thread-1',
turnId,
startedAtMs: Date.now(),
item: {
id: `command-${turnCount}`,
type: 'commandExecution',
command: 'printf fake',
cwd: process.cwd(),
status: 'inProgress',
commandActions: [],
aggregatedOutput: null,
exitCode: null,
durationMs: null,
},
},
});
send({
method: 'item/commandExecution/outputDelta',
params: {
threadId: 'thread-1',
turnId,
itemId: `command-${turnCount}`,
delta: 'pending',
},
});
const input = message.params.input[0].text;
if (input.includes('WAIT_FOR_INTERRUPT')) {
interrupted = { turnId, omitCompletion: input.includes('OMIT_COMPLETION') };
} else {
pendingApproval = { id: 10_000 + turnCount, input };
send({
id: pendingApproval.id,
method: 'item/commandExecution/requestApproval',
params: {
threadId: 'thread-1',
turnId,
itemId: `command-${turnCount}`,
},
});
}
} else if (pendingApproval && message.id === pendingApproval.id) {
const current = pendingApproval;
pendingApproval = undefined;
completeTurn(current.input, message.result?.decision);
} else if (message.method === 'turn/interrupt') {
send({ id: message.id, result: {} });
const turnId = interrupted?.turnId ?? message.params.turnId;
if (!interrupted?.omitCompletion) {
send({
method: 'turn/completed',
params: {
threadId: 'thread-1',
turn: { id: turnId, status: 'interrupted', items: [], error: null },
},
});
}
interrupted = undefined;
}
}