refactor(openapi): regenerate contract and wire difyctl to the new paths

Teach the oRPC codegen to split AIP-136 `resource:action` segments so custom
methods nest as clean sibling nodes (apps.byAppId.run) instead of collapsing
the param into a fake static key (apps.appIdRun). Regenerate the committed
oRPC/Zod contract and the split Markdown docs from the new spec.

Update the difyctl call-sites whose wire path or generated method name moved
(describe->get, export->dsl, check-dependencies->dependencies, files/upload->
files, human_input->human-input-forms:submit, role->PATCH) and the Hono mock
server + URL assertions in the CLI test suite.
This commit is contained in:
L1nSn0w
2026-07-03 13:36:38 +08:00
parent 3e48b62e75
commit eaa4c1540f
26 changed files with 381 additions and 410 deletions

View File

@ -93,21 +93,7 @@ User-scoped operations
| 422 | Validation error | **application/json**: [ErrorBody](#errorbody)<br> |
| default | Error | **application/json**: [ErrorBody](#errorbody)<br> |
### [GET] /apps/{app_id}/check-dependencies
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| app_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Dependencies checked | **application/json**: [CheckDependenciesResult](#checkdependenciesresult)<br> |
| default | Error | **application/json**: [ErrorBody](#errorbody)<br> |
### [GET] /apps/{app_id}/describe
### [GET] /apps/{app_id}
#### Parameters
| Name | Located in | Description | Required | Schema |
@ -123,7 +109,21 @@ User-scoped operations
| 422 | Validation error | **application/json**: [ErrorBody](#errorbody)<br> |
| default | Error | **application/json**: [ErrorBody](#errorbody)<br> |
### [GET] /apps/{app_id}/export
### [GET] /apps/{app_id}/dependencies
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| app_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Dependencies checked | **application/json**: [CheckDependenciesResult](#checkdependenciesresult)<br> |
| default | Error | **application/json**: [ErrorBody](#errorbody)<br> |
### [GET] /apps/{app_id}/dsl
#### Parameters
| Name | Located in | Description | Required | Schema |
@ -140,7 +140,7 @@ User-scoped operations
| 422 | Validation error | **application/json**: [ErrorBody](#errorbody)<br> |
| default | Error | **application/json**: [ErrorBody](#errorbody)<br> |
### [POST] /apps/{app_id}/files/upload
### [POST] /apps/{app_id}/files
Upload a file to use as an input variable when running the app
#### Parameters
@ -160,7 +160,7 @@ Upload a file to use as an input variable when running the app
| 415 | Unsupported file type or blocked extension | |
| default | Error | **application/json**: [ErrorBody](#errorbody)<br> |
### [GET] /apps/{app_id}/form/human_input/{form_token}
### [GET] /apps/{app_id}/human-input-forms/{form_token}
#### Parameters
| Name | Located in | Description | Required | Schema |
@ -174,7 +174,7 @@ Upload a file to use as an input variable when running the app
| ---- | ----------- | ------ |
| 200 | Form definition | **application/json**: [HumanInputFormDefinitionResponse](#humaninputformdefinitionresponse)<br> |
### [POST] /apps/{app_id}/form/human_input/{form_token}
### [POST] /apps/{app_id}/human-input-forms/{form_token}:submit
#### Parameters
| Name | Located in | Description | Required | Schema |
@ -196,7 +196,38 @@ Upload a file to use as an input variable when running the app
| 422 | Validation error | **application/json**: [ErrorBody](#errorbody)<br> |
| default | Error | **application/json**: [ErrorBody](#errorbody)<br> |
### [POST] /apps/{app_id}/run
### [GET] /apps/{app_id}/tasks/{task_id}/events
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| continue_on_pause | query | Whether to keep the event stream open on pause | No | boolean |
| include_state_snapshot | query | Whether to include workflow state snapshots | No | boolean |
| app_id | path | | Yes | string |
| task_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | SSE event stream | **application/json**: [EventStreamResponse](#eventstreamresponse)<br> |
### [POST] /apps/{app_id}/tasks/{task_id}:stop
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| app_id | path | | Yes | string |
| task_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Task stopped | **application/json**: [TaskStopResponse](#taskstopresponse)<br> |
| default | Error | **application/json**: [ErrorBody](#errorbody)<br> |
### [POST] /apps/{app_id}:run
#### Parameters
| Name | Located in | Description | Required | Schema |
@ -216,37 +247,6 @@ Upload a file to use as an input variable when running the app
| 200 | Run result (SSE stream) | **application/json**: [EventStreamResponse](#eventstreamresponse)<br> |
| 422 | Validation error | **application/json**: [ErrorBody](#errorbody)<br> |
### [GET] /apps/{app_id}/tasks/{task_id}/events
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| continue_on_pause | query | Whether to keep the event stream open on pause | No | boolean |
| include_state_snapshot | query | Whether to include workflow state snapshots | No | boolean |
| app_id | path | | Yes | string |
| task_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | SSE event stream | **application/json**: [EventStreamResponse](#eventstreamresponse)<br> |
### [POST] /apps/{app_id}/tasks/{task_id}/stop
#### Parameters
| Name | Located in | Description | Required | Schema |
| ---- | ---------- | ----------- | -------- | ------ |
| app_id | path | | Yes | string |
| task_id | path | | Yes | string |
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Task stopped | **application/json**: [TaskStopResponse](#taskstopresponse)<br> |
| default | Error | **application/json**: [ErrorBody](#errorbody)<br> |
### [POST] /oauth/device/approve
#### Request Body
@ -330,7 +330,7 @@ Upload a file to use as an input variable when running the app
| 422 | Validation error | **application/json**: [ErrorBody](#errorbody)<br> |
| default | Error | **application/json**: [ErrorBody](#errorbody)<br> |
### [GET] /permitted-external-apps/{app_id}/describe
### [GET] /permitted-external-apps/{app_id}
#### Parameters
| Name | Located in | Description | Required | Schema |
@ -391,7 +391,7 @@ Upload a file to use as an input variable when running the app
| 422 | Validation error | **application/json**: [ErrorBody](#errorbody)<br> |
| default | Error | **application/json**: [ErrorBody](#errorbody)<br> |
### [POST] /workspaces/{workspace_id}/apps/imports/{import_id}/confirm
### [POST] /workspaces/{workspace_id}/apps/imports/{import_id}:confirm
#### Parameters
| Name | Located in | Description | Required | Schema |
@ -460,7 +460,7 @@ Upload a file to use as an input variable when running the app
| 200 | Member removed | **application/json**: [MemberActionResponse](#memberactionresponse)<br> |
| default | Error | **application/json**: [ErrorBody](#errorbody)<br> |
### [PUT] /workspaces/{workspace_id}/members/{member_id}/role
### [PATCH] /workspaces/{workspace_id}/members/{member_id}
#### Parameters
| Name | Located in | Description | Required | Schema |
@ -482,7 +482,7 @@ Upload a file to use as an input variable when running the app
| 422 | Validation error | **application/json**: [ErrorBody](#errorbody)<br> |
| default | Error | **application/json**: [ErrorBody](#errorbody)<br> |
### [POST] /workspaces/{workspace_id}/switch
### [POST] /workspaces/{workspace_id}:switch
#### Parameters
| Name | Located in | Description | Required | Schema |
@ -532,7 +532,7 @@ Upload a file to use as an input variable when running the app
#### AppDescribeQuery
`?fields=` allow-list for GET /apps/<id>/describe.
`?fields=` allow-list for GET /apps/<id>.
Empty / omitted → all blocks. Unknown member → ValidationError → 422.
@ -550,7 +550,7 @@ Empty / omitted → all blocks. Unknown member → ValidationError → 422.
#### AppDslExportQuery
Query parameters for GET /apps/<app_id>/export.
Query parameters for GET /apps/<app_id>/dsl.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
@ -762,7 +762,7 @@ future server adds a code. Formatter tests pin emitted values to the enum.
#### FormSubmitResponse
Empty 200 body for POST /apps/<id>/form/human_input/<token>. `extra='forbid'`
Empty 200 body for POST /apps/<id>/human-input-forms/<token>:submit. `extra='forbid'`
pins `additionalProperties: false` so the generated contract is an exact `{}` rather
than an under-annotated open object.
@ -1022,7 +1022,7 @@ generated CLI whitelist all derive from it.
#### TaskStopResponse
200 body for POST /apps/<id>/tasks/<task_id>/stop. The handler always returns
200 body for POST /apps/<id>/tasks/<task_id>:stop. The handler always returns
{"result": "success"}, so `result` is required (no default) — the generated contract
types it as a required `'success'` rather than an optional field.

View File

@ -26,7 +26,7 @@ describe('AppDslClient.exportDsl', () => {
const yaml = await makeClient(stub.url).exportDsl('app-1')
expect(stub.captured.method).toBe('GET')
expect(stub.captured.url?.split('?')[0]).toBe('/openapi/v1/apps/app-1/export')
expect(stub.captured.url?.split('?')[0]).toBe('/openapi/v1/apps/app-1/dsl')
expect(yaml).toBe(DSL_YAML)
})
@ -90,7 +90,7 @@ describe('AppDslClient.confirmImport', () => {
const result = await makeClient(stub.url).confirmImport('ws-1', 'imp-1')
expect(stub.captured.method).toBe('POST')
expect(stub.captured.url).toBe('/openapi/v1/workspaces/ws-1/apps/imports/imp-1/confirm')
expect(stub.captured.url).toBe('/openapi/v1/workspaces/ws-1/apps/imports/imp-1:confirm')
expect(result.status).toBe('completed')
})
})
@ -107,7 +107,7 @@ describe('AppDslClient.checkDependencies', () => {
const result = await makeClient(stub.url).checkDependencies('app-1')
expect(stub.captured.url?.split('?')[0]).toBe('/openapi/v1/apps/app-1/check-dependencies')
expect(stub.captured.url?.split('?')[0]).toBe('/openapi/v1/apps/app-1/dependencies')
expect(result.leaked_dependencies).toEqual([])
})
})

View File

@ -33,7 +33,7 @@ export class AppDslClient {
}
async exportDsl(appId: string, query?: ExportQuery): Promise<string> {
const resp = await this.orpc.apps.byAppId.export.get({
const resp = await this.orpc.apps.byAppId.dsl.get({
params: { app_id: appId },
query: query !== undefined
? {
@ -52,7 +52,7 @@ export class AppDslClient {
}
async checkDependencies(appId: string): Promise<CheckDependenciesResult> {
return this.orpc.apps.byAppId.checkDependencies.get({
return this.orpc.apps.byAppId.dependencies.get({
params: { app_id: appId },
})
}

View File

@ -54,7 +54,7 @@ export class AppRunClient {
body: Record<string, unknown>,
opts: StreamOptions = {},
): Promise<AsyncIterable<SseEvent>> {
const res = await this.http.stream(`apps/${encodeURIComponent(appId)}/run`, {
const res = await this.http.stream(`apps/${encodeURIComponent(appId)}:run`, {
method: 'POST',
json: body,
headers: { Accept: 'text/event-stream' },
@ -79,7 +79,7 @@ export class AppRunClient {
action: string,
inputs: Record<string, unknown>,
): Promise<void> {
await this.orpc.apps.byAppId.form.humanInput.byFormToken.post({
await this.orpc.apps.byAppId.humanInputForms.byFormToken.submit.post({
params: { app_id: appId, form_token: formToken },
body: { action, inputs },
})

View File

@ -82,12 +82,12 @@ describe('AppsClient.describe', () => {
await stub?.stop()
})
it('hits /apps/<id>/describe, omits workspace_id and fields when not given', async () => {
it('hits /apps/<id>, omits workspace_id and fields when not given', async () => {
stub = await startStubServer(cap => jsonResponder(200, DESCRIBE_BODY, cap))
const res = await makeClient(stub.url).describe('app-1')
expect(stub.captured.url?.split('?')[0]).toBe('/openapi/v1/apps/app-1/describe')
expect(stub.captured.url?.split('?')[0]).toBe('/openapi/v1/apps/app-1')
const q = queryOf(stub.captured.url)
expect(q.has('workspace_id')).toBe(false)
expect(q.has('fields')).toBe(false)
@ -107,6 +107,6 @@ describe('AppsClient.describe', () => {
await makeClient(stub.url).describe('app/with space')
expect(stub.captured.url?.split('?')[0]).toBe('/openapi/v1/apps/app%2Fwith%20space/describe')
expect(stub.captured.url?.split('?')[0]).toBe('/openapi/v1/apps/app%2Fwith%20space')
})
})

View File

@ -37,7 +37,7 @@ export class AppsClient implements AppReader {
}
async describe(appId: string, fields?: readonly string[]): Promise<AppDescribeResponse> {
return this.orpc.apps.byAppId.describe.get({
return this.orpc.apps.byAppId.get({
params: { app_id: appId },
query: {
fields: fields !== undefined && fields.length > 0 ? fields.join(',') : undefined,

View File

@ -41,7 +41,7 @@ describe('FileUploadClient.upload', () => {
const result = await makeClient(stub.url).upload('app-1', filePath)
expect(stub.captured.method).toBe('POST')
expect(stub.captured.url).toBe('/openapi/v1/apps/app-1/files/upload')
expect(stub.captured.url).toBe('/openapi/v1/apps/app-1/files')
// The client must let fetch own the multipart Content-Type + boundary; it
// must NOT coerce this to application/json the way a json body would.
const contentType = stub.captured.headers?.['content-type'] ?? ''
@ -61,7 +61,7 @@ describe('FileUploadClient.upload', () => {
await makeClient(stub.url).upload('app/with space', filePath)
expect(stub.captured.url).toBe('/openapi/v1/apps/app%2Fwith%20space/files/upload')
expect(stub.captured.url).toBe('/openapi/v1/apps/app%2Fwith%20space/files')
})
it('propagates a server 413 as a classified BaseError', async () => {

View File

@ -65,7 +65,7 @@ export class FileUploadClient {
form.append('file', blob, filename)
return this.http.post<UploadedFile>(
`apps/${encodeURIComponent(appId)}/files/upload`,
`apps/${encodeURIComponent(appId)}/files`,
{ body: form, timeoutMs: 60_000 },
)
}

View File

@ -154,13 +154,13 @@ describe('MembersClient.updateRole', () => {
await stub?.stop()
})
it('PUTs role payload to /role subresource', async () => {
it('PATCHes role payload to the member resource', async () => {
stub = await startStubServer(cap => jsonResponder(200, { result: 'success' }, cap))
const result = await makeClient(stub.url).updateRole('ws-1', 'm-1', { role: 'admin' })
expect(stub.captured.method).toBe('PUT')
expect(stub.captured.url).toBe('/openapi/v1/workspaces/ws-1/members/m-1/role')
expect(stub.captured.method).toBe('PATCH')
expect(stub.captured.url).toBe('/openapi/v1/workspaces/ws-1/members/m-1')
expect(JSON.parse(stub.captured.body ?? '{}')).toEqual({ role: 'admin' })
expect(result.result).toBe('success')
})
@ -181,7 +181,7 @@ describe('WorkspacesClient.switch (integration with stub)', () => {
await stub?.stop()
})
it('POSTs /workspaces/<id>/switch and returns workspace detail', async () => {
it('POSTs /workspaces/<id>:switch and returns workspace detail', async () => {
stub = await startStubServer(cap =>
jsonResponder(
200,
@ -200,7 +200,7 @@ describe('WorkspacesClient.switch (integration with stub)', () => {
const result = await client.switch('ws-1')
expect(stub.captured.method).toBe('POST')
expect(stub.captured.url).toBe('/openapi/v1/workspaces/ws-1/switch')
expect(stub.captured.url).toBe('/openapi/v1/workspaces/ws-1:switch')
expect(result.current).toBe(true)
})

View File

@ -47,7 +47,7 @@ export class MembersClient {
memberId: string,
payload: MemberRoleUpdatePayload,
): Promise<MemberActionResponse> {
return this.orpc.workspaces.byWorkspaceId.members.byMemberId.role.put({
return this.orpc.workspaces.byWorkspaceId.members.byMemberId.patch({
params: { workspace_id: workspaceId, member_id: memberId },
body: payload,
})

View File

@ -12,15 +12,15 @@ describe('PermittedExternalAppsClient', () => {
it('list calls permittedExternalApps.get with paging/filter query', async () => {
const c = new PermittedExternalAppsClient(fakeHttp())
const get = vi.fn().mockResolvedValue({ page: 1, limit: 20, total: 0, has_more: false, data: [] })
;(c as unknown as WithOrpc).orpc = { permittedExternalApps: { get, byAppId: { describe: { get: vi.fn() } } } }
;(c as unknown as WithOrpc).orpc = { permittedExternalApps: { get, byAppId: { get: vi.fn() } } }
await c.list({ workspaceId: '', page: 2, limit: 5, mode: undefined, name: 'a' })
expect(get).toHaveBeenCalledWith({ query: { page: 2, limit: 5, mode: undefined, name: 'a' } })
})
it('describe calls permittedExternalApps.byAppId.describe.get with app_id + fields', async () => {
it('describe calls permittedExternalApps.byAppId.get with app_id + fields', async () => {
const c = new PermittedExternalAppsClient(fakeHttp())
const dget = vi.fn().mockResolvedValue({ info: null, parameters: null, input_schema: null })
;(c as unknown as WithOrpc).orpc = { permittedExternalApps: { get: vi.fn(), byAppId: { describe: { get: dget } } } }
;(c as unknown as WithOrpc).orpc = { permittedExternalApps: { get: vi.fn(), byAppId: { get: dget } } }
await c.describe('app-1', ['info'])
expect(dget).toHaveBeenCalledWith({ params: { app_id: 'app-1' }, query: { fields: 'info' } })
})

View File

@ -26,7 +26,7 @@ export class PermittedExternalAppsClient implements AppReader {
}
async describe(appId: string, fields?: readonly string[]): Promise<AppDescribeResponse> {
return this.orpc.permittedExternalApps.byAppId.describe.get({
return this.orpc.permittedExternalApps.byAppId.get({
params: { app_id: appId },
query: { fields: fields !== undefined && fields.length > 0 ? fields.join(',') : undefined },
})

View File

@ -19,7 +19,7 @@ export class WorkspacesClient {
/**
* Server-side workspace switch via OpenAPI POST
* `/workspaces/{id}/switch` — the bearer-authed equivalent of the
* `/workspaces/{id}:switch` — the bearer-authed equivalent of the
* console's POST `/workspaces/switch`. The server updates the caller's
* `current` tenant_account_join row. Callers MUST refresh their local
* `hosts.yml` only after this resolves — never fall back to a local

View File

@ -41,7 +41,7 @@ describe('resumeApp pre-flight subject strategy', () => {
const http = {
baseURL: 'http://localhost',
request: vi.fn().mockImplementation((opts: { path: string }) => {
if (typeof opts.path === 'string' && opts.path.includes('form/human_input')) {
if (typeof opts.path === 'string' && opts.path.includes('human-input-forms')) {
return Promise.resolve(FORM_RESP)
}
// reconnect stream — return an async iterable that ends immediately

View File

@ -50,7 +50,7 @@ export async function resumeApp(opts: ResumeAppOptions, deps: ResumeAppDeps): Pr
let action = opts.action
if (action === undefined) {
const formResp = await deps.http.get<{ user_actions: { id: string }[] }>(
`apps/${encodeURIComponent(opts.appId)}/form/human_input/${encodeURIComponent(opts.formToken)}`,
`apps/${encodeURIComponent(opts.appId)}/human-input-forms/${encodeURIComponent(opts.formToken)}`,
)
if (formResp.user_actions.length === 1) {
action = formResp.user_actions[0]?.id ?? ''

View File

@ -65,7 +65,7 @@ async function executeRun(
const m = await meta.get(opts.appId, [FieldInfo])
const mode = m.info?.mode ?? ''
if (mode === '')
throw new Error(`app ${opts.appId}: mode missing from /describe`)
throw new Error(`app ${opts.appId}: mode missing from app metadata`)
if (mode === RUN_MODES.Workflow && opts.message !== undefined && opts.message !== '') {
throw new BaseError({

View File

@ -27,7 +27,7 @@ export type UseWorkspaceDeps = {
* workspace list and let the caller pick one interactively (TTY only).
*
* The server-side switch is the source of truth: if POST
* `/workspaces/<id>/switch` fails we abort before touching `hosts.yml`, so
* `/workspaces/<id>:switch` fails we abort before touching `hosts.yml`, so
* local state never diverges from the server.
*/
export async function runUseWorkspace(

View File

@ -184,7 +184,7 @@ describe('http client', () => {
const client = createHttpClient({ baseURL: base(mock.url), bearer: 'dfoa_test' })
let caught: unknown
try {
await client.get('apps/nope/describe')
await client.get('apps/nope')
}
catch (err) { caught = err }
expect(isHttpClientError(caught)).toBe(true)
@ -545,7 +545,7 @@ describe('empty / No-Content bodies', () => {
})
try {
const client = createHttpClient({ baseURL: stub.url, bearer: 'dfoa_test' })
await expect(client.post('apps/app-1/tasks/t-1/stop', { json: {} })).resolves.toBeUndefined()
await expect(client.post('apps/app-1/tasks/t-1:stop', { json: {} })).resolves.toBeUndefined()
}
finally {
await stub.stop()

View File

@ -74,7 +74,7 @@ describe('classifyResponse — canonical ErrorBody', () => {
describe('classifyResponse 403', () => {
it('maps 403 to AccessDenied (exit 4 bucket)', async () => {
const req403 = new Request('https://x/openapi/v1/apps/abc/export')
const req403 = new Request('https://x/openapi/v1/apps/abc/dsl')
const res403 = new Response(
JSON.stringify({ code: 'unsupported_token_type', message: 'unsupported_token_type', status: 403 }),
{ status: 403, headers: { 'content-type': 'application/json' } },

View File

@ -3,7 +3,7 @@
*
* Test cases sourced from: Dify CLI Enhanced spec — Dify CLI/Discovery/Single App Query (22 cases)
*
* Note: difyctl get app <id> queries a single app via GET /apps/<id>/describe?fields=info.
* Note: difyctl get app <id> queries a single app via GET /apps/<id>?fields=info.
* The response is returned in list-envelope format {page,limit,total,data:[...]}.
*/

View File

@ -111,15 +111,15 @@ describe('dify-mock fixture server', () => {
expect(body.data.map(r => r.id).sort()).toEqual(['app-3', 'app-4'])
})
it('GET /openapi/v1/apps/:id/describe returns 404 for unknown id', async () => {
const r = await fetch(`${mock.url}/openapi/v1/apps/nope/describe?workspace_id=550e8400-e29b-41d4-a716-446655440000`, {
it('GET /openapi/v1/apps/:id returns 404 for unknown id', async () => {
const r = await fetch(`${mock.url}/openapi/v1/apps/nope?workspace_id=550e8400-e29b-41d4-a716-446655440000`, {
headers: { Authorization: 'Bearer dfoa_test' },
})
expect(r.status).toBe(404)
})
it('GET /openapi/v1/apps/:id/describe returns the app for known id', async () => {
const r = await fetch(`${mock.url}/openapi/v1/apps/app-1/describe?workspace_id=550e8400-e29b-41d4-a716-446655440000`, {
it('GET /openapi/v1/apps/:id returns the app for known id', async () => {
const r = await fetch(`${mock.url}/openapi/v1/apps/app-1?workspace_id=550e8400-e29b-41d4-a716-446655440000`, {
headers: { Authorization: 'Bearer dfoa_test' },
})
expect(r.status).toBe(200)
@ -127,8 +127,8 @@ describe('dify-mock fixture server', () => {
expect(body.info.id).toBe('app-1')
})
it('POST /openapi/v1/apps/:id/run returns SSE stream for chat app', async () => {
const r = await fetch(`${mock.url}/openapi/v1/apps/app-1/run`, {
it('POST /openapi/v1/apps/:id:run returns SSE stream for chat app', async () => {
const r = await fetch(`${mock.url}/openapi/v1/apps/app-1:run`, {
method: 'POST',
headers: {
'Authorization': 'Bearer dfoa_test',
@ -142,8 +142,8 @@ describe('dify-mock fixture server', () => {
expect(text).toContain('"answer":"echo: "')
})
it('POST /openapi/v1/apps/:id/run returns SSE stream for workflow app', async () => {
const r = await fetch(`${mock.url}/openapi/v1/apps/app-2/run`, {
it('POST /openapi/v1/apps/:id:run returns SSE stream for workflow app', async () => {
const r = await fetch(`${mock.url}/openapi/v1/apps/app-2:run`, {
method: 'POST',
headers: {
'Authorization': 'Bearer dfoa_test',
@ -157,8 +157,8 @@ describe('dify-mock fixture server', () => {
expect(text).toContain('"workflow_finished"')
})
it('GET /openapi/v1/apps/:id/describe?fields=info returns slim payload', async () => {
const r = await fetch(`${mock.url}/openapi/v1/apps/app-1/describe?workspace_id=550e8400-e29b-41d4-a716-446655440000&fields=info`, {
it('GET /openapi/v1/apps/:id?fields=info returns slim payload', async () => {
const r = await fetch(`${mock.url}/openapi/v1/apps/app-1?workspace_id=550e8400-e29b-41d4-a716-446655440000&fields=info`, {
headers: { Authorization: 'Bearer dfoa_test' },
})
expect(r.status).toBe(200)
@ -168,8 +168,8 @@ describe('dify-mock fixture server', () => {
expect(body.input_schema).toBeNull()
})
it('GET /openapi/v1/apps/:id/describe full returns parameters when present', async () => {
const r = await fetch(`${mock.url}/openapi/v1/apps/app-1/describe?workspace_id=550e8400-e29b-41d4-a716-446655440000`, {
it('GET /openapi/v1/apps/:id full returns parameters when present', async () => {
const r = await fetch(`${mock.url}/openapi/v1/apps/app-1?workspace_id=550e8400-e29b-41d4-a716-446655440000`, {
headers: { Authorization: 'Bearer dfoa_test' },
})
expect(r.status).toBe(200)

View File

@ -15,9 +15,9 @@ export type DifyMock = {
scenario: Scenario
setScenario: (s: Scenario) => void
stop: () => Promise<void>
/** Body of the most recent POST to /apps/:id/run */
/** Body of the most recent POST to /apps/:id:run */
lastRunBody: Record<string, unknown> | null
/** Number of times POST /apps/:id/files/upload was called */
/** Number of times POST /apps/:id/files was called */
uploadCallCount: number
/** Body of the most recent POST to /workspaces/:id/apps/imports */
lastImportBody: Record<string, unknown> | null
@ -251,7 +251,7 @@ export function buildApp(getScenario: () => Scenario, state?: MockState): Hono {
})
})
app.get('/openapi/v1/apps/:id/describe', (c) => {
app.get('/openapi/v1/apps/:id', (c) => {
const id = c.req.param('id')
const wsId = c.req.query('workspace_id')
const fieldsRaw = c.req.query('fields') ?? ''
@ -279,7 +279,7 @@ export function buildApp(getScenario: () => Scenario, state?: MockState): Hono {
})
})
app.get('/openapi/v1/permitted-external-apps/:id/describe', (c) => {
app.get('/openapi/v1/permitted-external-apps/:id', (c) => {
const id = c.req.param('id')
const fieldsRaw = c.req.query('fields') ?? ''
const fields = fieldsRaw === '' ? [] : fieldsRaw.split(',').map(s => s.trim()).filter(s => s !== '')
@ -307,7 +307,7 @@ export function buildApp(getScenario: () => Scenario, state?: MockState): Hono {
})
})
app.get('/openapi/v1/apps/:id/export', (c) => {
app.get('/openapi/v1/apps/:id/dsl', (c) => {
const id = c.req.param('id')
const found = APPS.find(a => a.id === id)
if (found === undefined)
@ -315,7 +315,7 @@ export function buildApp(getScenario: () => Scenario, state?: MockState): Hono {
return c.json({ data: DSL_YAML })
})
app.get('/openapi/v1/apps/:id/check-dependencies', (c) => {
app.get('/openapi/v1/apps/:id/dependencies', (c) => {
const id = c.req.param('id')
const found = APPS.find(a => a.id === id)
if (found === undefined)
@ -335,12 +335,13 @@ export function buildApp(getScenario: () => Scenario, state?: MockState): Hono {
return c.json({ id: 'imp-1', status: 'completed', app_id: 'app-1', app_mode: 'chat' }, { status: 200 })
})
app.post('/openapi/v1/workspaces/:wsId/apps/imports/:importId/confirm', (c) => {
app.post('/openapi/v1/workspaces/:wsId/apps/imports/:importId:confirm', (c) => {
return c.json({ id: 'imp-1', status: 'completed', app_id: 'app-1', app_mode: 'chat' }, { status: 200 })
})
app.post('/openapi/v1/apps/:id/run', async (c) => {
const id = c.req.param('id')
app.post('/openapi/v1/apps/:id:run', async (c) => {
// Hono drops the param adjacent to the `:run` literal; recover the app id from the path.
const id = c.req.path.replace(/^.*\/apps\//, '').replace(/:run$/, '')
const body = await c.req.json() as { query?: string, inputs?: unknown }
if (state !== undefined)
state.lastRunBody = body as Record<string, unknown>
@ -400,7 +401,7 @@ export function buildApp(getScenario: () => Scenario, state?: MockState): Hono {
return new Response(sse, { status: 200, headers: { 'content-type': 'text/event-stream' } })
})
app.post('/openapi/v1/apps/:id/files/upload', async (c) => {
app.post('/openapi/v1/apps/:id/files', async (c) => {
if (state !== undefined)
state.uploadCallCount++
const form = await c.req.formData()
@ -421,11 +422,11 @@ export function buildApp(getScenario: () => Scenario, state?: MockState): Hono {
)
})
app.post('/openapi/v1/apps/:id/tasks/:taskId/stop', (c) => {
app.post('/openapi/v1/apps/:id/tasks/:taskId:stop', (c) => {
return c.json({ result: 'success' })
})
app.post('/openapi/v1/apps/:id/form/human_input/:formToken', (c) => {
app.post('/openapi/v1/apps/:id/human-input-forms/:formToken:submit', (c) => {
return c.json({})
})

View File

@ -12,16 +12,16 @@ import {
zGetAccountResponse,
zGetAccountSessionsQuery,
zGetAccountSessionsResponse,
zGetAppsByAppIdCheckDependenciesPath,
zGetAppsByAppIdCheckDependenciesResponse,
zGetAppsByAppIdDescribePath,
zGetAppsByAppIdDescribeQuery,
zGetAppsByAppIdDescribeResponse,
zGetAppsByAppIdExportPath,
zGetAppsByAppIdExportQuery,
zGetAppsByAppIdExportResponse,
zGetAppsByAppIdFormHumanInputByFormTokenPath,
zGetAppsByAppIdFormHumanInputByFormTokenResponse,
zGetAppsByAppIdDependenciesPath,
zGetAppsByAppIdDependenciesResponse,
zGetAppsByAppIdDslPath,
zGetAppsByAppIdDslQuery,
zGetAppsByAppIdDslResponse,
zGetAppsByAppIdHumanInputFormsByFormTokenPath,
zGetAppsByAppIdHumanInputFormsByFormTokenResponse,
zGetAppsByAppIdPath,
zGetAppsByAppIdQuery,
zGetAppsByAppIdResponse,
zGetAppsByAppIdTasksByTaskIdEventsPath,
zGetAppsByAppIdTasksByTaskIdEventsQuery,
zGetAppsByAppIdTasksByTaskIdEventsResponse,
@ -30,9 +30,9 @@ import {
zGetHealthResponse,
zGetOauthDeviceLookupQuery,
zGetOauthDeviceLookupResponse,
zGetPermittedExternalAppsByAppIdDescribePath,
zGetPermittedExternalAppsByAppIdDescribeQuery,
zGetPermittedExternalAppsByAppIdDescribeResponse,
zGetPermittedExternalAppsByAppIdPath,
zGetPermittedExternalAppsByAppIdQuery,
zGetPermittedExternalAppsByAppIdResponse,
zGetPermittedExternalAppsQuery,
zGetPermittedExternalAppsResponse,
zGetVersionResponse,
@ -42,11 +42,14 @@ import {
zGetWorkspacesByWorkspaceIdPath,
zGetWorkspacesByWorkspaceIdResponse,
zGetWorkspacesResponse,
zPostAppsByAppIdFilesUploadPath,
zPostAppsByAppIdFilesUploadResponse,
zPostAppsByAppIdFormHumanInputByFormTokenBody,
zPostAppsByAppIdFormHumanInputByFormTokenPath,
zPostAppsByAppIdFormHumanInputByFormTokenResponse,
zPatchWorkspacesByWorkspaceIdMembersByMemberIdBody,
zPatchWorkspacesByWorkspaceIdMembersByMemberIdPath,
zPatchWorkspacesByWorkspaceIdMembersByMemberIdResponse,
zPostAppsByAppIdFilesPath,
zPostAppsByAppIdFilesResponse,
zPostAppsByAppIdHumanInputFormsByFormTokenSubmitBody,
zPostAppsByAppIdHumanInputFormsByFormTokenSubmitPath,
zPostAppsByAppIdHumanInputFormsByFormTokenSubmitResponse,
zPostAppsByAppIdRunBody,
zPostAppsByAppIdRunPath,
zPostAppsByAppIdRunResponse,
@ -70,9 +73,6 @@ import {
zPostWorkspacesByWorkspaceIdMembersResponse,
zPostWorkspacesByWorkspaceIdSwitchPath,
zPostWorkspacesByWorkspaceIdSwitchResponse,
zPutWorkspacesByWorkspaceIdMembersByMemberIdRoleBody,
zPutWorkspacesByWorkspaceIdMembersByMemberIdRolePath,
zPutWorkspacesByWorkspaceIdMembersByMemberIdRoleResponse,
} from './zod.gen'
export const get = oc
@ -168,14 +168,14 @@ export const get5 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
operationId: 'getAppsByAppIdCheckDependencies',
path: '/apps/{app_id}/check-dependencies',
operationId: 'getAppsByAppIdDependencies',
path: '/apps/{app_id}/dependencies',
tags: ['openapi'],
})
.input(z.object({ params: zGetAppsByAppIdCheckDependenciesPath }))
.output(zGetAppsByAppIdCheckDependenciesResponse)
.input(z.object({ params: zGetAppsByAppIdDependenciesPath }))
.output(zGetAppsByAppIdDependenciesResponse)
export const checkDependencies = {
export const dependencies = {
get: get5,
}
@ -183,39 +183,17 @@ export const get6 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
operationId: 'getAppsByAppIdDescribe',
path: '/apps/{app_id}/describe',
operationId: 'getAppsByAppIdDsl',
path: '/apps/{app_id}/dsl',
tags: ['openapi'],
})
.input(
z.object({
params: zGetAppsByAppIdDescribePath,
query: zGetAppsByAppIdDescribeQuery.optional(),
}),
)
.output(zGetAppsByAppIdDescribeResponse)
.input(z.object({ params: zGetAppsByAppIdDslPath, query: zGetAppsByAppIdDslQuery.optional() }))
.output(zGetAppsByAppIdDslResponse)
export const describe = {
export const dsl = {
get: get6,
}
export const get7 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
operationId: 'getAppsByAppIdExport',
path: '/apps/{app_id}/export',
tags: ['openapi'],
})
.input(
z.object({ params: zGetAppsByAppIdExportPath, query: zGetAppsByAppIdExportQuery.optional() }),
)
.output(zGetAppsByAppIdExportResponse)
export const export_ = {
get: get7,
}
/**
* Upload a file to use as an input variable when running the app
*/
@ -224,78 +202,59 @@ export const post = oc
description: 'Upload a file to use as an input variable when running the app',
inputStructure: 'detailed',
method: 'POST',
operationId: 'postAppsByAppIdFilesUpload',
path: '/apps/{app_id}/files/upload',
operationId: 'postAppsByAppIdFiles',
path: '/apps/{app_id}/files',
successStatus: 201,
tags: ['openapi'],
})
.input(z.object({ params: zPostAppsByAppIdFilesUploadPath }))
.output(zPostAppsByAppIdFilesUploadResponse)
export const upload = {
post,
}
.input(z.object({ params: zPostAppsByAppIdFilesPath }))
.output(zPostAppsByAppIdFilesResponse)
export const files = {
upload,
post,
}
export const get8 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
operationId: 'getAppsByAppIdFormHumanInputByFormToken',
path: '/apps/{app_id}/form/human_input/{form_token}',
tags: ['openapi'],
})
.input(z.object({ params: zGetAppsByAppIdFormHumanInputByFormTokenPath }))
.output(zGetAppsByAppIdFormHumanInputByFormTokenResponse)
export const post2 = oc
.route({
inputStructure: 'detailed',
method: 'POST',
operationId: 'postAppsByAppIdFormHumanInputByFormToken',
path: '/apps/{app_id}/form/human_input/{form_token}',
operationId: 'postAppsByAppIdHumanInputFormsByFormTokenSubmit',
path: '/apps/{app_id}/human-input-forms/{form_token}:submit',
tags: ['openapi'],
})
.input(
z.object({
body: zPostAppsByAppIdFormHumanInputByFormTokenBody,
params: zPostAppsByAppIdFormHumanInputByFormTokenPath,
body: zPostAppsByAppIdHumanInputFormsByFormTokenSubmitBody,
params: zPostAppsByAppIdHumanInputFormsByFormTokenSubmitPath,
}),
)
.output(zPostAppsByAppIdFormHumanInputByFormTokenResponse)
.output(zPostAppsByAppIdHumanInputFormsByFormTokenSubmitResponse)
export const byFormToken = {
get: get8,
export const submit = {
post: post2,
}
export const humanInput = {
export const get7 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
operationId: 'getAppsByAppIdHumanInputFormsByFormToken',
path: '/apps/{app_id}/human-input-forms/{form_token}',
tags: ['openapi'],
})
.input(z.object({ params: zGetAppsByAppIdHumanInputFormsByFormTokenPath }))
.output(zGetAppsByAppIdHumanInputFormsByFormTokenResponse)
export const byFormToken = {
get: get7,
submit,
}
export const humanInputForms = {
byFormToken,
}
export const form = {
humanInput,
}
export const post3 = oc
.route({
inputStructure: 'detailed',
method: 'POST',
operationId: 'postAppsByAppIdRun',
path: '/apps/{app_id}/run',
tags: ['openapi'],
})
.input(z.object({ body: zPostAppsByAppIdRunBody, params: zPostAppsByAppIdRunPath }))
.output(zPostAppsByAppIdRunResponse)
export const run = {
post: post3,
}
export const get9 = oc
export const get8 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
@ -312,22 +271,22 @@ export const get9 = oc
.output(zGetAppsByAppIdTasksByTaskIdEventsResponse)
export const events = {
get: get9,
get: get8,
}
export const post4 = oc
export const post3 = oc
.route({
inputStructure: 'detailed',
method: 'POST',
operationId: 'postAppsByAppIdTasksByTaskIdStop',
path: '/apps/{app_id}/tasks/{task_id}/stop',
path: '/apps/{app_id}/tasks/{task_id}:stop',
tags: ['openapi'],
})
.input(z.object({ params: zPostAppsByAppIdTasksByTaskIdStopPath }))
.output(zPostAppsByAppIdTasksByTaskIdStopResponse)
export const stop = {
post: post4,
post: post3,
}
export const byTaskId = {
@ -339,14 +298,40 @@ export const tasks = {
byTaskId,
}
export const post4 = oc
.route({
inputStructure: 'detailed',
method: 'POST',
operationId: 'postAppsByAppIdRun',
path: '/apps/{app_id}:run',
tags: ['openapi'],
})
.input(z.object({ body: zPostAppsByAppIdRunBody, params: zPostAppsByAppIdRunPath }))
.output(zPostAppsByAppIdRunResponse)
export const run = {
post: post4,
}
export const get9 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
operationId: 'getAppsByAppId',
path: '/apps/{app_id}',
tags: ['openapi'],
})
.input(z.object({ params: zGetAppsByAppIdPath, query: zGetAppsByAppIdQuery.optional() }))
.output(zGetAppsByAppIdResponse)
export const byAppId = {
checkDependencies,
describe,
export: export_,
get: get9,
dependencies,
dsl,
files,
form,
run,
humanInputForms,
tasks,
run,
}
export const get10 = oc
@ -456,24 +441,20 @@ export const get12 = oc
.route({
inputStructure: 'detailed',
method: 'GET',
operationId: 'getPermittedExternalAppsByAppIdDescribe',
path: '/permitted-external-apps/{app_id}/describe',
operationId: 'getPermittedExternalAppsByAppId',
path: '/permitted-external-apps/{app_id}',
tags: ['openapi'],
})
.input(
z.object({
params: zGetPermittedExternalAppsByAppIdDescribePath,
query: zGetPermittedExternalAppsByAppIdDescribeQuery.optional(),
params: zGetPermittedExternalAppsByAppIdPath,
query: zGetPermittedExternalAppsByAppIdQuery.optional(),
}),
)
.output(zGetPermittedExternalAppsByAppIdDescribeResponse)
export const describe2 = {
get: get12,
}
.output(zGetPermittedExternalAppsByAppIdResponse)
export const byAppId2 = {
describe: describe2,
get: get12,
}
export const get13 = oc
@ -497,7 +478,7 @@ export const post9 = oc
inputStructure: 'detailed',
method: 'POST',
operationId: 'postWorkspacesByWorkspaceIdAppsImportsByImportIdConfirm',
path: '/workspaces/{workspace_id}/apps/imports/{import_id}/confirm',
path: '/workspaces/{workspace_id}/apps/imports/{import_id}:confirm',
tags: ['openapi'],
})
.input(z.object({ params: zPostWorkspacesByWorkspaceIdAppsImportsByImportIdConfirmPath }))
@ -536,26 +517,6 @@ export const apps2 = {
imports,
}
export const put = oc
.route({
inputStructure: 'detailed',
method: 'PUT',
operationId: 'putWorkspacesByWorkspaceIdMembersByMemberIdRole',
path: '/workspaces/{workspace_id}/members/{member_id}/role',
tags: ['openapi'],
})
.input(
z.object({
body: zPutWorkspacesByWorkspaceIdMembersByMemberIdRoleBody,
params: zPutWorkspacesByWorkspaceIdMembersByMemberIdRolePath,
}),
)
.output(zPutWorkspacesByWorkspaceIdMembersByMemberIdRoleResponse)
export const role = {
put,
}
export const delete3 = oc
.route({
inputStructure: 'detailed',
@ -567,9 +528,25 @@ export const delete3 = oc
.input(z.object({ params: zDeleteWorkspacesByWorkspaceIdMembersByMemberIdPath }))
.output(zDeleteWorkspacesByWorkspaceIdMembersByMemberIdResponse)
export const patch = oc
.route({
inputStructure: 'detailed',
method: 'PATCH',
operationId: 'patchWorkspacesByWorkspaceIdMembersByMemberId',
path: '/workspaces/{workspace_id}/members/{member_id}',
tags: ['openapi'],
})
.input(
z.object({
body: zPatchWorkspacesByWorkspaceIdMembersByMemberIdBody,
params: zPatchWorkspacesByWorkspaceIdMembersByMemberIdPath,
}),
)
.output(zPatchWorkspacesByWorkspaceIdMembersByMemberIdResponse)
export const byMemberId = {
delete: delete3,
role,
patch,
}
export const get14 = oc
@ -616,7 +593,7 @@ export const post12 = oc
inputStructure: 'detailed',
method: 'POST',
operationId: 'postWorkspacesByWorkspaceIdSwitch',
path: '/workspaces/{workspace_id}/switch',
path: '/workspaces/{workspace_id}:switch',
tags: ['openapi'],
})
.input(z.object({ params: zPostWorkspacesByWorkspaceIdSwitchPath }))

View File

@ -617,30 +617,7 @@ export type GetAppsResponses = {
export type GetAppsResponse = GetAppsResponses[keyof GetAppsResponses]
export type GetAppsByAppIdCheckDependenciesData = {
body?: never
path: {
app_id: string
}
query?: never
url: '/apps/{app_id}/check-dependencies'
}
export type GetAppsByAppIdCheckDependenciesErrors = {
default: ErrorBody
}
export type GetAppsByAppIdCheckDependenciesError
= GetAppsByAppIdCheckDependenciesErrors[keyof GetAppsByAppIdCheckDependenciesErrors]
export type GetAppsByAppIdCheckDependenciesResponses = {
200: CheckDependenciesResult
}
export type GetAppsByAppIdCheckDependenciesResponse
= GetAppsByAppIdCheckDependenciesResponses[keyof GetAppsByAppIdCheckDependenciesResponses]
export type GetAppsByAppIdDescribeData = {
export type GetAppsByAppIdData = {
body?: never
path: {
app_id: string
@ -648,25 +625,46 @@ export type GetAppsByAppIdDescribeData = {
query?: {
fields?: string
}
url: '/apps/{app_id}/describe'
url: '/apps/{app_id}'
}
export type GetAppsByAppIdDescribeErrors = {
export type GetAppsByAppIdErrors = {
422: ErrorBody
default: ErrorBody
}
export type GetAppsByAppIdDescribeError
= GetAppsByAppIdDescribeErrors[keyof GetAppsByAppIdDescribeErrors]
export type GetAppsByAppIdError = GetAppsByAppIdErrors[keyof GetAppsByAppIdErrors]
export type GetAppsByAppIdDescribeResponses = {
export type GetAppsByAppIdResponses = {
200: AppDescribeResponse
}
export type GetAppsByAppIdDescribeResponse
= GetAppsByAppIdDescribeResponses[keyof GetAppsByAppIdDescribeResponses]
export type GetAppsByAppIdResponse = GetAppsByAppIdResponses[keyof GetAppsByAppIdResponses]
export type GetAppsByAppIdExportData = {
export type GetAppsByAppIdDependenciesData = {
body?: never
path: {
app_id: string
}
query?: never
url: '/apps/{app_id}/dependencies'
}
export type GetAppsByAppIdDependenciesErrors = {
default: ErrorBody
}
export type GetAppsByAppIdDependenciesError
= GetAppsByAppIdDependenciesErrors[keyof GetAppsByAppIdDependenciesErrors]
export type GetAppsByAppIdDependenciesResponses = {
200: CheckDependenciesResult
}
export type GetAppsByAppIdDependenciesResponse
= GetAppsByAppIdDependenciesResponses[keyof GetAppsByAppIdDependenciesResponses]
export type GetAppsByAppIdDslData = {
body?: never
path: {
app_id: string
@ -675,33 +673,32 @@ export type GetAppsByAppIdExportData = {
include_secret?: boolean
workflow_id?: string
}
url: '/apps/{app_id}/export'
url: '/apps/{app_id}/dsl'
}
export type GetAppsByAppIdExportErrors = {
export type GetAppsByAppIdDslErrors = {
422: ErrorBody
default: ErrorBody
}
export type GetAppsByAppIdExportError = GetAppsByAppIdExportErrors[keyof GetAppsByAppIdExportErrors]
export type GetAppsByAppIdDslError = GetAppsByAppIdDslErrors[keyof GetAppsByAppIdDslErrors]
export type GetAppsByAppIdExportResponses = {
export type GetAppsByAppIdDslResponses = {
200: AppDslExportResponse
}
export type GetAppsByAppIdExportResponse
= GetAppsByAppIdExportResponses[keyof GetAppsByAppIdExportResponses]
export type GetAppsByAppIdDslResponse = GetAppsByAppIdDslResponses[keyof GetAppsByAppIdDslResponses]
export type PostAppsByAppIdFilesUploadData = {
export type PostAppsByAppIdFilesData = {
body?: never
path: {
app_id: string
}
query?: never
url: '/apps/{app_id}/files/upload'
url: '/apps/{app_id}/files'
}
export type PostAppsByAppIdFilesUploadErrors = {
export type PostAppsByAppIdFilesErrors = {
400: unknown
401: unknown
413: unknown
@ -709,79 +706,56 @@ export type PostAppsByAppIdFilesUploadErrors = {
default: ErrorBody
}
export type PostAppsByAppIdFilesUploadError
= PostAppsByAppIdFilesUploadErrors[keyof PostAppsByAppIdFilesUploadErrors]
export type PostAppsByAppIdFilesError = PostAppsByAppIdFilesErrors[keyof PostAppsByAppIdFilesErrors]
export type PostAppsByAppIdFilesUploadResponses = {
export type PostAppsByAppIdFilesResponses = {
201: FileResponse
}
export type PostAppsByAppIdFilesUploadResponse
= PostAppsByAppIdFilesUploadResponses[keyof PostAppsByAppIdFilesUploadResponses]
export type PostAppsByAppIdFilesResponse
= PostAppsByAppIdFilesResponses[keyof PostAppsByAppIdFilesResponses]
export type GetAppsByAppIdFormHumanInputByFormTokenData = {
export type GetAppsByAppIdHumanInputFormsByFormTokenData = {
body?: never
path: {
app_id: string
form_token: string
}
query?: never
url: '/apps/{app_id}/form/human_input/{form_token}'
url: '/apps/{app_id}/human-input-forms/{form_token}'
}
export type GetAppsByAppIdFormHumanInputByFormTokenResponses = {
export type GetAppsByAppIdHumanInputFormsByFormTokenResponses = {
200: HumanInputFormDefinitionResponse
}
export type GetAppsByAppIdFormHumanInputByFormTokenResponse
= GetAppsByAppIdFormHumanInputByFormTokenResponses[keyof GetAppsByAppIdFormHumanInputByFormTokenResponses]
export type GetAppsByAppIdHumanInputFormsByFormTokenResponse
= GetAppsByAppIdHumanInputFormsByFormTokenResponses[keyof GetAppsByAppIdHumanInputFormsByFormTokenResponses]
export type PostAppsByAppIdFormHumanInputByFormTokenData = {
export type PostAppsByAppIdHumanInputFormsByFormTokenSubmitData = {
body: HumanInputFormSubmitPayload
path: {
app_id: string
form_token: string
}
query?: never
url: '/apps/{app_id}/form/human_input/{form_token}'
url: '/apps/{app_id}/human-input-forms/{form_token}:submit'
}
export type PostAppsByAppIdFormHumanInputByFormTokenErrors = {
export type PostAppsByAppIdHumanInputFormsByFormTokenSubmitErrors = {
422: ErrorBody
default: ErrorBody
}
export type PostAppsByAppIdFormHumanInputByFormTokenError
= PostAppsByAppIdFormHumanInputByFormTokenErrors[keyof PostAppsByAppIdFormHumanInputByFormTokenErrors]
export type PostAppsByAppIdHumanInputFormsByFormTokenSubmitError
= PostAppsByAppIdHumanInputFormsByFormTokenSubmitErrors[keyof PostAppsByAppIdHumanInputFormsByFormTokenSubmitErrors]
export type PostAppsByAppIdFormHumanInputByFormTokenResponses = {
export type PostAppsByAppIdHumanInputFormsByFormTokenSubmitResponses = {
200: FormSubmitResponse
}
export type PostAppsByAppIdFormHumanInputByFormTokenResponse
= PostAppsByAppIdFormHumanInputByFormTokenResponses[keyof PostAppsByAppIdFormHumanInputByFormTokenResponses]
export type PostAppsByAppIdRunData = {
body: AppRunRequest
path: {
app_id: string
}
query?: never
url: '/apps/{app_id}/run'
}
export type PostAppsByAppIdRunErrors = {
422: ErrorBody
}
export type PostAppsByAppIdRunError = PostAppsByAppIdRunErrors[keyof PostAppsByAppIdRunErrors]
export type PostAppsByAppIdRunResponses = {
200: EventStreamResponse
}
export type PostAppsByAppIdRunResponse
= PostAppsByAppIdRunResponses[keyof PostAppsByAppIdRunResponses]
export type PostAppsByAppIdHumanInputFormsByFormTokenSubmitResponse
= PostAppsByAppIdHumanInputFormsByFormTokenSubmitResponses[keyof PostAppsByAppIdHumanInputFormsByFormTokenSubmitResponses]
export type GetAppsByAppIdTasksByTaskIdEventsData = {
body?: never
@ -810,7 +784,7 @@ export type PostAppsByAppIdTasksByTaskIdStopData = {
task_id: string
}
query?: never
url: '/apps/{app_id}/tasks/{task_id}/stop'
url: '/apps/{app_id}/tasks/{task_id}:stop'
}
export type PostAppsByAppIdTasksByTaskIdStopErrors = {
@ -827,6 +801,28 @@ export type PostAppsByAppIdTasksByTaskIdStopResponses = {
export type PostAppsByAppIdTasksByTaskIdStopResponse
= PostAppsByAppIdTasksByTaskIdStopResponses[keyof PostAppsByAppIdTasksByTaskIdStopResponses]
export type PostAppsByAppIdRunData = {
body: AppRunRequest
path: {
app_id: string
}
query?: never
url: '/apps/{app_id}:run'
}
export type PostAppsByAppIdRunErrors = {
422: ErrorBody
}
export type PostAppsByAppIdRunError = PostAppsByAppIdRunErrors[keyof PostAppsByAppIdRunErrors]
export type PostAppsByAppIdRunResponses = {
200: EventStreamResponse
}
export type PostAppsByAppIdRunResponse
= PostAppsByAppIdRunResponses[keyof PostAppsByAppIdRunResponses]
export type PostOauthDeviceApproveData = {
body: DeviceMutateRequest
path?: never
@ -926,7 +922,7 @@ export type GetPermittedExternalAppsResponses = {
export type GetPermittedExternalAppsResponse
= GetPermittedExternalAppsResponses[keyof GetPermittedExternalAppsResponses]
export type GetPermittedExternalAppsByAppIdDescribeData = {
export type GetPermittedExternalAppsByAppIdData = {
body?: never
path: {
app_id: string
@ -934,23 +930,23 @@ export type GetPermittedExternalAppsByAppIdDescribeData = {
query?: {
fields?: string
}
url: '/permitted-external-apps/{app_id}/describe'
url: '/permitted-external-apps/{app_id}'
}
export type GetPermittedExternalAppsByAppIdDescribeErrors = {
export type GetPermittedExternalAppsByAppIdErrors = {
422: ErrorBody
default: ErrorBody
}
export type GetPermittedExternalAppsByAppIdDescribeError
= GetPermittedExternalAppsByAppIdDescribeErrors[keyof GetPermittedExternalAppsByAppIdDescribeErrors]
export type GetPermittedExternalAppsByAppIdError
= GetPermittedExternalAppsByAppIdErrors[keyof GetPermittedExternalAppsByAppIdErrors]
export type GetPermittedExternalAppsByAppIdDescribeResponses = {
export type GetPermittedExternalAppsByAppIdResponses = {
200: AppDescribeResponse
}
export type GetPermittedExternalAppsByAppIdDescribeResponse
= GetPermittedExternalAppsByAppIdDescribeResponses[keyof GetPermittedExternalAppsByAppIdDescribeResponses]
export type GetPermittedExternalAppsByAppIdResponse
= GetPermittedExternalAppsByAppIdResponses[keyof GetPermittedExternalAppsByAppIdResponses]
export type GetWorkspacesData = {
body?: never
@ -1027,7 +1023,7 @@ export type PostWorkspacesByWorkspaceIdAppsImportsByImportIdConfirmData = {
workspace_id: string
}
query?: never
url: '/workspaces/{workspace_id}/apps/imports/{import_id}/confirm'
url: '/workspaces/{workspace_id}/apps/imports/{import_id}:confirm'
}
export type PostWorkspacesByWorkspaceIdAppsImportsByImportIdConfirmErrors = {
@ -1120,30 +1116,30 @@ export type DeleteWorkspacesByWorkspaceIdMembersByMemberIdResponses = {
export type DeleteWorkspacesByWorkspaceIdMembersByMemberIdResponse
= DeleteWorkspacesByWorkspaceIdMembersByMemberIdResponses[keyof DeleteWorkspacesByWorkspaceIdMembersByMemberIdResponses]
export type PutWorkspacesByWorkspaceIdMembersByMemberIdRoleData = {
export type PatchWorkspacesByWorkspaceIdMembersByMemberIdData = {
body: MemberRoleUpdatePayload
path: {
member_id: string
workspace_id: string
}
query?: never
url: '/workspaces/{workspace_id}/members/{member_id}/role'
url: '/workspaces/{workspace_id}/members/{member_id}'
}
export type PutWorkspacesByWorkspaceIdMembersByMemberIdRoleErrors = {
export type PatchWorkspacesByWorkspaceIdMembersByMemberIdErrors = {
422: ErrorBody
default: ErrorBody
}
export type PutWorkspacesByWorkspaceIdMembersByMemberIdRoleError
= PutWorkspacesByWorkspaceIdMembersByMemberIdRoleErrors[keyof PutWorkspacesByWorkspaceIdMembersByMemberIdRoleErrors]
export type PatchWorkspacesByWorkspaceIdMembersByMemberIdError
= PatchWorkspacesByWorkspaceIdMembersByMemberIdErrors[keyof PatchWorkspacesByWorkspaceIdMembersByMemberIdErrors]
export type PutWorkspacesByWorkspaceIdMembersByMemberIdRoleResponses = {
export type PatchWorkspacesByWorkspaceIdMembersByMemberIdResponses = {
200: MemberActionResponse
}
export type PutWorkspacesByWorkspaceIdMembersByMemberIdRoleResponse
= PutWorkspacesByWorkspaceIdMembersByMemberIdRoleResponses[keyof PutWorkspacesByWorkspaceIdMembersByMemberIdRoleResponses]
export type PatchWorkspacesByWorkspaceIdMembersByMemberIdResponse
= PatchWorkspacesByWorkspaceIdMembersByMemberIdResponses[keyof PatchWorkspacesByWorkspaceIdMembersByMemberIdResponses]
export type PostWorkspacesByWorkspaceIdSwitchData = {
body?: never
@ -1151,7 +1147,7 @@ export type PostWorkspacesByWorkspaceIdSwitchData = {
workspace_id: string
}
query?: never
url: '/workspaces/{workspace_id}/switch'
url: '/workspaces/{workspace_id}:switch'
}
export type PostWorkspacesByWorkspaceIdSwitchErrors = {

View File

@ -27,7 +27,7 @@ export const zAppDescribeInfo = z.object({
/**
* AppDescribeQuery
*
* `?fields=` allow-list for GET /apps/<id>/describe.
* `?fields=` allow-list for GET /apps/<id>.
*
* Empty / omitted → all blocks. Unknown member → ValidationError → 422.
*/
@ -47,7 +47,7 @@ export const zAppDescribeResponse = z.object({
/**
* AppDslExportQuery
*
* Query parameters for GET /apps/<app_id>/export.
* Query parameters for GET /apps/<app_id>/dsl.
*/
export const zAppDslExportQuery = z.object({
include_secret: z.boolean().optional().default(false),
@ -254,7 +254,7 @@ export const zFileResponse = z.object({
/**
* FormSubmitResponse
*
* Empty 200 body for POST /apps/<id>/form/human_input/<token>. `extra='forbid'`
* Empty 200 body for POST /apps/<id>/human-input-forms/<token>:submit. `extra='forbid'`
* pins `additionalProperties: false` so the generated contract is an exact `{}` rather
* than an under-annotated open object.
*/
@ -581,7 +581,7 @@ export const zPermittedExternalAppsListQuery = z.object({
/**
* TaskStopResponse
*
* 200 body for POST /apps/<id>/tasks/<task_id>/stop. The handler always returns
* 200 body for POST /apps/<id>/tasks/<task_id>:stop. The handler always returns
* {"result": "success"}, so `result` is required (no default) — the generated contract
* types it as a required `'success'` rather than an optional field.
*/
@ -740,33 +740,33 @@ export const zGetAppsQuery = z.object({
*/
export const zGetAppsResponse = zAppListResponse
export const zGetAppsByAppIdCheckDependenciesPath = z.object({
export const zGetAppsByAppIdPath = z.object({
app_id: z.string(),
})
/**
* Dependencies checked
*/
export const zGetAppsByAppIdCheckDependenciesResponse = zCheckDependenciesResult
export const zGetAppsByAppIdDescribePath = z.object({
app_id: z.string(),
})
export const zGetAppsByAppIdDescribeQuery = z.object({
export const zGetAppsByAppIdQuery = z.object({
fields: z.string().optional(),
})
/**
* App description
*/
export const zGetAppsByAppIdDescribeResponse = zAppDescribeResponse
export const zGetAppsByAppIdResponse = zAppDescribeResponse
export const zGetAppsByAppIdExportPath = z.object({
export const zGetAppsByAppIdDependenciesPath = z.object({
app_id: z.string(),
})
export const zGetAppsByAppIdExportQuery = z.object({
/**
* Dependencies checked
*/
export const zGetAppsByAppIdDependenciesResponse = zCheckDependenciesResult
export const zGetAppsByAppIdDslPath = z.object({
app_id: z.string(),
})
export const zGetAppsByAppIdDslQuery = z.object({
include_secret: z.boolean().optional().default(false),
workflow_id: z.string().optional(),
})
@ -774,18 +774,18 @@ export const zGetAppsByAppIdExportQuery = z.object({
/**
* Export successful
*/
export const zGetAppsByAppIdExportResponse = zAppDslExportResponse
export const zGetAppsByAppIdDslResponse = zAppDslExportResponse
export const zPostAppsByAppIdFilesUploadPath = z.object({
export const zPostAppsByAppIdFilesPath = z.object({
app_id: z.string(),
})
/**
* File uploaded successfully
*/
export const zPostAppsByAppIdFilesUploadResponse = zFileResponse
export const zPostAppsByAppIdFilesResponse = zFileResponse
export const zGetAppsByAppIdFormHumanInputByFormTokenPath = z.object({
export const zGetAppsByAppIdHumanInputFormsByFormTokenPath = z.object({
app_id: z.string(),
form_token: z.string(),
})
@ -793,11 +793,11 @@ export const zGetAppsByAppIdFormHumanInputByFormTokenPath = z.object({
/**
* Form definition
*/
export const zGetAppsByAppIdFormHumanInputByFormTokenResponse = zHumanInputFormDefinitionResponse
export const zGetAppsByAppIdHumanInputFormsByFormTokenResponse = zHumanInputFormDefinitionResponse
export const zPostAppsByAppIdFormHumanInputByFormTokenBody = zHumanInputFormSubmitPayload
export const zPostAppsByAppIdHumanInputFormsByFormTokenSubmitBody = zHumanInputFormSubmitPayload
export const zPostAppsByAppIdFormHumanInputByFormTokenPath = z.object({
export const zPostAppsByAppIdHumanInputFormsByFormTokenSubmitPath = z.object({
app_id: z.string(),
form_token: z.string(),
})
@ -805,18 +805,7 @@ export const zPostAppsByAppIdFormHumanInputByFormTokenPath = z.object({
/**
* Form submitted
*/
export const zPostAppsByAppIdFormHumanInputByFormTokenResponse = zFormSubmitResponse
export const zPostAppsByAppIdRunBody = zAppRunRequest
export const zPostAppsByAppIdRunPath = z.object({
app_id: z.string(),
})
/**
* Run result (SSE stream)
*/
export const zPostAppsByAppIdRunResponse = zEventStreamResponse
export const zPostAppsByAppIdHumanInputFormsByFormTokenSubmitResponse = zFormSubmitResponse
export const zGetAppsByAppIdTasksByTaskIdEventsPath = z.object({
app_id: z.string(),
@ -843,6 +832,17 @@ export const zPostAppsByAppIdTasksByTaskIdStopPath = z.object({
*/
export const zPostAppsByAppIdTasksByTaskIdStopResponse = zTaskStopResponse
export const zPostAppsByAppIdRunBody = zAppRunRequest
export const zPostAppsByAppIdRunPath = z.object({
app_id: z.string(),
})
/**
* Run result (SSE stream)
*/
export const zPostAppsByAppIdRunResponse = zEventStreamResponse
export const zPostOauthDeviceApproveBody = zDeviceMutateRequest
/**
@ -892,18 +892,18 @@ export const zGetPermittedExternalAppsQuery = z.object({
*/
export const zGetPermittedExternalAppsResponse = zPermittedExternalAppsListResponse
export const zGetPermittedExternalAppsByAppIdDescribePath = z.object({
export const zGetPermittedExternalAppsByAppIdPath = z.object({
app_id: z.string(),
})
export const zGetPermittedExternalAppsByAppIdDescribeQuery = z.object({
export const zGetPermittedExternalAppsByAppIdQuery = z.object({
fields: z.string().optional(),
})
/**
* Permitted external app description
*/
export const zGetPermittedExternalAppsByAppIdDescribeResponse = zAppDescribeResponse
export const zGetPermittedExternalAppsByAppIdResponse = zAppDescribeResponse
/**
* Workspace list
@ -975,9 +975,9 @@ export const zDeleteWorkspacesByWorkspaceIdMembersByMemberIdPath = z.object({
*/
export const zDeleteWorkspacesByWorkspaceIdMembersByMemberIdResponse = zMemberActionResponse
export const zPutWorkspacesByWorkspaceIdMembersByMemberIdRoleBody = zMemberRoleUpdatePayload
export const zPatchWorkspacesByWorkspaceIdMembersByMemberIdBody = zMemberRoleUpdatePayload
export const zPutWorkspacesByWorkspaceIdMembersByMemberIdRolePath = z.object({
export const zPatchWorkspacesByWorkspaceIdMembersByMemberIdPath = z.object({
member_id: z.string(),
workspace_id: z.string(),
})
@ -985,7 +985,7 @@ export const zPutWorkspacesByWorkspaceIdMembersByMemberIdRolePath = z.object({
/**
* Role updated
*/
export const zPutWorkspacesByWorkspaceIdMembersByMemberIdRoleResponse = zMemberActionResponse
export const zPatchWorkspacesByWorkspaceIdMembersByMemberIdResponse = zMemberActionResponse
export const zPostWorkspacesByWorkspaceIdSwitchPath = z.object({
workspace_id: z.string(),

View File

@ -102,11 +102,11 @@ const segmentWords = (segment: string) => {
return toWords(segment)
}
// Split on `:` too so custom methods nest as their own node (apps.byAppId.run), not apps.appIdRun.
const routeNamingSegments = (routePath: string) => routePath.split(/[/:]/).filter(Boolean)
const routeWords = (routePath: string) => {
return routePath
.split('/')
.filter(Boolean)
.flatMap(segmentWords)
return routeNamingSegments(routePath).flatMap(segmentWords)
}
const operationId = (method: string, routePath: string) => {
@ -114,10 +114,7 @@ const operationId = (method: string, routePath: string) => {
}
const contractPathSegments = (operation: ApiContractOperation) => {
const segments = operation.path
.split('/')
.filter(Boolean)
.map(segment => toCamelCase(segmentWords(segment)))
const segments = routeNamingSegments(operation.path).map(segment => toCamelCase(segmentWords(segment)))
return [...(segments.length > 0 ? segments : ['root']), operation.method.toLowerCase()]
}