mirror of
https://github.com/langgenius/dify.git
synced 2026-05-13 08:57:28 +08:00
Merge remote-tracking branch 'origin/main' into feat/support-agent-sandbox
This commit is contained in:
commit
31427e9c42
1
.agent/skills
Symbolic link
1
.agent/skills
Symbolic link
@ -0,0 +1 @@
|
||||
../.claude/skills
|
||||
46
.claude/skills/orpc-contract-first/SKILL.md
Normal file
46
.claude/skills/orpc-contract-first/SKILL.md
Normal file
@ -0,0 +1,46 @@
|
||||
---
|
||||
name: orpc-contract-first
|
||||
description: Guide for implementing oRPC contract-first API patterns in Dify frontend. Triggers when creating new API contracts, adding service endpoints, integrating TanStack Query with typed contracts, or migrating legacy service calls to oRPC. Use for all API layer work in web/contract and web/service directories.
|
||||
---
|
||||
|
||||
# oRPC Contract-First Development
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
web/contract/
|
||||
├── base.ts # Base contract (inputStructure: 'detailed')
|
||||
├── router.ts # Router composition & type exports
|
||||
├── marketplace.ts # Marketplace contracts
|
||||
└── console/ # Console contracts by domain
|
||||
├── system.ts
|
||||
└── billing.ts
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Create contract** in `web/contract/console/{domain}.ts`
|
||||
- Import `base` from `../base` and `type` from `@orpc/contract`
|
||||
- Define route with `path`, `method`, `input`, `output`
|
||||
|
||||
2. **Register in router** at `web/contract/router.ts`
|
||||
- Import directly from domain file (no barrel files)
|
||||
- Nest by API prefix: `billing: { invoices, bindPartnerStack }`
|
||||
|
||||
3. **Create hooks** in `web/service/use-{domain}.ts`
|
||||
- Use `consoleQuery.{group}.{contract}.queryKey()` for query keys
|
||||
- Use `consoleClient.{group}.{contract}()` for API calls
|
||||
|
||||
## Key Rules
|
||||
|
||||
- **Input structure**: Always use `{ params, query?, body? }` format
|
||||
- **Path params**: Use `{paramName}` in path, match in `params` object
|
||||
- **Router nesting**: Group by API prefix (e.g., `/billing/*` → `billing: {}`)
|
||||
- **No barrel files**: Import directly from specific files
|
||||
- **Types**: Import from `@/types/`, use `type<T>()` helper
|
||||
|
||||
## Type Export
|
||||
|
||||
```typescript
|
||||
export type ConsoleInputs = InferContractRouterInputs<typeof consoleRouterContract>
|
||||
```
|
||||
@ -81,7 +81,7 @@ class ExternalKnowledgeApiPayload(BaseModel):
|
||||
class ExternalDatasetCreatePayload(BaseModel):
|
||||
external_knowledge_api_id: str
|
||||
external_knowledge_id: str
|
||||
name: str = Field(..., min_length=1, max_length=40)
|
||||
name: str = Field(..., min_length=1, max_length=100)
|
||||
description: str | None = Field(None, max_length=400)
|
||||
external_retrieval_model: dict[str, object] | None = None
|
||||
|
||||
|
||||
@ -0,0 +1 @@
|
||||
"""Unit tests for `controllers.console.datasets` controllers."""
|
||||
@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
|
||||
"""
|
||||
Unit tests for the external dataset controller payload schemas.
|
||||
|
||||
These tests focus on Pydantic validation rules so we can catch regressions
|
||||
in request constraints (e.g. max length changes) without exercising the
|
||||
full Flask/RESTX request stack.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from controllers.console.datasets.external import ExternalDatasetCreatePayload
|
||||
|
||||
|
||||
def test_external_dataset_create_payload_allows_name_length_100() -> None:
|
||||
"""Ensure the `name` field accepts up to 100 characters (inclusive)."""
|
||||
|
||||
# Build a request payload with a boundary-length name value.
|
||||
name_100: str = "a" * 100
|
||||
payload = {
|
||||
"external_knowledge_api_id": "ek-api-1",
|
||||
"external_knowledge_id": "ek-1",
|
||||
"name": name_100,
|
||||
}
|
||||
|
||||
model = ExternalDatasetCreatePayload.model_validate(payload)
|
||||
assert model.name == name_100
|
||||
|
||||
|
||||
def test_external_dataset_create_payload_rejects_name_length_101() -> None:
|
||||
"""Ensure the `name` field rejects values longer than 100 characters."""
|
||||
|
||||
# Build a request payload that exceeds the max length by 1.
|
||||
name_101: str = "a" * 101
|
||||
payload: dict[str, object] = {
|
||||
"external_knowledge_api_id": "ek-api-1",
|
||||
"external_knowledge_id": "ek-1",
|
||||
"name": name_101,
|
||||
}
|
||||
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
ExternalDatasetCreatePayload.model_validate(payload)
|
||||
|
||||
errors = exc_info.value.errors()
|
||||
assert errors[0]["loc"] == ("name",)
|
||||
assert errors[0]["type"] == "string_too_long"
|
||||
assert errors[0]["ctx"]["max_length"] == 100
|
||||
Loading…
Reference in New Issue
Block a user