from __future__ import annotations LINE_CONFIG_SCHEMA = { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "title": "LINE Channel Configuration", "description": "LINE Messaging API channel configuration schema", "properties": { "name": { "type": "string", "description": "Channel display name", "default": "line", }, "dm_policy": { "type": "string", "enum": ["open", "allowlist", "pairing", "disabled"], "default": "open", "description": "Direct message access policy", }, "group_policy": { "type": "string", "enum": ["open", "allowlist", "disabled"], "default": "open", "description": "Group access policy", }, "allow_from": { "type": "array", "items": {"type": "string"}, "description": "DM allowlist (LINE user IDs)", "default": [], }, "groups": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string", "description": "Group/room ID or '*' for default", }, "enabled": { "type": "boolean", "default": True, }, "require_mention": { "type": "boolean", "default": False, }, "auto_thread_id": { "type": "boolean", "default": False, }, "system_prompt": { "type": "string", "description": "Group-specific system prompt override", }, "skills": { "type": "array", "items": {"type": "string"}, "description": "Group-specific enabled skills", "default": [], }, "allow_from": { "type": "array", "items": {"type": "string"}, "description": "Group-specific user allowlist override", }, }, "required": ["id"], }, "default": [], }, "webhook_path": { "type": "string", "description": "Custom webhook path (per-account)", "default": "line/callback", }, "enabled": { "type": "boolean", "description": "Enable/disable LINE channel", "default": True, }, "proxy": { "type": "string", "description": "HTTP/HTTPS proxy URL for LINE API requests", }, "quickstart_allow_from": { "type": "array", "items": {"type": "string"}, "description": "Quick start allowFrom list", "default": [], }, "default_account": { "type": "string", "description": "Default account ID for multi-account setups", }, "response_prefix": { "type": "string", "description": "Text prefix prepended to every response", "default": "", }, "media_max_mb": { "type": "integer", "description": "Maximum media download size in MB", "default": 10, }, "agent_prompt": { "type": "string", "description": "Custom agent system prompt for LINE channel", }, "token_file": { "type": "string", "description": "Path to file containing channel access token", }, "secret_file": { "type": "string", "description": "Path to file containing channel secret", }, "thread_bindings": { "type": "object", "properties": { "enabled": { "type": "boolean", "default": False, }, "idle_hours": { "type": "number", "default": 6, "description": "Hours of inactivity before closing thread binding", }, "max_age_hours": { "type": "number", "default": 72, "description": "Maximum age in hours before thread binding expires", }, "spawn_subagent_sessions": { "type": "boolean", "default": False, }, "spawn_acp_sessions": { "type": "boolean", "default": False, }, }, "description": "Thread binding configuration", }, "conversation_bindings": { "type": "object", "properties": { "default_top_level_placement": { "type": "string", "enum": ["current", "new", "none"], "default": "current", }, }, "description": "Conversation binding configuration", }, "accounts": { "type": "object", "properties": { "default": { "type": "object", "properties": { "channel_access_token": {"type": "string"}, "channel_secret": {"type": "string"}, "token_file": {"type": "string"}, "secret_file": {"type": "string"}, }, }, }, "patternProperties": { "^[a-zA-Z0-9_]+$": { "type": "object", "properties": { "channel_access_token": {"type": "string"}, "channel_secret": {"type": "string"}, "token_file": {"type": "string"}, "secret_file": {"type": "string"}, }, }, }, "description": "LINE account configurations", }, }, "required": ["accounts"], } def validate_line_config(config: dict) -> tuple[bool, str | None]: try: import jsonschema jsonschema.validate(instance=config, schema=LINE_CONFIG_SCHEMA) return True, None except ImportError: return _manual_validate(config) except jsonschema.ValidationError as e: return False, str(e) def _manual_validate(config: dict) -> tuple[bool, str | None]: if not isinstance(config, dict): return False, "Config must be a dict" if "accounts" not in config: return False, "Missing required field: accounts" accounts = config.get("accounts", {}) if not isinstance(accounts, dict): return False, "accounts must be a dict" default = accounts.get("default", {}) if not default.get("channel_access_token") and not default.get("channel_secret"): return False, "accounts.default requires channel_access_token or channel_secret" dm_policy = config.get("dm_policy", "open") if dm_policy not in ("open", "allowlist", "pairing", "disabled"): return False, f"Invalid dm_policy: {dm_policy}" group_policy = config.get("group_policy", "open") if group_policy not in ("open", "allowlist", "disabled"): return False, f"Invalid group_policy: {group_policy}" return True, None