- Add class-based ErrorBoundary component wrapping all 7 main views inside WorkspaceChrome; fallback shows view name, error, reload button - Add 30 new unit tests (boot null-project path × 9, onboarding pure-function logic × 21); all 43 web/lib tests pass - Add web/README.md: architecture, auth flow, 7 views, dev setup, API route pattern, test instructions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
218 lines
7.4 KiB
TypeScript
218 lines
7.4 KiB
TypeScript
// SF — Provider Capabilities Registry (ADR-005 Phase 1)
|
|
// Declarative registry of what each API provider supports, consolidating
|
|
// scattered knowledge from *-shared.ts files into a queryable data structure.
|
|
|
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Declarative capability profile for an API provider.
|
|
* Used by the model router to filter incompatible models and by the tool
|
|
* system to adjust tool sets per provider.
|
|
*/
|
|
export interface ProviderCapabilities {
|
|
/** Whether models from this provider support tool/function calling */
|
|
toolCalling: boolean;
|
|
/** Maximum number of tools the provider handles well (0 = unlimited) */
|
|
maxTools: number;
|
|
/** Whether tool results can contain images */
|
|
imageToolResults: boolean;
|
|
/** Whether the provider supports structured JSON output */
|
|
structuredOutput: boolean;
|
|
/** Tool call ID format constraints */
|
|
toolCallIdFormat: {
|
|
maxLength: number;
|
|
allowedChars: RegExp;
|
|
};
|
|
/** Whether thinking/reasoning blocks are preserved cross-turn */
|
|
thinkingPersistence: "full" | "text-only" | "none";
|
|
/** Schema features NOT supported (tools using these get filtered) */
|
|
unsupportedSchemaFeatures: string[];
|
|
}
|
|
|
|
// ─── Registry ───────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Built-in provider capability profiles.
|
|
*
|
|
* Sources (consolidated from scattered *-shared.ts files):
|
|
* - anthropic-shared.ts: normalizeToolCallId (64-char, [a-zA-Z0-9_-])
|
|
* - openai-responses-shared.ts: ID normalization (64-char, fc_ prefix), image-in-tool-result workaround
|
|
* - google-shared.ts: sanitizeSchemaForGoogle (patternProperties, const), requiresToolCallId
|
|
* - mistral.ts: MISTRAL_TOOL_CALL_ID_LENGTH = 9
|
|
* - amazon-bedrock.ts: normalizeToolCallId (64-char, [a-zA-Z0-9_-])
|
|
*/
|
|
export const PROVIDER_CAPABILITIES: Record<string, ProviderCapabilities> = {
|
|
"anthropic-messages": {
|
|
toolCalling: true,
|
|
maxTools: 0,
|
|
imageToolResults: true,
|
|
structuredOutput: true,
|
|
toolCallIdFormat: { maxLength: 64, allowedChars: /^[a-zA-Z0-9_-]+$/ },
|
|
thinkingPersistence: "full",
|
|
unsupportedSchemaFeatures: [],
|
|
},
|
|
"anthropic-vertex": {
|
|
toolCalling: true,
|
|
maxTools: 0,
|
|
imageToolResults: true,
|
|
structuredOutput: true,
|
|
toolCallIdFormat: { maxLength: 64, allowedChars: /^[a-zA-Z0-9_-]+$/ },
|
|
thinkingPersistence: "full",
|
|
unsupportedSchemaFeatures: [],
|
|
},
|
|
"openai-responses": {
|
|
toolCalling: true,
|
|
maxTools: 0,
|
|
imageToolResults: false, // images sent as separate user message, not in tool result
|
|
structuredOutput: true,
|
|
toolCallIdFormat: { maxLength: 512, allowedChars: /^.+$/ },
|
|
thinkingPersistence: "text-only",
|
|
unsupportedSchemaFeatures: [],
|
|
},
|
|
"azure-openai-responses": {
|
|
toolCalling: true,
|
|
maxTools: 0,
|
|
imageToolResults: false,
|
|
structuredOutput: true,
|
|
toolCallIdFormat: { maxLength: 512, allowedChars: /^.+$/ },
|
|
thinkingPersistence: "text-only",
|
|
unsupportedSchemaFeatures: [],
|
|
},
|
|
"openai-codex-responses": {
|
|
toolCalling: true,
|
|
maxTools: 0,
|
|
imageToolResults: false,
|
|
structuredOutput: true,
|
|
toolCallIdFormat: { maxLength: 64, allowedChars: /^[a-zA-Z0-9_-]+$/ },
|
|
thinkingPersistence: "text-only",
|
|
unsupportedSchemaFeatures: [],
|
|
},
|
|
"openai-completions": {
|
|
toolCalling: true,
|
|
maxTools: 0,
|
|
imageToolResults: false,
|
|
structuredOutput: true,
|
|
toolCallIdFormat: { maxLength: 64, allowedChars: /^[a-zA-Z0-9_-]+$/ },
|
|
thinkingPersistence: "text-only",
|
|
unsupportedSchemaFeatures: [],
|
|
},
|
|
"google-generative-ai": {
|
|
toolCalling: true,
|
|
maxTools: 0,
|
|
imageToolResults: true,
|
|
structuredOutput: true,
|
|
toolCallIdFormat: { maxLength: 64, allowedChars: /^[a-zA-Z0-9_-]+$/ },
|
|
thinkingPersistence: "text-only",
|
|
unsupportedSchemaFeatures: ["patternProperties", "const"],
|
|
},
|
|
"google-gemini-cli": {
|
|
toolCalling: true,
|
|
maxTools: 0,
|
|
imageToolResults: true,
|
|
structuredOutput: true,
|
|
toolCallIdFormat: { maxLength: 64, allowedChars: /^[a-zA-Z0-9_-]+$/ },
|
|
thinkingPersistence: "text-only",
|
|
unsupportedSchemaFeatures: ["patternProperties", "const"],
|
|
},
|
|
"google-vertex": {
|
|
toolCalling: true,
|
|
maxTools: 0,
|
|
imageToolResults: true,
|
|
structuredOutput: true,
|
|
toolCallIdFormat: { maxLength: 64, allowedChars: /^[a-zA-Z0-9_-]+$/ },
|
|
thinkingPersistence: "text-only",
|
|
unsupportedSchemaFeatures: ["patternProperties", "const"],
|
|
},
|
|
"mistral-conversations": {
|
|
toolCalling: true,
|
|
maxTools: 0,
|
|
imageToolResults: false,
|
|
structuredOutput: true,
|
|
toolCallIdFormat: { maxLength: 9, allowedChars: /^[a-zA-Z0-9]+$/ },
|
|
thinkingPersistence: "none",
|
|
unsupportedSchemaFeatures: [],
|
|
},
|
|
"bedrock-converse-stream": {
|
|
toolCalling: true,
|
|
maxTools: 0,
|
|
imageToolResults: true, // Bedrock supports image content blocks in tool results
|
|
structuredOutput: true,
|
|
toolCallIdFormat: { maxLength: 64, allowedChars: /^[a-zA-Z0-9_-]+$/ },
|
|
thinkingPersistence: "text-only",
|
|
unsupportedSchemaFeatures: [],
|
|
},
|
|
"ollama-chat": {
|
|
toolCalling: true,
|
|
maxTools: 0,
|
|
imageToolResults: false,
|
|
structuredOutput: false,
|
|
toolCallIdFormat: { maxLength: 64, allowedChars: /^[a-zA-Z0-9_-]+$/ },
|
|
thinkingPersistence: "none",
|
|
unsupportedSchemaFeatures: [],
|
|
},
|
|
};
|
|
|
|
// ─── Default (permissive) profile for unknown providers ─────────────────────
|
|
|
|
const DEFAULT_CAPABILITIES: ProviderCapabilities = {
|
|
toolCalling: true,
|
|
maxTools: 0,
|
|
imageToolResults: true,
|
|
structuredOutput: true,
|
|
toolCallIdFormat: { maxLength: 512, allowedChars: /^.+$/ },
|
|
thinkingPersistence: "text-only",
|
|
unsupportedSchemaFeatures: [],
|
|
};
|
|
|
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Get capabilities for a provider API. Returns a permissive default for
|
|
* unknown providers (preserving existing behavior per ADR-005 principle 5).
|
|
*/
|
|
export function getProviderCapabilities(api: string): ProviderCapabilities {
|
|
return PROVIDER_CAPABILITIES[api] ?? DEFAULT_CAPABILITIES;
|
|
}
|
|
|
|
/**
|
|
* Check if a provider supports all required schema features.
|
|
* Returns the list of unsupported features (empty if all supported).
|
|
*/
|
|
export function getUnsupportedFeatures(
|
|
api: string,
|
|
requiredFeatures: string[],
|
|
): string[] {
|
|
const caps = getProviderCapabilities(api);
|
|
return requiredFeatures.filter((f) =>
|
|
caps.unsupportedSchemaFeatures.includes(f),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Deep-merge user-provided capability overrides with built-in defaults.
|
|
* Partial overrides merge with the built-in profile for the given API.
|
|
*/
|
|
export function mergeCapabilityOverrides(
|
|
api: string,
|
|
overrides: Partial<Omit<ProviderCapabilities, "toolCallIdFormat">> & {
|
|
toolCallIdFormat?: Partial<ProviderCapabilities["toolCallIdFormat"]>;
|
|
},
|
|
): ProviderCapabilities {
|
|
const base = getProviderCapabilities(api);
|
|
return {
|
|
...base,
|
|
...overrides,
|
|
toolCallIdFormat: overrides.toolCallIdFormat
|
|
? { ...base.toolCallIdFormat, ...overrides.toolCallIdFormat }
|
|
: base.toolCallIdFormat,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get all registered API names in the capability registry.
|
|
* Used by lint rules to verify all providers in register-builtins.ts
|
|
* have corresponding capability entries.
|
|
*/
|
|
export function getRegisteredApis(): string[] {
|
|
return Object.keys(PROVIDER_CAPABILITIES);
|
|
}
|