Implemented the next missing parity slice around SDK core types.

This commit is contained in:
Abdelrahman Abdallah
2026-04-20 00:27:41 +02:00
parent 9541d5d701
commit f770b794ae
3 changed files with 495 additions and 1 deletions
+319
View File
@@ -0,0 +1,319 @@
"""Foundational SDK types — Python port of entrypoints/sdk/coreTypes.ts.
Mirrors the public constants and a starter set of dataclasses for the
serializable types defined in `entrypoints/sdk/coreSchemas.ts`. The full
schema set (1800+ lines of Zod) is too large to port wholesale; this file
covers the foundational pieces other modules already reference and gives
later slices a place to extend.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Literal
# ---------------------------------------------------------------------------
# Const arrays (HOOK_EVENTS, EXIT_REASONS) — mirror coreTypes.ts top-level
# ---------------------------------------------------------------------------
HOOK_EVENTS: tuple[str, ...] = (
'PreToolUse',
'PostToolUse',
'PostToolUseFailure',
'Notification',
'UserPromptSubmit',
'SessionStart',
'SessionEnd',
'Stop',
'StopFailure',
'SubagentStart',
'SubagentStop',
'PreCompact',
'PostCompact',
'PermissionRequest',
'PermissionDenied',
'Setup',
'TeammateIdle',
'TaskCreated',
'TaskCompleted',
'Elicitation',
'ElicitationResult',
'ConfigChange',
'WorktreeCreate',
'WorktreeRemove',
'InstructionsLoaded',
'CwdChanged',
'FileChanged',
)
EXIT_REASONS: tuple[str, ...] = (
'clear',
'resume',
'logout',
'prompt_input_exit',
'other',
'bypass_permissions_disabled',
)
API_KEY_SOURCES: tuple[str, ...] = ('user', 'project', 'org', 'temporary', 'oauth')
CONFIG_SCOPES: tuple[str, ...] = ('local', 'user', 'project')
SDK_BETAS: tuple[str, ...] = ('context-1m-2025-08-07',)
ApiKeySource = Literal['user', 'project', 'org', 'temporary', 'oauth']
ConfigScope = Literal['local', 'user', 'project']
SdkBeta = Literal['context-1m-2025-08-07']
# ---------------------------------------------------------------------------
# Usage / model
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class ModelUsage:
input_tokens: int
output_tokens: int
cache_read_input_tokens: int
cache_creation_input_tokens: int
web_search_requests: int
cost_usd: float
context_window: int
max_output_tokens: int
@classmethod
def from_dict(cls, data: dict[str, Any]) -> 'ModelUsage':
return cls(
input_tokens=int(data['inputTokens']),
output_tokens=int(data['outputTokens']),
cache_read_input_tokens=int(data['cacheReadInputTokens']),
cache_creation_input_tokens=int(data['cacheCreationInputTokens']),
web_search_requests=int(data['webSearchRequests']),
cost_usd=float(data['costUSD']),
context_window=int(data['contextWindow']),
max_output_tokens=int(data['maxOutputTokens']),
)
def to_dict(self) -> dict[str, Any]:
return {
'inputTokens': self.input_tokens,
'outputTokens': self.output_tokens,
'cacheReadInputTokens': self.cache_read_input_tokens,
'cacheCreationInputTokens': self.cache_creation_input_tokens,
'webSearchRequests': self.web_search_requests,
'costUSD': self.cost_usd,
'contextWindow': self.context_window,
'maxOutputTokens': self.max_output_tokens,
}
# ---------------------------------------------------------------------------
# Thinking config
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class ThinkingAdaptive:
type: Literal['adaptive'] = 'adaptive'
def to_dict(self) -> dict[str, Any]:
return {'type': self.type}
@dataclass(frozen=True)
class ThinkingEnabled:
budget_tokens: int | None = None
type: Literal['enabled'] = 'enabled'
def to_dict(self) -> dict[str, Any]:
out: dict[str, Any] = {'type': self.type}
if self.budget_tokens is not None:
out['budgetTokens'] = self.budget_tokens
return out
@dataclass(frozen=True)
class ThinkingDisabled:
type: Literal['disabled'] = 'disabled'
def to_dict(self) -> dict[str, Any]:
return {'type': self.type}
ThinkingConfig = ThinkingAdaptive | ThinkingEnabled | ThinkingDisabled
def thinking_config_from_dict(data: dict[str, Any]) -> ThinkingConfig:
kind = data.get('type')
if kind == 'adaptive':
return ThinkingAdaptive()
if kind == 'enabled':
budget = data.get('budgetTokens')
return ThinkingEnabled(
budget_tokens=int(budget) if budget is not None else None,
)
if kind == 'disabled':
return ThinkingDisabled()
raise ValueError(f'unknown thinking config type: {kind!r}')
# ---------------------------------------------------------------------------
# MCP server configs
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class McpStdioServerConfig:
command: str
args: list[str] | None = None
env: dict[str, str] | None = None
type: Literal['stdio'] = 'stdio'
def to_dict(self) -> dict[str, Any]:
out: dict[str, Any] = {'type': self.type, 'command': self.command}
if self.args is not None:
out['args'] = list(self.args)
if self.env is not None:
out['env'] = dict(self.env)
return out
@dataclass(frozen=True)
class McpSSEServerConfig:
url: str
headers: dict[str, str] | None = None
type: Literal['sse'] = 'sse'
def to_dict(self) -> dict[str, Any]:
out: dict[str, Any] = {'type': self.type, 'url': self.url}
if self.headers is not None:
out['headers'] = dict(self.headers)
return out
@dataclass(frozen=True)
class McpHttpServerConfig:
url: str
headers: dict[str, str] | None = None
type: Literal['http'] = 'http'
def to_dict(self) -> dict[str, Any]:
out: dict[str, Any] = {'type': self.type, 'url': self.url}
if self.headers is not None:
out['headers'] = dict(self.headers)
return out
@dataclass(frozen=True)
class McpSdkServerConfig:
name: str
type: Literal['sdk'] = 'sdk'
def to_dict(self) -> dict[str, Any]:
return {'type': self.type, 'name': self.name}
@dataclass(frozen=True)
class McpClaudeAIProxyServerConfig:
url: str
id: str
type: Literal['claudeai-proxy'] = 'claudeai-proxy'
def to_dict(self) -> dict[str, Any]:
return {'type': self.type, 'url': self.url, 'id': self.id}
McpServerConfigForProcessTransport = (
McpStdioServerConfig
| McpSSEServerConfig
| McpHttpServerConfig
| McpSdkServerConfig
)
McpServerStatusConfig = (
McpServerConfigForProcessTransport | McpClaudeAIProxyServerConfig
)
def mcp_server_config_from_dict(data: dict[str, Any]) -> McpServerStatusConfig:
"""Discriminate by `type` and route to the matching dataclass."""
raw_type = data.get('type')
if raw_type is None or raw_type == 'stdio':
command = data.get('command')
if not isinstance(command, str):
raise ValueError('stdio server config requires a string command')
return McpStdioServerConfig(
command=command,
args=list(data['args']) if isinstance(data.get('args'), list) else None,
env=dict(data['env']) if isinstance(data.get('env'), dict) else None,
)
if raw_type == 'sse':
return McpSSEServerConfig(
url=str(data['url']),
headers=dict(data['headers']) if isinstance(data.get('headers'), dict) else None,
)
if raw_type == 'http':
return McpHttpServerConfig(
url=str(data['url']),
headers=dict(data['headers']) if isinstance(data.get('headers'), dict) else None,
)
if raw_type == 'sdk':
return McpSdkServerConfig(name=str(data['name']))
if raw_type == 'claudeai-proxy':
return McpClaudeAIProxyServerConfig(
url=str(data['url']),
id=str(data['id']),
)
raise ValueError(f'unknown MCP server config type: {raw_type!r}')
# ---------------------------------------------------------------------------
# Output format
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class JsonSchemaOutputFormat:
schema: dict[str, Any] = field(default_factory=dict)
type: Literal['json_schema'] = 'json_schema'
def to_dict(self) -> dict[str, Any]:
return {'type': self.type, 'schema': dict(self.schema)}
@classmethod
def from_dict(cls, data: dict[str, Any]) -> 'JsonSchemaOutputFormat':
if data.get('type') != 'json_schema':
raise ValueError(
f'expected output format type "json_schema", got {data.get("type")!r}'
)
schema = data.get('schema')
if not isinstance(schema, dict):
raise ValueError('json_schema output format requires a "schema" mapping')
return cls(schema=dict(schema))
OutputFormat = JsonSchemaOutputFormat
__all__ = [
'HOOK_EVENTS',
'EXIT_REASONS',
'API_KEY_SOURCES',
'CONFIG_SCOPES',
'SDK_BETAS',
'ApiKeySource',
'ConfigScope',
'SdkBeta',
'ModelUsage',
'ThinkingAdaptive',
'ThinkingEnabled',
'ThinkingDisabled',
'ThinkingConfig',
'thinking_config_from_dict',
'McpStdioServerConfig',
'McpSSEServerConfig',
'McpHttpServerConfig',
'McpSdkServerConfig',
'McpClaudeAIProxyServerConfig',
'McpServerConfigForProcessTransport',
'McpServerStatusConfig',
'mcp_server_config_from_dict',
'JsonSchemaOutputFormat',
'OutputFormat',
]