diff --git a/api/openapi/markdown/openapi-openapi.md b/api/openapi/markdown/openapi-openapi.md index a649c2fa74f..7501e5d71a2 100644 --- a/api/openapi/markdown/openapi-openapi.md +++ b/api/openapi/markdown/openapi-openapi.md @@ -93,21 +93,7 @@ User-scoped operations | 422 | Validation error | **application/json**: [ErrorBody](#errorbody)
| | default | Error | **application/json**: [ErrorBody](#errorbody)
| -### [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)
| -| default | Error | **application/json**: [ErrorBody](#errorbody)
| - -### [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)
| | default | Error | **application/json**: [ErrorBody](#errorbody)
| -### [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)
| +| default | Error | **application/json**: [ErrorBody](#errorbody)
| + +### [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)
| | default | Error | **application/json**: [ErrorBody](#errorbody)
| -### [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)
| -### [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)
| -### [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)
| | default | Error | **application/json**: [ErrorBody](#errorbody)
| -### [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)
| + +### [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)
| +| default | Error | **application/json**: [ErrorBody](#errorbody)
| + +### [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)
| | 422 | Validation error | **application/json**: [ErrorBody](#errorbody)
| -### [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)
| - -### [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)
| -| default | Error | **application/json**: [ErrorBody](#errorbody)
| - ### [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)
| | default | Error | **application/json**: [ErrorBody](#errorbody)
| -### [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)
| | default | Error | **application/json**: [ErrorBody](#errorbody)
| -### [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)
| | default | Error | **application/json**: [ErrorBody](#errorbody)
| -### [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)
| | default | Error | **application/json**: [ErrorBody](#errorbody)
| -### [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//describe. +`?fields=` allow-list for GET /apps/. 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//export. +Query parameters for GET /apps//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//form/human_input/. `extra='forbid'` +Empty 200 body for POST /apps//human-input-forms/: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//tasks//stop. The handler always returns +200 body for POST /apps//tasks/: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. diff --git a/cli/src/api/app-dsl.test.ts b/cli/src/api/app-dsl.test.ts index 66d4507317e..9056a228212 100644 --- a/cli/src/api/app-dsl.test.ts +++ b/cli/src/api/app-dsl.test.ts @@ -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([]) }) }) diff --git a/cli/src/api/app-dsl.ts b/cli/src/api/app-dsl.ts index acb10a95351..733f1fa8fac 100644 --- a/cli/src/api/app-dsl.ts +++ b/cli/src/api/app-dsl.ts @@ -33,7 +33,7 @@ export class AppDslClient { } async exportDsl(appId: string, query?: ExportQuery): Promise { - 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 { - return this.orpc.apps.byAppId.checkDependencies.get({ + return this.orpc.apps.byAppId.dependencies.get({ params: { app_id: appId }, }) } diff --git a/cli/src/api/app-run.ts b/cli/src/api/app-run.ts index cbd36de2049..a75b3f0c63a 100644 --- a/cli/src/api/app-run.ts +++ b/cli/src/api/app-run.ts @@ -54,7 +54,7 @@ export class AppRunClient { body: Record, opts: StreamOptions = {}, ): Promise> { - 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, ): Promise { - 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 }, }) diff --git a/cli/src/api/apps.test.ts b/cli/src/api/apps.test.ts index 861f60feb26..ba9126b7b98 100644 --- a/cli/src/api/apps.test.ts +++ b/cli/src/api/apps.test.ts @@ -82,12 +82,12 @@ describe('AppsClient.describe', () => { await stub?.stop() }) - it('hits /apps//describe, omits workspace_id and fields when not given', async () => { + it('hits /apps/, 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') }) }) diff --git a/cli/src/api/apps.ts b/cli/src/api/apps.ts index 1189fdeaa06..fa29d66fd14 100644 --- a/cli/src/api/apps.ts +++ b/cli/src/api/apps.ts @@ -37,7 +37,7 @@ export class AppsClient implements AppReader { } async describe(appId: string, fields?: readonly string[]): Promise { - 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, diff --git a/cli/src/api/file-upload.test.ts b/cli/src/api/file-upload.test.ts index 018389916b0..602cf351e60 100644 --- a/cli/src/api/file-upload.test.ts +++ b/cli/src/api/file-upload.test.ts @@ -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 () => { diff --git a/cli/src/api/file-upload.ts b/cli/src/api/file-upload.ts index 7a032737a59..011f898c74e 100644 --- a/cli/src/api/file-upload.ts +++ b/cli/src/api/file-upload.ts @@ -65,7 +65,7 @@ export class FileUploadClient { form.append('file', blob, filename) return this.http.post( - `apps/${encodeURIComponent(appId)}/files/upload`, + `apps/${encodeURIComponent(appId)}/files`, { body: form, timeoutMs: 60_000 }, ) } diff --git a/cli/src/api/members.test.ts b/cli/src/api/members.test.ts index b4e01b76b24..a8fdc633f77 100644 --- a/cli/src/api/members.test.ts +++ b/cli/src/api/members.test.ts @@ -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//switch and returns workspace detail', async () => { + it('POSTs /workspaces/: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) }) diff --git a/cli/src/api/members.ts b/cli/src/api/members.ts index 7b1f80c08ff..8a9bc13081f 100644 --- a/cli/src/api/members.ts +++ b/cli/src/api/members.ts @@ -47,7 +47,7 @@ export class MembersClient { memberId: string, payload: MemberRoleUpdatePayload, ): Promise { - 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, }) diff --git a/cli/src/api/permitted-external-apps.test.ts b/cli/src/api/permitted-external-apps.test.ts index f6fa38cb3eb..58f47b4566f 100644 --- a/cli/src/api/permitted-external-apps.test.ts +++ b/cli/src/api/permitted-external-apps.test.ts @@ -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' } }) }) diff --git a/cli/src/api/permitted-external-apps.ts b/cli/src/api/permitted-external-apps.ts index 497c398d0ba..c0164d3c536 100644 --- a/cli/src/api/permitted-external-apps.ts +++ b/cli/src/api/permitted-external-apps.ts @@ -26,7 +26,7 @@ export class PermittedExternalAppsClient implements AppReader { } async describe(appId: string, fields?: readonly string[]): Promise { - 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 }, }) diff --git a/cli/src/api/workspaces.ts b/cli/src/api/workspaces.ts index 3ef587b574d..08495a01fce 100644 --- a/cli/src/api/workspaces.ts +++ b/cli/src/api/workspaces.ts @@ -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 diff --git a/cli/src/commands/resume/app/run.test.ts b/cli/src/commands/resume/app/run.test.ts index a72b0a93b25..8e1882906ae 100644 --- a/cli/src/commands/resume/app/run.test.ts +++ b/cli/src/commands/resume/app/run.test.ts @@ -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 diff --git a/cli/src/commands/resume/app/run.ts b/cli/src/commands/resume/app/run.ts index 1dc2855a118..0610f68d5e6 100644 --- a/cli/src/commands/resume/app/run.ts +++ b/cli/src/commands/resume/app/run.ts @@ -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 ?? '' diff --git a/cli/src/commands/run/app/run.ts b/cli/src/commands/run/app/run.ts index ab468678a72..c0598b0eab0 100644 --- a/cli/src/commands/run/app/run.ts +++ b/cli/src/commands/run/app/run.ts @@ -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({ diff --git a/cli/src/commands/use/workspace/use.ts b/cli/src/commands/use/workspace/use.ts index 3070a76aa3e..94f335c940e 100644 --- a/cli/src/commands/use/workspace/use.ts +++ b/cli/src/commands/use/workspace/use.ts @@ -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//switch` fails we abort before touching `hosts.yml`, so + * `/workspaces/:switch` fails we abort before touching `hosts.yml`, so * local state never diverges from the server. */ export async function runUseWorkspace( diff --git a/cli/src/http/client.test.ts b/cli/src/http/client.test.ts index ae4448843a4..fa397852c98 100644 --- a/cli/src/http/client.test.ts +++ b/cli/src/http/client.test.ts @@ -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() diff --git a/cli/src/http/error-mapper.test.ts b/cli/src/http/error-mapper.test.ts index 3244222da07..610b24fcfe4 100644 --- a/cli/src/http/error-mapper.test.ts +++ b/cli/src/http/error-mapper.test.ts @@ -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' } }, diff --git a/cli/test/e2e/suites/discovery/get-app-single.e2e.ts b/cli/test/e2e/suites/discovery/get-app-single.e2e.ts index b620eb383ef..528c08c9fa0 100644 --- a/cli/test/e2e/suites/discovery/get-app-single.e2e.ts +++ b/cli/test/e2e/suites/discovery/get-app-single.e2e.ts @@ -3,7 +3,7 @@ * * Test cases sourced from: Dify CLI Enhanced spec — Dify CLI/Discovery/Single App Query (22 cases) * - * Note: difyctl get app queries a single app via GET /apps//describe?fields=info. + * Note: difyctl get app queries a single app via GET /apps/?fields=info. * The response is returned in list-envelope format {page,limit,total,data:[...]}. */ diff --git a/cli/test/fixtures/dify-mock/server.test.ts b/cli/test/fixtures/dify-mock/server.test.ts index 7233f2a23b3..e80e20643c7 100644 --- a/cli/test/fixtures/dify-mock/server.test.ts +++ b/cli/test/fixtures/dify-mock/server.test.ts @@ -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) diff --git a/cli/test/fixtures/dify-mock/server.ts b/cli/test/fixtures/dify-mock/server.ts index 766963cd0d5..9f612669d4a 100644 --- a/cli/test/fixtures/dify-mock/server.ts +++ b/cli/test/fixtures/dify-mock/server.ts @@ -15,9 +15,9 @@ export type DifyMock = { scenario: Scenario setScenario: (s: Scenario) => void stop: () => Promise - /** Body of the most recent POST to /apps/:id/run */ + /** Body of the most recent POST to /apps/:id:run */ lastRunBody: Record | 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 | 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 @@ -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({}) }) diff --git a/packages/contracts/generated/api/openapi/orpc.gen.ts b/packages/contracts/generated/api/openapi/orpc.gen.ts index 47aa1b90d6a..b936a6b2e04 100644 --- a/packages/contracts/generated/api/openapi/orpc.gen.ts +++ b/packages/contracts/generated/api/openapi/orpc.gen.ts @@ -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 })) diff --git a/packages/contracts/generated/api/openapi/types.gen.ts b/packages/contracts/generated/api/openapi/types.gen.ts index cd422cd1f92..fd0a7545afd 100644 --- a/packages/contracts/generated/api/openapi/types.gen.ts +++ b/packages/contracts/generated/api/openapi/types.gen.ts @@ -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 = { diff --git a/packages/contracts/generated/api/openapi/zod.gen.ts b/packages/contracts/generated/api/openapi/zod.gen.ts index 70ece880a68..0594a061547 100644 --- a/packages/contracts/generated/api/openapi/zod.gen.ts +++ b/packages/contracts/generated/api/openapi/zod.gen.ts @@ -27,7 +27,7 @@ export const zAppDescribeInfo = z.object({ /** * AppDescribeQuery * - * `?fields=` allow-list for GET /apps//describe. + * `?fields=` allow-list for GET /apps/. * * Empty / omitted → all blocks. Unknown member → ValidationError → 422. */ @@ -47,7 +47,7 @@ export const zAppDescribeResponse = z.object({ /** * AppDslExportQuery * - * Query parameters for GET /apps//export. + * Query parameters for GET /apps//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//form/human_input/. `extra='forbid'` + * Empty 200 body for POST /apps//human-input-forms/: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//tasks//stop. The handler always returns + * 200 body for POST /apps//tasks/: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(), diff --git a/packages/contracts/openapi-ts.api.config.ts b/packages/contracts/openapi-ts.api.config.ts index efb1a226f1e..0fb424bcfb5 100644 --- a/packages/contracts/openapi-ts.api.config.ts +++ b/packages/contracts/openapi-ts.api.config.ts @@ -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()] }