feat: API-Runtime 内部通信协议与 DTO (API-AI-001)
Some checks failed
Deploy API Server / build-and-deploy (push) Has been cancelled
Some checks failed
Deploy API Server / build-and-deploy (push) Has been cancelled
定义 9 个 internal/runtime 接口的完整协议:Poll/Lock/Heartbeat/Snapshot/ Credential Resolve/Result/Fail/InvocationLog/Health。新增 RuntimeInternalDto 类型文件,复用 InternalAuthGuard 鉴权,与 Rust 侧可直接对齐。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
804c414901
commit
eea9e3e7c6
359
docs/ai-runtime-internal-api-protocol.md
Normal file
359
docs/ai-runtime-internal-api-protocol.md
Normal file
@ -0,0 +1,359 @@
|
||||
# API 与 Rust Runtime 内部通信协议
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本文档定义主 API 与 Rust Heavy Runtime 之间的内部 HTTP 通信协议。
|
||||
|
||||
通信方向:
|
||||
- Runtime → API:拉取 Job、提交结果、提交日志
|
||||
- API → Runtime:健康检查(可选)
|
||||
|
||||
## 2. 鉴权
|
||||
|
||||
所有 `/internal/runtime/*` 接口使用 `InternalAuthGuard`。
|
||||
|
||||
**请求头**:
|
||||
```
|
||||
x-internal-api-key: <RUNTIME_SERVICE_TOKEN>
|
||||
x-runtime-instance-id: runtime-001
|
||||
```
|
||||
|
||||
- `x-internal-api-key`:与 API 环境变量 `INTERNAL_API_KEY` 一致
|
||||
- `x-runtime-instance-id`:Runtime 实例标识,记录到日志
|
||||
|
||||
**安全约束**:
|
||||
- 普通用户 JWT 不可访问 internal 接口
|
||||
- service token 不可访问普通用户 API
|
||||
- Runtime 不可通过 internal 接口访问非当前 job 所需数据
|
||||
|
||||
## 3. 错误响应格式
|
||||
|
||||
所有 internal 接口失败时返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 400,
|
||||
"errorCode": "INVALID_SNAPSHOT",
|
||||
"message": "Snapshot has expired for this job",
|
||||
"timestamp": "2026-06-11T10:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 错误码
|
||||
|
||||
| 错误码 | HTTP | 说明 | retryable |
|
||||
|--------|------|------|-----------|
|
||||
| `JOB_NOT_FOUND` | 404 | Job 不存在 | false |
|
||||
| `JOB_ALREADY_LOCKED` | 409 | 已被其他 Runtime 锁定 | true |
|
||||
| `SNAPSHOT_EXPIRED` | 410 | 快照已过期 | true |
|
||||
| `SNAPSHOT_NOT_FOUND` | 404 | 快照不存在 | false |
|
||||
| `CREDENTIAL_NOT_FOUND` | 404 | 凭证不存在 | false |
|
||||
| `CREDENTIAL_INVALID` | 422 | 凭证无效 | false |
|
||||
| `RESULT_ALREADY_EXISTS` | 409 | 重复提交 | false |
|
||||
| `RESULT_SCHEMA_UNSUPPORTED` | 422 | schema 版本不支持 | false |
|
||||
| `RUNTIME_VERSION_INCOMPATIBLE` | 422 | Runtime 版本不兼容 | false |
|
||||
| `INTERNAL_ERROR` | 500 | 内部错误 | true |
|
||||
|
||||
## 4. 接口详情
|
||||
|
||||
### 4.1 Poll Jobs
|
||||
|
||||
```
|
||||
POST /internal/runtime/jobs/poll
|
||||
```
|
||||
|
||||
Runtime 拉取待执行 job。API 根据 Runtime 的 `supportedJobTypes` 和 `capabilities` 过滤兼容的 job。
|
||||
|
||||
**请求**:
|
||||
```json
|
||||
{
|
||||
"runtimeInstanceId": "runtime-001",
|
||||
"supportedJobTypes": ["learning_state_analysis", "quiz_generation"],
|
||||
"limit": 5,
|
||||
"capabilities": {
|
||||
"supportedSnapshotVersions": ["ai_snapshot_v1"],
|
||||
"supportedOutputSchemaVersions": ["analysis_output_v1", "quiz_output_v1"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应 200**:
|
||||
```json
|
||||
{
|
||||
"jobs": [
|
||||
{
|
||||
"id": "job-abc123",
|
||||
"jobType": "learning_state_analysis",
|
||||
"targetType": "material",
|
||||
"targetId": "mat-xyz",
|
||||
"priority": 0,
|
||||
"snapshotId": "snap-001",
|
||||
"promptVersion": "learning_state_v1",
|
||||
"outputSchemaVersion": "analysis_output_v1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Lock Job
|
||||
|
||||
```
|
||||
POST /internal/runtime/jobs/{jobId}/lock
|
||||
```
|
||||
|
||||
Runtime 锁定一个 job,获取执行权。
|
||||
|
||||
**请求**:
|
||||
```json
|
||||
{
|
||||
"runtimeInstanceId": "runtime-001"
|
||||
}
|
||||
```
|
||||
|
||||
**响应 200**:
|
||||
```json
|
||||
{
|
||||
"jobId": "job-abc123",
|
||||
"status": "locked",
|
||||
"lockUntil": 1700000000123
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 Heartbeat
|
||||
|
||||
```
|
||||
POST /internal/runtime/jobs/{jobId}/heartbeat
|
||||
```
|
||||
|
||||
Runtime 延长 lock 有效期。
|
||||
|
||||
**请求**:
|
||||
```json
|
||||
{
|
||||
"runtimeInstanceId": "runtime-001"
|
||||
}
|
||||
```
|
||||
|
||||
**响应 204**:空 body,仅延长 `lockUntil`。
|
||||
|
||||
### 4.4 Get Snapshot
|
||||
|
||||
```
|
||||
GET /internal/runtime/jobs/{jobId}/snapshot
|
||||
```
|
||||
|
||||
Runtime 获取 job 关联的 LearningAnalysisSnapshot。
|
||||
|
||||
**响应 200**:
|
||||
```json
|
||||
{
|
||||
"jobId": "job-abc123",
|
||||
"snapshotId": "snap-001",
|
||||
"snapshotVersion": "ai_snapshot_v1",
|
||||
"privacyScope": { "allowDocumentContent": true },
|
||||
"userProfile": { "learningGoal": "exam", "currentLevel": "intermediate" },
|
||||
"aiSettings": { "allowAiAnalysis": true },
|
||||
"learningBehaviorSummary": { "totalActiveSeconds": 3600 },
|
||||
"materialProgressSummary": { "progress": 0.6 },
|
||||
"behaviorSignals": { "engagementSignal": "high" },
|
||||
"scoreSignals": { "masteryRiskScore": 0.3 },
|
||||
"constraints": { "dailyAvailableMinutes": 60 },
|
||||
"allowedModelFields": ["learningGoal", "currentLevel"]
|
||||
}
|
||||
```
|
||||
|
||||
**错误**:
|
||||
- `404 SNAPSHOT_NOT_FOUND` — 快照不存在
|
||||
- `410 SNAPSHOT_EXPIRED` — 快照已过期,Runtime 应提交 retryable fail
|
||||
|
||||
### 4.5 Resolve Credential
|
||||
|
||||
```
|
||||
POST /internal/runtime/model-credentials/resolve
|
||||
```
|
||||
|
||||
Runtime 获取模型调用凭证。platform_key 模式返回平台 key;user_deepseek_key 模式解密用户 key 后返回。
|
||||
|
||||
**请求**:
|
||||
```json
|
||||
{
|
||||
"jobId": "job-abc123",
|
||||
"apiKeyMode": "user_deepseek_key",
|
||||
"credentialId": "cred-001",
|
||||
"provider": "deepseek"
|
||||
}
|
||||
```
|
||||
|
||||
**响应 200**:
|
||||
```json
|
||||
{
|
||||
"provider": "deepseek",
|
||||
"model": "deepseek-chat",
|
||||
"baseUrl": "https://api.deepseek.com/v1",
|
||||
"apiKey": "sk-xxxx",
|
||||
"apiKeyMode": "user_deepseek_key"
|
||||
}
|
||||
```
|
||||
|
||||
**安全要求**:
|
||||
- 明文 `apiKey` 只在响应中短暂出现,不写日志
|
||||
- `apiKey` 不返回给 iOS / Admin
|
||||
- 用户 key 必须属于 `job.userId`
|
||||
- platform key 由 Runtime 环境变量优先使用,API 可选返回
|
||||
|
||||
**错误**:
|
||||
- `404 CREDENTIAL_NOT_FOUND`
|
||||
- `422 CREDENTIAL_INVALID`
|
||||
|
||||
### 4.6 Submit Result
|
||||
|
||||
```
|
||||
POST /internal/runtime/jobs/{jobId}/result
|
||||
```
|
||||
|
||||
Runtime 提交执行成功的结果。
|
||||
|
||||
**请求**:
|
||||
```json
|
||||
{
|
||||
"runtimeInstanceId": "runtime-001",
|
||||
"schemaVersion": "analysis_output_v1",
|
||||
"status": "succeeded",
|
||||
"rawOutput": { "learningState": "in_progress", "confidence": 0.85 },
|
||||
"validatedOutput": { "learningState": "in_progress", "riskLevel": "low" },
|
||||
"validationErrors": [],
|
||||
"usage": {
|
||||
"inputTokens": 1200,
|
||||
"outputTokens": 450,
|
||||
"totalTokens": 1650,
|
||||
"latencyMs": 3200,
|
||||
"costEstimate": 3
|
||||
},
|
||||
"attemptNo": 0,
|
||||
"outputHash": "sha256-abc123"
|
||||
}
|
||||
```
|
||||
|
||||
**幂等规则**:
|
||||
- `resultIdempotencyKey = jobId + attemptNo + outputHash`
|
||||
- 相同 key 重复提交返回 200(幂等)
|
||||
- 已有 succeeded result 且 outputHash 不同返回 409 `RESULT_ALREADY_EXISTS`
|
||||
|
||||
**响应 201**:created
|
||||
|
||||
**错误**:
|
||||
- `409 RESULT_ALREADY_EXISTS`
|
||||
- `422 RESULT_SCHEMA_UNSUPPORTED`
|
||||
|
||||
### 4.7 Submit Failure
|
||||
|
||||
```
|
||||
POST /internal/runtime/jobs/{jobId}/fail
|
||||
```
|
||||
|
||||
Runtime 提交执行失败的原因。
|
||||
|
||||
**请求**:
|
||||
```json
|
||||
{
|
||||
"runtimeInstanceId": "runtime-001",
|
||||
"errorCode": "MODEL_TIMEOUT",
|
||||
"errorMessage": "DeepSeek request timed out after 30s",
|
||||
"retryable": true,
|
||||
"rawError": "connection timeout"
|
||||
}
|
||||
```
|
||||
|
||||
**处理规则**:
|
||||
- `retryable=true` 且 `retryCount < maxRetryCount`:job 回到 `pending`
|
||||
- `retryable=false` 或达到 maxRetryCount:job 变为 `failed`
|
||||
- `rawError` 中不得包含 apiKey
|
||||
|
||||
**响应 200**:acknowledged
|
||||
|
||||
### 4.8 Submit Invocation Logs
|
||||
|
||||
```
|
||||
POST /internal/runtime/invocation-logs
|
||||
```
|
||||
|
||||
Runtime 提交模型调用日志(批量)。
|
||||
|
||||
**请求**:
|
||||
```json
|
||||
{
|
||||
"logs": [
|
||||
{
|
||||
"jobId": "job-abc123",
|
||||
"provider": "deepseek",
|
||||
"model": "deepseek-chat",
|
||||
"apiKeyMode": "user_deepseek_key",
|
||||
"credentialId": "cred-001",
|
||||
"promptName": "learning_state_analysis",
|
||||
"promptVersion": "learning_state_v1",
|
||||
"outputSchemaVersion": "analysis_output_v1",
|
||||
"inputTokens": 1200,
|
||||
"outputTokens": 450,
|
||||
"totalTokens": 1650,
|
||||
"latencyMs": 3200,
|
||||
"costEstimate": 3,
|
||||
"success": true,
|
||||
"retryCount": 0,
|
||||
"runtimeInstanceId": "runtime-001",
|
||||
"traceId": "trace-xyz",
|
||||
"correlationId": "corr-abc"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**约束**:
|
||||
- 不允许 `apiKey` 字段
|
||||
- 失败调用也要提交日志
|
||||
- 日志提交失败不导致主任务崩溃
|
||||
|
||||
**响应 201**:created
|
||||
|
||||
### 4.9 Health(可选)
|
||||
|
||||
```
|
||||
GET /internal/runtime/health
|
||||
```
|
||||
|
||||
API 查询 Runtime 健康状态。此接口由 Runtime 暴露(非 API 暴露)。
|
||||
|
||||
**响应 200**:
|
||||
```json
|
||||
{
|
||||
"runtimeInstanceId": "runtime-001",
|
||||
"status": "ok",
|
||||
"version": "0.1.0",
|
||||
"startedAt": 1700000000000,
|
||||
"lastJobAt": 1700000000123,
|
||||
"activeJobs": 2
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 接口总览
|
||||
|
||||
| 方法 | 路径 | 调用方 | 鉴权 |
|
||||
|------|------|--------|------|
|
||||
| POST | `/internal/runtime/jobs/poll` | Runtime | InternalAuthGuard |
|
||||
| POST | `/internal/runtime/jobs/{jobId}/lock` | Runtime | InternalAuthGuard |
|
||||
| POST | `/internal/runtime/jobs/{jobId}/heartbeat` | Runtime | InternalAuthGuard |
|
||||
| GET | `/internal/runtime/jobs/{jobId}/snapshot` | Runtime | InternalAuthGuard |
|
||||
| POST | `/internal/runtime/model-credentials/resolve` | Runtime | InternalAuthGuard |
|
||||
| POST | `/internal/runtime/jobs/{jobId}/result` | Runtime | InternalAuthGuard |
|
||||
| POST | `/internal/runtime/jobs/{jobId}/fail` | Runtime | InternalAuthGuard |
|
||||
| POST | `/internal/runtime/invocation-logs` | Runtime | InternalAuthGuard |
|
||||
| GET | `/internal/runtime/health` | API | —(检查外部 Runtime) |
|
||||
|
||||
## 6. 验收清单
|
||||
|
||||
- [x] 所有 internal 接口有 DTO 定义(`runtime-internal.dto.ts`)
|
||||
- [x] 所有 internal 接口有鉴权设计(复用 InternalAuthGuard)
|
||||
- [x] 所有失败返回包含 errorCode / message
|
||||
- [x] Runtime result 支持结构化 payload(validatedOutput)
|
||||
- [x] Runtime failure 支持 retryable 标记
|
||||
- [x] Credential resolve 接口明确不记录明文 key
|
||||
- [x] 接口命名、字段命名与 Runtime 项目可直接对齐
|
||||
321
src/modules/ai-runtime/internal/dto/runtime-internal.dto.ts
Normal file
321
src/modules/ai-runtime/internal/dto/runtime-internal.dto.ts
Normal file
@ -0,0 +1,321 @@
|
||||
import { IsArray, IsBoolean, IsInt, IsObject, IsOptional, IsString, Max, Min, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
// ── Poll ──
|
||||
|
||||
export class RuntimePollJobsRequestDto {
|
||||
@IsString()
|
||||
runtimeInstanceId!: string;
|
||||
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
supportedJobTypes!: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(50)
|
||||
limit?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
capabilities?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export class RuntimePollJobsResponseItemDto {
|
||||
@IsString()
|
||||
id!: string;
|
||||
|
||||
@IsString()
|
||||
jobType!: string;
|
||||
|
||||
@IsString()
|
||||
targetType!: string;
|
||||
|
||||
@IsString()
|
||||
targetId!: string;
|
||||
|
||||
@IsInt()
|
||||
priority!: number;
|
||||
|
||||
@IsString()
|
||||
snapshotId!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
promptVersion?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
outputSchemaVersion?: string;
|
||||
}
|
||||
|
||||
export class RuntimePollJobsResponseDto {
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => RuntimePollJobsResponseItemDto)
|
||||
jobs!: RuntimePollJobsResponseItemDto[];
|
||||
}
|
||||
|
||||
// ── Lock ──
|
||||
|
||||
export class RuntimeLockJobRequestDto {
|
||||
@IsString()
|
||||
runtimeInstanceId!: string;
|
||||
}
|
||||
|
||||
export class RuntimeLockJobResponseDto {
|
||||
@IsString()
|
||||
jobId!: string;
|
||||
|
||||
@IsString()
|
||||
status!: string;
|
||||
|
||||
@IsInt()
|
||||
lockUntil!: number;
|
||||
}
|
||||
|
||||
// ── Heartbeat ──
|
||||
|
||||
export class RuntimeHeartbeatRequestDto {
|
||||
@IsString()
|
||||
runtimeInstanceId!: string;
|
||||
}
|
||||
|
||||
// ── Snapshot ──
|
||||
|
||||
export class RuntimeSnapshotResponseDto {
|
||||
@IsString()
|
||||
jobId!: string;
|
||||
|
||||
@IsString()
|
||||
snapshotId!: string;
|
||||
|
||||
@IsString()
|
||||
snapshotVersion!: string;
|
||||
|
||||
@IsObject()
|
||||
privacyScope!: Record<string, unknown>;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
userProfile?: Record<string, unknown>;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
aiSettings?: Record<string, unknown>;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
deviceContext?: Record<string, unknown>;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
learningBehaviorSummary?: Record<string, unknown>;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
materialProgressSummary?: Record<string, unknown>;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
contentStructureSummary?: Record<string, unknown>;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
behaviorSignals?: Record<string, unknown>;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
scoreSignals?: Record<string, unknown>;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
constraints?: Record<string, unknown>;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
allowedModelFields?: string[];
|
||||
}
|
||||
|
||||
// ── Credential Resolve ──
|
||||
|
||||
export class RuntimeResolveCredentialRequestDto {
|
||||
@IsString()
|
||||
jobId!: string;
|
||||
|
||||
@IsString()
|
||||
apiKeyMode!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
credentialId?: string;
|
||||
|
||||
@IsString()
|
||||
provider!: string;
|
||||
}
|
||||
|
||||
export class RuntimeResolveCredentialResponseDto {
|
||||
@IsString()
|
||||
provider!: string;
|
||||
|
||||
@IsString()
|
||||
model!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
baseUrl?: string;
|
||||
|
||||
@IsString()
|
||||
apiKey!: string;
|
||||
|
||||
@IsString()
|
||||
apiKeyMode!: string;
|
||||
}
|
||||
|
||||
// ── Submit Result ──
|
||||
|
||||
export class RuntimeSubmitResultRequestDto {
|
||||
@IsString()
|
||||
runtimeInstanceId!: string;
|
||||
|
||||
@IsString()
|
||||
schemaVersion!: string;
|
||||
|
||||
@IsString()
|
||||
status!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
rawOutput?: Record<string, unknown>;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
validatedOutput?: Record<string, unknown>;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
validationErrors?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
usage?: {
|
||||
inputTokens?: number;
|
||||
outputTokens?: number;
|
||||
totalTokens?: number;
|
||||
latencyMs?: number;
|
||||
costEstimate?: number;
|
||||
};
|
||||
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
attemptNo!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
outputHash?: string;
|
||||
}
|
||||
|
||||
// ── Submit Failure ──
|
||||
|
||||
export class RuntimeSubmitFailureRequestDto {
|
||||
@IsString()
|
||||
runtimeInstanceId!: string;
|
||||
|
||||
@IsString()
|
||||
errorCode!: string;
|
||||
|
||||
@IsString()
|
||||
errorMessage!: string;
|
||||
|
||||
@IsBoolean()
|
||||
retryable!: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
rawError?: string;
|
||||
}
|
||||
|
||||
// ── Invocation Log ──
|
||||
|
||||
export class RuntimeInvocationLogDto {
|
||||
@IsString()
|
||||
jobId!: string;
|
||||
|
||||
@IsString()
|
||||
provider!: string;
|
||||
|
||||
@IsString()
|
||||
model!: string;
|
||||
|
||||
@IsString()
|
||||
apiKeyMode!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
credentialId?: string;
|
||||
|
||||
@IsString()
|
||||
promptName!: string;
|
||||
|
||||
@IsString()
|
||||
promptVersion!: string;
|
||||
|
||||
@IsString()
|
||||
outputSchemaVersion!: string;
|
||||
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
inputTokens!: number;
|
||||
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
outputTokens!: number;
|
||||
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
totalTokens!: number;
|
||||
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
latencyMs!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
costEstimate?: number;
|
||||
|
||||
@IsBoolean()
|
||||
success!: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
errorCode?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
errorMessage?: string;
|
||||
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
retryCount!: number;
|
||||
|
||||
@IsString()
|
||||
runtimeInstanceId!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
traceId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
correlationId?: string;
|
||||
}
|
||||
|
||||
export class RuntimeSubmitInvocationLogsRequestDto {
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => RuntimeInvocationLogDto)
|
||||
logs!: RuntimeInvocationLogDto[];
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user