@orthacms/copilot-server 0.0.0-reserve.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/lib/chat/application/appliers-registrar.d.ts +16 -0
- package/dist/lib/chat/application/appliers-registrar.d.ts.map +1 -0
- package/dist/lib/chat/application/appliers-registrar.js +50 -0
- package/dist/lib/chat/application/capability-profile.service.d.ts +43 -0
- package/dist/lib/chat/application/capability-profile.service.d.ts.map +1 -0
- package/dist/lib/chat/application/capability-profile.service.js +75 -0
- package/dist/lib/chat/application/content-type-summary.service.d.ts +32 -0
- package/dist/lib/chat/application/content-type-summary.service.d.ts.map +1 -0
- package/dist/lib/chat/application/content-type-summary.service.js +64 -0
- package/dist/lib/chat/application/decide-proposal.service.d.ts +80 -0
- package/dist/lib/chat/application/decide-proposal.service.d.ts.map +1 -0
- package/dist/lib/chat/application/decide-proposal.service.js +112 -0
- package/dist/lib/chat/application/dto/create-run.dto.d.ts +138 -0
- package/dist/lib/chat/application/dto/create-run.dto.d.ts.map +1 -0
- package/dist/lib/chat/application/dto/create-run.dto.js +253 -0
- package/dist/lib/chat/application/dto/decide-tool-permission.dto.d.ts +20 -0
- package/dist/lib/chat/application/dto/decide-tool-permission.dto.d.ts.map +1 -0
- package/dist/lib/chat/application/dto/decide-tool-permission.dto.js +44 -0
- package/dist/lib/chat/application/dto/extend-tool-permission.dto.d.ts +18 -0
- package/dist/lib/chat/application/dto/extend-tool-permission.dto.d.ts.map +1 -0
- package/dist/lib/chat/application/dto/extend-tool-permission.dto.js +33 -0
- package/dist/lib/chat/application/dto/list-conversations-query.dto.d.ts +18 -0
- package/dist/lib/chat/application/dto/list-conversations-query.dto.d.ts.map +1 -0
- package/dist/lib/chat/application/dto/list-conversations-query.dto.js +39 -0
- package/dist/lib/chat/application/dto/list-proposals-query.dto.d.ts +15 -0
- package/dist/lib/chat/application/dto/list-proposals-query.dto.d.ts.map +1 -0
- package/dist/lib/chat/application/dto/list-proposals-query.dto.js +41 -0
- package/dist/lib/chat/application/dto/update-conversation.dto.d.ts +68 -0
- package/dist/lib/chat/application/dto/update-conversation.dto.d.ts.map +1 -0
- package/dist/lib/chat/application/dto/update-conversation.dto.js +115 -0
- package/dist/lib/chat/application/proposal-applier.registry.d.ts +27 -0
- package/dist/lib/chat/application/proposal-applier.registry.d.ts.map +1 -0
- package/dist/lib/chat/application/proposal-applier.registry.js +62 -0
- package/dist/lib/chat/application/run-engine.service.d.ts +243 -0
- package/dist/lib/chat/application/run-engine.service.d.ts.map +1 -0
- package/dist/lib/chat/application/run-engine.service.js +1058 -0
- package/dist/lib/chat/application/summarize-tool-output.d.ts +15 -0
- package/dist/lib/chat/application/summarize-tool-output.d.ts.map +1 -0
- package/dist/lib/chat/application/summarize-tool-output.js +61 -0
- package/dist/lib/chat/application/system-prompt.d.ts +81 -0
- package/dist/lib/chat/application/system-prompt.d.ts.map +1 -0
- package/dist/lib/chat/application/system-prompt.js +477 -0
- package/dist/lib/chat/application/tool-permission.broker.d.ts +118 -0
- package/dist/lib/chat/application/tool-permission.broker.d.ts.map +1 -0
- package/dist/lib/chat/application/tool-permission.broker.js +221 -0
- package/dist/lib/chat/http/controllers/create-run.controller.d.ts +30 -0
- package/dist/lib/chat/http/controllers/create-run.controller.d.ts.map +1 -0
- package/dist/lib/chat/http/controllers/create-run.controller.js +159 -0
- package/dist/lib/chat/http/controllers/get-conversation.controller.d.ts +22 -0
- package/dist/lib/chat/http/controllers/get-conversation.controller.d.ts.map +1 -0
- package/dist/lib/chat/http/controllers/get-conversation.controller.js +50 -0
- package/dist/lib/chat/http/controllers/list-conversations.controller.d.ts +20 -0
- package/dist/lib/chat/http/controllers/list-conversations.controller.d.ts.map +1 -0
- package/dist/lib/chat/http/controllers/list-conversations.controller.js +47 -0
- package/dist/lib/chat/http/controllers/list-models.controller.d.ts +35 -0
- package/dist/lib/chat/http/controllers/list-models.controller.d.ts.map +1 -0
- package/dist/lib/chat/http/controllers/list-models.controller.js +48 -0
- package/dist/lib/chat/http/controllers/proposals.controller.d.ts +43 -0
- package/dist/lib/chat/http/controllers/proposals.controller.d.ts.map +1 -0
- package/dist/lib/chat/http/controllers/proposals.controller.js +98 -0
- package/dist/lib/chat/http/controllers/tool-permission.controller.d.ts +57 -0
- package/dist/lib/chat/http/controllers/tool-permission.controller.d.ts.map +1 -0
- package/dist/lib/chat/http/controllers/tool-permission.controller.js +116 -0
- package/dist/lib/chat/http/controllers/update-conversation.controller.d.ts +37 -0
- package/dist/lib/chat/http/controllers/update-conversation.controller.d.ts.map +1 -0
- package/dist/lib/chat/http/controllers/update-conversation.controller.js +108 -0
- package/dist/lib/chat/http/sse-stream.d.ts +41 -0
- package/dist/lib/chat/http/sse-stream.d.ts.map +1 -0
- package/dist/lib/chat/http/sse-stream.js +95 -0
- package/dist/lib/chat/infrastructure/persistence/conversation.repository.d.ts +159 -0
- package/dist/lib/chat/infrastructure/persistence/conversation.repository.d.ts.map +1 -0
- package/dist/lib/chat/infrastructure/persistence/conversation.repository.js +249 -0
- package/dist/lib/chat/infrastructure/persistence/derive-title.d.ts +13 -0
- package/dist/lib/chat/infrastructure/persistence/derive-title.d.ts.map +1 -0
- package/dist/lib/chat/infrastructure/persistence/derive-title.js +35 -0
- package/dist/lib/chat/infrastructure/persistence/proposal.repository.d.ts +86 -0
- package/dist/lib/chat/infrastructure/persistence/proposal.repository.d.ts.map +1 -0
- package/dist/lib/chat/infrastructure/persistence/proposal.repository.js +152 -0
- package/dist/lib/chat/infrastructure/schema/conversations.d.ts +189 -0
- package/dist/lib/chat/infrastructure/schema/conversations.d.ts.map +1 -0
- package/dist/lib/chat/infrastructure/schema/conversations.js +100 -0
- package/dist/lib/chat/infrastructure/schema/external-refs.d.ts +67 -0
- package/dist/lib/chat/infrastructure/schema/external-refs.d.ts.map +1 -0
- package/dist/lib/chat/infrastructure/schema/external-refs.js +28 -0
- package/dist/lib/chat/infrastructure/schema/index.d.ts +5 -0
- package/dist/lib/chat/infrastructure/schema/index.d.ts.map +1 -0
- package/dist/lib/chat/infrastructure/schema/index.js +12 -0
- package/dist/lib/chat/infrastructure/schema/messages.d.ts +264 -0
- package/dist/lib/chat/infrastructure/schema/messages.d.ts.map +1 -0
- package/dist/lib/chat/infrastructure/schema/messages.js +96 -0
- package/dist/lib/chat/infrastructure/schema/proposals.d.ts +328 -0
- package/dist/lib/chat/infrastructure/schema/proposals.d.ts.map +1 -0
- package/dist/lib/chat/infrastructure/schema/proposals.js +87 -0
- package/dist/lib/chat/infrastructure/schema/tool-calls.d.ts +203 -0
- package/dist/lib/chat/infrastructure/schema/tool-calls.d.ts.map +1 -0
- package/dist/lib/chat/infrastructure/schema/tool-calls.js +50 -0
- package/dist/lib/copilot.module.d.ts +30 -0
- package/dist/lib/copilot.module.d.ts.map +1 -0
- package/dist/lib/copilot.module.js +146 -0
- package/dist/lib/copilot.tokens.d.ts +34 -0
- package/dist/lib/copilot.tokens.d.ts.map +1 -0
- package/dist/lib/copilot.tokens.js +38 -0
- package/dist/lib/infrastructure/model-registry.d.ts +22 -0
- package/dist/lib/infrastructure/model-registry.d.ts.map +1 -0
- package/dist/lib/infrastructure/model-registry.js +54 -0
- package/dist/lib/skills/application/dto/create-skill.dto.d.ts +36 -0
- package/dist/lib/skills/application/dto/create-skill.dto.d.ts.map +1 -0
- package/dist/lib/skills/application/dto/create-skill.dto.js +106 -0
- package/dist/lib/skills/application/dto/update-skill.dto.d.ts +28 -0
- package/dist/lib/skills/application/dto/update-skill.dto.d.ts.map +1 -0
- package/dist/lib/skills/application/dto/update-skill.dto.js +96 -0
- package/dist/lib/skills/application/skill-catalog.service.d.ts +97 -0
- package/dist/lib/skills/application/skill-catalog.service.d.ts.map +1 -0
- package/dist/lib/skills/application/skill-catalog.service.js +157 -0
- package/dist/lib/skills/http/controllers/list-skills.controller.d.ts +19 -0
- package/dist/lib/skills/http/controllers/list-skills.controller.d.ts.map +1 -0
- package/dist/lib/skills/http/controllers/list-skills.controller.js +50 -0
- package/dist/lib/skills/http/controllers/manage-skills.controller.d.ts +64 -0
- package/dist/lib/skills/http/controllers/manage-skills.controller.d.ts.map +1 -0
- package/dist/lib/skills/http/controllers/manage-skills.controller.js +182 -0
- package/dist/lib/skills/infrastructure/persistence/skill.repository.d.ts +78 -0
- package/dist/lib/skills/infrastructure/persistence/skill.repository.d.ts.map +1 -0
- package/dist/lib/skills/infrastructure/persistence/skill.repository.js +116 -0
- package/dist/lib/skills/infrastructure/schema/index.d.ts +2 -0
- package/dist/lib/skills/infrastructure/schema/index.d.ts.map +1 -0
- package/dist/lib/skills/infrastructure/schema/index.js +6 -0
- package/dist/lib/skills/infrastructure/schema/skills.d.ts +212 -0
- package/dist/lib/skills/infrastructure/schema/skills.d.ts.map +1 -0
- package/dist/lib/skills/infrastructure/schema/skills.js +78 -0
- package/dist/lib/types/copilot-config.d.ts +55 -0
- package/dist/lib/types/copilot-config.d.ts.map +1 -0
- package/dist/lib/types/copilot-config.js +2 -0
- package/dist/lib/utils/copilot-plugin.d.ts +66 -0
- package/dist/lib/utils/copilot-plugin.d.ts.map +1 -0
- package/dist/lib/utils/copilot-plugin.js +87 -0
- package/migrations/0000_copilot_chat.sql +48 -0
- package/migrations/0001_proposals.sql +34 -0
- package/migrations/0002_drop_workspace_policies.sql +1 -0
- package/migrations/0003_conversation_allowed_tools.sql +1 -0
- package/migrations/0004_message_attachments.sql +1 -0
- package/migrations/0005_skills.sql +19 -0
- package/migrations/0006_conversation_model_choice.sql +1 -0
- package/migrations/meta/0000_snapshot.json +390 -0
- package/migrations/meta/0001_snapshot.json +657 -0
- package/migrations/meta/0002_snapshot.json +610 -0
- package/migrations/meta/0003_snapshot.json +617 -0
- package/migrations/meta/0004_snapshot.json +623 -0
- package/migrations/meta/0005_snapshot.json +771 -0
- package/migrations/meta/0006_snapshot.json +777 -0
- package/migrations/meta/_journal.json +55 -0
- package/package.json +46 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ListConversationsController = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
7
|
+
const identity_server_1 = require("@orthacms/identity-server");
|
|
8
|
+
const workspaces_server_1 = require("@orthacms/workspaces-server");
|
|
9
|
+
const conversation_repository_1 = require("../../infrastructure/persistence/conversation.repository");
|
|
10
|
+
const list_conversations_query_dto_1 = require("../../application/dto/list-conversations-query.dto");
|
|
11
|
+
/**
|
|
12
|
+
* `GET /api/copilot/conversations` — the signed-in user's threads in the open
|
|
13
|
+
* workspace, most recently used first.
|
|
14
|
+
*
|
|
15
|
+
* No `OriginGuard`: it guards state-changing requests, and this reads. The
|
|
16
|
+
* scoping that matters is in the repository, which filters on user **and**
|
|
17
|
+
* workspace — the guard proves the caller belongs to the workspace, and the
|
|
18
|
+
* query proves the threads belong to the caller.
|
|
19
|
+
*/
|
|
20
|
+
let ListConversationsController = class ListConversationsController {
|
|
21
|
+
conversations;
|
|
22
|
+
constructor(conversations) {
|
|
23
|
+
this.conversations = conversations;
|
|
24
|
+
}
|
|
25
|
+
async list(query, user, workspaceId) {
|
|
26
|
+
return {
|
|
27
|
+
items: await this.conversations.list(user.id, workspaceId, query.archived ?? false)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
exports.ListConversationsController = ListConversationsController;
|
|
32
|
+
tslib_1.__decorate([
|
|
33
|
+
(0, common_1.Get)('conversations'),
|
|
34
|
+
(0, swagger_1.ApiOperation)({ summary: 'List the current user’s copilot conversations' }),
|
|
35
|
+
tslib_1.__param(0, (0, common_1.Query)()),
|
|
36
|
+
tslib_1.__param(1, (0, identity_server_1.CurrentUser)()),
|
|
37
|
+
tslib_1.__param(2, (0, workspaces_server_1.CurrentWorkspace)()),
|
|
38
|
+
tslib_1.__metadata("design:type", Function),
|
|
39
|
+
tslib_1.__metadata("design:paramtypes", [list_conversations_query_dto_1.ListConversationsQueryDto, Object, String]),
|
|
40
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
41
|
+
], ListConversationsController.prototype, "list", null);
|
|
42
|
+
exports.ListConversationsController = ListConversationsController = tslib_1.__decorate([
|
|
43
|
+
(0, common_1.UseGuards)(identity_server_1.PermissionsGuard, workspaces_server_1.WorkspaceGuard),
|
|
44
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.COPILOT_USE),
|
|
45
|
+
(0, common_1.Controller)('copilot'),
|
|
46
|
+
tslib_1.__metadata("design:paramtypes", [conversation_repository_1.ConversationRepository])
|
|
47
|
+
], ListConversationsController);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ModelChoice, type ModelRegistry } from '@orthacms/copilot-domain';
|
|
2
|
+
/** What the model picker renders. */
|
|
3
|
+
export interface ModelCatalogue {
|
|
4
|
+
/**
|
|
5
|
+
* Every provider × model pair on offer, in registration order.
|
|
6
|
+
*
|
|
7
|
+
* The order carries the only default there is: `items[0]` is the first
|
|
8
|
+
* registered provider's first model, which is what a run that names
|
|
9
|
+
* neither gets, and what the picker opens on. There is no separate
|
|
10
|
+
* `defaultProvider` field for a client to reconcile with this list.
|
|
11
|
+
*/
|
|
12
|
+
items: ModelChoice[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* `GET /api/copilot/models` — the backends a run may choose from.
|
|
16
|
+
*
|
|
17
|
+
* This is `ModelRegistry.catalogue()` served verbatim, which is what phase 0
|
|
18
|
+
* built it for: a user switches model mid-conversation by picking another row,
|
|
19
|
+
* and an operator changes what is on offer by changing the provider list in
|
|
20
|
+
* `plugins.ts` — the list is the whole configuration. The registry is
|
|
21
|
+
* fixed at boot from `plugins.ts`, so this leaks nothing an operator did not
|
|
22
|
+
* already choose to offer — **names only**, never a credential or a base URL.
|
|
23
|
+
*
|
|
24
|
+
* No `WorkspaceGuard`: the catalogue is deployment-wide, not workspace-owned,
|
|
25
|
+
* and the picker renders before a workspace is necessarily resolved. Gated on
|
|
26
|
+
* `copilot:use` like the rest of the surface. (Registering models at runtime,
|
|
27
|
+
* with encrypted credentials, is the separate phase-4 surface behind
|
|
28
|
+
* `copilot:configure`.)
|
|
29
|
+
*/
|
|
30
|
+
export declare class ListModelsController {
|
|
31
|
+
private readonly registry;
|
|
32
|
+
constructor(registry: ModelRegistry);
|
|
33
|
+
list(): ModelCatalogue;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=list-models.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-models.controller.d.ts","sourceRoot":"","sources":["../../../../../src/lib/chat/http/controllers/list-models.controller.ts"],"names":[],"mappings":"AAEA,OAAO,EAEH,KAAK,WAAW,EAChB,KAAK,aAAa,EACrB,MAAM,0BAA0B,CAAC;AAOlC,qCAAqC;AACrC,MAAM,WAAW,cAAc;IAC3B;;;;;;;OAOG;IACH,KAAK,EAAE,WAAW,EAAE,CAAC;CACxB;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAGa,oBAAoB;IAED,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,aAAa;IAKpE,IAAI,IAAI,cAAc;CAGzB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ListModelsController = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
7
|
+
const copilot_domain_1 = require("@orthacms/copilot-domain");
|
|
8
|
+
const identity_server_1 = require("@orthacms/identity-server");
|
|
9
|
+
/**
|
|
10
|
+
* `GET /api/copilot/models` — the backends a run may choose from.
|
|
11
|
+
*
|
|
12
|
+
* This is `ModelRegistry.catalogue()` served verbatim, which is what phase 0
|
|
13
|
+
* built it for: a user switches model mid-conversation by picking another row,
|
|
14
|
+
* and an operator changes what is on offer by changing the provider list in
|
|
15
|
+
* `plugins.ts` — the list is the whole configuration. The registry is
|
|
16
|
+
* fixed at boot from `plugins.ts`, so this leaks nothing an operator did not
|
|
17
|
+
* already choose to offer — **names only**, never a credential or a base URL.
|
|
18
|
+
*
|
|
19
|
+
* No `WorkspaceGuard`: the catalogue is deployment-wide, not workspace-owned,
|
|
20
|
+
* and the picker renders before a workspace is necessarily resolved. Gated on
|
|
21
|
+
* `copilot:use` like the rest of the surface. (Registering models at runtime,
|
|
22
|
+
* with encrypted credentials, is the separate phase-4 surface behind
|
|
23
|
+
* `copilot:configure`.)
|
|
24
|
+
*/
|
|
25
|
+
let ListModelsController = class ListModelsController {
|
|
26
|
+
registry;
|
|
27
|
+
constructor(registry) {
|
|
28
|
+
this.registry = registry;
|
|
29
|
+
}
|
|
30
|
+
list() {
|
|
31
|
+
return { items: this.registry.catalogue() };
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
exports.ListModelsController = ListModelsController;
|
|
35
|
+
tslib_1.__decorate([
|
|
36
|
+
(0, common_1.Get)('models'),
|
|
37
|
+
(0, swagger_1.ApiOperation)({ summary: 'List the model backends a run may choose from' }),
|
|
38
|
+
tslib_1.__metadata("design:type", Function),
|
|
39
|
+
tslib_1.__metadata("design:paramtypes", []),
|
|
40
|
+
tslib_1.__metadata("design:returntype", Object)
|
|
41
|
+
], ListModelsController.prototype, "list", null);
|
|
42
|
+
exports.ListModelsController = ListModelsController = tslib_1.__decorate([
|
|
43
|
+
(0, common_1.UseGuards)(identity_server_1.PermissionsGuard),
|
|
44
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.COPILOT_USE),
|
|
45
|
+
(0, common_1.Controller)('copilot'),
|
|
46
|
+
tslib_1.__param(0, (0, common_1.Inject)(copilot_domain_1.MODEL_REGISTRY)),
|
|
47
|
+
tslib_1.__metadata("design:paramtypes", [Object])
|
|
48
|
+
], ListModelsController);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type PublicUser } from '@orthacms/identity-server';
|
|
2
|
+
import { ProposalRepository, type ProposalView } from '../../infrastructure/persistence/proposal.repository';
|
|
3
|
+
import { ConversationRepository } from '../../infrastructure/persistence/conversation.repository';
|
|
4
|
+
import { ListProposalsQueryDto } from '../../application/dto/list-proposals-query.dto';
|
|
5
|
+
/**
|
|
6
|
+
* `GET /api/copilot/proposals…` — the record of what the copilot changed.
|
|
7
|
+
*
|
|
8
|
+
* **Reads only.** These used to sit alongside `accept` and `reject`, and this
|
|
9
|
+
* was the accept boundary
|
|
10
|
+
* ([ADR-0005](../../../../../../docs/adr/0005-copilot-authority-model.md) §5).
|
|
11
|
+
* [ADR-0009](../../../../../../docs/adr/0009-copilot-applies-directly.md)
|
|
12
|
+
* removed the human step, so a proposal is now a **receipt** rather than a
|
|
13
|
+
* decision waiting to be made: the engine applies it as it is drafted, and what
|
|
14
|
+
* is left to serve is the row saying what happened.
|
|
15
|
+
*
|
|
16
|
+
* They are still worth serving, and both callers prove it: the chat panel joins
|
|
17
|
+
* them onto a reopened thread so the cards come back, and the row is the paper
|
|
18
|
+
* trail a change made without review depends on. Keeping the read while
|
|
19
|
+
* deleting the write is the point — "undoable, never invisible" is now carried
|
|
20
|
+
* entirely by this record.
|
|
21
|
+
*
|
|
22
|
+
* No `OriginGuard`: nothing here is state-changing any more.
|
|
23
|
+
*/
|
|
24
|
+
export declare class ProposalsController {
|
|
25
|
+
private readonly proposals;
|
|
26
|
+
private readonly conversations;
|
|
27
|
+
constructor(proposals: ProposalRepository, conversations: ConversationRepository);
|
|
28
|
+
/**
|
|
29
|
+
* The list is **workspace**-scoped, not user-scoped, and that is the right
|
|
30
|
+
* shape: a proposal is the receipt for a change to the workspace's content,
|
|
31
|
+
* and every member can already read that content. What it must not become
|
|
32
|
+
* is a way to learn about someone else's *thread* — `GET
|
|
33
|
+
* /conversations/:id` 404s a thread that is not yours, and a filter that
|
|
34
|
+
* answered "yes, that id is in this workspace" would undo it from the other
|
|
35
|
+
* side. So the filter is only honoured for a conversation the caller owns;
|
|
36
|
+
* anything else is the same 404 the conversation route gives.
|
|
37
|
+
*/
|
|
38
|
+
list(query: ListProposalsQueryDto, user: PublicUser, workspaceId: string): Promise<{
|
|
39
|
+
items: ProposalView[];
|
|
40
|
+
}>;
|
|
41
|
+
get(id: string, workspaceId: string): Promise<ProposalView>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=proposals.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proposals.controller.d.ts","sourceRoot":"","sources":["../../../../../src/lib/chat/http/controllers/proposals.controller.ts"],"names":[],"mappings":"AAUA,OAAO,EAKH,KAAK,UAAU,EAClB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACH,kBAAkB,EAClB,KAAK,YAAY,EACpB,MAAM,sDAAsD,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0DAA0D,CAAC;AAClG,OAAO,EAAE,qBAAqB,EAAE,MAAM,gDAAgD,CAAC;AAEvF;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAGa,mBAAmB;IAExB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,aAAa;gBADb,SAAS,EAAE,kBAAkB,EAC7B,aAAa,EAAE,sBAAsB;IAG1D;;;;;;;;;OASG;IAGG,IAAI,CACG,KAAK,EAAE,qBAAqB,EACtB,IAAI,EAAE,UAAU,EACX,WAAW,EAAE,MAAM,GACxC,OAAO,CAAC;QAAE,KAAK,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;IAyB/B,GAAG,CACuB,EAAE,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GACxC,OAAO,CAAC,YAAY,CAAC;CAO3B"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProposalsController = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
7
|
+
const identity_server_1 = require("@orthacms/identity-server");
|
|
8
|
+
const workspaces_server_1 = require("@orthacms/workspaces-server");
|
|
9
|
+
const proposal_repository_1 = require("../../infrastructure/persistence/proposal.repository");
|
|
10
|
+
const conversation_repository_1 = require("../../infrastructure/persistence/conversation.repository");
|
|
11
|
+
const list_proposals_query_dto_1 = require("../../application/dto/list-proposals-query.dto");
|
|
12
|
+
/**
|
|
13
|
+
* `GET /api/copilot/proposals…` — the record of what the copilot changed.
|
|
14
|
+
*
|
|
15
|
+
* **Reads only.** These used to sit alongside `accept` and `reject`, and this
|
|
16
|
+
* was the accept boundary
|
|
17
|
+
* ([ADR-0005](../../../../../../docs/adr/0005-copilot-authority-model.md) §5).
|
|
18
|
+
* [ADR-0009](../../../../../../docs/adr/0009-copilot-applies-directly.md)
|
|
19
|
+
* removed the human step, so a proposal is now a **receipt** rather than a
|
|
20
|
+
* decision waiting to be made: the engine applies it as it is drafted, and what
|
|
21
|
+
* is left to serve is the row saying what happened.
|
|
22
|
+
*
|
|
23
|
+
* They are still worth serving, and both callers prove it: the chat panel joins
|
|
24
|
+
* them onto a reopened thread so the cards come back, and the row is the paper
|
|
25
|
+
* trail a change made without review depends on. Keeping the read while
|
|
26
|
+
* deleting the write is the point — "undoable, never invisible" is now carried
|
|
27
|
+
* entirely by this record.
|
|
28
|
+
*
|
|
29
|
+
* No `OriginGuard`: nothing here is state-changing any more.
|
|
30
|
+
*/
|
|
31
|
+
let ProposalsController = class ProposalsController {
|
|
32
|
+
proposals;
|
|
33
|
+
conversations;
|
|
34
|
+
constructor(proposals, conversations) {
|
|
35
|
+
this.proposals = proposals;
|
|
36
|
+
this.conversations = conversations;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The list is **workspace**-scoped, not user-scoped, and that is the right
|
|
40
|
+
* shape: a proposal is the receipt for a change to the workspace's content,
|
|
41
|
+
* and every member can already read that content. What it must not become
|
|
42
|
+
* is a way to learn about someone else's *thread* — `GET
|
|
43
|
+
* /conversations/:id` 404s a thread that is not yours, and a filter that
|
|
44
|
+
* answered "yes, that id is in this workspace" would undo it from the other
|
|
45
|
+
* side. So the filter is only honoured for a conversation the caller owns;
|
|
46
|
+
* anything else is the same 404 the conversation route gives.
|
|
47
|
+
*/
|
|
48
|
+
async list(query, user, workspaceId) {
|
|
49
|
+
if (query.conversationId) {
|
|
50
|
+
const thread = await this.conversations.find(query.conversationId, user.id, workspaceId);
|
|
51
|
+
if (!thread) {
|
|
52
|
+
throw new common_1.NotFoundException(`No conversation "${query.conversationId}".`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
items: await this.proposals.list(workspaceId, {
|
|
57
|
+
...(query.status ? { status: query.status } : {}),
|
|
58
|
+
...(query.conversationId
|
|
59
|
+
? { conversationId: query.conversationId }
|
|
60
|
+
: {})
|
|
61
|
+
})
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async get(id, workspaceId) {
|
|
65
|
+
const proposal = await this.proposals.find(id, workspaceId);
|
|
66
|
+
if (!proposal) {
|
|
67
|
+
throw new common_1.NotFoundException(`No proposal "${id}".`);
|
|
68
|
+
}
|
|
69
|
+
return proposal;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
exports.ProposalsController = ProposalsController;
|
|
73
|
+
tslib_1.__decorate([
|
|
74
|
+
(0, common_1.Get)('proposals'),
|
|
75
|
+
(0, swagger_1.ApiOperation)({ summary: 'List the workspace’s copilot changes' }),
|
|
76
|
+
tslib_1.__param(0, (0, common_1.Query)()),
|
|
77
|
+
tslib_1.__param(1, (0, identity_server_1.CurrentUser)()),
|
|
78
|
+
tslib_1.__param(2, (0, workspaces_server_1.CurrentWorkspace)()),
|
|
79
|
+
tslib_1.__metadata("design:type", Function),
|
|
80
|
+
tslib_1.__metadata("design:paramtypes", [list_proposals_query_dto_1.ListProposalsQueryDto, Object, String]),
|
|
81
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
82
|
+
], ProposalsController.prototype, "list", null);
|
|
83
|
+
tslib_1.__decorate([
|
|
84
|
+
(0, common_1.Get)('proposals/:id'),
|
|
85
|
+
(0, swagger_1.ApiOperation)({ summary: 'Read one copilot change' }),
|
|
86
|
+
tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseUUIDPipe)),
|
|
87
|
+
tslib_1.__param(1, (0, workspaces_server_1.CurrentWorkspace)()),
|
|
88
|
+
tslib_1.__metadata("design:type", Function),
|
|
89
|
+
tslib_1.__metadata("design:paramtypes", [String, String]),
|
|
90
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
91
|
+
], ProposalsController.prototype, "get", null);
|
|
92
|
+
exports.ProposalsController = ProposalsController = tslib_1.__decorate([
|
|
93
|
+
(0, common_1.UseGuards)(identity_server_1.PermissionsGuard, workspaces_server_1.WorkspaceGuard),
|
|
94
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.COPILOT_USE),
|
|
95
|
+
(0, common_1.Controller)('copilot'),
|
|
96
|
+
tslib_1.__metadata("design:paramtypes", [proposal_repository_1.ProposalRepository,
|
|
97
|
+
conversation_repository_1.ConversationRepository])
|
|
98
|
+
], ProposalsController);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type PublicUser } from '@orthacms/identity-server';
|
|
2
|
+
import { ToolPermissionBroker } from '../../application/tool-permission.broker';
|
|
3
|
+
import { DecideToolPermissionDto } from '../../application/dto/decide-tool-permission.dto';
|
|
4
|
+
import { ExtendToolPermissionDto } from '../../application/dto/extend-tool-permission.dto';
|
|
5
|
+
/**
|
|
6
|
+
* `POST /api/copilot/runs/:runId/permission` — the answer to a parked run.
|
|
7
|
+
*
|
|
8
|
+
* **A second request against a run that is still streaming**, which is the only
|
|
9
|
+
* shape available: the run is an SSE response, and a response cannot be asked
|
|
10
|
+
* questions. The run yields a `tool-permission-request` carrying its `runId`
|
|
11
|
+
* and the call id, and this route resolves the promise it is awaiting.
|
|
12
|
+
*
|
|
13
|
+
* It intentionally does **not** re-check whether the caller may use the tool.
|
|
14
|
+
* That is not a gap: the offer filtered it, and `executeTool` re-authorizes
|
|
15
|
+
* against freshly resolved grants immediately after this returns. What this
|
|
16
|
+
* route decides is whether the user *wants* the call to happen, which is a
|
|
17
|
+
* different question from whether they are allowed to make it — and answering
|
|
18
|
+
* "yes" to a tool their role has since lost still ends in a refusal downstream.
|
|
19
|
+
*
|
|
20
|
+
* It very much **does** check whose run it is, and the guards above cannot do
|
|
21
|
+
* it for us. `copilot:use` is held by every role including viewers, and
|
|
22
|
+
* `WorkspaceGuard` proves the caller belongs to *the workspace they named* —
|
|
23
|
+
* neither says anything about the run. Without the owner check a colleague
|
|
24
|
+
* could allow (or refuse) a write in someone else's chat, and a member of an
|
|
25
|
+
* unrelated workspace could do it by naming their own: the `runId` is not a
|
|
26
|
+
* secret, it is handed to the client in the `run-started` frame.
|
|
27
|
+
*
|
|
28
|
+
* The 404 is load-bearing rather than cosmetic, and it is deliberately the same
|
|
29
|
+
* answer for "nothing is waiting" and "not yours". Nothing waiting means the
|
|
30
|
+
* decision arrived after the timeout, or the run is parked on another instance
|
|
31
|
+
* (the broker is in-memory — see its notes). Both are worth telling the client
|
|
32
|
+
* about, because in both cases the answer it just gave had no effect — and
|
|
33
|
+
* whether someone else's run exists is not information this route hands out.
|
|
34
|
+
*/
|
|
35
|
+
export declare class ToolPermissionController {
|
|
36
|
+
private readonly broker;
|
|
37
|
+
constructor(broker: ToolPermissionBroker);
|
|
38
|
+
decide(runId: string, body: DecideToolPermissionDto, user: PublicUser, workspaceId: string): void;
|
|
39
|
+
/**
|
|
40
|
+
* `POST /api/copilot/runs/:runId/permission/extend` — "I need more time".
|
|
41
|
+
*
|
|
42
|
+
* The prompt used to have a five-minute limit with no warning, no countdown
|
|
43
|
+
* and no way to ask for longer: it simply disappeared, and the model
|
|
44
|
+
* reported that nobody had answered. WCAG 2.2.1 requires that a time limit
|
|
45
|
+
* set by the content can be turned off, adjusted, or **extended after a
|
|
46
|
+
* warning** — and none of its exceptions apply here, since
|
|
47
|
+
* [ADR-0009](../../../../../../docs/adr/0009-copilot-applies-directly.md)
|
|
48
|
+
* justifies the limit as cost, not correctness (`ORT-118`).
|
|
49
|
+
*
|
|
50
|
+
* Same guards, same ownership check and same 404 as {@link decide}: this
|
|
51
|
+
* grants time on a parked run, so who may ask is exactly who may answer.
|
|
52
|
+
*/
|
|
53
|
+
extend(runId: string, body: ExtendToolPermissionDto, user: PublicUser, workspaceId: string): {
|
|
54
|
+
expiresAt: string;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=tool-permission.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-permission.controller.d.ts","sourceRoot":"","sources":["../../../../../src/lib/chat/http/controllers/tool-permission.controller.ts"],"names":[],"mappings":"AAWA,OAAO,EAMH,KAAK,UAAU,EAClB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,kDAAkD,CAAC;AAC3F,OAAO,EAAE,uBAAuB,EAAE,MAAM,kDAAkD,CAAC;AAE3F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAGa,wBAAwB;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,oBAAoB;IAQzD,MAAM,CAC6B,KAAK,EAAE,MAAM,EACpC,IAAI,EAAE,uBAAuB,EACtB,IAAI,EAAE,UAAU,EACX,WAAW,EAAE,MAAM,GACxC,IAAI;IAiBP;;;;;;;;;;;;;OAaG;IAMH,MAAM,CAC6B,KAAK,EAAE,MAAM,EACpC,IAAI,EAAE,uBAAuB,EACtB,IAAI,EAAE,UAAU,EACX,WAAW,EAAE,MAAM,GACxC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE;CAY3B"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ToolPermissionController = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
7
|
+
const identity_server_1 = require("@orthacms/identity-server");
|
|
8
|
+
const workspaces_server_1 = require("@orthacms/workspaces-server");
|
|
9
|
+
const tool_permission_broker_1 = require("../../application/tool-permission.broker");
|
|
10
|
+
const decide_tool_permission_dto_1 = require("../../application/dto/decide-tool-permission.dto");
|
|
11
|
+
const extend_tool_permission_dto_1 = require("../../application/dto/extend-tool-permission.dto");
|
|
12
|
+
/**
|
|
13
|
+
* `POST /api/copilot/runs/:runId/permission` — the answer to a parked run.
|
|
14
|
+
*
|
|
15
|
+
* **A second request against a run that is still streaming**, which is the only
|
|
16
|
+
* shape available: the run is an SSE response, and a response cannot be asked
|
|
17
|
+
* questions. The run yields a `tool-permission-request` carrying its `runId`
|
|
18
|
+
* and the call id, and this route resolves the promise it is awaiting.
|
|
19
|
+
*
|
|
20
|
+
* It intentionally does **not** re-check whether the caller may use the tool.
|
|
21
|
+
* That is not a gap: the offer filtered it, and `executeTool` re-authorizes
|
|
22
|
+
* against freshly resolved grants immediately after this returns. What this
|
|
23
|
+
* route decides is whether the user *wants* the call to happen, which is a
|
|
24
|
+
* different question from whether they are allowed to make it — and answering
|
|
25
|
+
* "yes" to a tool their role has since lost still ends in a refusal downstream.
|
|
26
|
+
*
|
|
27
|
+
* It very much **does** check whose run it is, and the guards above cannot do
|
|
28
|
+
* it for us. `copilot:use` is held by every role including viewers, and
|
|
29
|
+
* `WorkspaceGuard` proves the caller belongs to *the workspace they named* —
|
|
30
|
+
* neither says anything about the run. Without the owner check a colleague
|
|
31
|
+
* could allow (or refuse) a write in someone else's chat, and a member of an
|
|
32
|
+
* unrelated workspace could do it by naming their own: the `runId` is not a
|
|
33
|
+
* secret, it is handed to the client in the `run-started` frame.
|
|
34
|
+
*
|
|
35
|
+
* The 404 is load-bearing rather than cosmetic, and it is deliberately the same
|
|
36
|
+
* answer for "nothing is waiting" and "not yours". Nothing waiting means the
|
|
37
|
+
* decision arrived after the timeout, or the run is parked on another instance
|
|
38
|
+
* (the broker is in-memory — see its notes). Both are worth telling the client
|
|
39
|
+
* about, because in both cases the answer it just gave had no effect — and
|
|
40
|
+
* whether someone else's run exists is not information this route hands out.
|
|
41
|
+
*/
|
|
42
|
+
let ToolPermissionController = class ToolPermissionController {
|
|
43
|
+
broker;
|
|
44
|
+
constructor(broker) {
|
|
45
|
+
this.broker = broker;
|
|
46
|
+
}
|
|
47
|
+
decide(runId, body, user, workspaceId) {
|
|
48
|
+
const delivered = this.broker.decide(runId, body.callId, body.decision, {
|
|
49
|
+
userId: user.id,
|
|
50
|
+
workspaceId
|
|
51
|
+
});
|
|
52
|
+
if (!delivered) {
|
|
53
|
+
throw new common_1.NotFoundException('That request is no longer waiting for an answer.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* `POST /api/copilot/runs/:runId/permission/extend` — "I need more time".
|
|
58
|
+
*
|
|
59
|
+
* The prompt used to have a five-minute limit with no warning, no countdown
|
|
60
|
+
* and no way to ask for longer: it simply disappeared, and the model
|
|
61
|
+
* reported that nobody had answered. WCAG 2.2.1 requires that a time limit
|
|
62
|
+
* set by the content can be turned off, adjusted, or **extended after a
|
|
63
|
+
* warning** — and none of its exceptions apply here, since
|
|
64
|
+
* [ADR-0009](../../../../../../docs/adr/0009-copilot-applies-directly.md)
|
|
65
|
+
* justifies the limit as cost, not correctness (`ORT-118`).
|
|
66
|
+
*
|
|
67
|
+
* Same guards, same ownership check and same 404 as {@link decide}: this
|
|
68
|
+
* grants time on a parked run, so who may ask is exactly who may answer.
|
|
69
|
+
*/
|
|
70
|
+
extend(runId, body, user, workspaceId) {
|
|
71
|
+
const grantedMs = this.broker.extend(runId, body.callId, {
|
|
72
|
+
userId: user.id,
|
|
73
|
+
workspaceId
|
|
74
|
+
});
|
|
75
|
+
if (grantedMs === null) {
|
|
76
|
+
throw new common_1.NotFoundException('That request is no longer waiting for an answer.');
|
|
77
|
+
}
|
|
78
|
+
return { expiresAt: new Date(Date.now() + grantedMs).toISOString() };
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
exports.ToolPermissionController = ToolPermissionController;
|
|
82
|
+
tslib_1.__decorate([
|
|
83
|
+
(0, common_1.Post)('runs/:runId/permission'),
|
|
84
|
+
(0, common_1.HttpCode)(204),
|
|
85
|
+
(0, common_1.UseGuards)(identity_server_1.OriginGuard),
|
|
86
|
+
(0, swagger_1.ApiOperation)({
|
|
87
|
+
summary: 'Allow or refuse one tool call in a running turn'
|
|
88
|
+
}),
|
|
89
|
+
tslib_1.__param(0, (0, common_1.Param)('runId', common_1.ParseUUIDPipe)),
|
|
90
|
+
tslib_1.__param(1, (0, common_1.Body)()),
|
|
91
|
+
tslib_1.__param(2, (0, identity_server_1.CurrentUser)()),
|
|
92
|
+
tslib_1.__param(3, (0, workspaces_server_1.CurrentWorkspace)()),
|
|
93
|
+
tslib_1.__metadata("design:type", Function),
|
|
94
|
+
tslib_1.__metadata("design:paramtypes", [String, decide_tool_permission_dto_1.DecideToolPermissionDto, Object, String]),
|
|
95
|
+
tslib_1.__metadata("design:returntype", void 0)
|
|
96
|
+
], ToolPermissionController.prototype, "decide", null);
|
|
97
|
+
tslib_1.__decorate([
|
|
98
|
+
(0, common_1.Post)('runs/:runId/permission/extend'),
|
|
99
|
+
(0, common_1.UseGuards)(identity_server_1.OriginGuard),
|
|
100
|
+
(0, swagger_1.ApiOperation)({
|
|
101
|
+
summary: 'Give the user more time to answer a parked tool call'
|
|
102
|
+
}),
|
|
103
|
+
tslib_1.__param(0, (0, common_1.Param)('runId', common_1.ParseUUIDPipe)),
|
|
104
|
+
tslib_1.__param(1, (0, common_1.Body)()),
|
|
105
|
+
tslib_1.__param(2, (0, identity_server_1.CurrentUser)()),
|
|
106
|
+
tslib_1.__param(3, (0, workspaces_server_1.CurrentWorkspace)()),
|
|
107
|
+
tslib_1.__metadata("design:type", Function),
|
|
108
|
+
tslib_1.__metadata("design:paramtypes", [String, extend_tool_permission_dto_1.ExtendToolPermissionDto, Object, String]),
|
|
109
|
+
tslib_1.__metadata("design:returntype", Object)
|
|
110
|
+
], ToolPermissionController.prototype, "extend", null);
|
|
111
|
+
exports.ToolPermissionController = ToolPermissionController = tslib_1.__decorate([
|
|
112
|
+
(0, common_1.UseGuards)(identity_server_1.PermissionsGuard, workspaces_server_1.WorkspaceGuard),
|
|
113
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.COPILOT_USE),
|
|
114
|
+
(0, common_1.Controller)('copilot'),
|
|
115
|
+
tslib_1.__metadata("design:paramtypes", [tool_permission_broker_1.ToolPermissionBroker])
|
|
116
|
+
], ToolPermissionController);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ModelRegistry } from '@orthacms/copilot-domain';
|
|
2
|
+
import { type PublicUser } from '@orthacms/identity-server';
|
|
3
|
+
import { ConversationRepository, type ConversationView } from '../../infrastructure/persistence/conversation.repository';
|
|
4
|
+
import { UpdateConversationDto } from '../../application/dto/update-conversation.dto';
|
|
5
|
+
/**
|
|
6
|
+
* `PATCH /api/copilot/conversations/:id` — rename a thread, file it away, bring
|
|
7
|
+
* it back, or record the model picked for it.
|
|
8
|
+
*
|
|
9
|
+
* **Archiving is the only removal this API offers, and it is reversible.** A
|
|
10
|
+
* thread's proposals are the receipts for changes that were actually made to
|
|
11
|
+
* the caller's content (ADR-0009), so deleting a conversation would take the
|
|
12
|
+
* only record of those edits with it. Hiding it from the list is the part
|
|
13
|
+
* people want; destroying the audit trail is not, and if a hard delete is ever
|
|
14
|
+
* added it needs to answer for the proposals first.
|
|
15
|
+
*
|
|
16
|
+
* A thread the caller doesn't own is a **404, not a 403** — the repository
|
|
17
|
+
* filters on user and workspace and reports "not found" either way, so an id
|
|
18
|
+
* cannot be probed for existence. `OriginGuard` because this changes state.
|
|
19
|
+
*/
|
|
20
|
+
export declare class UpdateConversationController {
|
|
21
|
+
private readonly conversations;
|
|
22
|
+
private readonly models;
|
|
23
|
+
constructor(conversations: ConversationRepository, models: ModelRegistry);
|
|
24
|
+
update(id: string, body: UpdateConversationDto, user: PublicUser, workspaceId: string): Promise<ConversationView>;
|
|
25
|
+
/**
|
|
26
|
+
* Refuses a choice that names no registered backend.
|
|
27
|
+
*
|
|
28
|
+
* The column is read back into the picker and offered as the next run's
|
|
29
|
+
* `provider`/`model`, so an unchecked string is a value the user writes and
|
|
30
|
+
* the UI then renders — and a run that would fail on a backend that does not
|
|
31
|
+
* exist. Checking against `catalogue()` keeps the stored value inside what
|
|
32
|
+
* the operator configured at boot, which is the same boundary the run route
|
|
33
|
+
* enforces when a request names a provider.
|
|
34
|
+
*/
|
|
35
|
+
private assertKnownModel;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=update-conversation.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-conversation.controller.d.ts","sourceRoot":"","sources":["../../../../../src/lib/chat/http/controllers/update-conversation.controller.ts"],"names":[],"mappings":"AAYA,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9E,OAAO,EAMH,KAAK,UAAU,EAClB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACH,sBAAsB,EACtB,KAAK,gBAAgB,EACxB,MAAM,0DAA0D,CAAC;AAClE,OAAO,EAEH,qBAAqB,EACxB,MAAM,+CAA+C,CAAC;AAEvD;;;;;;;;;;;;;;GAcG;AACH,qBAGa,4BAA4B;IAEjC,OAAO,CAAC,QAAQ,CAAC,aAAa;IACN,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAD9B,aAAa,EAAE,sBAAsB,EACb,MAAM,EAAE,aAAa;IAQ5D,MAAM,CACoB,EAAE,EAAE,MAAM,EAC9B,IAAI,EAAE,qBAAqB,EACpB,IAAI,EAAE,UAAU,EACX,WAAW,EAAE,MAAM,GACxC,OAAO,CAAC,gBAAgB,CAAC;IAuC5B;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;CAoB3B"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UpdateConversationController = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
7
|
+
const copilot_domain_1 = require("@orthacms/copilot-domain");
|
|
8
|
+
const identity_server_1 = require("@orthacms/identity-server");
|
|
9
|
+
const workspaces_server_1 = require("@orthacms/workspaces-server");
|
|
10
|
+
const conversation_repository_1 = require("../../infrastructure/persistence/conversation.repository");
|
|
11
|
+
const update_conversation_dto_1 = require("../../application/dto/update-conversation.dto");
|
|
12
|
+
/**
|
|
13
|
+
* `PATCH /api/copilot/conversations/:id` — rename a thread, file it away, bring
|
|
14
|
+
* it back, or record the model picked for it.
|
|
15
|
+
*
|
|
16
|
+
* **Archiving is the only removal this API offers, and it is reversible.** A
|
|
17
|
+
* thread's proposals are the receipts for changes that were actually made to
|
|
18
|
+
* the caller's content (ADR-0009), so deleting a conversation would take the
|
|
19
|
+
* only record of those edits with it. Hiding it from the list is the part
|
|
20
|
+
* people want; destroying the audit trail is not, and if a hard delete is ever
|
|
21
|
+
* added it needs to answer for the proposals first.
|
|
22
|
+
*
|
|
23
|
+
* A thread the caller doesn't own is a **404, not a 403** — the repository
|
|
24
|
+
* filters on user and workspace and reports "not found" either way, so an id
|
|
25
|
+
* cannot be probed for existence. `OriginGuard` because this changes state.
|
|
26
|
+
*/
|
|
27
|
+
let UpdateConversationController = class UpdateConversationController {
|
|
28
|
+
conversations;
|
|
29
|
+
models;
|
|
30
|
+
constructor(conversations, models) {
|
|
31
|
+
this.conversations = conversations;
|
|
32
|
+
this.models = models;
|
|
33
|
+
}
|
|
34
|
+
async update(id, body, user, workspaceId) {
|
|
35
|
+
// An empty patch cannot mean anything, and answering 200 to it would
|
|
36
|
+
// report success for a write that never happened. The pipe cannot catch
|
|
37
|
+
// this: every property is legitimately optional on its own.
|
|
38
|
+
if (body.title === undefined &&
|
|
39
|
+
body.archived === undefined &&
|
|
40
|
+
body.modelChoice === undefined) {
|
|
41
|
+
throw new common_1.BadRequestException('Provide a title, an archived flag, a model choice, or several.');
|
|
42
|
+
}
|
|
43
|
+
if (body.modelChoice !== undefined) {
|
|
44
|
+
this.assertKnownModel(body.modelChoice);
|
|
45
|
+
}
|
|
46
|
+
const updated = await this.conversations.update(id, user.id, workspaceId, {
|
|
47
|
+
...(body.title !== undefined ? { title: body.title } : {}),
|
|
48
|
+
...(body.archived !== undefined
|
|
49
|
+
? { archived: body.archived }
|
|
50
|
+
: {}),
|
|
51
|
+
...(body.modelChoice !== undefined
|
|
52
|
+
? { modelChoice: body.modelChoice }
|
|
53
|
+
: {})
|
|
54
|
+
});
|
|
55
|
+
if (!updated) {
|
|
56
|
+
throw new common_1.NotFoundException('Conversation not found.');
|
|
57
|
+
}
|
|
58
|
+
return updated;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Refuses a choice that names no registered backend.
|
|
62
|
+
*
|
|
63
|
+
* The column is read back into the picker and offered as the next run's
|
|
64
|
+
* `provider`/`model`, so an unchecked string is a value the user writes and
|
|
65
|
+
* the UI then renders — and a run that would fail on a backend that does not
|
|
66
|
+
* exist. Checking against `catalogue()` keeps the stored value inside what
|
|
67
|
+
* the operator configured at boot, which is the same boundary the run route
|
|
68
|
+
* enforces when a request names a provider.
|
|
69
|
+
*/
|
|
70
|
+
assertKnownModel(choice) {
|
|
71
|
+
if (choice === update_conversation_dto_1.MODEL_CHOICE_DEFAULT) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// The first colon only: a provider name cannot contain one, and a model
|
|
75
|
+
// id routinely does (`llama3.1:8b`).
|
|
76
|
+
const separator = choice.indexOf(':');
|
|
77
|
+
const provider = separator > 0 ? choice.slice(0, separator) : '';
|
|
78
|
+
const model = separator > 0 ? choice.slice(separator + 1) : '';
|
|
79
|
+
const known = this.models
|
|
80
|
+
.catalogue()
|
|
81
|
+
.some((entry) => entry.provider === provider && entry.model === model);
|
|
82
|
+
if (!known) {
|
|
83
|
+
throw new common_1.BadRequestException('That model choice names no registered backend.');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
exports.UpdateConversationController = UpdateConversationController;
|
|
88
|
+
tslib_1.__decorate([
|
|
89
|
+
(0, common_1.Patch)('conversations/:id'),
|
|
90
|
+
(0, common_1.UseGuards)(identity_server_1.OriginGuard),
|
|
91
|
+
(0, swagger_1.ApiOperation)({
|
|
92
|
+
summary: 'Rename, archive, or set the model of a copilot conversation'
|
|
93
|
+
}),
|
|
94
|
+
tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseUUIDPipe)),
|
|
95
|
+
tslib_1.__param(1, (0, common_1.Body)()),
|
|
96
|
+
tslib_1.__param(2, (0, identity_server_1.CurrentUser)()),
|
|
97
|
+
tslib_1.__param(3, (0, workspaces_server_1.CurrentWorkspace)()),
|
|
98
|
+
tslib_1.__metadata("design:type", Function),
|
|
99
|
+
tslib_1.__metadata("design:paramtypes", [String, update_conversation_dto_1.UpdateConversationDto, Object, String]),
|
|
100
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
101
|
+
], UpdateConversationController.prototype, "update", null);
|
|
102
|
+
exports.UpdateConversationController = UpdateConversationController = tslib_1.__decorate([
|
|
103
|
+
(0, common_1.UseGuards)(identity_server_1.PermissionsGuard, workspaces_server_1.WorkspaceGuard),
|
|
104
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.COPILOT_USE),
|
|
105
|
+
(0, common_1.Controller)('copilot'),
|
|
106
|
+
tslib_1.__param(1, (0, common_1.Inject)(copilot_domain_1.MODEL_REGISTRY)),
|
|
107
|
+
tslib_1.__metadata("design:paramtypes", [conversation_repository_1.ConversationRepository, Object])
|
|
108
|
+
], UpdateConversationController);
|