Files
ci/src/backends/types.ts
T
Jon Chery a8b50f5109
CI / build-and-test (push) Has been cancelled
Publish to npm / publish (push) Has been cancelled
feat(ci): v0.9.0 — Distribution & Expansion milestone complete
---ci---
project: ci
phase: 6
milestone: v0.9
status: complete
artifacts:
  tags: [v0.9.0]
decisions:
  - id: D-047
    decision: v0.9 theme = Distribution & Expansion
    rationale: npm publish + OpenAI/Anthropic backends + agent flesh + parallel execution
    confidence: 0.92
  - id: D-049
    decision: Feature milestone — patch tags v0.8.1-v0.8.6 then v0.9.0
    rationale: OpenAI backend, agent flesh, npm publish all feat
    confidence: 0.95
  - id: D-059
    decision: Rename OllamaBaseBackend to LLMBaseBackend + thin OllamaBaseBackend subclass
    rationale: 15 of 17 methods backend-agnostic
    confidence: 0.92
  - id: D-060
    decision: OpenAI/Anthropic backends use native fetch() not SDK packages
    rationale: No dependency bloat; fetch native in Node 18+
    confidence: 0.85
  - id: D-066
    decision: Concurrency limiter internal (no p-limit dependency)
    rationale: 15 lines; avoids dependency for trivial feature
    confidence: 0.90
  - id: D-067
    decision: Promise.allSettled for review agents at orchestrator lines 373-400
    rationale: Current sequential loop replaced with parallel execution
    confidence: 0.88
requirements:
  covered: [PUBLISH-01, PUBLISH-02, PUBLISH-03, PUBLISH-04, OPENAI-01, OPENAI-02, OPENAI-03, OPENAI-04, OPENAI-05, FLESH-01, FLESH-02, FLESH-03, FLESH-04, FLESH-05, ANTHROPIC-01, ANTHROPIC-02, FLESH-06, FLESH-07, NPM-01, NPM-02, PARALLEL-01, PARALLEL-02, PARALLEL-03, INTEG-01, INTEG-02, INTEG-03, INTEG-04, INTEG-05]
---/ci---

6 phases, 28 tasks, 4077 net lines added, 57 test suites, 527 tests, zero stub agents
2026-05-30 02:19:44 +00:00

220 lines
5.9 KiB
TypeScript

