refactor(agent-providers): add toObjectRecord helper and fix type-aware lint errors in codex and claude agents (#699)
This commit is contained in:
@@ -142,6 +142,13 @@ export function normalizeClaudeAskUserQuestionUpdatedInput(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toObjectRecord(value: unknown): Record<string, unknown> | undefined {
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return value as Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
type TurnState = "idle" | "foreground" | "autonomous";
|
type TurnState = "idle" | "foreground" | "autonomous";
|
||||||
|
|
||||||
interface EventIdentifiers {
|
interface EventIdentifiers {
|
||||||
@@ -389,11 +396,11 @@ function summarizeClaudeOptionsForLog(options: ClaudeOptions): ClaudeOptionsLogS
|
|||||||
if (typeof systemPromptRaw === "string") {
|
if (typeof systemPromptRaw === "string") {
|
||||||
return { mode: "string" as const, preset: null };
|
return { mode: "string" as const, preset: null };
|
||||||
}
|
}
|
||||||
const prompt = systemPromptRaw as Record<string, unknown>;
|
const prompt = toObjectRecord(systemPromptRaw);
|
||||||
const promptType = typeof prompt.type === "string" ? prompt.type : "custom";
|
const promptType = typeof prompt?.type === "string" ? prompt.type : "custom";
|
||||||
return {
|
return {
|
||||||
mode: promptType === "preset" ? ("preset" as const) : ("custom" as const),
|
mode: promptType === "preset" ? ("preset" as const) : ("custom" as const),
|
||||||
preset: typeof prompt.preset === "string" && prompt.preset.length > 0 ? prompt.preset : null,
|
preset: typeof prompt?.preset === "string" && prompt.preset.length > 0 ? prompt.preset : null,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
const mcpServerNames = options.mcpServers ? Object.keys(options.mcpServers).sort() : [];
|
const mcpServerNames = options.mcpServers ? Object.keys(options.mcpServers).sort() : [];
|
||||||
@@ -464,7 +471,11 @@ function normalizeForDeterministicString(value: unknown, seen: WeakSet<object>):
|
|||||||
return "[circular]";
|
return "[circular]";
|
||||||
}
|
}
|
||||||
seen.add(objectValue);
|
seen.add(objectValue);
|
||||||
const record = value as Record<string, unknown>;
|
const record = toObjectRecord(value);
|
||||||
|
if (!record) {
|
||||||
|
seen.delete(objectValue);
|
||||||
|
return "[invalid]";
|
||||||
|
}
|
||||||
const normalized: Record<string, unknown> = {};
|
const normalized: Record<string, unknown> = {};
|
||||||
for (const key of Object.keys(record).sort()) {
|
for (const key of Object.keys(record).sort()) {
|
||||||
normalized[key] = normalizeForDeterministicString(record[key], seen);
|
normalized[key] = normalizeForDeterministicString(record[key], seen);
|
||||||
@@ -472,7 +483,10 @@ function normalizeForDeterministicString(value: unknown, seen: WeakSet<object>):
|
|||||||
seen.delete(objectValue);
|
seen.delete(objectValue);
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
return String(value);
|
if (typeof value === "number" || typeof value === "boolean") {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
return "[unsupported]";
|
||||||
}
|
}
|
||||||
|
|
||||||
function deterministicStringify(value: unknown): string {
|
function deterministicStringify(value: unknown): string {
|
||||||
@@ -486,11 +500,13 @@ function deterministicStringify(value: unknown): string {
|
|||||||
}
|
}
|
||||||
return JSON.stringify(normalized);
|
return JSON.stringify(normalized);
|
||||||
} catch {
|
} catch {
|
||||||
try {
|
if (typeof value === "string") {
|
||||||
return String(value);
|
return value;
|
||||||
} catch {
|
|
||||||
return "[unserializable]";
|
|
||||||
}
|
}
|
||||||
|
if (typeof value === "number" || typeof value === "boolean") {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
return "[unserializable]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -902,7 +918,7 @@ class TimelineAssembler {
|
|||||||
runId: string | null,
|
runId: string | null,
|
||||||
messageIdHint: string | null,
|
messageIdHint: string | null,
|
||||||
): AgentTimelineItem[] {
|
): AgentTimelineItem[] {
|
||||||
const event = message.event as unknown as Record<string, unknown>;
|
const event = toObjectRecord(message.event) ?? {};
|
||||||
const eventType = readTrimmedString(event.type);
|
const eventType = readTrimmedString(event.type);
|
||||||
const streamEventMessageId = this.readMessageIdFromStreamEvent(event) ?? messageIdHint;
|
const streamEventMessageId = this.readMessageIdFromStreamEvent(event) ?? messageIdHint;
|
||||||
|
|
||||||
|
|||||||
@@ -541,6 +541,10 @@ function toCodexMcpConfig(config: McpServerConfig): CodexMcpServerConfig {
|
|||||||
url: config.url,
|
url: config.url,
|
||||||
http_headers: config.headers,
|
http_headers: config.headers,
|
||||||
};
|
};
|
||||||
|
default: {
|
||||||
|
const _exhaustive = config as { type: never };
|
||||||
|
throw new Error(`Unsupported MCP config type: ${String(_exhaustive.type)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
interface JsonRpcRequest {
|
interface JsonRpcRequest {
|
||||||
@@ -562,13 +566,36 @@ interface JsonRpcNotification {
|
|||||||
|
|
||||||
type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotification;
|
type JsonRpcMessage = JsonRpcRequest | JsonRpcResponse | JsonRpcNotification;
|
||||||
|
|
||||||
|
function isJsonRpcResponse(msg: JsonRpcMessage): msg is JsonRpcResponse {
|
||||||
|
const m = msg as unknown as Record<string, unknown>;
|
||||||
|
if (typeof m.id !== "number") return false;
|
||||||
|
return m.result !== undefined || !!m.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isJsonRpcRequest(msg: JsonRpcMessage): msg is JsonRpcRequest {
|
||||||
|
const m = msg as unknown as Record<string, unknown>;
|
||||||
|
return typeof m.id === "number" && typeof m.method === "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isJsonRpcNotification(msg: JsonRpcMessage): msg is JsonRpcNotification {
|
||||||
|
const m = msg as unknown as Record<string, unknown>;
|
||||||
|
return typeof m.method === "string" && typeof m.id !== "number";
|
||||||
|
}
|
||||||
|
|
||||||
|
function toObjectRecord(value: unknown): Record<string, unknown> | undefined {
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return value as Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
interface PendingRequest {
|
interface PendingRequest {
|
||||||
resolve: (value: unknown) => void;
|
resolve: (value: unknown) => void;
|
||||||
reject: (error: Error) => void;
|
reject: (error: Error) => void;
|
||||||
timer: ReturnType<typeof setTimeout>;
|
timer: ReturnType<typeof setTimeout>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestHandler = (params: unknown) => Promise<unknown> | unknown;
|
type RequestHandler = (params: unknown) => unknown;
|
||||||
|
|
||||||
type NotificationHandler = (method: string, params: unknown) => void;
|
type NotificationHandler = (method: string, params: unknown) => void;
|
||||||
|
|
||||||
@@ -592,10 +619,6 @@ interface CodexModelListResponse {
|
|||||||
data?: CodexModel[];
|
data?: CodexModel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CodexThreadStartResponse {
|
|
||||||
thread?: { id?: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
class CodexAppServerClient {
|
class CodexAppServerClient {
|
||||||
private readonly rl: readline.Interface;
|
private readonly rl: readline.Interface;
|
||||||
private readonly pending = new Map<number, PendingRequest>();
|
private readonly pending = new Map<number, PendingRequest>();
|
||||||
@@ -717,32 +740,31 @@ class CodexAppServerClient {
|
|||||||
|
|
||||||
private async handleLine(line: string): Promise<void> {
|
private async handleLine(line: string): Promise<void> {
|
||||||
if (!line.trim()) return;
|
if (!line.trim()) return;
|
||||||
let msg: JsonRpcMessage;
|
const raw = JSON.parse(line);
|
||||||
try {
|
const msg = toObjectRecord(raw) as unknown as JsonRpcMessage;
|
||||||
msg = JSON.parse(line);
|
if (!msg) {
|
||||||
} catch (error) {
|
this.logger.warn({ line }, "Parsed JSON is not an object");
|
||||||
this.logger.warn({ error, line }, "Failed to parse Codex app-server JSON");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof (msg as JsonRpcResponse).id === "number") {
|
if (isJsonRpcResponse(msg)) {
|
||||||
const id = (msg as JsonRpcResponse).id;
|
const id = msg.id;
|
||||||
if ((msg as JsonRpcResponse).result !== undefined || (msg as JsonRpcResponse).error) {
|
if (msg.result !== undefined || msg.error) {
|
||||||
const pending = this.pending.get(id);
|
const pending = this.pending.get(id);
|
||||||
if (!pending) return;
|
if (!pending) return;
|
||||||
clearTimeout(pending.timer);
|
clearTimeout(pending.timer);
|
||||||
this.pending.delete(id);
|
this.pending.delete(id);
|
||||||
if ((msg as JsonRpcResponse).error) {
|
if (msg.error) {
|
||||||
pending.reject(new Error((msg as JsonRpcResponse).error?.message ?? "Unknown error"));
|
pending.reject(new Error(msg.error.message ?? "Unknown error"));
|
||||||
} else {
|
} else {
|
||||||
pending.resolve((msg as JsonRpcResponse).result);
|
pending.resolve(msg.result);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server-initiated request
|
// Server-initiated request
|
||||||
if (typeof (msg as JsonRpcRequest).method === "string") {
|
if (isJsonRpcRequest(msg)) {
|
||||||
const request = msg as JsonRpcRequest;
|
const request = msg;
|
||||||
const handler = this.requestHandlers.get(request.method);
|
const handler = this.requestHandlers.get(request.method);
|
||||||
try {
|
try {
|
||||||
const result = handler ? await handler(request.params) : {};
|
const result = handler ? await handler(request.params) : {};
|
||||||
@@ -757,38 +779,26 @@ class CodexAppServerClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof (msg as JsonRpcNotification).method === "string") {
|
if (isJsonRpcNotification(msg)) {
|
||||||
const notification = msg as JsonRpcNotification;
|
this.notificationHandler?.(msg.method, msg.params);
|
||||||
this.notificationHandler?.(notification.method, notification.params);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toAgentUsage(tokenUsage: unknown): AgentUsage | undefined {
|
function toAgentUsage(tokenUsage: unknown): AgentUsage | undefined {
|
||||||
if (!tokenUsage || typeof tokenUsage !== "object") return undefined;
|
const usage = toObjectRecord(tokenUsage);
|
||||||
const usage = tokenUsage as {
|
if (!usage) return undefined;
|
||||||
model_context_window?: number;
|
const last = toObjectRecord(usage.last);
|
||||||
modelContextWindow?: number;
|
|
||||||
last?: {
|
|
||||||
inputTokens?: number;
|
|
||||||
cachedInputTokens?: number;
|
|
||||||
outputTokens?: number;
|
|
||||||
total_tokens?: number;
|
|
||||||
totalTokens?: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const contextWindowMaxTokens = firstPositiveFiniteNumber(
|
const contextWindowMaxTokens = firstPositiveFiniteNumber(
|
||||||
usage.model_context_window,
|
usage.model_context_window,
|
||||||
usage.modelContextWindow,
|
usage.modelContextWindow,
|
||||||
);
|
);
|
||||||
const contextWindowUsedTokens = firstPositiveFiniteNumber(
|
const contextWindowUsedTokens = firstPositiveFiniteNumber(last?.total_tokens, last?.totalTokens);
|
||||||
usage.last?.total_tokens,
|
|
||||||
usage.last?.totalTokens,
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
inputTokens: usage.last?.inputTokens,
|
inputTokens: typeof last?.inputTokens === "number" ? last.inputTokens : undefined,
|
||||||
cachedInputTokens: usage.last?.cachedInputTokens,
|
cachedInputTokens:
|
||||||
outputTokens: usage.last?.outputTokens,
|
typeof last?.cachedInputTokens === "number" ? last.cachedInputTokens : undefined,
|
||||||
|
outputTokens: typeof last?.outputTokens === "number" ? last.outputTokens : undefined,
|
||||||
...(contextWindowMaxTokens !== undefined ? { contextWindowMaxTokens } : {}),
|
...(contextWindowMaxTokens !== undefined ? { contextWindowMaxTokens } : {}),
|
||||||
...(contextWindowUsedTokens !== undefined ? { contextWindowUsedTokens } : {}),
|
...(contextWindowUsedTokens !== undefined ? { contextWindowUsedTokens } : {}),
|
||||||
};
|
};
|
||||||
@@ -798,11 +808,12 @@ function extractUserText(content: unknown): string | null {
|
|||||||
if (!Array.isArray(content)) return null;
|
if (!Array.isArray(content)) return null;
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
for (const item of content) {
|
for (const item of content) {
|
||||||
if (item && typeof item === "object") {
|
const record = toObjectRecord(item);
|
||||||
const obj = item as { type?: string; text?: string };
|
if (!record) {
|
||||||
if (obj.type === "text" && typeof obj.text === "string") {
|
continue;
|
||||||
parts.push(obj.text);
|
}
|
||||||
}
|
if (record.type === "text" && typeof record.text === "string") {
|
||||||
|
parts.push(record.text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return parts.length > 0 ? parts.join("\n") : null;
|
return parts.length > 0 ? parts.join("\n") : null;
|
||||||
@@ -919,10 +930,10 @@ function normalizeCodexQuestionPrompts(raw: unknown): CodexQuestionPrompt[] {
|
|||||||
}
|
}
|
||||||
const questions: CodexQuestionPrompt[] = [];
|
const questions: CodexQuestionPrompt[] = [];
|
||||||
for (const item of raw) {
|
for (const item of raw) {
|
||||||
if (!item || typeof item !== "object") {
|
const record = toObjectRecord(item);
|
||||||
|
if (!record) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const record = item as Record<string, unknown>;
|
|
||||||
const id = nonEmptyString(record.id);
|
const id = nonEmptyString(record.id);
|
||||||
const header = nonEmptyString(record.header);
|
const header = nonEmptyString(record.header);
|
||||||
const question = nonEmptyString(record.question);
|
const question = nonEmptyString(record.question);
|
||||||
@@ -931,10 +942,10 @@ function normalizeCodexQuestionPrompts(raw: unknown): CodexQuestionPrompt[] {
|
|||||||
}
|
}
|
||||||
const options = Array.isArray(record.options)
|
const options = Array.isArray(record.options)
|
||||||
? record.options.flatMap((option): CodexQuestionOption[] => {
|
? record.options.flatMap((option): CodexQuestionOption[] => {
|
||||||
if (!option || typeof option !== "object") {
|
const optionRecord = toObjectRecord(option);
|
||||||
|
if (!optionRecord) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const optionRecord = option as Record<string, unknown>;
|
|
||||||
const label = nonEmptyString(optionRecord.label);
|
const label = nonEmptyString(optionRecord.label);
|
||||||
if (!label) {
|
if (!label) {
|
||||||
return [];
|
return [];
|
||||||
@@ -1045,13 +1056,9 @@ function mapCodexQuestionResponseByHeader(params: {
|
|||||||
if (params.response.behavior !== "allow") {
|
if (params.response.behavior !== "allow") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const answersRecord =
|
const updatedInputRecord = toObjectRecord(params.response.updatedInput);
|
||||||
params.response.updatedInput && typeof params.response.updatedInput === "object"
|
const answersRecord = toObjectRecord(updatedInputRecord?.answers);
|
||||||
? ((params.response.updatedInput as Record<string, unknown>).answers as
|
if (!answersRecord) {
|
||||||
| Record<string, unknown>
|
|
||||||
| undefined)
|
|
||||||
: undefined;
|
|
||||||
if (!answersRecord || typeof answersRecord !== "object") {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1086,10 +1093,10 @@ interface CodexPatchFileChange {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function extractPatchLikeText(value: unknown): string | undefined {
|
function extractPatchLikeText(value: unknown): string | undefined {
|
||||||
if (!value || typeof value !== "object") {
|
const record = toObjectRecord(value);
|
||||||
|
if (!record) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const record = value as Record<string, unknown>;
|
|
||||||
const candidates = [
|
const candidates = [
|
||||||
record.diff,
|
record.diff,
|
||||||
record.patch,
|
record.patch,
|
||||||
@@ -1194,10 +1201,10 @@ function parseCodexPatchChanges(changes: unknown): CodexPatchFileChange[] {
|
|||||||
if (Array.isArray(changes)) {
|
if (Array.isArray(changes)) {
|
||||||
return changes
|
return changes
|
||||||
.map((entry): CodexPatchFileChange | null => {
|
.map((entry): CodexPatchFileChange | null => {
|
||||||
if (!entry || typeof entry !== "object") {
|
const record = toObjectRecord(entry);
|
||||||
|
if (!record) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const record = entry as Record<string, unknown>;
|
|
||||||
const pathValue = resolvePathFromRecord(record);
|
const pathValue = resolvePathFromRecord(record);
|
||||||
if (!pathValue) {
|
if (!pathValue) {
|
||||||
return null;
|
return null;
|
||||||
@@ -1214,7 +1221,10 @@ function parseCodexPatchChanges(changes: unknown): CodexPatchFileChange[] {
|
|||||||
.filter((entry): entry is CodexPatchFileChange => entry !== null);
|
.filter((entry): entry is CodexPatchFileChange => entry !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const recordChanges = changes as Record<string, unknown>;
|
const recordChanges = toObjectRecord(changes);
|
||||||
|
if (!recordChanges) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const directPathValue = resolvePathFromRecord(recordChanges);
|
const directPathValue = resolvePathFromRecord(recordChanges);
|
||||||
if (directPathValue) {
|
if (directPathValue) {
|
||||||
return [
|
return [
|
||||||
@@ -1472,8 +1482,8 @@ function threadItemToTimeline(
|
|||||||
item: unknown,
|
item: unknown,
|
||||||
options?: { includeUserMessage?: boolean; cwd?: string | null },
|
options?: { includeUserMessage?: boolean; cwd?: string | null },
|
||||||
): AgentTimelineItem | null {
|
): AgentTimelineItem | null {
|
||||||
if (!item || typeof item !== "object") return null;
|
const itemRecord = toObjectRecord(item);
|
||||||
const itemRecord = item as Record<string, unknown>;
|
if (!itemRecord) return null;
|
||||||
const includeUserMessage = options?.includeUserMessage ?? true;
|
const includeUserMessage = options?.includeUserMessage ?? true;
|
||||||
const cwd = options?.cwd ?? null;
|
const cwd = options?.cwd ?? null;
|
||||||
const normalizedType = normalizeCodexThreadItemType(
|
const normalizedType = normalizeCodexThreadItemType(
|
||||||
@@ -2397,17 +2407,14 @@ async function readCodexConfiguredDefaults(
|
|||||||
): Promise<CodexConfiguredDefaults> {
|
): Promise<CodexConfiguredDefaults> {
|
||||||
let savedConfigDefaults: CodexConfiguredDefaults = {};
|
let savedConfigDefaults: CodexConfiguredDefaults = {};
|
||||||
try {
|
try {
|
||||||
const response = (await client.request("getUserSavedConfig", {})) as {
|
const response = toObjectRecord(await client.request("getUserSavedConfig", {}));
|
||||||
config?: {
|
const config = toObjectRecord(response?.config);
|
||||||
model?: string | null;
|
const modelValue = typeof config?.model === "string" ? config.model : undefined;
|
||||||
modelReasoningEffort?: string | null;
|
const thinkingOptionValue =
|
||||||
};
|
typeof config?.modelReasoningEffort === "string" ? config.modelReasoningEffort : null;
|
||||||
};
|
|
||||||
savedConfigDefaults = {
|
savedConfigDefaults = {
|
||||||
model: normalizeCodexModelId(response?.config?.model),
|
model: normalizeCodexModelId(modelValue),
|
||||||
thinkingOptionId: normalizeCodexThinkingOptionId(
|
thinkingOptionId: normalizeCodexThinkingOptionId(thinkingOptionValue),
|
||||||
response?.config?.modelReasoningEffort ?? null,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug({ error }, "Failed to read Codex saved config defaults");
|
logger.debug({ error }, "Failed to read Codex saved config defaults");
|
||||||
@@ -2419,17 +2426,14 @@ async function readCodexConfiguredDefaults(
|
|||||||
|
|
||||||
let configReadDefaults: CodexConfiguredDefaults = {};
|
let configReadDefaults: CodexConfiguredDefaults = {};
|
||||||
try {
|
try {
|
||||||
const response = (await client.request("config/read", {})) as {
|
const response = toObjectRecord(await client.request("config/read", {}));
|
||||||
config?: {
|
const config = toObjectRecord(response?.config);
|
||||||
model?: string | null;
|
const modelValue = typeof config?.model === "string" ? config.model : undefined;
|
||||||
model_reasoning_effort?: string | null;
|
const thinkingOptionValue =
|
||||||
};
|
typeof config?.model_reasoning_effort === "string" ? config.model_reasoning_effort : null;
|
||||||
};
|
|
||||||
configReadDefaults = {
|
configReadDefaults = {
|
||||||
model: normalizeCodexModelId(response?.config?.model),
|
model: normalizeCodexModelId(modelValue),
|
||||||
thinkingOptionId: normalizeCodexThinkingOptionId(
|
thinkingOptionId: normalizeCodexThinkingOptionId(thinkingOptionValue),
|
||||||
response?.config?.model_reasoning_effort ?? null,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug({ error }, "Failed to read Codex config defaults");
|
logger.debug({ error }, "Failed to read Codex config defaults");
|
||||||
@@ -2678,19 +2682,22 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
private async loadCollaborationModes(): Promise<void> {
|
private async loadCollaborationModes(): Promise<void> {
|
||||||
if (!this.client) return;
|
if (!this.client) return;
|
||||||
try {
|
try {
|
||||||
const response = (await this.client.request("collaborationMode/list", {})) as {
|
const response = toObjectRecord(await this.client.request("collaborationMode/list", {}));
|
||||||
data?: Array<Record<string, unknown>>;
|
|
||||||
};
|
|
||||||
const data = Array.isArray(response?.data) ? response.data : [];
|
const data = Array.isArray(response?.data) ? response.data : [];
|
||||||
this.collaborationModes = data.map((entry) => ({
|
this.collaborationModes = data.map((entry) => {
|
||||||
name: typeof entry.name === "string" ? entry.name : "",
|
const record = toObjectRecord(entry);
|
||||||
mode: typeof entry.mode === "string" ? entry.mode : null,
|
return {
|
||||||
model: typeof entry.model === "string" ? entry.model : null,
|
name: typeof record?.name === "string" ? record.name : "",
|
||||||
reasoning_effort:
|
mode: typeof record?.mode === "string" ? record.mode : null,
|
||||||
typeof entry.reasoning_effort === "string" ? entry.reasoning_effort : null,
|
model: typeof record?.model === "string" ? record.model : null,
|
||||||
developer_instructions:
|
reasoning_effort:
|
||||||
typeof entry.developer_instructions === "string" ? entry.developer_instructions : null,
|
typeof record?.reasoning_effort === "string" ? record.reasoning_effort : null,
|
||||||
}));
|
developer_instructions:
|
||||||
|
typeof record?.developer_instructions === "string"
|
||||||
|
? record.developer_instructions
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.trace({ error }, "Failed to load collaboration modes");
|
this.logger.trace({ error }, "Failed to load collaboration modes");
|
||||||
this.collaborationModes = [];
|
this.collaborationModes = [];
|
||||||
@@ -2701,21 +2708,24 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
private async loadSkills(): Promise<void> {
|
private async loadSkills(): Promise<void> {
|
||||||
if (!this.client) return;
|
if (!this.client) return;
|
||||||
try {
|
try {
|
||||||
const response = (await this.client.request("skills/list", {
|
const response = toObjectRecord(
|
||||||
cwd: [this.config.cwd],
|
await this.client.request("skills/list", {
|
||||||
})) as { data?: Array<Record<string, unknown>> };
|
cwd: [this.config.cwd],
|
||||||
|
}),
|
||||||
|
);
|
||||||
const entries = Array.isArray(response?.data) ? response.data : [];
|
const entries = Array.isArray(response?.data) ? response.data : [];
|
||||||
const skills: Array<{ name: string; description: string; path: string }> = [];
|
const skills: Array<{ name: string; description: string; path: string }> = [];
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const list = Array.isArray(entry.skills)
|
const entryRecord = toObjectRecord(entry);
|
||||||
? (entry.skills as Array<Record<string, unknown>>)
|
const list = Array.isArray(entryRecord?.skills) ? entryRecord.skills : [];
|
||||||
: [];
|
|
||||||
for (const skill of list) {
|
for (const skill of list) {
|
||||||
if (typeof skill?.name !== "string" || typeof skill?.path !== "string") continue;
|
const skillRecord = toObjectRecord(skill);
|
||||||
|
if (typeof skillRecord?.name !== "string" || typeof skillRecord?.path !== "string")
|
||||||
|
continue;
|
||||||
skills.push({
|
skills.push({
|
||||||
name: skill.name,
|
name: skillRecord.name,
|
||||||
description: resolveSkillDescription(skill),
|
description: resolveSkillDescription(skillRecord),
|
||||||
path: skill.path,
|
path: skillRecord.path,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2896,7 +2906,7 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
private async ensureThreadLoaded(): Promise<void> {
|
private async ensureThreadLoaded(): Promise<void> {
|
||||||
if (!this.client || !this.currentThreadId) return;
|
if (!this.client || !this.currentThreadId) return;
|
||||||
try {
|
try {
|
||||||
const loaded = (await this.client.request("thread/loaded/list", {})) as { data?: string[] };
|
const loaded = toObjectRecord(await this.client.request("thread/loaded/list", {}));
|
||||||
const ids = Array.isArray(loaded?.data) ? loaded.data : [];
|
const ids = Array.isArray(loaded?.data) ? loaded.data : [];
|
||||||
if (ids.includes(this.currentThreadId)) {
|
if (ids.includes(this.currentThreadId)) {
|
||||||
return;
|
return;
|
||||||
@@ -3439,8 +3449,21 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!model || !thinkingOptionId) {
|
if (!model || !thinkingOptionId) {
|
||||||
const modelResponse = (await this.client.request("model/list", {})) as CodexModelListResponse;
|
const modelResponse = toObjectRecord(await this.client.request("model/list", {}));
|
||||||
const models = modelResponse?.data ?? [];
|
const modelData = Array.isArray(modelResponse?.data) ? modelResponse.data : [];
|
||||||
|
const models = modelData
|
||||||
|
.map((m) => {
|
||||||
|
const record = toObjectRecord(m);
|
||||||
|
return {
|
||||||
|
id: typeof record?.id === "string" ? record.id : "",
|
||||||
|
isDefault: !!record?.isDefault,
|
||||||
|
defaultReasoningEffort:
|
||||||
|
typeof record?.defaultReasoningEffort === "string"
|
||||||
|
? record.defaultReasoningEffort
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((m) => m.id);
|
||||||
const defaultModel = models.find((m) => m.isDefault) ?? models[0];
|
const defaultModel = models.find((m) => m.isDefault) ?? models[0];
|
||||||
if (!defaultModel) {
|
if (!defaultModel) {
|
||||||
throw new Error("No models available from Codex app-server");
|
throw new Error("No models available from Codex app-server");
|
||||||
@@ -3473,7 +3496,7 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
const approvalPolicy = this.config.approvalPolicy ?? preset.approvalPolicy;
|
const approvalPolicy = this.config.approvalPolicy ?? preset.approvalPolicy;
|
||||||
const sandbox = this.config.sandboxMode ?? preset.sandbox;
|
const sandbox = this.config.sandboxMode ?? preset.sandbox;
|
||||||
const innerConfig = this.buildCodexInnerConfig();
|
const innerConfig = this.buildCodexInnerConfig();
|
||||||
const response = (await this.client.request("thread/start", {
|
const rawResponse = await this.client.request("thread/start", {
|
||||||
model,
|
model,
|
||||||
cwd: this.config.cwd ?? null,
|
cwd: this.config.cwd ?? null,
|
||||||
approvalPolicy,
|
approvalPolicy,
|
||||||
@@ -3483,8 +3506,10 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
: {}),
|
: {}),
|
||||||
...(innerConfig ? { config: innerConfig } : {}),
|
...(innerConfig ? { config: innerConfig } : {}),
|
||||||
...(this.ephemeral ? { ephemeral: true } : {}),
|
...(this.ephemeral ? { ephemeral: true } : {}),
|
||||||
})) as CodexThreadStartResponse;
|
});
|
||||||
const threadId = response?.thread?.id;
|
const response = toObjectRecord(rawResponse);
|
||||||
|
const threadRecord = toObjectRecord(response?.thread);
|
||||||
|
const threadId = typeof threadRecord?.id === "string" ? threadRecord.id : undefined;
|
||||||
if (!threadId) {
|
if (!threadId) {
|
||||||
throw new Error("Codex app-server did not return thread id");
|
throw new Error("Codex app-server did not return thread id");
|
||||||
}
|
}
|
||||||
@@ -4512,9 +4537,7 @@ export class CodexAppServerAgentClient implements AgentClient {
|
|||||||
client.notify("initialized", {});
|
client.notify("initialized", {});
|
||||||
|
|
||||||
const limit = options?.limit ?? 20;
|
const limit = options?.limit ?? 20;
|
||||||
const response = (await client.request("thread/list", { limit })) as {
|
const response = toObjectRecord(await client.request("thread/list", { limit }));
|
||||||
data?: Array<Record<string, unknown>>;
|
|
||||||
};
|
|
||||||
const threads = Array.isArray(response?.data) ? response.data : [];
|
const threads = Array.isArray(response?.data) ? response.data : [];
|
||||||
const descriptors: PersistedAgentDescriptor[] = await Promise.all(
|
const descriptors: PersistedAgentDescriptor[] = await Promise.all(
|
||||||
threads.slice(0, limit).map(async (thread) => {
|
threads.slice(0, limit).map(async (thread) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user