Files
AirCoding/packages/contracts/src/provider.ts
AirCoding bac285d412 fix: 主线 A 事件落库地基 + 主线 B1/B2 执行原语
主线 A(事件驱动落库):
- 统一 EventStore 模块单例:RuntimeApp 不再 new EventStore,改用 eventStore
  并 setRepositories(14 个 domain repo),消除事件流向空 DB 的割裂
- Scheduler.create_tasks 改为 async,真正发出 task.created 事件
- run.ts dispatchTask 加 await
- 主线 A 独立复审发现并修复关键假绿:四个 repo(Task/Agent/ToolRun/
  TaskAttempt)的 *Update 类型 Omit<'status'> 且 update() 主动丢弃 status,
  导致 EventStore.project() 的状态写入全部静默失效,DB 行内容 tasks.status
  永远冻结在 pending,UI 显示的 completed 来自内存 graph。已修,DB 现
  真实反映 task.status=completed
- 补 agent.started/agent.completed/agent.failed 事件发出(之前 agents 表
  恒空),修复后 agents 表有正确行+status

主线 B1(结构化工具调用块类型,N1):
- 新增 content-block.ts 定义 Anthropic canonical content blocks
  (TextBlock/ThinkingBlock/ToolUseBlock/ToolResultBlock/CanonicalMessage)
- provider.ts ProviderCompletionInput 去掉 unknown 逃生舱:
  messages: CanonicalMessage[], tools?: ToolDefinitionBlock[],
  tool_choice?: ToolChoice, system?: string | TextBlock[]

主线 B2(read-before-edit 代码层强制,FR-009):
- fs/index.ts 新增 readFileState 机制(移植 claude-code FileEditTool),
  fs.edit 执行前检查:未读先改报 "File has not been read yet",外部修改
  报 "File has been unexpectedly modified"
- 修复 fs.edit 参数名不匹配:兼容 old_str/new_str (ExecutorRole) 和
  find/replace (UI) 两种命名
- fs_edit 唯一性检查(非 global 模式下 old_str 出现多次报错)

真实验收:
- TSC=0
- air run 后 DB:events=5(原 3,+agent.started/completed),
  tasks.status=completed(原 frozen pending),agents 1 行 status=completed
- read-before-edit 行为测试:未读先改 status=error,读后再改 status=ok

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-08 12:10:07 +08:00

298 lines
8.1 KiB
TypeScript
Executable File

