@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,118 @@
|
|
|
1
|
+
import type { ToolPermissionDecision } from '@orthacms/copilot-domain';
|
|
2
|
+
/**
|
|
3
|
+
* How long a parked run waits for an answer before giving up — **for the whole
|
|
4
|
+
* run**, not per call.
|
|
5
|
+
*
|
|
6
|
+
* The SSE stream heartbeats every 15s, so the connection itself will survive
|
|
7
|
+
* far longer than this; the limit is about the user, not the socket. Five
|
|
8
|
+
* minutes is "they went to look at the entry and came back".
|
|
9
|
+
*
|
|
10
|
+
* Per **run** rather than per **call** because a turn may ask about several
|
|
11
|
+
* calls, and the ceilings in `RunLimits` cannot bound any of it: the wall clock
|
|
12
|
+
* is checked at the top of a step and a park happens inside one. Charging each
|
|
13
|
+
* call its own five minutes made a single turn requesting thirty writes hold
|
|
14
|
+
* the connection, the generator and the model context for two and a half
|
|
15
|
+
* hours — while "they went to look and came back" is one absence, not thirty.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DEFAULT_RUN_DECISION_BUDGET_MS: number;
|
|
18
|
+
/** A decision, plus how it was reached — the timeout is not a user's `deny`. */
|
|
19
|
+
export interface PermissionOutcome {
|
|
20
|
+
decision: ToolPermissionDecision;
|
|
21
|
+
/** True when nobody answered in time. */
|
|
22
|
+
timedOut: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Who a parked run belongs to. Only they may answer it.
|
|
26
|
+
*
|
|
27
|
+
* Both halves are load-bearing and neither is redundant. `WorkspaceGuard` on
|
|
28
|
+
* the answering route proves the caller belongs to *the workspace they named*,
|
|
29
|
+
* which says nothing about the run: a member of workspace B naming B passes it
|
|
30
|
+
* while answering a run parked in A. And `copilot:use` is held by every role,
|
|
31
|
+
* so a colleague in the same workspace passes every guard on the route.
|
|
32
|
+
*/
|
|
33
|
+
export interface RunOwner {
|
|
34
|
+
userId: string;
|
|
35
|
+
workspaceId: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Parks a run while its user decides whether one tool call may proceed.
|
|
39
|
+
*
|
|
40
|
+
* **In-memory, and that is a real deployment constraint, not an oversight.**
|
|
41
|
+
* The promise a run awaits lives in this process, so the `POST …/permission`
|
|
42
|
+
* that resolves it has to reach the same instance. A single-node self-hosted
|
|
43
|
+
* CMS — which is what this is — is fine. A horizontally scaled one needs sticky
|
|
44
|
+
* routing by `runId`, or this registry moved behind a shared channel. Say so in
|
|
45
|
+
* the deployment notes rather than discovering it as an occasional hang.
|
|
46
|
+
*
|
|
47
|
+
* Four things it has to get right. The first three are ways a run could
|
|
48
|
+
* otherwise be stranded forever; the fourth is who is allowed to end the wait.
|
|
49
|
+
*
|
|
50
|
+
* - **A timeout.** Documented above, and spent against a per-run budget. It
|
|
51
|
+
* resolves as a refusal rather than throwing, so the model is told and the
|
|
52
|
+
* answer still lands.
|
|
53
|
+
* - **Abort.** The user closing the window is the common case, and the engine's
|
|
54
|
+
* `signal` fires; the waiter has to reject then, or the generator never
|
|
55
|
+
* unwinds and the run leaks.
|
|
56
|
+
* - **Cleanup.** Every exit path clears the entry. A registry that only deletes
|
|
57
|
+
* on the happy path grows one dangling promise per abandoned run.
|
|
58
|
+
* - **Ownership.** A `runId` is not a secret — it is handed to the client in
|
|
59
|
+
* the `run-started` frame — and it is the *only* thing the answering route
|
|
60
|
+
* used to key on. See {@link RunOwner}.
|
|
61
|
+
*/
|
|
62
|
+
export declare class ToolPermissionBroker {
|
|
63
|
+
private readonly logger;
|
|
64
|
+
/**
|
|
65
|
+
* The waiting budget, in ms. Set once from the plugin config at
|
|
66
|
+
* composition; {@link DEFAULT_RUN_DECISION_BUDGET_MS} when the operator
|
|
67
|
+
* said nothing.
|
|
68
|
+
*/
|
|
69
|
+
private budgetMs;
|
|
70
|
+
/**
|
|
71
|
+
* Applies the operator's configured budget. Called by the plugin module at
|
|
72
|
+
* startup — a setter rather than constructor injection because the broker
|
|
73
|
+
* is also constructed directly in tests, where the default is what is
|
|
74
|
+
* wanted.
|
|
75
|
+
*/
|
|
76
|
+
configure(budgetMs: number | undefined): void;
|
|
77
|
+
/**
|
|
78
|
+
* What is left of `runId`'s waiting budget, in ms — what the run engine puts
|
|
79
|
+
* on the permission frame as a deadline, so the prompt can show a countdown
|
|
80
|
+
* and warn before it expires instead of simply vanishing (`ORT-118`).
|
|
81
|
+
*/
|
|
82
|
+
budgetRemaining(runId: string): number;
|
|
83
|
+
/** Waiters by `runId:callId`. */
|
|
84
|
+
private readonly waiting;
|
|
85
|
+
/** Milliseconds each run has already spent parked, and when it last did. */
|
|
86
|
+
private readonly spent;
|
|
87
|
+
/**
|
|
88
|
+
* Waits for the user's answer to one call.
|
|
89
|
+
*
|
|
90
|
+
* Resolves rather than rejects on a timeout, because a refusal is an
|
|
91
|
+
* ordinary tool error the model can report — the same treatment every other
|
|
92
|
+
* refused call gets. Only an abort rejects, and only because that means
|
|
93
|
+
* nobody is listening any more.
|
|
94
|
+
*/
|
|
95
|
+
ask(runId: string, callId: string, owner: RunOwner, signal: AbortSignal): Promise<PermissionOutcome>;
|
|
96
|
+
/**
|
|
97
|
+
* Delivers a decision. Returns false when nothing was waiting, or when the
|
|
98
|
+
* caller does not own the parked run.
|
|
99
|
+
*
|
|
100
|
+
* **Both answer false, hence the same 404**, deliberately: whether a run
|
|
101
|
+
* exists is not something a caller who does not own it may learn, and the
|
|
102
|
+
* route's message ("no longer waiting for an answer") is true either way
|
|
103
|
+
* from where they stand.
|
|
104
|
+
*/
|
|
105
|
+
decide(runId: string, callId: string, decision: ToolPermissionDecision, by: RunOwner): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Grants a parked call more time, at the user's request. Returns the new
|
|
108
|
+
* allowance in ms, or `null` when nothing is waiting or the caller does not
|
|
109
|
+
* own the run — the same conflation, for the same reason, as
|
|
110
|
+
* {@link decide}.
|
|
111
|
+
*/
|
|
112
|
+
extend(runId: string, callId: string, by: RunOwner): number | null;
|
|
113
|
+
/** What is left of `runId`'s waiting budget, pruning stale entries first. */
|
|
114
|
+
private remainingBudget;
|
|
115
|
+
/** Charges `ms` of waiting to `runId`. */
|
|
116
|
+
private charge;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=tool-permission.broker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-permission.broker.d.ts","sourceRoot":"","sources":["../../../../src/lib/chat/application/tool-permission.broker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEvE;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,8BAA8B,QAAa,CAAC;AAsBzD,gFAAgF;AAChF,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,EAAE,sBAAsB,CAAC;IACjC,yCAAyC;IACzC,QAAQ,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,QAAQ;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACvB;AAUD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBACa,oBAAoB;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyC;IAEhE;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAkC;IAElD;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAI7C;;;;OAIG;IACH,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAItC,iCAAiC;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IAErD,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiD;IAEvE;;;;;;;OAOG;IACG,GAAG,CACL,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,WAAW,GACpB,OAAO,CAAC,iBAAiB,CAAC;IA6D7B;;;;;;;;OAQG;IACH,MAAM,CACF,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,sBAAsB,EAChC,EAAE,EAAE,QAAQ,GACb,OAAO;IAoBV;;;;;OAKG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI;IAgBlE,6EAA6E;IAC7E,OAAO,CAAC,eAAe;IAUvB,0CAA0C;IAC1C,OAAO,CAAC,MAAM;CAOjB"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var ToolPermissionBroker_1;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ToolPermissionBroker = exports.DEFAULT_RUN_DECISION_BUDGET_MS = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const common_1 = require("@nestjs/common");
|
|
7
|
+
/**
|
|
8
|
+
* How long a parked run waits for an answer before giving up — **for the whole
|
|
9
|
+
* run**, not per call.
|
|
10
|
+
*
|
|
11
|
+
* The SSE stream heartbeats every 15s, so the connection itself will survive
|
|
12
|
+
* far longer than this; the limit is about the user, not the socket. Five
|
|
13
|
+
* minutes is "they went to look at the entry and came back".
|
|
14
|
+
*
|
|
15
|
+
* Per **run** rather than per **call** because a turn may ask about several
|
|
16
|
+
* calls, and the ceilings in `RunLimits` cannot bound any of it: the wall clock
|
|
17
|
+
* is checked at the top of a step and a park happens inside one. Charging each
|
|
18
|
+
* call its own five minutes made a single turn requesting thirty writes hold
|
|
19
|
+
* the connection, the generator and the model context for two and a half
|
|
20
|
+
* hours — while "they went to look and came back" is one absence, not thirty.
|
|
21
|
+
*/
|
|
22
|
+
exports.DEFAULT_RUN_DECISION_BUDGET_MS = 5 * 60_000;
|
|
23
|
+
/**
|
|
24
|
+
* How much time one {@link ToolPermissionBroker.extend} grants.
|
|
25
|
+
*
|
|
26
|
+
* A full fresh budget rather than a token top-up: the user pressing "I need
|
|
27
|
+
* more time" is saying the first allowance was not enough, and WCAG 2.2.1 asks
|
|
28
|
+
* that a limit be extendable "at least ten times" over — which repeated
|
|
29
|
+
* extensions of the full amount satisfy without inventing a second number.
|
|
30
|
+
*/
|
|
31
|
+
const EXTENSION_MS = exports.DEFAULT_RUN_DECISION_BUDGET_MS;
|
|
32
|
+
/**
|
|
33
|
+
* How long a run's spent budget is remembered after its last park.
|
|
34
|
+
*
|
|
35
|
+
* There is no "run ended" signal to clean up on — the generator simply stops
|
|
36
|
+
* asking — so entries are pruned opportunistically, on the next `ask`. Anything
|
|
37
|
+
* older than the budget can no longer constrain a live run, because a run that
|
|
38
|
+
* parked that long ago has already exhausted it.
|
|
39
|
+
*/
|
|
40
|
+
const BUDGET_TTL_MS = exports.DEFAULT_RUN_DECISION_BUDGET_MS;
|
|
41
|
+
/**
|
|
42
|
+
* Parks a run while its user decides whether one tool call may proceed.
|
|
43
|
+
*
|
|
44
|
+
* **In-memory, and that is a real deployment constraint, not an oversight.**
|
|
45
|
+
* The promise a run awaits lives in this process, so the `POST …/permission`
|
|
46
|
+
* that resolves it has to reach the same instance. A single-node self-hosted
|
|
47
|
+
* CMS — which is what this is — is fine. A horizontally scaled one needs sticky
|
|
48
|
+
* routing by `runId`, or this registry moved behind a shared channel. Say so in
|
|
49
|
+
* the deployment notes rather than discovering it as an occasional hang.
|
|
50
|
+
*
|
|
51
|
+
* Four things it has to get right. The first three are ways a run could
|
|
52
|
+
* otherwise be stranded forever; the fourth is who is allowed to end the wait.
|
|
53
|
+
*
|
|
54
|
+
* - **A timeout.** Documented above, and spent against a per-run budget. It
|
|
55
|
+
* resolves as a refusal rather than throwing, so the model is told and the
|
|
56
|
+
* answer still lands.
|
|
57
|
+
* - **Abort.** The user closing the window is the common case, and the engine's
|
|
58
|
+
* `signal` fires; the waiter has to reject then, or the generator never
|
|
59
|
+
* unwinds and the run leaks.
|
|
60
|
+
* - **Cleanup.** Every exit path clears the entry. A registry that only deletes
|
|
61
|
+
* on the happy path grows one dangling promise per abandoned run.
|
|
62
|
+
* - **Ownership.** A `runId` is not a secret — it is handed to the client in
|
|
63
|
+
* the `run-started` frame — and it is the *only* thing the answering route
|
|
64
|
+
* used to key on. See {@link RunOwner}.
|
|
65
|
+
*/
|
|
66
|
+
let ToolPermissionBroker = ToolPermissionBroker_1 = class ToolPermissionBroker {
|
|
67
|
+
logger = new common_1.Logger(ToolPermissionBroker_1.name);
|
|
68
|
+
/**
|
|
69
|
+
* The waiting budget, in ms. Set once from the plugin config at
|
|
70
|
+
* composition; {@link DEFAULT_RUN_DECISION_BUDGET_MS} when the operator
|
|
71
|
+
* said nothing.
|
|
72
|
+
*/
|
|
73
|
+
budgetMs = exports.DEFAULT_RUN_DECISION_BUDGET_MS;
|
|
74
|
+
/**
|
|
75
|
+
* Applies the operator's configured budget. Called by the plugin module at
|
|
76
|
+
* startup — a setter rather than constructor injection because the broker
|
|
77
|
+
* is also constructed directly in tests, where the default is what is
|
|
78
|
+
* wanted.
|
|
79
|
+
*/
|
|
80
|
+
configure(budgetMs) {
|
|
81
|
+
if (budgetMs !== undefined && budgetMs > 0)
|
|
82
|
+
this.budgetMs = budgetMs;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* What is left of `runId`'s waiting budget, in ms — what the run engine puts
|
|
86
|
+
* on the permission frame as a deadline, so the prompt can show a countdown
|
|
87
|
+
* and warn before it expires instead of simply vanishing (`ORT-118`).
|
|
88
|
+
*/
|
|
89
|
+
budgetRemaining(runId) {
|
|
90
|
+
return this.remainingBudget(runId);
|
|
91
|
+
}
|
|
92
|
+
/** Waiters by `runId:callId`. */
|
|
93
|
+
waiting = new Map();
|
|
94
|
+
/** Milliseconds each run has already spent parked, and when it last did. */
|
|
95
|
+
spent = new Map();
|
|
96
|
+
/**
|
|
97
|
+
* Waits for the user's answer to one call.
|
|
98
|
+
*
|
|
99
|
+
* Resolves rather than rejects on a timeout, because a refusal is an
|
|
100
|
+
* ordinary tool error the model can report — the same treatment every other
|
|
101
|
+
* refused call gets. Only an abort rejects, and only because that means
|
|
102
|
+
* nobody is listening any more.
|
|
103
|
+
*/
|
|
104
|
+
async ask(runId, callId, owner, signal) {
|
|
105
|
+
const key = `${runId}:${callId}`;
|
|
106
|
+
const remaining = this.remainingBudget(runId);
|
|
107
|
+
if (remaining <= 0) {
|
|
108
|
+
// The run has already spent its whole waiting budget on earlier
|
|
109
|
+
// calls in this turn. Refusing straight away is the same outcome a
|
|
110
|
+
// timeout produces, without holding the connection for it.
|
|
111
|
+
this.logger.warn(`Run ${runId}: the waiting budget is exhausted; call ${callId} was refused without asking.`);
|
|
112
|
+
return { decision: 'deny', timedOut: true };
|
|
113
|
+
}
|
|
114
|
+
const askedAt = Date.now();
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
const settle = (outcome) => {
|
|
117
|
+
cleanup();
|
|
118
|
+
resolve(outcome);
|
|
119
|
+
};
|
|
120
|
+
let timer;
|
|
121
|
+
const expire = (after) => {
|
|
122
|
+
timer = setTimeout(() => {
|
|
123
|
+
this.logger.warn(`Run ${runId}: nobody answered the permission request ` +
|
|
124
|
+
`for call ${callId} within ${after}ms; refusing.`);
|
|
125
|
+
settle({ decision: 'deny', timedOut: true });
|
|
126
|
+
}, after);
|
|
127
|
+
};
|
|
128
|
+
expire(remaining);
|
|
129
|
+
// Re-arms the timer and tells the caller how long they now have.
|
|
130
|
+
// The budget already spent is *not* refunded: this grants a fresh
|
|
131
|
+
// allowance rather than rewinding the clock, so a run cannot be
|
|
132
|
+
// held open indefinitely by a client that never stops asking
|
|
133
|
+
// without a user pressing the button each time.
|
|
134
|
+
const extend = () => {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
expire(EXTENSION_MS);
|
|
137
|
+
return EXTENSION_MS;
|
|
138
|
+
};
|
|
139
|
+
const onAbort = () => {
|
|
140
|
+
cleanup();
|
|
141
|
+
reject(signal.reason ?? new Error('aborted'));
|
|
142
|
+
};
|
|
143
|
+
const cleanup = () => {
|
|
144
|
+
clearTimeout(timer);
|
|
145
|
+
signal.removeEventListener('abort', onAbort);
|
|
146
|
+
this.waiting.delete(key);
|
|
147
|
+
this.charge(runId, Date.now() - askedAt);
|
|
148
|
+
};
|
|
149
|
+
if (signal.aborted) {
|
|
150
|
+
onAbort();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
154
|
+
this.waiting.set(key, { owner, settle, extend });
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Delivers a decision. Returns false when nothing was waiting, or when the
|
|
159
|
+
* caller does not own the parked run.
|
|
160
|
+
*
|
|
161
|
+
* **Both answer false, hence the same 404**, deliberately: whether a run
|
|
162
|
+
* exists is not something a caller who does not own it may learn, and the
|
|
163
|
+
* route's message ("no longer waiting for an answer") is true either way
|
|
164
|
+
* from where they stand.
|
|
165
|
+
*/
|
|
166
|
+
decide(runId, callId, decision, by) {
|
|
167
|
+
const key = `${runId}:${callId}`;
|
|
168
|
+
const waiter = this.waiting.get(key);
|
|
169
|
+
if (!waiter) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
if (waiter.owner.userId !== by.userId ||
|
|
173
|
+
waiter.owner.workspaceId !== by.workspaceId) {
|
|
174
|
+
this.logger.warn(`Run ${runId}: user ${by.userId} tried to answer a permission ` +
|
|
175
|
+
'request for a run they do not own; refused.');
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
waiter.settle({ decision, timedOut: false });
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Grants a parked call more time, at the user's request. Returns the new
|
|
183
|
+
* allowance in ms, or `null` when nothing is waiting or the caller does not
|
|
184
|
+
* own the run — the same conflation, for the same reason, as
|
|
185
|
+
* {@link decide}.
|
|
186
|
+
*/
|
|
187
|
+
extend(runId, callId, by) {
|
|
188
|
+
const waiter = this.waiting.get(`${runId}:${callId}`);
|
|
189
|
+
if (!waiter)
|
|
190
|
+
return null;
|
|
191
|
+
if (waiter.owner.userId !== by.userId ||
|
|
192
|
+
waiter.owner.workspaceId !== by.workspaceId) {
|
|
193
|
+
this.logger.warn(`Run ${runId}: user ${by.userId} tried to extend a permission ` +
|
|
194
|
+
'request for a run they do not own; refused.');
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return waiter.extend();
|
|
198
|
+
}
|
|
199
|
+
/** What is left of `runId`'s waiting budget, pruning stale entries first. */
|
|
200
|
+
remainingBudget(runId) {
|
|
201
|
+
const now = Date.now();
|
|
202
|
+
for (const [id, entry] of this.spent) {
|
|
203
|
+
if (now - entry.at > BUDGET_TTL_MS) {
|
|
204
|
+
this.spent.delete(id);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return this.budgetMs - (this.spent.get(runId)?.ms ?? 0);
|
|
208
|
+
}
|
|
209
|
+
/** Charges `ms` of waiting to `runId`. */
|
|
210
|
+
charge(runId, ms) {
|
|
211
|
+
const entry = this.spent.get(runId);
|
|
212
|
+
this.spent.set(runId, {
|
|
213
|
+
ms: (entry?.ms ?? 0) + Math.max(0, ms),
|
|
214
|
+
at: Date.now()
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
exports.ToolPermissionBroker = ToolPermissionBroker;
|
|
219
|
+
exports.ToolPermissionBroker = ToolPermissionBroker = ToolPermissionBroker_1 = tslib_1.__decorate([
|
|
220
|
+
(0, common_1.Injectable)()
|
|
221
|
+
], ToolPermissionBroker);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Response } from 'express';
|
|
2
|
+
import { type PublicUser } from '@orthacms/identity-server';
|
|
3
|
+
import { CreateRunDto } from '../../application/dto/create-run.dto';
|
|
4
|
+
import { RunEngine } from '../../application/run-engine.service';
|
|
5
|
+
import { ContentTypeSummaryService } from '../../application/content-type-summary.service';
|
|
6
|
+
/**
|
|
7
|
+
* `POST /api/copilot/runs` — starts a turn and streams it back as Server-Sent
|
|
8
|
+
* Events (`docs/design/copilot.md` §5).
|
|
9
|
+
*
|
|
10
|
+
* **Every guard is explicit, and that is the point.** Only `AuthGuard` is
|
|
11
|
+
* global (`APP_GUARD`); `OriginGuard`, `PermissionsGuard` and `WorkspaceGuard`
|
|
12
|
+
* are per-controller decorators, so a copilot route that forgot
|
|
13
|
+
* `WorkspaceGuard` would authenticate fine, pass its permission check, and then
|
|
14
|
+
* run every tool with an **unvalidated** workspace id taken straight from a
|
|
15
|
+
* client header — silently unscoping the whole tool catalogue. The three below
|
|
16
|
+
* are, in order: reject cross-site POSTs, require `copilot:use`, and prove the
|
|
17
|
+
* caller is a member of the workspace they named.
|
|
18
|
+
*
|
|
19
|
+
* SSE on a POST rather than a GET + `EventSource`, because the turn has a body
|
|
20
|
+
* (the message and its context) and `EventSource` cannot send one. Verified to
|
|
21
|
+
* stream unbuffered through the admin's Vite dev proxy.
|
|
22
|
+
*/
|
|
23
|
+
export declare class CreateRunController {
|
|
24
|
+
private readonly engine;
|
|
25
|
+
private readonly types;
|
|
26
|
+
private readonly logger;
|
|
27
|
+
constructor(engine: RunEngine, types: ContentTypeSummaryService);
|
|
28
|
+
create(body: CreateRunDto, user: PublicUser, workspaceId: string, res: Response): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=create-run.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-run.controller.d.ts","sourceRoot":"","sources":["../../../../../src/lib/chat/http/controllers/create-run.controller.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAMH,KAAK,UAAU,EAClB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACpE,OAAO,EAGH,SAAS,EAEZ,MAAM,sCAAsC,CAAC;AAE9C,OAAO,EAAE,yBAAyB,EAAE,MAAM,gDAAgD,CAAC;AAI3F;;;;;;;;;;;;;;;;GAgBG;AACH,qBAGa,mBAAmB;IAIxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAJ1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwC;gBAG1C,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,yBAAyB;IAkB/C,MAAM,CACA,IAAI,EAAE,YAAY,EACX,IAAI,EAAE,UAAU,EACX,WAAW,EAAE,MAAM,EAChC,GAAG,EAAE,QAAQ,GACrB,OAAO,CAAC,IAAI,CAAC;CA6FnB"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var CreateRunController_1;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.CreateRunController = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const common_1 = require("@nestjs/common");
|
|
7
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
8
|
+
const identity_server_1 = require("@orthacms/identity-server");
|
|
9
|
+
const workspaces_server_1 = require("@orthacms/workspaces-server");
|
|
10
|
+
const create_run_dto_1 = require("../../application/dto/create-run.dto");
|
|
11
|
+
const run_engine_service_1 = require("../../application/run-engine.service");
|
|
12
|
+
const copilot_domain_1 = require("@orthacms/copilot-domain");
|
|
13
|
+
const content_type_summary_service_1 = require("../../application/content-type-summary.service");
|
|
14
|
+
const skill_catalog_service_1 = require("../../../skills/application/skill-catalog.service");
|
|
15
|
+
const sse_stream_1 = require("../sse-stream");
|
|
16
|
+
/**
|
|
17
|
+
* `POST /api/copilot/runs` — starts a turn and streams it back as Server-Sent
|
|
18
|
+
* Events (`docs/design/copilot.md` §5).
|
|
19
|
+
*
|
|
20
|
+
* **Every guard is explicit, and that is the point.** Only `AuthGuard` is
|
|
21
|
+
* global (`APP_GUARD`); `OriginGuard`, `PermissionsGuard` and `WorkspaceGuard`
|
|
22
|
+
* are per-controller decorators, so a copilot route that forgot
|
|
23
|
+
* `WorkspaceGuard` would authenticate fine, pass its permission check, and then
|
|
24
|
+
* run every tool with an **unvalidated** workspace id taken straight from a
|
|
25
|
+
* client header — silently unscoping the whole tool catalogue. The three below
|
|
26
|
+
* are, in order: reject cross-site POSTs, require `copilot:use`, and prove the
|
|
27
|
+
* caller is a member of the workspace they named.
|
|
28
|
+
*
|
|
29
|
+
* SSE on a POST rather than a GET + `EventSource`, because the turn has a body
|
|
30
|
+
* (the message and its context) and `EventSource` cannot send one. Verified to
|
|
31
|
+
* stream unbuffered through the admin's Vite dev proxy.
|
|
32
|
+
*/
|
|
33
|
+
let CreateRunController = CreateRunController_1 = class CreateRunController {
|
|
34
|
+
engine;
|
|
35
|
+
types;
|
|
36
|
+
logger = new common_1.Logger(CreateRunController_1.name);
|
|
37
|
+
constructor(engine, types) {
|
|
38
|
+
this.engine = engine;
|
|
39
|
+
this.types = types;
|
|
40
|
+
}
|
|
41
|
+
async create(body, user, workspaceId, res) {
|
|
42
|
+
const stream = new sse_stream_1.SseStream(res);
|
|
43
|
+
const abort = new AbortController();
|
|
44
|
+
// Wired before the first await: a client that gives up during setup
|
|
45
|
+
// must still cancel the run.
|
|
46
|
+
stream.onClientDisconnect(abort);
|
|
47
|
+
const typeSummaries = await this.types.summaries(workspaceId);
|
|
48
|
+
stream.open();
|
|
49
|
+
try {
|
|
50
|
+
const events = this.engine.run({
|
|
51
|
+
userId: user.id,
|
|
52
|
+
userEmail: user.email,
|
|
53
|
+
roleId: user.roleId,
|
|
54
|
+
workspaceId,
|
|
55
|
+
conversationId: body.conversationId,
|
|
56
|
+
message: body.message,
|
|
57
|
+
...(body.attachments?.length
|
|
58
|
+
? {
|
|
59
|
+
attachments: body.attachments.map((attachment) => attachment.assetId)
|
|
60
|
+
}
|
|
61
|
+
: {}),
|
|
62
|
+
...(body.skills?.length
|
|
63
|
+
? { skills: body.skills.map((skill) => skill.name) }
|
|
64
|
+
: {}),
|
|
65
|
+
context: body.context ?? {},
|
|
66
|
+
uiLocale: body.uiLocale ?? 'en',
|
|
67
|
+
...(body.provider || body.model
|
|
68
|
+
? {
|
|
69
|
+
choice: {
|
|
70
|
+
...(body.provider
|
|
71
|
+
? { provider: body.provider }
|
|
72
|
+
: {}),
|
|
73
|
+
...(body.model ? { model: body.model } : {})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
: {}),
|
|
77
|
+
typeSummaries,
|
|
78
|
+
signal: abort.signal
|
|
79
|
+
});
|
|
80
|
+
for await (const event of events) {
|
|
81
|
+
stream.send(event);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// The headers went out with `stream.open()`, so an error here can
|
|
86
|
+
// no longer become a status code — rethrowing would hand Nest's
|
|
87
|
+
// exception filter a response it cannot write to. It has to arrive
|
|
88
|
+
// as a frame instead, and be logged here rather than by the filter.
|
|
89
|
+
if (error instanceof run_engine_service_1.CopilotDisabledError) {
|
|
90
|
+
stream.send({
|
|
91
|
+
type: 'error',
|
|
92
|
+
message: 'Ortha AI is turned off for this deployment. An administrator can enable it.'
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
else if (error instanceof run_engine_service_1.AttachmentError ||
|
|
96
|
+
error instanceof skill_catalog_service_1.SkillResolutionError ||
|
|
97
|
+
error instanceof run_engine_service_1.UnknownModelChoiceError ||
|
|
98
|
+
error instanceof copilot_domain_1.UnknownModelError) {
|
|
99
|
+
// A bad attachment, an unavailable skill or a bad
|
|
100
|
+
// provider/model is the caller's
|
|
101
|
+
// mistake, and its own
|
|
102
|
+
// message already names what was asked for and what exists —
|
|
103
|
+
// far more useful than a generic failure, and safe to show
|
|
104
|
+
// because the catalogue is public to anyone with `copilot:use`.
|
|
105
|
+
stream.send({ type: 'error', message: error.message });
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
this.logger.error('Copilot run failed after the stream was opened', error instanceof Error ? error.stack : String(error));
|
|
109
|
+
stream.send({
|
|
110
|
+
type: 'error',
|
|
111
|
+
message: 'Ortha AI could not complete this run.'
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// Always terminate with `done`, whatever went wrong: the client's
|
|
115
|
+
// reducer keys "the turn is over" off this one frame, and a stream
|
|
116
|
+
// that just stops would leave the UI spinning forever.
|
|
117
|
+
stream.send({
|
|
118
|
+
type: 'done',
|
|
119
|
+
stopReason: 'error',
|
|
120
|
+
usage: { inputTokens: 0, outputTokens: 0 }
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
stream.close();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
exports.CreateRunController = CreateRunController;
|
|
129
|
+
tslib_1.__decorate([
|
|
130
|
+
(0, common_1.Post)('runs')
|
|
131
|
+
// The response is written by hand, so Nest must not also send one.
|
|
132
|
+
,
|
|
133
|
+
(0, common_1.HttpCode)(common_1.HttpStatus.OK),
|
|
134
|
+
(0, swagger_1.ApiOperation)({
|
|
135
|
+
summary: 'Start a copilot run',
|
|
136
|
+
description: 'Streams the run back as Server-Sent Events: `run-started`, then any ' +
|
|
137
|
+
'number of `text-delta` / `tool-call` / `tool-result` frames, then exactly ' +
|
|
138
|
+
'one `done`. An `error` frame may precede `done`.'
|
|
139
|
+
}),
|
|
140
|
+
(0, swagger_1.ApiBody)({ type: create_run_dto_1.CreateRunDto }),
|
|
141
|
+
(0, swagger_1.ApiResponse)({
|
|
142
|
+
status: 200,
|
|
143
|
+
description: 'An event stream (`text/event-stream`).'
|
|
144
|
+
}),
|
|
145
|
+
tslib_1.__param(0, (0, common_1.Body)()),
|
|
146
|
+
tslib_1.__param(1, (0, identity_server_1.CurrentUser)()),
|
|
147
|
+
tslib_1.__param(2, (0, workspaces_server_1.CurrentWorkspace)()),
|
|
148
|
+
tslib_1.__param(3, (0, common_1.Res)()),
|
|
149
|
+
tslib_1.__metadata("design:type", Function),
|
|
150
|
+
tslib_1.__metadata("design:paramtypes", [create_run_dto_1.CreateRunDto, Object, String, Object]),
|
|
151
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
152
|
+
], CreateRunController.prototype, "create", null);
|
|
153
|
+
exports.CreateRunController = CreateRunController = CreateRunController_1 = tslib_1.__decorate([
|
|
154
|
+
(0, common_1.UseGuards)(identity_server_1.OriginGuard, identity_server_1.PermissionsGuard, workspaces_server_1.WorkspaceGuard),
|
|
155
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.COPILOT_USE),
|
|
156
|
+
(0, common_1.Controller)('copilot'),
|
|
157
|
+
tslib_1.__metadata("design:paramtypes", [run_engine_service_1.RunEngine,
|
|
158
|
+
content_type_summary_service_1.ContentTypeSummaryService])
|
|
159
|
+
], CreateRunController);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type PublicUser } from '@orthacms/identity-server';
|
|
2
|
+
import { ConversationRepository, type ConversationView, type MessageView } from '../../infrastructure/persistence/conversation.repository';
|
|
3
|
+
/** A thread plus its transcript. */
|
|
4
|
+
export interface ConversationDetail {
|
|
5
|
+
conversation: ConversationView;
|
|
6
|
+
messages: MessageView[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* `GET /api/copilot/conversations/:id` — one thread and its transcript, so a
|
|
10
|
+
* reopened panel or a reloaded tab renders what was persisted rather than only
|
|
11
|
+
* what streamed.
|
|
12
|
+
*
|
|
13
|
+
* A thread the caller doesn't own is a **404, not a 403** — the repository
|
|
14
|
+
* filters on user and workspace and reports "not found" either way, so an id
|
|
15
|
+
* cannot be probed for existence.
|
|
16
|
+
*/
|
|
17
|
+
export declare class GetConversationController {
|
|
18
|
+
private readonly conversations;
|
|
19
|
+
constructor(conversations: ConversationRepository);
|
|
20
|
+
get(id: string, user: PublicUser, workspaceId: string): Promise<ConversationDetail>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=get-conversation.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-conversation.controller.d.ts","sourceRoot":"","sources":["../../../../../src/lib/chat/http/controllers/get-conversation.controller.ts"],"names":[],"mappings":"AAQA,OAAO,EAKH,KAAK,UAAU,EAClB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACH,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACnB,MAAM,0DAA0D,CAAC;AAElE,oCAAoC;AACpC,MAAM,WAAW,kBAAkB;IAC/B,YAAY,EAAE,gBAAgB,CAAC;IAC/B,QAAQ,EAAE,WAAW,EAAE,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,qBAGa,yBAAyB;IACtB,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAAb,aAAa,EAAE,sBAAsB;IAM5D,GAAG,CACuB,EAAE,EAAE,MAAM,EACvB,IAAI,EAAE,UAAU,EACX,WAAW,EAAE,MAAM,GACxC,OAAO,CAAC,kBAAkB,CAAC;CAWjC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GetConversationController = 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
|
+
/**
|
|
11
|
+
* `GET /api/copilot/conversations/:id` — one thread and its transcript, so a
|
|
12
|
+
* reopened panel or a reloaded tab renders what was persisted rather than only
|
|
13
|
+
* what streamed.
|
|
14
|
+
*
|
|
15
|
+
* A thread the caller doesn't own is a **404, not a 403** — the repository
|
|
16
|
+
* filters on user and workspace and reports "not found" either way, so an id
|
|
17
|
+
* cannot be probed for existence.
|
|
18
|
+
*/
|
|
19
|
+
let GetConversationController = class GetConversationController {
|
|
20
|
+
conversations;
|
|
21
|
+
constructor(conversations) {
|
|
22
|
+
this.conversations = conversations;
|
|
23
|
+
}
|
|
24
|
+
async get(id, user, workspaceId) {
|
|
25
|
+
const conversation = await this.conversations.findOrFail(id, user.id, workspaceId);
|
|
26
|
+
return {
|
|
27
|
+
conversation,
|
|
28
|
+
messages: await this.conversations.messages(conversation.id)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
exports.GetConversationController = GetConversationController;
|
|
33
|
+
tslib_1.__decorate([
|
|
34
|
+
(0, common_1.Get)('conversations/:id'),
|
|
35
|
+
(0, swagger_1.ApiOperation)({
|
|
36
|
+
summary: 'Get one copilot conversation and its transcript'
|
|
37
|
+
}),
|
|
38
|
+
tslib_1.__param(0, (0, common_1.Param)('id', common_1.ParseUUIDPipe)),
|
|
39
|
+
tslib_1.__param(1, (0, identity_server_1.CurrentUser)()),
|
|
40
|
+
tslib_1.__param(2, (0, workspaces_server_1.CurrentWorkspace)()),
|
|
41
|
+
tslib_1.__metadata("design:type", Function),
|
|
42
|
+
tslib_1.__metadata("design:paramtypes", [String, Object, String]),
|
|
43
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
44
|
+
], GetConversationController.prototype, "get", null);
|
|
45
|
+
exports.GetConversationController = GetConversationController = tslib_1.__decorate([
|
|
46
|
+
(0, common_1.UseGuards)(identity_server_1.PermissionsGuard, workspaces_server_1.WorkspaceGuard),
|
|
47
|
+
(0, identity_server_1.RequirePermissions)(identity_server_1.PERMISSIONS.COPILOT_USE),
|
|
48
|
+
(0, common_1.Controller)('copilot'),
|
|
49
|
+
tslib_1.__metadata("design:paramtypes", [conversation_repository_1.ConversationRepository])
|
|
50
|
+
], GetConversationController);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type PublicUser } from '@orthacms/identity-server';
|
|
2
|
+
import { ConversationRepository, type ConversationView } from '../../infrastructure/persistence/conversation.repository';
|
|
3
|
+
import { ListConversationsQueryDto } from '../../application/dto/list-conversations-query.dto';
|
|
4
|
+
/**
|
|
5
|
+
* `GET /api/copilot/conversations` — the signed-in user's threads in the open
|
|
6
|
+
* workspace, most recently used first.
|
|
7
|
+
*
|
|
8
|
+
* No `OriginGuard`: it guards state-changing requests, and this reads. The
|
|
9
|
+
* scoping that matters is in the repository, which filters on user **and**
|
|
10
|
+
* workspace — the guard proves the caller belongs to the workspace, and the
|
|
11
|
+
* query proves the threads belong to the caller.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ListConversationsController {
|
|
14
|
+
private readonly conversations;
|
|
15
|
+
constructor(conversations: ConversationRepository);
|
|
16
|
+
list(query: ListConversationsQueryDto, user: PublicUser, workspaceId: string): Promise<{
|
|
17
|
+
items: ConversationView[];
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=list-conversations.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-conversations.controller.d.ts","sourceRoot":"","sources":["../../../../../src/lib/chat/http/controllers/list-conversations.controller.ts"],"names":[],"mappings":"AAEA,OAAO,EAKH,KAAK,UAAU,EAClB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACH,sBAAsB,EACtB,KAAK,gBAAgB,EACxB,MAAM,0DAA0D,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,oDAAoD,CAAC;AAE/F;;;;;;;;GAQG;AACH,qBAGa,2BAA2B;IACxB,OAAO,CAAC,QAAQ,CAAC,aAAa;gBAAb,aAAa,EAAE,sBAAsB;IAI5D,IAAI,CACG,KAAK,EAAE,yBAAyB,EAC1B,IAAI,EAAE,UAAU,EACX,WAAW,EAAE,MAAM,GACxC,OAAO,CAAC;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAA;KAAE,CAAC;CAS5C"}
|