import { z } from "zod";
import { AgentName, AutonomyLevel, ModelProfile } from "../types/config.js";
import { AgentContext } from "../agents/base.js";
import { Decision } from "../types/decisions.js";
import { Escalation } from "../types/escalation.js";
export type BackendType = "llm" | "agent";
export const ArtifactSchema = z.object({
path: z.string().min(1, "Artifact path must not be empty"),
content: z.string(),
operation: z.enum(["create", "update", "delete"]),
});
export const TokenUsageSchema = z.object({
input_tokens: z.number().min(0),
output_tokens: z.number().min(0),
total_tokens: z.number().min(0),
estimated_cost_usd: z.number().min(0),
});
export const BackendResultSchema = z.object({
success: z.boolean(),
output: z.string(),
artifacts: z.array(ArtifactSchema),
decisions: z.array(z.unknown()),
escalations: z.array(z.unknown()),
usage: TokenUsageSchema,
error: z.string().optional(),
}).refine(
(r) => !(r.success === true && r.error && r.error.length > 0),
{ message: "Result cannot be both success and have an error message" }
);
export function validateBackendResult(raw: unknown): { result: BackendResult | null; errors: string[] } {
const parseResult = BackendResultSchema.safeParse(raw);
if (!parseResult.success) {
return {
result: null,
errors: parseResult.error.errors.map((e) => `${e.path.join(".")}: ${e.message}`),
};
}
const data = parseResult.data;
if (!Array.isArray(data.artifacts)) {
return { result: null, errors: ["artifacts: expected array"] };
}
for (const a of data.artifacts) {
if (a.path.includes("..")) {
return { result: null, errors: [`artifacts: path "${a.path}" contains ".." (path traversal risk)`] };
}
if (a.path.startsWith("/")) {
return { result: null, errors: [`artifacts: path "${a.path}" is absolute (must be relative)`] };
}
}
return { result: data as BackendResult, errors: [] };
}
export interface BackendRequest {
persona: AgentName;
workflow: string;
task: string;
context: AgentContext;
autonomy: AutonomyLevel;
}
export interface Artifact {
path: string;
content: string;
operation: "create" | "update" | "delete";
}
export interface TokenUsage {
input_tokens: number;
output_tokens: number;
total_tokens: number;
estimated_cost_usd: number;
}
export interface BackendResult {
success: boolean;
output: string;
artifacts: Artifact[];
decisions: Decision[];
escalations: Escalation[];
usage: TokenUsage;
error?: string;
}
export interface IntelligenceBackend {
readonly name: string;
readonly type: BackendType;
isAvailable(): Promise<boolean>;
execute(request: BackendRequest): Promise<BackendResult>;
}
export interface LLMBackendConfig {
base_url: string;
model_profile: ModelProfile;
model?: string;
timeout_ms?: number;
}
export interface OllamaLocalConfig extends LLMBackendConfig {
base_url: string;
model_profile: ModelProfile;
model?: string;
timeout_ms?: number;
}
export interface OllamaCloudConfig extends LLMBackendConfig {
base_url: string;
api_key_env: string;
model_profile: ModelProfile;
model?: string;
timeout_ms?: number;
}
export interface OpenAIConfig extends LLMBackendConfig {
api_key_env: string;
model: string;
organization?: string;
}
export interface AnthropicConfig extends LLMBackendConfig {
api_key_env: string;
model: string;
api_version?: string;
}
export interface OpencodeBackendConfig {
enabled: boolean;
executable?: string;
}
export interface BackendConfigSection {
provider: "auto" | "opencode" | "openai" | "ollama-local" | "ollama-cloud" | "anthropic";
fallback?: "opencode" | "openai" | "ollama-local" | "ollama-cloud" | "anthropic";
agent_backends: {
opencode?: OpencodeBackendConfig;
};
llm_backends: {
"openai"?: OpenAIConfig;
"ollama-local"?: OllamaLocalConfig;
"ollama-cloud"?: OllamaCloudConfig;
"anthropic"?: AnthropicConfig;
};
}
export const DEFAULT_BACKEND_CONFIG: BackendConfigSection = {
provider: "auto",
agent_backends: {
opencode: { enabled: true },
},
llm_backends: {
"openai": {
base_url: "https://api.openai.com/v1",
api_key_env: "OPENAI_API_KEY",
model: "gpt-4o",
model_profile: "quality",
timeout_ms: 60000,
},
"ollama-local": {
base_url: "http://localhost:11434",
model_profile: "balanced",
},
"ollama-cloud": {
base_url: "",
api_key_env: "OLLAMA_CLOUD_API_KEY",
model_profile: "quality",
timeout_ms: 60000,
},
"anthropic": {
base_url: "https://api.anthropic.com",
api_key_env: "ANTHROPIC_API_KEY",
model: "claude-sonnet-4-20250514",
api_version: "2023-06-01",
model_profile: "quality",
timeout_ms: 60000,
},
},
};
export class BackendUnavailableError extends Error {
readonly backendName: string;
readonly agentName?: string;
constructor(backendName: string, agentName?: string) {
const agentMsg = agentName ? ` (agent: ${agentName})` : "";
super(
`Intelligence backend "${backendName}" is not available${agentMsg}. ` +
`Configure one of:\n` +
` 1. Install opencode: npm i -g opencode\n` +
` 2. Set OPENAI_API_KEY for OpenAI API access\n` +
` 3. Set ANTHROPIC_API_KEY for Anthropic API access\n` +
` 4. Run Ollama locally: ollama serve\n` +
` 5. Set OLLAMA_CLOUD_API_KEY for remote inference`
);
this.name = "BackendUnavailableError";
this.backendName = backendName;
this.agentName = agentName;
}
}
export function emptyTokenUsage(): TokenUsage {
return { input_tokens: 0, output_tokens: 0, total_tokens: 0, estimated_cost_usd: 0 };
}
export function emptyBackendResult(error?: string): BackendResult {
return {
success: false,
output: "",
artifacts: [],
decisions: [],
escalations: [],
usage: emptyTokenUsage(),
error,
};
}
export { ChatMessage, ChatCompletionResponse } from "./llm-base.js";