Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions frontend/server/migration/contracts.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .models import (
MIGRATION_FRAMEWORKS,
STRUCTURED_MIGRATION_FRAMEWORKS,
is_valid_model_id,
is_valid_structured_entry,
)

Expand Down Expand Up @@ -178,6 +179,7 @@ def validate_migration_request(
"session_ttl_seconds",
"created_at",
},
optional={"model_id"},
)
if (
value.get("schema_version") != 1
Expand All @@ -192,6 +194,8 @@ def validate_migration_request(
):
raise MigrationContractError("invalid source file name")
_text(value.get("instruction"), maximum=_MAX_TEXT_LENGTH)
if "model_id" in value and not is_valid_model_id(value.get("model_id")):
raise MigrationContractError("invalid model id")
created_at = value.get("created_at")
if isinstance(created_at, str):
_timestamp_text(created_at)
Expand Down
27 changes: 27 additions & 0 deletions frontend/server/migration/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import requests
from agentkit.sdk.tools import types as tools_types
from agentkit.toolkit.cli.sandbox.env_config import build_exec_session_envs
from agentkit.toolkit.cli.sandbox.sandbox_client import (
SANDBOX_FILE_DOWNLOAD_ROUTE,
build_bash_exec_url,
Expand Down Expand Up @@ -60,6 +61,11 @@
"start_analysis": ANALYSIS_START_MARKER,
"start_migration": MIGRATION_START_MARKER,
}
_SESSION_CREDENTIAL_ENV_KEYS = {
"ANTHROPIC_AUTH_TOKEN",
"CODEX_API_KEY",
"OPENCODE_API_KEY",
}
_RELEASED_SESSION_STATUSES = {
"createfailed",
"deleted",
Expand Down Expand Up @@ -126,6 +132,7 @@ def create_session(
creator_name: str,
display_name: str,
ttl_seconds: int,
model_id: str | None = None,
) -> MigrationSandboxSession: ...

def list_sessions(self, owner_id: str) -> list[MigrationSandboxSession]: ...
Expand Down Expand Up @@ -506,6 +513,7 @@ def create_session(
creator_name: str,
display_name: str,
ttl_seconds: int,
model_id: str | None = None,
) -> MigrationSandboxSession:
_, region = self._get_tool()
existing = self._list_region(
Expand Down Expand Up @@ -539,6 +547,25 @@ def create_session(
username=owner_id,
creator_name=creator_name,
)
if model_id:
model_provider, model_base_url = _sandbox_model_config(
cloud_provider_from_env()
)
session_envs = build_exec_session_envs(
model_name=model_id,
model_provider=model_provider,
model_base_url=model_base_url,
model_provider_was_provided=True,
model_base_url_was_provided=True,
include_codex_config=True,
)
safe_session_envs = [
item
for item in session_envs or []
if item.key not in _SESSION_CREDENTIAL_ENV_KEYS
]
if safe_session_envs:
request = request.model_copy(update={"envs": safe_session_envs})
try:
response = self._client(region).create_session(request)
except Exception as error:
Expand Down
9 changes: 9 additions & 0 deletions frontend/server/migration/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
_SOURCE_FILE_NAME_RE = re.compile(r"^[^/\\\x00-\x1f]{1,255}\.zip$", re.IGNORECASE)
_APP_NAME_RE = re.compile(r"^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$")
_TASK_ID_RE = re.compile(r"^migration-v1-[0-9a-f]{32}$")
_MODEL_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$")
STRUCTURED_ENTRY_PATTERN = (
r"^[A-Za-z0-9_./-]+\.(?:py|json)(?::[A-Za-z_][A-Za-z0-9_]*)?$"
)
Expand All @@ -71,6 +72,7 @@ class CreateMigrationTaskBody(BaseModel):
task_id: str | None = Field(default=None, alias="taskId", max_length=45)
source_file_name: str = Field(alias="sourceFileName", min_length=1, max_length=255)
instruction: str = Field(default="", max_length=20_000)
model_id: str | None = Field(default=None, alias="modelId", max_length=128)

model_config = {"populate_by_name": True, "extra": "forbid"}

Expand All @@ -79,13 +81,20 @@ def normalize(self) -> CreateMigrationTaskBody:
self.task_id = (self.task_id or "").strip() or None
self.source_file_name = self.source_file_name.strip()
self.instruction = self.instruction.strip()
self.model_id = (self.model_id or "").strip() or None
if self.task_id is not None and not _TASK_ID_RE.fullmatch(self.task_id):
raise ValueError("迁移会话 ID 无效")
if not _SOURCE_FILE_NAME_RE.fullmatch(self.source_file_name):
raise ValueError("请选择名称有效的 ZIP 文件")
if self.model_id is not None and not _MODEL_ID_RE.fullmatch(self.model_id):
raise ValueError("模型 ID 格式无效")
return self


def is_valid_model_id(value: object) -> bool:
return isinstance(value, str) and _MODEL_ID_RE.fullmatch(value) is not None


class ConfirmMigrationBody(BaseModel):
framework: MigrationFramework
entry: str | None = Field(default=None, max_length=512)
Expand Down
7 changes: 7 additions & 0 deletions frontend/server/migration/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,7 @@ def _accept_request_command(candidate_path: str, expected_sha256: str) -> str:
"task_id",
"source_file_name",
"instruction",
"model_id",
"session_ttl_seconds",
)

Expand Down Expand Up @@ -2425,13 +2426,16 @@ def create_task(
"instruction": body.instruction,
"session_ttl_seconds": MIGRATION_SESSION_TTL_SECONDS,
}
if body.model_id:
request["model_id"] = body.model_id
try:
session = self._gateway.create_session(
task_id=task_id,
owner_id=owner_id,
creator_name=creator_name,
display_name="存量迁移",
ttl_seconds=MIGRATION_SESSION_TTL_SECONDS,
model_id=body.model_id,
)
self._validate_session_timing(session)
existing_request = self._read_json(
Expand Down Expand Up @@ -2596,6 +2600,7 @@ def _validate_request(
if (
existing.get("source_file_name") != expected["source_file_name"]
or existing.get("instruction") != expected["instruction"]
or existing.get("model_id") != expected.get("model_id")
or existing.get("session_ttl_seconds") != expected["session_ttl_seconds"]
):
raise MigrationError(
Expand Down Expand Up @@ -2786,6 +2791,8 @@ def _task_payload(
"canStop": state in _STOPPABLE_STATES,
"artifact": artifact_status,
}
if request.get("model_id"):
payload["modelId"] = str(request["model_id"])
if analysis is not None:
payload["analysis"] = analysis
payload["analysisRef"] = {
Expand Down
20 changes: 19 additions & 1 deletion frontend/src/adk/migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export type MigrationTaskState =
export interface MigrationCapabilities {
enabled: boolean;
reason: string;
model?: {
configured: boolean;
id: string;
};
maxUploadBytes: number;
sessionTtlSeconds: number;
frameworks: MigrationFramework[];
Expand Down Expand Up @@ -87,6 +91,7 @@ export interface MigrationTask {
message: string;
sourceFileName: string;
instruction: string;
modelId?: string;
createdAt: string | number;
expiresAt: string;
sessionTtlSeconds: number;
Expand Down Expand Up @@ -432,6 +437,9 @@ function normalizeTask(value: unknown): MigrationTask {
deployReady: artifact.deployReady === true,
},
};
if (typeof task.modelId === "string" && task.modelId.trim()) {
normalized.modelId = task.modelId;
}
if (task.analysis !== undefined) normalized.analysis = normalizeAnalysis(task.analysis);
if (task.analysisRef !== undefined) {
const reference = record(task.analysisRef, "分析结果引用");
Expand Down Expand Up @@ -809,13 +817,21 @@ export async function getMigrationCapabilities(
) {
throw new Error("迁移能力格式错误。");
}
return {
const capability: MigrationCapabilities = {
enabled: body.enabled,
reason: body.reason,
maxUploadBytes: body.maxUploadBytes,
sessionTtlSeconds: body.sessionTtlSeconds,
frameworks: body.frameworks.map((item) => framework(item, "迁移框架")),
};
if (body.model !== undefined) {
const model = record(body.model, "迁移模型能力");
if (typeof model.configured !== "boolean" || typeof model.id !== "string") {
throw new Error("迁移模型能力格式错误。");
}
capability.model = { configured: model.configured, id: model.id };
}
return capability;
}

export async function listMigrationTasks(
Expand All @@ -833,6 +849,7 @@ export async function createMigrationTask(args: {
taskId: string;
sourceFileName: string;
instruction: string;
modelId?: string;
signal?: AbortSignal;
}): Promise<MigrationTask> {
return normalizeTask(
Expand All @@ -846,6 +863,7 @@ export async function createMigrationTask(args: {
taskId: args.taskId,
sourceFileName: args.sourceFileName,
instruction: args.instruction,
...(args.modelId ? { modelId: args.modelId } : {}),
}),
signal: args.signal,
},
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/migrations/MigrationWorkspace.css
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,25 @@
justify-content: space-between;
}

.migration-composer__tools {
min-width: 0;
display: flex;
align-items: center;
gap: 4px;
}

.migration-composer__model-select,
.migration-composer__model-select .new-chat-compact-select {
width: 220px;
max-width: 220px;
}

.migration-composer__model-select .new-chat-compact-select__menu {
top: auto;
bottom: calc(100% + 6px);
width: min(320px, calc(100vw - 32px));
}

.migration-attach-button {
min-height: 36px;
display: inline-flex;
Expand Down
Loading
Loading