@teaflask/assistant 0.7.1 → 0.8.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/README.md +1 -1
- package/dist/components/tool-views/action-values.js +43 -0
- package/dist/components/tool-views/action-view.js +6 -42
- package/dist/components/tool-views/graphql-action-view.js +95 -0
- package/dist/contract/actions-adapter.d.ts +42 -4
- package/dist/core/tool-view.js +2 -0
- package/dist/generated/models/apiErrorCode.d.ts +9 -0
- package/dist/generated/models/apiErrorCode.js +9 -0
- package/dist/headless.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/transport/actions-adapter.js +83 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ import { TeaflaskAssistant } from "@teaflask/assistant";
|
|
|
63
63
|
|
|
64
64
|
## What your agent can do
|
|
65
65
|
|
|
66
|
-
- **Take actions.** It calls your API on the user's behalf, with the user's own session. You set every action to Allow, Ask or Deny.
|
|
66
|
+
- **Take actions.** It calls your API on the user's behalf, with the user's own session. You set every action to Allow, Ask or Deny. On a [GraphQL](https://docs.teaflask.com/adapters#graphql) API, it runs them through your app's own client.
|
|
67
67
|
- **Follow your workflows.** Write a procedure once, like how refunds work or when to escalate, and the agent follows it.
|
|
68
68
|
- **Guide users around your app.** It takes people to the right page and highlights what to click.
|
|
69
69
|
- **Fill in forms.** It fills the fields; the user reviews and submits.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { prettyPrintParameters } from "../pretty-print.js";
|
|
2
|
+
import { CARD_BLOCK_CLASS } from "./parts-classes.js";
|
|
3
|
+
import { el, factRows, recordOf, scrollRegion } from "./view-dom.js";
|
|
4
|
+
const VALUE_PANE_CLASS = "tf:m-0 tf:whitespace-pre-wrap tf:font-mono tf:text-xs tf:text-tf-card-foreground";
|
|
5
|
+
export function valueRows(value) {
|
|
6
|
+
const record = recordOf(value);
|
|
7
|
+
if (record !== null) {
|
|
8
|
+
return scrollRegion([
|
|
9
|
+
factRows(Object.entries(record).map(([key, entry]) => [key, _valueOf(entry)])),
|
|
10
|
+
]);
|
|
11
|
+
}
|
|
12
|
+
return valueBlock(value);
|
|
13
|
+
}
|
|
14
|
+
export function valueBlock(value) {
|
|
15
|
+
const wrapper = el("div", CARD_BLOCK_CLASS);
|
|
16
|
+
wrapper.dataset.tfActionValueBlock = "";
|
|
17
|
+
wrapper.append(el("pre", VALUE_PANE_CLASS, prettyPrintParameters(value)));
|
|
18
|
+
return scrollRegion([wrapper]);
|
|
19
|
+
}
|
|
20
|
+
function _valueOf(value) {
|
|
21
|
+
if (typeof value === "string") {
|
|
22
|
+
return value === "" ? "—" : value;
|
|
23
|
+
}
|
|
24
|
+
if (typeof value === "number" ||
|
|
25
|
+
typeof value === "boolean" ||
|
|
26
|
+
typeof value === "bigint") {
|
|
27
|
+
return String(value);
|
|
28
|
+
}
|
|
29
|
+
if (value === null || value === undefined) {
|
|
30
|
+
return "—";
|
|
31
|
+
}
|
|
32
|
+
return el("pre", VALUE_PANE_CLASS, prettyPrintParameters(value));
|
|
33
|
+
}
|
|
34
|
+
export function statusBadgeOf(status) {
|
|
35
|
+
const badge = el("span", status >= 400
|
|
36
|
+
? "tf:font-medium tf:text-tf-destructive"
|
|
37
|
+
: "tf:text-tf-muted-foreground", `HTTP ${String(status)}`);
|
|
38
|
+
badge.dataset.tfActionStatus = String(status);
|
|
39
|
+
return badge;
|
|
40
|
+
}
|
|
41
|
+
export function isEmptyBody(body) {
|
|
42
|
+
return body === undefined || body === null || body === "";
|
|
43
|
+
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { actionEnvelopeOf, actionFailureOf, captionRow, card, el, factRows, note, recordOf, scrollRegion, settledOutcomeNotesOf, settledWithResult, statelessToolView, } from "./view-dom.js";
|
|
1
|
+
import { isEmptyBody, statusBadgeOf, valueRows } from "./action-values.js";
|
|
2
|
+
import { actionEnvelopeOf, actionFailureOf, captionRow, card, el, factRows, note, recordOf, settledOutcomeNotesOf, settledWithResult, statelessToolView, } from "./view-dom.js";
|
|
4
3
|
const REQUEST_SECTIONS = [
|
|
5
4
|
["path_params", "Path parameters"],
|
|
6
5
|
["query", "Query"],
|
|
7
6
|
["body", "Body"],
|
|
8
7
|
];
|
|
9
|
-
const VALUE_PANE_CLASS = "tf:m-0 tf:whitespace-pre-wrap tf:font-mono tf:text-xs tf:text-tf-card-foreground";
|
|
10
8
|
export const actionToolView = statelessToolView(_renderAction);
|
|
11
9
|
function _renderAction({ call }) {
|
|
12
10
|
const root = el("div", "tf:flex tf:flex-col tf:gap-2");
|
|
@@ -21,7 +19,7 @@ function _renderAction({ call }) {
|
|
|
21
19
|
if (record !== null && Object.keys(record).length === 0) {
|
|
22
20
|
continue;
|
|
23
21
|
}
|
|
24
|
-
sections.push(captionRow(label),
|
|
22
|
+
sections.push(captionRow(label), valueRows(value));
|
|
25
23
|
}
|
|
26
24
|
if (sections.length > 0) {
|
|
27
25
|
root.append(card(sections));
|
|
@@ -52,32 +50,6 @@ function _appendFieldProblems(root, call) {
|
|
|
52
50
|
}
|
|
53
51
|
root.append(card(parts));
|
|
54
52
|
}
|
|
55
|
-
function _valueRows(value) {
|
|
56
|
-
const record = recordOf(value);
|
|
57
|
-
if (record !== null) {
|
|
58
|
-
return scrollRegion([
|
|
59
|
-
factRows(Object.entries(record).map(([key, entry]) => [key, _valueOf(entry)])),
|
|
60
|
-
]);
|
|
61
|
-
}
|
|
62
|
-
const wrapper = el("div", CARD_BLOCK_CLASS);
|
|
63
|
-
wrapper.dataset.tfActionValueBlock = "";
|
|
64
|
-
wrapper.append(el("pre", VALUE_PANE_CLASS, prettyPrintParameters(value)));
|
|
65
|
-
return scrollRegion([wrapper]);
|
|
66
|
-
}
|
|
67
|
-
function _valueOf(value) {
|
|
68
|
-
if (typeof value === "string") {
|
|
69
|
-
return value === "" ? "—" : value;
|
|
70
|
-
}
|
|
71
|
-
if (typeof value === "number" ||
|
|
72
|
-
typeof value === "boolean" ||
|
|
73
|
-
typeof value === "bigint") {
|
|
74
|
-
return String(value);
|
|
75
|
-
}
|
|
76
|
-
if (value === null || value === undefined) {
|
|
77
|
-
return "—";
|
|
78
|
-
}
|
|
79
|
-
return el("pre", VALUE_PANE_CLASS, prettyPrintParameters(value));
|
|
80
|
-
}
|
|
81
53
|
function _appendResponse(root, call) {
|
|
82
54
|
if (call.resultText === undefined) {
|
|
83
55
|
return;
|
|
@@ -88,24 +60,16 @@ function _appendResponse(root, call) {
|
|
|
88
60
|
return;
|
|
89
61
|
}
|
|
90
62
|
const rows = [];
|
|
91
|
-
|
|
92
|
-
if (envelope.status !== null) {
|
|
93
|
-
status = el("span", envelope.status >= 400
|
|
94
|
-
? "tf:font-medium tf:text-tf-destructive"
|
|
95
|
-
: "tf:text-tf-muted-foreground", `HTTP ${String(envelope.status)}`);
|
|
96
|
-
status.dataset.tfActionStatus = String(envelope.status);
|
|
97
|
-
}
|
|
63
|
+
const status = envelope.status === null ? undefined : statusBadgeOf(envelope.status);
|
|
98
64
|
rows.push(captionRow(envelope.truncated ? "Response (truncated)" : "Response", status));
|
|
99
65
|
if (envelope.truncated) {
|
|
100
66
|
rows.push(captionRow("The response body was truncated by the client to fit the size cap."));
|
|
101
67
|
}
|
|
102
|
-
if (envelope.body
|
|
103
|
-
envelope.body === null ||
|
|
104
|
-
envelope.body === "") {
|
|
68
|
+
if (isEmptyBody(envelope.body)) {
|
|
105
69
|
rows.push(captionRow("Empty response body."));
|
|
106
70
|
}
|
|
107
71
|
else {
|
|
108
|
-
rows.push(
|
|
72
|
+
rows.push(valueRows(envelope.body));
|
|
109
73
|
}
|
|
110
74
|
root.append(card(rows));
|
|
111
75
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { prettyPrintParameters } from "../pretty-print.js";
|
|
2
|
+
import { isEmptyBody, statusBadgeOf, valueBlock, valueRows, } from "./action-values.js";
|
|
3
|
+
import { actionEnvelopeOf, captionRow, card, el, factRows, note, recordOf, settledOutcomeNotesOf, scrollRegion, settledWithResult, statelessToolView, } from "./view-dom.js";
|
|
4
|
+
export const graphqlActionToolView = statelessToolView(_renderGraphQLAction);
|
|
5
|
+
function _renderGraphQLAction({ call }) {
|
|
6
|
+
const root = el("div", "tf:flex tf:flex-col tf:gap-2");
|
|
7
|
+
root.dataset.tfGraphqlActionView = "";
|
|
8
|
+
const variables = recordOf(call.args.variables);
|
|
9
|
+
if (variables !== null && Object.keys(variables).length > 0) {
|
|
10
|
+
root.append(card([captionRow("Variables"), _valuesOf(variables)]));
|
|
11
|
+
}
|
|
12
|
+
root.append(...settledOutcomeNotesOf(call));
|
|
13
|
+
if (settledWithResult(call)) {
|
|
14
|
+
_appendResponse(root, call);
|
|
15
|
+
}
|
|
16
|
+
return root;
|
|
17
|
+
}
|
|
18
|
+
function _appendResponse(root, call) {
|
|
19
|
+
const envelope = actionEnvelopeOf(call.resultText);
|
|
20
|
+
if (envelope === null) {
|
|
21
|
+
root.append(note("The response couldn't be read as an action result and isn't rendered."));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const response = recordOf(envelope.body);
|
|
25
|
+
const errors = Array.isArray(response?.errors) ? response.errors : [];
|
|
26
|
+
const graphqlShaped = _readsAsGraphQLResponse(response, errors);
|
|
27
|
+
const rows = [
|
|
28
|
+
captionRow(envelope.truncated ? "Response (truncated)" : "Response", _statusOf(envelope.status)),
|
|
29
|
+
];
|
|
30
|
+
if (envelope.truncated) {
|
|
31
|
+
rows.push(captionRow(_cutSentenceOf(graphqlShaped, errors, response)));
|
|
32
|
+
}
|
|
33
|
+
if (response === null || !graphqlShaped) {
|
|
34
|
+
rows.push(isEmptyBody(envelope.body)
|
|
35
|
+
? captionRow("Empty response body.")
|
|
36
|
+
: valueRows(envelope.body));
|
|
37
|
+
root.append(card(rows));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (errors.length > 0) {
|
|
41
|
+
rows.push(_errorsCaption(errors.length), _errorRows(errors));
|
|
42
|
+
}
|
|
43
|
+
const hasData = response.data !== null && response.data !== undefined;
|
|
44
|
+
rows.push(hasData ? captionRow("Data") : captionRow("Data", "none"));
|
|
45
|
+
if (hasData) {
|
|
46
|
+
rows.push(_valuesOf(_withoutCutMarker(response.data)));
|
|
47
|
+
}
|
|
48
|
+
root.append(card(rows));
|
|
49
|
+
}
|
|
50
|
+
function _readsAsGraphQLResponse(response, errors) {
|
|
51
|
+
return response !== null && (errors.length > 0 || "data" in response);
|
|
52
|
+
}
|
|
53
|
+
function _statusOf(status) {
|
|
54
|
+
return status === null || status === 200 ? undefined : statusBadgeOf(status);
|
|
55
|
+
}
|
|
56
|
+
function _cutSentenceOf(graphqlShaped, errors, response) {
|
|
57
|
+
if (!graphqlShaped || errors.length === 0) {
|
|
58
|
+
return "Too large to show in full: the response is shortened.";
|
|
59
|
+
}
|
|
60
|
+
return typeof response?.data === "string"
|
|
61
|
+
? "Too large to show in full: the data is shortened and any other response fields dropped; the errors are complete."
|
|
62
|
+
: "Too large to show in full: other response fields are dropped; the data and errors are complete.";
|
|
63
|
+
}
|
|
64
|
+
function _errorsCaption(count) {
|
|
65
|
+
const row = captionRow("");
|
|
66
|
+
row.replaceChildren(el("span", "tf:font-medium tf:text-tf-destructive", `${String(count)} ${count === 1 ? "error" : "errors"}`));
|
|
67
|
+
return row;
|
|
68
|
+
}
|
|
69
|
+
function _errorRows(errors) {
|
|
70
|
+
const rows = factRows(errors.map((error) => {
|
|
71
|
+
const record = recordOf(error);
|
|
72
|
+
const path = Array.isArray(record?.path)
|
|
73
|
+
? record.path.map(String).join(".")
|
|
74
|
+
: "";
|
|
75
|
+
const message = typeof record?.message === "string" && record.message !== ""
|
|
76
|
+
? record.message
|
|
77
|
+
: prettyPrintParameters(error);
|
|
78
|
+
return [
|
|
79
|
+
path === "" ? "—" : path.replaceAll(".", ".\u200b"),
|
|
80
|
+
message,
|
|
81
|
+
];
|
|
82
|
+
}));
|
|
83
|
+
rows.dataset.tfGraphqlErrors = String(errors.length);
|
|
84
|
+
return scrollRegion([rows]);
|
|
85
|
+
}
|
|
86
|
+
function _valuesOf(value) {
|
|
87
|
+
const record = recordOf(value);
|
|
88
|
+
const flat = record !== null &&
|
|
89
|
+
Object.values(record).every((entry) => entry === null || typeof entry !== "object");
|
|
90
|
+
return flat ? valueRows(value) : valueBlock(value);
|
|
91
|
+
}
|
|
92
|
+
const CUT_MARKER = /… \[truncated: [^\]]*\]$/;
|
|
93
|
+
function _withoutCutMarker(data) {
|
|
94
|
+
return typeof data === "string" ? data.replace(CUT_MARKER, "…") : data;
|
|
95
|
+
}
|
|
@@ -39,9 +39,41 @@ export interface ActionIntent {
|
|
|
39
39
|
query: Record<string, unknown>;
|
|
40
40
|
/**
|
|
41
41
|
* The JSON request body; absent when the action sends none. The cookies and
|
|
42
|
-
* headers adapters send it with Content-Type application/json.
|
|
42
|
+
* headers adapters send it with Content-Type application/json. For a
|
|
43
|
+
* GraphQL action it is the standard GraphQL request, {query, operationName,
|
|
44
|
+
* variables}.
|
|
43
45
|
*/
|
|
44
46
|
body?: unknown;
|
|
47
|
+
/**
|
|
48
|
+
* Present when the action is an operation of your app's GraphQL API, so a
|
|
49
|
+
* request adapter can run it through your own GraphQL client; absent for
|
|
50
|
+
* every other action.
|
|
51
|
+
*/
|
|
52
|
+
graphql?: ActionIntentGraphQL;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The GraphQL operation an action runs, one your app's own code defines. Run it
|
|
56
|
+
* through your app's GraphQL client. Looking it up by operationName among your
|
|
57
|
+
* app's own operations sends exactly the request your app sends; parsing
|
|
58
|
+
* document lets a client print it again, which a persisted-query allowlist may
|
|
59
|
+
* refuse.
|
|
60
|
+
*/
|
|
61
|
+
export interface ActionIntentGraphQL {
|
|
62
|
+
/** The operation's name in your app's code, such as "RenamePost". */
|
|
63
|
+
operationName: string;
|
|
64
|
+
/** Whether the operation is a "query" or a "mutation". */
|
|
65
|
+
operationType: "query" | "mutation";
|
|
66
|
+
/**
|
|
67
|
+
* The operation's GraphQL document as found in your app's code, with every
|
|
68
|
+
* fragment it uses; a client that sends parsed documents, such as Apollo
|
|
69
|
+
* Client, can parse and run it.
|
|
70
|
+
*/
|
|
71
|
+
document: string;
|
|
72
|
+
/**
|
|
73
|
+
* The operation's variables, already validated against the action's
|
|
74
|
+
* variables schema; empty when it takes none.
|
|
75
|
+
*/
|
|
76
|
+
variables: Record<string, unknown>;
|
|
45
77
|
}
|
|
46
78
|
/**
|
|
47
79
|
* For APIs authenticated by session cookies. The package fetches baseUrl plus
|
|
@@ -88,8 +120,10 @@ export interface RequestActionsAdapter {
|
|
|
88
120
|
kind: "request";
|
|
89
121
|
/**
|
|
90
122
|
* Performs the request and resolves with your API's response body, which
|
|
91
|
-
* the agent receives with status 200.
|
|
92
|
-
*
|
|
123
|
+
* the agent receives with status 200. For a GraphQL action, resolve the
|
|
124
|
+
* whole GraphQL response, {data, errors}, even when it carries errors.
|
|
125
|
+
* Reject (for example by throwing on a non-2xx response) to report the
|
|
126
|
+
* action as failed.
|
|
93
127
|
*/
|
|
94
128
|
execute: (intent: ActionIntent) => Promise<unknown>;
|
|
95
129
|
}
|
|
@@ -99,6 +133,10 @@ export interface RequestActionsAdapter {
|
|
|
99
133
|
* actions and tells the user to do them. The response's status and body (parsed
|
|
100
134
|
* as JSON, otherwise text) go back to the agent; a non-2xx status counts as
|
|
101
135
|
* your API's answer, not a failure, and bodies over about 16,000 characters are
|
|
102
|
-
* truncated.
|
|
136
|
+
* truncated. A GraphQL response that carries errors keeps all of them unless
|
|
137
|
+
* the errors alone exceed the limit: its other top-level fields such as
|
|
138
|
+
* extensions are dropped first, and its data is shortened only if it still does
|
|
139
|
+
* not fit. A GraphQL response without errors is truncated whole, like any other
|
|
140
|
+
* body.
|
|
103
141
|
*/
|
|
104
142
|
export type ActionsAdapter = CookiesActionsAdapter | HeadersActionsAdapter | RequestActionsAdapter;
|
package/dist/core/tool-view.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { actionToolView } from "../components/tool-views/action-view.js";
|
|
2
|
+
import { graphqlActionToolView } from "../components/tool-views/graphql-action-view.js";
|
|
2
3
|
import { commandToolView } from "../components/tool-views/command-view.js";
|
|
3
4
|
import { docsSearchToolView } from "../components/tool-views/docs-search-view.js";
|
|
4
5
|
import { fileEditToolView } from "../components/tool-views/file-edit-view.js";
|
|
@@ -10,6 +11,7 @@ export function isReservedToolViewKey(key) {
|
|
|
10
11
|
export const PACKAGE_TOOL_VIEWS = Object.freeze({
|
|
11
12
|
"teaflask.questions": { version: 1, view: questionsToolView },
|
|
12
13
|
"teaflask.action": { version: 1, view: actionToolView },
|
|
14
|
+
"teaflask.graphql-action": { version: 1, view: graphqlActionToolView },
|
|
13
15
|
"teaflask.command": { version: 1, view: commandToolView },
|
|
14
16
|
"teaflask.file-edit": { version: 1, view: fileEditToolView },
|
|
15
17
|
"teaflask.docs-search": { version: 1, view: docsSearchToolView },
|
|
@@ -90,6 +90,9 @@ export declare const ApiErrorCode: {
|
|
|
90
90
|
readonly ORG_ACTION_PROPOSAL_CHANGED: "ORG_ACTION_PROPOSAL_CHANGED";
|
|
91
91
|
readonly ORG_ACTION_PROPOSAL_NOT_EDITABLE: "ORG_ACTION_PROPOSAL_NOT_EDITABLE";
|
|
92
92
|
readonly ORG_ACTION_SCAN_ALREADY_RUNNING: "ORG_ACTION_SCAN_ALREADY_RUNNING";
|
|
93
|
+
readonly ORG_ACTION_SPEC_UPLOADS_NOT_AVAILABLE: "ORG_ACTION_SPEC_UPLOADS_NOT_AVAILABLE";
|
|
94
|
+
readonly ORG_ACTION_SPEC_UPLOAD_INVALID: "ORG_ACTION_SPEC_UPLOAD_INVALID";
|
|
95
|
+
readonly ORG_ACTION_SPEC_UPLOAD_TOO_LARGE: "ORG_ACTION_SPEC_UPLOAD_TOO_LARGE";
|
|
93
96
|
readonly ORG_SITEMAP_PAGE_INVALID: "ORG_SITEMAP_PAGE_INVALID";
|
|
94
97
|
readonly ORG_SITEMAP_ROUTE_ALREADY_REGISTERED: "ORG_SITEMAP_ROUTE_ALREADY_REGISTERED";
|
|
95
98
|
readonly ORG_SITEMAP_PAGE_ALREADY_ARCHIVED: "ORG_SITEMAP_PAGE_ALREADY_ARCHIVED";
|
|
@@ -141,6 +144,12 @@ export declare const ApiErrorCode: {
|
|
|
141
144
|
readonly SSO_REQUIRED_FOR_DOMAIN: "SSO_REQUIRED_FOR_DOMAIN";
|
|
142
145
|
readonly SSO_CONNECTION_NOT_ACTIVE: "SSO_CONNECTION_NOT_ACTIVE";
|
|
143
146
|
readonly EDGE_ORIGIN_REQUIRED: "EDGE_ORIGIN_REQUIRED";
|
|
147
|
+
readonly MCP_CONNECTIONS_NOT_AVAILABLE: "MCP_CONNECTIONS_NOT_AVAILABLE";
|
|
148
|
+
readonly MCP_AUTHORIZATION_EXPIRED: "MCP_AUTHORIZATION_EXPIRED";
|
|
149
|
+
readonly MCP_CAPABILITY_ABOVE_ROLE: "MCP_CAPABILITY_ABOVE_ROLE";
|
|
150
|
+
readonly MCP_CAPABILITIES_INVALID: "MCP_CAPABILITIES_INVALID";
|
|
151
|
+
readonly MCP_AUTHORIZATION_SERVER_UNAVAILABLE: "MCP_AUTHORIZATION_SERVER_UNAVAILABLE";
|
|
152
|
+
readonly MCP_CONNECTION_CONFLICT: "MCP_CONNECTION_CONFLICT";
|
|
144
153
|
readonly DEMO_ERROR: "DEMO_ERROR";
|
|
145
154
|
readonly HTTP_ERROR: "HTTP_ERROR";
|
|
146
155
|
readonly INTERNAL_ERROR: "INTERNAL_ERROR";
|
|
@@ -89,6 +89,9 @@ export const ApiErrorCode = {
|
|
|
89
89
|
ORG_ACTION_PROPOSAL_CHANGED: "ORG_ACTION_PROPOSAL_CHANGED",
|
|
90
90
|
ORG_ACTION_PROPOSAL_NOT_EDITABLE: "ORG_ACTION_PROPOSAL_NOT_EDITABLE",
|
|
91
91
|
ORG_ACTION_SCAN_ALREADY_RUNNING: "ORG_ACTION_SCAN_ALREADY_RUNNING",
|
|
92
|
+
ORG_ACTION_SPEC_UPLOADS_NOT_AVAILABLE: "ORG_ACTION_SPEC_UPLOADS_NOT_AVAILABLE",
|
|
93
|
+
ORG_ACTION_SPEC_UPLOAD_INVALID: "ORG_ACTION_SPEC_UPLOAD_INVALID",
|
|
94
|
+
ORG_ACTION_SPEC_UPLOAD_TOO_LARGE: "ORG_ACTION_SPEC_UPLOAD_TOO_LARGE",
|
|
92
95
|
ORG_SITEMAP_PAGE_INVALID: "ORG_SITEMAP_PAGE_INVALID",
|
|
93
96
|
ORG_SITEMAP_ROUTE_ALREADY_REGISTERED: "ORG_SITEMAP_ROUTE_ALREADY_REGISTERED",
|
|
94
97
|
ORG_SITEMAP_PAGE_ALREADY_ARCHIVED: "ORG_SITEMAP_PAGE_ALREADY_ARCHIVED",
|
|
@@ -140,6 +143,12 @@ export const ApiErrorCode = {
|
|
|
140
143
|
SSO_REQUIRED_FOR_DOMAIN: "SSO_REQUIRED_FOR_DOMAIN",
|
|
141
144
|
SSO_CONNECTION_NOT_ACTIVE: "SSO_CONNECTION_NOT_ACTIVE",
|
|
142
145
|
EDGE_ORIGIN_REQUIRED: "EDGE_ORIGIN_REQUIRED",
|
|
146
|
+
MCP_CONNECTIONS_NOT_AVAILABLE: "MCP_CONNECTIONS_NOT_AVAILABLE",
|
|
147
|
+
MCP_AUTHORIZATION_EXPIRED: "MCP_AUTHORIZATION_EXPIRED",
|
|
148
|
+
MCP_CAPABILITY_ABOVE_ROLE: "MCP_CAPABILITY_ABOVE_ROLE",
|
|
149
|
+
MCP_CAPABILITIES_INVALID: "MCP_CAPABILITIES_INVALID",
|
|
150
|
+
MCP_AUTHORIZATION_SERVER_UNAVAILABLE: "MCP_AUTHORIZATION_SERVER_UNAVAILABLE",
|
|
151
|
+
MCP_CONNECTION_CONFLICT: "MCP_CONNECTION_CONFLICT",
|
|
143
152
|
DEMO_ERROR: "DEMO_ERROR",
|
|
144
153
|
HTTP_ERROR: "HTTP_ERROR",
|
|
145
154
|
INTERNAL_ERROR: "INTERNAL_ERROR",
|
package/dist/headless.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { TeaflaskAssistantProvider, type TeaflaskAssistantAnonymousProps, type TeaflaskAssistantIdentityProps, type TeaflaskAssistantProviderBaseProps, type TeaflaskAssistantProviderProps, } from "./components/teaflask-assistant-provider.js";
|
|
2
2
|
export type { AssistantTheme, AssistantThemeDarkTokens, AssistantThemeMode, AssistantThemeTokens, } from "./appearance/theme.js";
|
|
3
|
-
export type { ActionsAdapter, ActionIntent, ActionIntentAction, CookiesActionsAdapter, HeadersActionsAdapter, RequestActionsAdapter, } from "./contract/actions-adapter.js";
|
|
3
|
+
export type { ActionsAdapter, ActionIntent, ActionIntentAction, ActionIntentGraphQL, CookiesActionsAdapter, HeadersActionsAdapter, RequestActionsAdapter, } from "./contract/actions-adapter.js";
|
|
4
4
|
export type { ApprovalSubmitTelemetryEvent, AssistantTelemetryEvent, StreamFailedTelemetryEvent, } from "./contract/telemetry.js";
|
|
5
5
|
export type { AssistantSuggestion, AssistantSuggestionInput, AssistantSuggestionKind, } from "./components/conversation-view.js";
|
|
6
6
|
export type { IconAdapter, JsonSchema, ToolViewAdapter, ToolViewCall, ToolViewInstance, ToolViewProps, ToolViewRegistration, ToolViewRegistry, } from "./core/tool-view.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type { AssistantActivityResult, AssistantActivitySnapshot, } from "./core
|
|
|
9
9
|
export type { PendingDecisionGap } from "./core/pending-decision-gap.js";
|
|
10
10
|
export { resetAssistant } from "./persistence/stored-thread.js";
|
|
11
11
|
export { ServingApiError } from "./transport/serving-error.js";
|
|
12
|
-
export type { ActionIntent, ActionIntentAction, ActionsAdapter, CookiesActionsAdapter, HeadersActionsAdapter, RequestActionsAdapter, } from "./contract/actions-adapter.js";
|
|
12
|
+
export type { ActionIntent, ActionIntentAction, ActionIntentGraphQL, ActionsAdapter, CookiesActionsAdapter, HeadersActionsAdapter, RequestActionsAdapter, } from "./contract/actions-adapter.js";
|
|
13
13
|
export type { ServingErrorCode, ServingErrorCodeOnTheWire, } from "./contract/errors.js";
|
|
14
14
|
export { ASSISTANT_MESSAGE_MAX_CHARS, type ServingAssistantThread, type TurnStatus, } from "./contract/threads.js";
|
|
15
15
|
export { activeFoldElapsedOf, settledFoldDurationOf, type BlockTiming, type SegmentDuration, } from "./core/segment-timing.js";
|
|
@@ -27,19 +27,96 @@ export function actionIntentOf(wire) {
|
|
|
27
27
|
if (wire.body !== null && wire.body !== undefined) {
|
|
28
28
|
intent.body = wire.body;
|
|
29
29
|
}
|
|
30
|
+
const graphql = _graphqlOf(wire);
|
|
31
|
+
if (graphql !== null) {
|
|
32
|
+
intent.graphql = graphql;
|
|
33
|
+
}
|
|
30
34
|
return intent;
|
|
31
35
|
}
|
|
36
|
+
function _graphqlOf(wire) {
|
|
37
|
+
const descriptor = _recordOf(wire.graphql);
|
|
38
|
+
const body = _recordOf(wire.body);
|
|
39
|
+
if (descriptor === null || body === null) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const halves = {
|
|
43
|
+
operationName: _nonEmptyStringOf(descriptor.operation_name),
|
|
44
|
+
operationType: descriptor.operation_type,
|
|
45
|
+
document: _nonEmptyStringOf(body.query),
|
|
46
|
+
variables: body.variables === undefined || body.variables === null
|
|
47
|
+
? {}
|
|
48
|
+
: _recordOf(body.variables),
|
|
49
|
+
};
|
|
50
|
+
if (!_descriptorAgreesWithBody(halves, body.operationName)) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const { operationName, operationType, document, variables } = halves;
|
|
54
|
+
return { operationName, operationType, document, variables };
|
|
55
|
+
}
|
|
56
|
+
function _descriptorAgreesWithBody(halves, bodyOperationName) {
|
|
57
|
+
return (halves.operationName !== null &&
|
|
58
|
+
halves.operationName === bodyOperationName &&
|
|
59
|
+
(halves.operationType === "query" || halves.operationType === "mutation") &&
|
|
60
|
+
halves.document !== null &&
|
|
61
|
+
halves.variables !== null);
|
|
62
|
+
}
|
|
32
63
|
export async function performActionIntent(adapter, intent) {
|
|
64
|
+
const land = intent.graphql === undefined ? _landableBodyOf : _landableGraphQLBodyOf;
|
|
33
65
|
if (adapter.kind === "request") {
|
|
34
|
-
return _intentResultOf(200, await adapter.execute(intent));
|
|
66
|
+
return _intentResultOf(200, land(await adapter.execute(intent)));
|
|
35
67
|
}
|
|
36
68
|
const response = await fetch(_urlOf(adapter.baseUrl, intent), await _requestInitOf(adapter, intent));
|
|
37
|
-
return _resultOf(response);
|
|
69
|
+
return _resultOf(response, land);
|
|
38
70
|
}
|
|
39
|
-
function _intentResultOf(status,
|
|
40
|
-
const landed = _landableBodyOf(rawBody);
|
|
71
|
+
function _intentResultOf(status, landed) {
|
|
41
72
|
return { status, body: landed.body, truncated: landed.truncated };
|
|
42
73
|
}
|
|
74
|
+
function _landableGraphQLBodyOf(body) {
|
|
75
|
+
const whole = _landableBodyOf(body);
|
|
76
|
+
const errored = whole.truncated ? _erroredResponseOf(body) : null;
|
|
77
|
+
if (errored === null) {
|
|
78
|
+
return whole;
|
|
79
|
+
}
|
|
80
|
+
return (_errorsAndDataWhole(errored) ??
|
|
81
|
+
_errorsWithDataCut(errored) ??
|
|
82
|
+
_wholeCutErrorsFirst(errored));
|
|
83
|
+
}
|
|
84
|
+
function _erroredResponseOf(body) {
|
|
85
|
+
const response = _recordOf(body);
|
|
86
|
+
if (response === null) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const errors = JSON.parse(JSON.stringify(response.errors ?? null));
|
|
90
|
+
if (!Array.isArray(errors) || errors.length === 0) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return { errors, data: response.data ?? null, response };
|
|
94
|
+
}
|
|
95
|
+
function _errorsAndDataWhole({ errors, data, }) {
|
|
96
|
+
const kept = { errors, data };
|
|
97
|
+
return doorLengthCeilingOf(JSON.stringify(kept)) <= RESPONSE_BODY_MAX_CHARS
|
|
98
|
+
? { body: kept, truncated: true }
|
|
99
|
+
: null;
|
|
100
|
+
}
|
|
101
|
+
function _errorsWithDataCut({ errors, data, }) {
|
|
102
|
+
const dataBudget = RESPONSE_BODY_MAX_CHARS -
|
|
103
|
+
doorLengthCeilingOf(JSON.stringify({ errors, data: "" })) -
|
|
104
|
+
doorCostOfText(TRUNCATION_NOTICE);
|
|
105
|
+
if (dataBudget < 0) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const dataText = JSON.stringify(data);
|
|
109
|
+
const bounded = boundedTextOf(dataText, Math.min(dataBudget, doorCostOfText(dataText) - 1));
|
|
110
|
+
return {
|
|
111
|
+
body: { errors, data: bounded + TRUNCATION_NOTICE },
|
|
112
|
+
truncated: true,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function _wholeCutErrorsFirst({ errors, response, }) {
|
|
116
|
+
const rest = { ...response };
|
|
117
|
+
delete rest.errors;
|
|
118
|
+
return _landableBodyOf(JSON.stringify({ errors, ...rest }));
|
|
119
|
+
}
|
|
43
120
|
function _landableBodyOf(body) {
|
|
44
121
|
if (body === undefined) {
|
|
45
122
|
return { body: null, truncated: false };
|
|
@@ -105,8 +182,8 @@ function _queryStringOf(query) {
|
|
|
105
182
|
const encoded = parameters.toString();
|
|
106
183
|
return encoded === "" ? "" : `?${encoded}`;
|
|
107
184
|
}
|
|
108
|
-
async function _resultOf(response) {
|
|
109
|
-
return _intentResultOf(response.status, _jsonOrTextOf(await response.text()));
|
|
185
|
+
async function _resultOf(response, land) {
|
|
186
|
+
return _intentResultOf(response.status, land(_jsonOrTextOf(await response.text())));
|
|
110
187
|
}
|
|
111
188
|
function _jsonOrTextOf(text) {
|
|
112
189
|
try {
|
package/package.json
CHANGED