/**
* AirCoding Provider Contracts
*
* Implements ProviderCapabilityMatrix, ModelRequirement, ProviderCompletionInput,
* ProviderStreamEvent, ProviderAdapter, and ProviderManager interfaces
* per interface-contracts-v1.md §15 and system-detailed-design.md §22.6.
*/
// Import IDs needed for these types
import type {
ProviderID,
ModelID,
JsonObject,
} from './ids'
// Import content block types for canonical message format
import type {
CanonicalMessage,
TextBlock,
ToolDefinitionBlock,
ToolChoice,
} from './content-block'
// =============================================================================
// §15 — Provider Contracts
// =============================================================================
/**
* Provider kind types supported by the system.
* Matches provider-capability-matrix-v1.md §2.
*/
export type ProviderKind =
| 'anthropic'
| 'openai'
| 'openrouter'
| 'ollama'
| 'anthropic_compatible'
| 'openai_compatible'
| 'custom'
/**
* Quality tier classification for models.
*/
export type QualityTier = 'frontier' | 'strong' | 'standard' | 'cheap' | 'local' | 'unknown'
/**
* Cost tier classification for models.
*/
export type CostTier = 'high' | 'medium' | 'low' | 'free' | 'unknown'
/**
* Provider identity - core provider metadata.
* Matches provider-capability-matrix-v1.md §2.
*/
export interface ProviderIdentity {
provider_id: ProviderID
provider_kind: ProviderKind
display_name: string
base_url?: string
auth_ref?: string
local: boolean
}
/**
* Capability matrix for a specific model on a provider.
* Matches interface-contracts-v1.md §15 and provider-capability-matrix-v1.md §3.
*/
export interface ProviderCapabilityMatrix {
provider_id: ProviderID
provider_kind: ProviderKind
model_id: ModelID
display_name?: string
enabled: boolean
quality_tier: QualityTier
cost_tier: CostTier
context_window_tokens?: number
max_output_tokens?: number
supports: ProviderSupports
conversion: ProviderConversion
limits?: ProviderLimits
default_use?: ProviderDefaultUse
notes?: string[]
}
/**
* Supported capabilities for a model.
* Per provider-capability-matrix-v1.md §3.
*/
export interface ProviderSupports {
text_input: boolean
text_output: boolean
streaming: boolean
tool_use: boolean
parallel_tool_use: boolean
structured_output: boolean
json_mode: boolean
thinking: boolean
prompt_cache: boolean
system_prompt: boolean
image_input: boolean
image_output: boolean
audio_input: boolean
audio_output: boolean
file_input: boolean
computer_use: boolean
long_context: boolean
batch: boolean
}
/**
* Conversion behavior for provider adapter.
* Per provider-capability-matrix-v1.md §3.
*/
export interface ProviderConversion {
from_anthropic_canonical: 'lossless' | 'lossy' | 'unsupported'
tool_schema: 'native' | 'converted' | 'emulated' | 'unsupported'
image_input: 'native' | 'artifact_link' | 'unsupported'
thinking: 'native' | 'stripped' | 'unsupported'
cache_control: 'native' | 'ignored' | 'unsupported'
}
/**
* Rate limits for a model.
* Per provider-capability-matrix-v1.md §3.
*/
export interface ProviderLimits {
requests_per_minute?: number
tokens_per_minute?: number
concurrent_requests?: number
max_tool_schema_bytes?: number
max_image_count?: number
max_file_bytes?: number
}
/**
* Default use cases for a model.
* Per provider-capability-matrix-v1.md §3.
*/
export interface ProviderDefaultUse {
main?: boolean
architecture?: boolean
execute?: boolean
review?: boolean
debug?: boolean
compact?: boolean
mine_experience?: boolean
}
/**
* Model requirement specification for task execution.
* Per interface-contracts-v1.md §15 and provider-capability-matrix-v1.md §5.
*/
export interface ModelRequirement {
required: Partial<ProviderSupports>
preferred?: Partial<ProviderSupports>
min_quality_tier?: 'frontier' | 'strong' | 'standard' | 'cheap' | 'local'
max_cost_tier?: 'high' | 'medium' | 'low' | 'free'
min_context_window_tokens?: number
allow_lossy_conversion?: boolean
}
/**
* Model assignment mode - how the model was selected.
* Per provider-capability-matrix-v1.md §6.
*/
export type ModelAssignmentMode = 'scheduler_forced' | 'agent_select'
/**
* Model assignment - the selected model for a task.
* Per interface-contracts-v1.md §9 and §15, and provider-capability-matrix-v1.md §6.
* Note: This is also defined in task.ts for scheduler use - this re-export ensures
* both modules have access to the same type definition.
*/
export interface ModelAssignment {
mode: ModelAssignmentMode
provider_id?: ProviderID
model_id?: ModelID
allowed_models?: Array<{ provider_id: ProviderID; model_id: ModelID }>
requirement: ModelRequirement
reason: string
}
/**
* Input for a provider completion request.
* Per interface-contracts-v1.md §15 and provider-capability-matrix-v1.md §7.
*/
export interface ProviderCompletionInput {
provider_id: ProviderID
model_id: ModelID
canonical_format: 'anthropic'
messages: CanonicalMessage[]
tools?: ToolDefinitionBlock[]
tool_choice?: ToolChoice
system?: string | TextBlock[]
max_output_tokens?: number
temperature?: number
metadata?: JsonObject
}
/**
* Stream event types from provider.
* Per interface-contracts-v1.md §15.
*/
export type ProviderStreamEventType =
| 'message_start'
| 'content_delta'
| 'tool_use'
| 'message_stop'
| 'error'
/**
* A stream event from the provider.
* Per interface-contracts-v1.md §15.
*/
export interface ProviderStreamEvent {
type: ProviderStreamEventType
payload: unknown
}
/**
* Conversion report for provider adaptation.
* Per provider-capability-matrix-v1.md §8.
*/
export interface ProviderConversionReport {
status: 'lossless' | 'lossy' | 'unsupported'
omissions: string[]
warnings: string[]
required_confirmation?: boolean
}
/**
* Provider adapter interface - the contract for all LLM provider implementations.
* Per interface-contracts-v1.md §15 and system-detailed-design.md §22.6.
*
* Adapter responsibilities (per provider-capability-matrix-v1.md §7):
* 1. Convert Anthropic canonical messages to provider format.
* 2. Convert provider output back to Anthropic canonical content blocks or RuntimeEvents.
* 3. Validate tool-call and structured-output compatibility.
* 4. Record conversion omissions/losses.
* 5. Never leak credentials into logs, events, artifacts, or model-visible messages.
*/
export interface ProviderAdapter {
/** Unique identifier for this adapter instance */
provider_id: ProviderID
/**
* List all available models for this provider.
* Returns capability matrix for each model.
*/
list_models(): Promise<ProviderCapabilityMatrix[]>
/**
* Validate and get capability matrix for a specific model.
* @throws Error if model is not available
*/
validate_model(model_id: ModelID): Promise<ProviderCapabilityMatrix>
/**
* Execute a completion request.
* Yields stream events as they arrive from the provider.
*/
complete(input: ProviderCompletionInput): AsyncIterable<ProviderStreamEvent>
/**
* Optional: Count tokens for a given input.
* Useful for context budgeting and cost estimation.
*/
count_tokens?(input: unknown): Promise<number>
}
/**
* Provider manager interface - the orchestration layer for model selection and completion.
* Per interface-contracts-v1.md §15 and system-detailed-design.md §12.1.
*
* The ProviderManager is the runtime facade that:
* - Loads and manages provider configuration
* - Selects appropriate models based on requirements
* - Routes completion requests to the appropriate adapter
*/
export interface ProviderManager {
/**
* Load provider configuration from global and project sources.
* Should be called at startup or when configuration changes.
*/
load_config(): Promise<void>
/**
* Select an appropriate model based on requirements.
* @returns ModelAssignment with the selected provider/model
*/
select_model(requirement: ModelRequirement): Promise<ModelAssignment>
/**
* Execute a completion request.
* Yields normalized stream events.
*/
complete(input: ProviderCompletionInput): AsyncIterable<ProviderStreamEvent>
}