Merge remote-tracking branch 'upstream/main' into pr-45

# Conflicts:
#	src/config/index.ts
#	src/types/index.ts
This commit is contained in:
Joseph Magly committed 2026-07-16 12:11:16 -04:00
commit 5a12593baa
93 files changed
+9379 -649

No files matched your search

+15 -6
View File
@@ -748,9 +748,12 @@ class LocalAdapter implements LLMProviderAdapter {
return { valid: true };
}
// A /v1 base URL means an OpenAI-compatible server (/chat/completions, choices[]).
// A versioned base URL (/v1, /v2, /v4, …) means an OpenAI-compatible server
// (/chat/completions, choices[]). Many OpenAI-compatible providers version their
// API at paths other than /v1 (e.g. Zhipu/z.ai exposes /api/paas/v4), so match any
// /vN rather than literally /v1.
private isOpenAIWire(baseUrl: string): boolean {
return /\/v1(\/|$)/.test(baseUrl);
return /\/v\d+(\/|$)/.test(baseUrl);
}
// Inject the Arsenal contract as a system turn when tools are offered, and
@@ -764,9 +767,12 @@ class LocalAdapter implements LLMProviderAdapter {
+ (contract
? ' You drive a ReAct loop: REQUEST tools, the harness runs them and returns results, you reason until the surface is exhausted.' + contract
: ' Operate in planning and analysis mode; when JSON is requested, return ONLY the requested block — no preamble.');
const out: { role: string; content: string }[] = [{ role: 'system', content: preamble }];
const systemParts = [preamble];
const out: { role: string; content: string }[] = [];
for (const m of messages) {
if (m.role === 'assistant' && m.toolCalls?.length) {
if (m.role === 'system') {
if (m.content.trim()) systemParts.push(m.content);
} else if (m.role === 'assistant' && m.toolCalls?.length) {
out.push({ role: 'assistant', content: `[requested tools: ${m.toolCalls.map(t => t.name).join(', ')}]` });
} else if (m.role === 'tool') {
out.push({ role: 'user', content: `TOOL RESULT (${m.name || 'tool'}):\n${m.content}` });
@@ -774,7 +780,7 @@ class LocalAdapter implements LLMProviderAdapter {
out.push({ role: m.role, content: m.content });
}
}
return out;
return [{ role: 'system', content: systemParts.join('\n\n') }, ...out];
}
async chat(messages: LLMMessage[], options?: ChatOptions): Promise<LLMResponse> {
@@ -1090,7 +1096,8 @@ class LocalAgentAdapter implements LLMProviderAdapter {
async chat(messages: LLMMessage[], options?: ChatOptions): Promise<LLMResponse> {
const agentId = this.config.model || 'codex';
const prompt = this.formatPrompt(messages, options);
const content = (await localAgentChat(agentId, prompt, { timeoutMs: this.config.timeout || 240000 })).trim();
const timeoutMs = typeof this.config.timeout === 'number' && this.config.timeout > 0 ? this.config.timeout : undefined;
const content = (await localAgentChat(agentId, prompt, { timeoutMs })).trim();
// Tool-calling over text: if the Arsenal was offered, parse the agent's tool requests so the
// ReAct loop EXECUTES them instead of treating this planning turn as the (abstaining) final answer.
const toolCalls = options?.tools?.length ? parseTextToolCalls(content) : undefined;
@@ -1228,6 +1235,8 @@ export class LLMBackbone extends EventEmitter<LLMEvents> {
return new OpenAIAdapter(config);
case 'xai':
return new OpenAIAdapter(config); // xAI (Grok Build / grok-*) is OpenAI-compatible
case 'gemini':
return new OpenAIAdapter(config); // Gemini via Google's OpenAI-compatible endpoint (baseUrl ends in /v1beta/openai)
case 'codex':
return new CodexAdapter(config);
case 'mock':