@copilotkit/vue 1.69.0 → 1.69.2
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/PARITY.md +1 -0
- package/README.md +1 -1
- package/dist/components/copilot-provider/types.d.ts +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +2 -2
- package/dist/use-render-activity-message-CIlNtfvE.cjs +85 -0
- package/dist/use-render-activity-message-CIlNtfvE.cjs.map +1 -0
- package/dist/{use-render-activity-message-BQADVWev.js → use-render-activity-message-Db2rOZkW.js} +2810 -2730
- package/dist/use-render-activity-message-Db2rOZkW.js.map +1 -0
- package/dist/v2/components/OpenGenerativeUIRenderer.d.ts +1 -1
- package/dist/v2/components/OpenGenerativeUIRenderer.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
- package/dist/v2/hooks/use-threads.d.ts +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.mjs +1 -1
- package/dist/v2/providers/CopilotKitProvider.types.d.ts +1 -1
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
- package/dist/v2/providers/use-inspector-thread-override.d.ts +10 -0
- package/dist/v2/providers/use-inspector-thread-override.d.ts.map +1 -0
- package/package.json +5 -5
- package/src/components/copilot-provider/types.ts +1 -1
- package/src/v2/components/OpenGenerativeUIRenderer.ts +2 -9
- package/src/v2/components/chat/CopilotChat.vue +64 -4
- package/src/v2/hooks/use-threads.ts +1 -1
- package/src/v2/providers/CopilotKitProvider.types.ts +1 -1
- package/src/v2/providers/__tests__/inspector-thread-override.test.ts +214 -0
- package/src/v2/providers/use-inspector-thread-override.ts +99 -0
- package/dist/use-render-activity-message-BQADVWev.js.map +0 -1
- package/dist/use-render-activity-message-DYb_lsQs.cjs +0 -85
- package/dist/use-render-activity-message-DYb_lsQs.cjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OpenGenerativeUIRenderer.d.ts","sourceRoot":"","sources":["../../../src/v2/components/OpenGenerativeUIRenderer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OpenGenerativeUIRenderer.d.ts","sourceRoot":"","sources":["../../../src/v2/components/OpenGenerativeUIRenderer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOlD,eAAO,MAAM,4BAA4B,uBAAuB,CAAC;AAEjE,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWxC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAC3C,OAAO,6BAA6B,CACrC,CAAC;AAEF,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;EAOxC,CAAC;AAEH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAC3C,OAAO,6BAA6B,CACrC,CAAC;AA0DF,eAAO,MAAM,wBAAwB;;cAIf,QAAQ,CAAC,uBAAuB,CAAC;;;;;;;cAAjC,QAAQ,CAAC,uBAAuB,CAAC;;;iGAsXrD,CAAC;AAEH,eAAO,MAAM,gCAAgC;;;;;;cAKvB,QAAQ,CAAC,uBAAuB,CAAC;;;;cAGxB,QAAQ,CAAC,OAAO,CAAC;;;;cAE1B,QAAQ,CAAC,OAAO,CAAC;;;;;;;;;;;;cALjB,QAAQ,CAAC,uBAAuB,CAAC;;;;cAGxB,QAAQ,CAAC,OAAO,CAAC;;;;cAE1B,QAAQ,CAAC,OAAO,CAAC;;;;;;4EAQrC,CAAC;AAEH,eAAO,MAAM,4BAA4B;;;;;;cAKnB,QAAQ,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;;;;cAGlC,QAAQ,CAAC,cAAc,CAAC;;;;cACxB,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC;;;;;;;;;;;cAJpC,QAAQ,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;;;;cAGlC,QAAQ,CAAC,cAAc,CAAC;;;;cACxB,QAAQ,CAAC,MAAM,GAAG,SAAS,CAAC;;;iGAmCxD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CopilotChat.vue.d.ts","sourceRoot":"","sources":["../../../../src/v2/components/chat/CopilotChat.vue"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CopilotChat.vue.d.ts","sourceRoot":"","sources":["../../../../src/v2/components/chat/CopilotChat.vue"],"names":[],"mappings":"AA43BA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAiBnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,KAAK,EACV,yBAAyB,EACzB,gBAAgB,EAChB,6BAA6B,EAC7B,kCAAkC,EAClC,gCAAgC,EAChC,iCAAiC,EAClC,MAAM,SAAS,CAAC;AAcjB,KAAK,WAAW,GAAG;IACjB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,gCAAgC,KAAK,OAAO,CAAC;IACnE,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE;QACvB,QAAQ,EAAE,OAAO,EAAE,CAAC;QACpB,SAAS,EAAE,OAAO,CAAC;KACpB,KAAK,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,6BAA6B,KAAK,OAAO,CAAC;IAC9D,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,OAAO,CAAC;IACtD,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,kCAAkC,KAAK,OAAO,CAAC;IAC3E,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,iCAAiC,KAAK,OAAO,CAAC;IACzE,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC;IAElC,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC;CACtD,CAAC;AAk3BF,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;6EAQnB,CAAC;wBACkB,eAAe,CAAC,OAAO,eAAe,EAAE,WAAW,CAAC;AAAzE,wBAA0E;AAa1E,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
|
|
@@ -50,7 +50,7 @@ export interface UseThreadsResult {
|
|
|
50
50
|
deleteThread: (threadId: string) => Promise<void>;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
* Vue composable for listing and managing Intelligence
|
|
53
|
+
* Vue composable for listing and managing CopilotKit Intelligence threads.
|
|
54
54
|
*
|
|
55
55
|
* The hook fetches threads for the runtime-authenticated user and the given
|
|
56
56
|
* `agentId`, then keeps the result in sync via the core thread store's realtime
|
package/dist/v2/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("@copilotkit/core"),i=require("@ag-ui/client"),e=require("../use-render-activity-message-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("@copilotkit/core"),i=require("@ag-ui/client"),e=require("../use-render-activity-message-CIlNtfvE.cjs"),n=require("@copilotkit/shared");exports.A2UIActivityContentSchema=e.A2UIActivityContentSchema;exports.A2UISurfaceActivityRenderer=e._sfc_main;exports.A2UISurfaceActivityType=e.A2UISurfaceActivityType;exports.CopilotChat=e.CopilotChat;exports.CopilotChatAssistantMessage=e._sfc_main$1;exports.CopilotChatAttachmentQueue=e._sfc_main$2;exports.CopilotChatAttachmentRenderer=e._sfc_main$3;exports.CopilotChatAudioRecorder=e._sfc_main$4;exports.CopilotChatConfigurationProvider=e._sfc_main$5;exports.CopilotChatDefaultLabels=e.CopilotChatDefaultLabels;exports.CopilotChatInput=e._sfc_main$6;exports.CopilotChatMessageView=e._sfc_main$7;exports.CopilotChatReasoningMessage=e._sfc_main$8;exports.CopilotChatSuggestionPill=e._sfc_main$9;exports.CopilotChatSuggestionView=e._sfc_main$10;exports.CopilotChatToggleButton=e.CopilotChatToggleButton;exports.CopilotChatToggleButtonCloseIcon=e.CopilotChatToggleButtonCloseIcon;exports.CopilotChatToggleButtonOpenIcon=e.CopilotChatToggleButtonOpenIcon;exports.CopilotChatToolCallsView=e._sfc_main$11;exports.CopilotChatUserMessage=e._sfc_main$12;exports.CopilotChatView=e._sfc_main$13;exports.CopilotKitCoreVue=e.CopilotKitCoreVue;exports.CopilotKitInspector=e._sfc_main$14;exports.CopilotKitProvider=e._sfc_main$15;exports.CopilotModalHeader=e.CopilotModalHeader;exports.CopilotPopup=e._sfc_main$16;exports.CopilotPopupView=e.CopilotPopupView;exports.CopilotSidebar=e._sfc_main$17;exports.CopilotSidebarView=e.CopilotSidebarView;exports.CopilotThreadsDrawer=e._sfc_main$18;exports.GenerateSandboxedUiArgsSchema=e.GenerateSandboxedUiArgsSchema;exports.LicenseContextKey=e.LicenseContextKey;exports.MCPAppsActivityContentSchema=e.MCPAppsActivityContentSchema;exports.MCPAppsActivityRenderer=e.MCPAppsActivityRenderer;exports.MCPAppsActivityType=e.MCPAppsActivityType;exports.OpenGenerativeUIActivityRenderer=e.OpenGenerativeUIActivityRenderer;exports.OpenGenerativeUIActivityType=e.OpenGenerativeUIActivityType;exports.OpenGenerativeUIContentSchema=e.OpenGenerativeUIContentSchema;exports.OpenGenerativeUIRenderer=e.OpenGenerativeUIRenderer;exports.OpenGenerativeUIToolRenderer=e.OpenGenerativeUIToolRenderer;exports.UseAgentUpdate=e.UseAgentUpdate;exports.createA2UIMessageRenderer=e.createA2UIMessageRenderer;exports.createDefaultLicenseRef=e.createDefaultLicenseRef;exports.defineToolCallRenderer=e.defineToolCallRenderer;exports.extractCompleteStyles=e.extractCompleteStyles;exports.getOperationSurfaceId=e.getOperationSurfaceId;exports.processPartialHtml=e.processPartialHtml;exports.useAgent=e.useAgent;exports.useAgentContext=e.useAgentContext;exports.useAttachments=e.useAttachments;exports.useCapabilities=e.useCapabilities;exports.useComponent=e.useComponent;exports.useConfigureSuggestions=e.useConfigureSuggestions;exports.useCopilotChatConfiguration=e.useCopilotChatConfiguration;exports.useCopilotKit=e.useCopilotKit;exports.useDefaultRenderTool=e.useDefaultRenderTool;exports.useFrontendTool=e.useFrontendTool;exports.useHumanInTheLoop=e.useHumanInTheLoop;exports.useInterrupt=e.useInterrupt;exports.useKatexStyles=e.useKatexStyles;exports.useKeyboardHeight=e.useKeyboardHeight;exports.useLicenseContext=e.useLicenseContext;exports.useRenderActivityMessage=e.useRenderActivityMessage;exports.useRenderCustomMessages=e.useRenderCustomMessages;exports.useRenderTool=e.useRenderTool;exports.useSandboxFunctions=e.useSandboxFunctions;exports.useSuggestions=e.useSuggestions;exports.useThreads=e.useThreads;exports.vueBasicCatalog=e.vueBasicCatalog;Object.defineProperty(exports,"createLicenseContextValue",{enumerable:!0,get:()=>n.createLicenseContextValue});Object.keys(o).forEach(t=>{t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:()=>o[t]})});Object.keys(i).forEach(t=>{t!=="default"&&!Object.prototype.hasOwnProperty.call(exports,t)&&Object.defineProperty(exports,t,{enumerable:!0,get:()=>i[t]})});
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/v2/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from "@copilotkit/core";
|
|
2
2
|
export * from "@ag-ui/client";
|
|
3
|
-
import { A as o, _ as i, a as n, C, b as r, c as p, d as l, e as u, f as c, g, h as d, i as h, j as A, k as v, l as S, m, n as y, o as I, p as f, q as R, r as T, s as U, t as x, u as M, v as P, w as b, x as V, y as w, z as K, B as O, G, L, M as B, D, E as H, O as F, F as Q, H as j, I as k, J as q, U as z, K as E, N as J, P as N, Q as W, R as X, S as Y, T as Z, V as _, W as $, X as ee, Y as ae, Z as te, $ as se, a0 as oe, a1 as ie, a2 as ne, a3 as Ce, a4 as re, a5 as pe, a6 as le, a7 as ue, a8 as ce, a9 as ge, aa as de, ab as he, ac as Ae, ad as ve, ae as Se } from "../use-render-activity-message-
|
|
3
|
+
import { A as o, _ as i, a as n, C, b as r, c as p, d as l, e as u, f as c, g, h as d, i as h, j as A, k as v, l as S, m, n as y, o as I, p as f, q as R, r as T, s as U, t as x, u as M, v as P, w as b, x as V, y as w, z as K, B as O, G, L, M as B, D, E as H, O as F, F as Q, H as j, I as k, J as q, U as z, K as E, N as J, P as N, Q as W, R as X, S as Y, T as Z, V as _, W as $, X as ee, Y as ae, Z as te, $ as se, a0 as oe, a1 as ie, a2 as ne, a3 as Ce, a4 as re, a5 as pe, a6 as le, a7 as ue, a8 as ce, a9 as ge, aa as de, ab as he, ac as Ae, ad as ve, ae as Se } from "../use-render-activity-message-Db2rOZkW.js";
|
|
4
4
|
import { createLicenseContextValue as ye } from "@copilotkit/shared";
|
|
5
5
|
export {
|
|
6
6
|
o as A2UIActivityContentSchema,
|
|
@@ -11,7 +11,7 @@ export interface CopilotKitProviderProps {
|
|
|
11
11
|
publicApiKey?: string;
|
|
12
12
|
publicLicenseKey?: string;
|
|
13
13
|
/**
|
|
14
|
-
* Signed license token for offline verification of
|
|
14
|
+
* Signed license token for offline verification of CopilotKit Intelligence features.
|
|
15
15
|
* Obtain from https://dashboard.operations.copilotkit.ai.
|
|
16
16
|
*/
|
|
17
17
|
licenseToken?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CopilotKitProvider.vue.d.ts","sourceRoot":"","sources":["../../../src/v2/providers/CopilotKitProvider.vue"],"names":[],"mappings":"AAsqBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAiCnD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAC1B,eAAe,EACf,iBAAiB,EAElB,MAAM,UAAU,CAAC;AAuqBlB,QAAA,IAAI,OAAO,IAAW,CAAE;AACxB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AA2B/C,QAAA,MAAM,eAAe;;;;;;;;;;;wBAtvBrB,
|
|
1
|
+
{"version":3,"file":"CopilotKitProvider.vue.d.ts","sourceRoot":"","sources":["../../../src/v2/providers/CopilotKitProvider.vue"],"names":[],"mappings":"AAsqBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAiCnD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAC1B,eAAe,EACf,iBAAiB,EAElB,MAAM,UAAU,CAAC;AAuqBlB,QAAA,IAAI,OAAO,IAAW,CAAE;AACxB,KAAK,WAAW,GAAG,EAAE,GACnB;IAAE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,KAAK,GAAG,CAAA;CAAE,CAAC;AA2B/C,QAAA,MAAM,eAAe;;;;;;;;;;;wBAtvBrB,CApnBgB;mBAonBhB,CAnnByB;;;;aAmnBzB,CAhnB2B;eAgnB3B,CAhnBoD;wBAgnBpD,CA/mBK;qBA+mBL,CA7mBG;;6EA02CD,CAAC;wBACkB,eAAe,CAAC,OAAO,eAAe,EAAE,WAAW,CAAC;AAAzE,wBAA0E;AAa1E,KAAK,eAAe,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG;IAChC,QAAO;QACN,MAAM,EAAE,CAAC,CAAC;KAEV,CAAA;CACD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Ref } from "vue";
|
|
2
|
+
export declare function useInspectorThreadOverride(args: {
|
|
3
|
+
agentId: Ref<string>;
|
|
4
|
+
baseThreadId: Ref<string>;
|
|
5
|
+
}): {
|
|
6
|
+
inspectorThreadId: Ref<string | null>;
|
|
7
|
+
inspectorRequestId: Ref<string | null>;
|
|
8
|
+
failInspectorOverride: (requestId: string) => void;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=use-inspector-thread-override.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-inspector-thread-override.d.ts","sourceRoot":"","sources":["../../../src/v2/providers/use-inspector-thread-override.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAgB/B,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACrB,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC3B,GAAG;IACF,iBAAiB,EAAE,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACtC,kBAAkB,EAAE,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,qBAAqB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACpD,CA0EA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/vue",
|
|
3
|
-
"version": "1.69.
|
|
3
|
+
"version": "1.69.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Vue 3 components and composables for CopilotKit",
|
|
6
6
|
"keywords": [
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
"streamdown-vue": "^1.0.29",
|
|
71
71
|
"zod": "^3.25.75",
|
|
72
72
|
"zod-to-json-schema": "^3.24.5",
|
|
73
|
-
"@copilotkit/
|
|
74
|
-
"@copilotkit/web-components": "1.69.
|
|
75
|
-
"@copilotkit/
|
|
76
|
-
"@copilotkit/
|
|
73
|
+
"@copilotkit/core": "1.69.2",
|
|
74
|
+
"@copilotkit/web-components": "1.69.2",
|
|
75
|
+
"@copilotkit/shared": "1.69.2",
|
|
76
|
+
"@copilotkit/web-inspector": "1.69.2"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@eslint/js": "^9.39.2",
|
|
@@ -13,7 +13,7 @@ export interface CopilotKitProps extends CopilotKitProviderProps {
|
|
|
13
13
|
publicApiKey?: string;
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
* Your public license key for accessing
|
|
16
|
+
* Your public license key for accessing CopilotKit Intelligence features.
|
|
17
17
|
*/
|
|
18
18
|
publicLicenseKey?: string;
|
|
19
19
|
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
defineComponent,
|
|
4
|
-
h,
|
|
5
|
-
onBeforeUnmount,
|
|
6
|
-
ref,
|
|
7
|
-
watch,
|
|
8
|
-
type PropType,
|
|
9
|
-
} from "vue";
|
|
1
|
+
import { computed, defineComponent, h, onBeforeUnmount, ref, watch } from "vue";
|
|
2
|
+
import type { PropType } from "vue";
|
|
10
3
|
import { z } from "zod";
|
|
11
4
|
import { ToolCallStatus } from "@copilotkit/core";
|
|
12
5
|
import {
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
useSlots,
|
|
21
21
|
watch,
|
|
22
22
|
} from "vue";
|
|
23
|
+
import { CopilotKitCoreErrorCode } from "@copilotkit/core";
|
|
23
24
|
import type { Suggestion } from "@copilotkit/core";
|
|
24
25
|
import CopilotChatConfigurationProvider from "../../providers/CopilotChatConfigurationProvider.vue";
|
|
25
26
|
import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
|
|
@@ -39,6 +40,7 @@ import { LastUserMessageKey } from "./last-user-message-context";
|
|
|
39
40
|
import type { LastUserMessageState } from "./last-user-message-context";
|
|
40
41
|
import type { Message } from "@ag-ui/core";
|
|
41
42
|
import type { InputContent } from "@copilotkit/shared";
|
|
43
|
+
import { useInspectorThreadOverride } from "../../providers/use-inspector-thread-override";
|
|
42
44
|
import type {
|
|
43
45
|
CopilotChatInputSlotProps,
|
|
44
46
|
CopilotChatProps,
|
|
@@ -105,6 +107,7 @@ type ActiveConnectCycle = {
|
|
|
105
107
|
core: object;
|
|
106
108
|
agent: AbstractAgent;
|
|
107
109
|
threadId: string;
|
|
110
|
+
inspectorRequestId: string | null;
|
|
108
111
|
abortController: AbortController;
|
|
109
112
|
detached: boolean;
|
|
110
113
|
};
|
|
@@ -122,16 +125,27 @@ const resolvedAgentId = computed(
|
|
|
122
125
|
const providedThreadId = computed(
|
|
123
126
|
() => props.threadId ?? existingConfig.value?.threadId,
|
|
124
127
|
);
|
|
125
|
-
const
|
|
128
|
+
const baseThreadId = computed(
|
|
126
129
|
() => providedThreadId.value ?? generatedThreadId.value,
|
|
127
130
|
);
|
|
128
131
|
// "Explicit" means the caller actually picked this thread — via the
|
|
129
132
|
// `threadId` prop on CopilotChat or a wrapping provider that flagged its
|
|
130
133
|
// threadId as caller-chosen. An auto-minted UUID leaking down through a
|
|
131
134
|
// CopilotChatConfigurationProvider does NOT count.
|
|
132
|
-
const
|
|
135
|
+
const baseHasExplicitThreadId = computed(
|
|
133
136
|
() => !!props.threadId || !!existingConfig.value?.hasExplicitThreadId,
|
|
134
137
|
);
|
|
138
|
+
const { inspectorThreadId, inspectorRequestId, failInspectorOverride } =
|
|
139
|
+
useInspectorThreadOverride({
|
|
140
|
+
agentId: resolvedAgentId,
|
|
141
|
+
baseThreadId,
|
|
142
|
+
});
|
|
143
|
+
const resolvedThreadId = computed(
|
|
144
|
+
() => inspectorThreadId.value ?? baseThreadId.value,
|
|
145
|
+
);
|
|
146
|
+
const hasExplicitThreadId = computed(
|
|
147
|
+
() => inspectorThreadId.value !== null || baseHasExplicitThreadId.value,
|
|
148
|
+
);
|
|
135
149
|
const lastConnectedThreadId = ref<string | null>(null);
|
|
136
150
|
const isConnecting = computed(
|
|
137
151
|
() =>
|
|
@@ -297,6 +311,28 @@ watch(
|
|
|
297
311
|
{ immediate: true },
|
|
298
312
|
);
|
|
299
313
|
|
|
314
|
+
watch(
|
|
315
|
+
[
|
|
316
|
+
() => copilotkit.value,
|
|
317
|
+
resolvedAgentId,
|
|
318
|
+
resolvedThreadId,
|
|
319
|
+
inspectorRequestId,
|
|
320
|
+
],
|
|
321
|
+
([core, agentId, threadId, requestId], _old, onCleanup) => {
|
|
322
|
+
if (!requestId) return;
|
|
323
|
+
const subscription = core.subscribe({
|
|
324
|
+
onError: (event) => {
|
|
325
|
+
if (event.code !== CopilotKitCoreErrorCode.AGENT_CONNECT_FAILED) return;
|
|
326
|
+
if (event.context?.agentId !== agentId) return;
|
|
327
|
+
if (event.context?.threadId !== threadId) return;
|
|
328
|
+
failInspectorOverride(requestId);
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
onCleanup(() => subscription.unsubscribe());
|
|
332
|
+
},
|
|
333
|
+
{ immediate: true },
|
|
334
|
+
);
|
|
335
|
+
|
|
300
336
|
watch(
|
|
301
337
|
[
|
|
302
338
|
isMounted,
|
|
@@ -304,8 +340,13 @@ watch(
|
|
|
304
340
|
() => agent.value,
|
|
305
341
|
resolvedThreadId,
|
|
306
342
|
hasExplicitThreadId,
|
|
343
|
+
inspectorRequestId,
|
|
307
344
|
],
|
|
308
|
-
(
|
|
345
|
+
(
|
|
346
|
+
[mounted, core, currentAgent, threadId, isExplicit, requestId],
|
|
347
|
+
_old,
|
|
348
|
+
onCleanup,
|
|
349
|
+
) => {
|
|
309
350
|
if (!mounted) {
|
|
310
351
|
return;
|
|
311
352
|
}
|
|
@@ -316,6 +357,20 @@ watch(
|
|
|
316
357
|
return;
|
|
317
358
|
}
|
|
318
359
|
|
|
360
|
+
const previousCycle = activeConnectCycle.value;
|
|
361
|
+
const inspectorTransition =
|
|
362
|
+
previousCycle !== null &&
|
|
363
|
+
previousCycle.threadId !== threadId &&
|
|
364
|
+
previousCycle.inspectorRequestId !== requestId &&
|
|
365
|
+
(previousCycle.inspectorRequestId !== null || requestId !== null);
|
|
366
|
+
if (inspectorTransition) {
|
|
367
|
+
try {
|
|
368
|
+
core.stopAgent({ agent: currentAgent });
|
|
369
|
+
} catch {
|
|
370
|
+
// No live run to stop.
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
319
374
|
// Pin the thread this chat renders onto its agent, before anything issues a
|
|
320
375
|
// request. `CopilotKitCore.connectAgent` reads `agent.threadId` synchronously
|
|
321
376
|
// to decide whether this is a fresh restore, so a later assignment would let
|
|
@@ -357,7 +412,8 @@ watch(
|
|
|
357
412
|
existingCycle &&
|
|
358
413
|
existingCycle.core === (core as object) &&
|
|
359
414
|
existingCycle.agent === currentAgent &&
|
|
360
|
-
existingCycle.threadId === threadId
|
|
415
|
+
existingCycle.threadId === threadId &&
|
|
416
|
+
existingCycle.inspectorRequestId === requestId;
|
|
361
417
|
|
|
362
418
|
let cycle: ActiveConnectCycle;
|
|
363
419
|
if (hasSameDeps && existingCycle) {
|
|
@@ -372,6 +428,7 @@ watch(
|
|
|
372
428
|
core: core as object,
|
|
373
429
|
agent: currentAgent,
|
|
374
430
|
threadId,
|
|
431
|
+
inspectorRequestId: requestId,
|
|
375
432
|
abortController: connectAbortController,
|
|
376
433
|
detached: false,
|
|
377
434
|
};
|
|
@@ -387,6 +444,9 @@ watch(
|
|
|
387
444
|
return;
|
|
388
445
|
}
|
|
389
446
|
console.error("CopilotChat: connectAgent failed", error);
|
|
447
|
+
if (requestId) {
|
|
448
|
+
failInspectorOverride(requestId);
|
|
449
|
+
}
|
|
390
450
|
})
|
|
391
451
|
.finally(() => {
|
|
392
452
|
// Whether the connect succeeded or failed, we're no longer in
|
|
@@ -80,7 +80,7 @@ function bindThreadStoreSelector<T>(
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* Vue composable for listing and managing Intelligence
|
|
83
|
+
* Vue composable for listing and managing CopilotKit Intelligence threads.
|
|
84
84
|
*
|
|
85
85
|
* The hook fetches threads for the runtime-authenticated user and the given
|
|
86
86
|
* `agentId`, then keeps the result in sync via the core thread store's realtime
|
|
@@ -20,7 +20,7 @@ export interface CopilotKitProviderProps {
|
|
|
20
20
|
publicApiKey?: string;
|
|
21
21
|
publicLicenseKey?: string;
|
|
22
22
|
/**
|
|
23
|
-
* Signed license token for offline verification of
|
|
23
|
+
* Signed license token for offline verification of CopilotKit Intelligence features.
|
|
24
24
|
* Obtain from https://dashboard.operations.copilotkit.ai.
|
|
25
25
|
*/
|
|
26
26
|
licenseToken?: string;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cleanup,
|
|
3
|
+
fireEvent,
|
|
4
|
+
render,
|
|
5
|
+
screen,
|
|
6
|
+
waitFor,
|
|
7
|
+
} from "@testing-library/vue";
|
|
8
|
+
import { defineComponent } from "vue";
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
10
|
+
import {
|
|
11
|
+
emitInspectorStopViewing,
|
|
12
|
+
emitInspectorViewThread,
|
|
13
|
+
onInspectorActiveThread,
|
|
14
|
+
onInspectorViewThreadResult,
|
|
15
|
+
} from "@copilotkit/core";
|
|
16
|
+
import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
|
|
17
|
+
import { MockStepwiseAgent } from "../../__tests__/utils/test-helpers";
|
|
18
|
+
import CopilotChat from "../../components/chat/CopilotChat.vue";
|
|
19
|
+
import CopilotKitProvider from "../CopilotKitProvider.vue";
|
|
20
|
+
import CopilotChatConfigurationProvider from "../CopilotChatConfigurationProvider.vue";
|
|
21
|
+
import { useCopilotChatConfiguration } from "../useCopilotChatConfiguration";
|
|
22
|
+
|
|
23
|
+
class TrackingAgent extends MockStepwiseAgent {
|
|
24
|
+
connectCalls: string[] = [];
|
|
25
|
+
failThreadId: string | null = null;
|
|
26
|
+
|
|
27
|
+
async connectAgent(
|
|
28
|
+
_params: unknown,
|
|
29
|
+
_subscriber: unknown,
|
|
30
|
+
): Promise<{ result: unknown; newMessages: [] }> {
|
|
31
|
+
this.connectCalls.push(this.threadId ?? "");
|
|
32
|
+
if (this.threadId === this.failThreadId) {
|
|
33
|
+
throw new Error("connect failed");
|
|
34
|
+
}
|
|
35
|
+
return { result: undefined, newMessages: [] };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ThreadControls = defineComponent({
|
|
40
|
+
setup() {
|
|
41
|
+
const configuration = useCopilotChatConfiguration();
|
|
42
|
+
return { configuration };
|
|
43
|
+
},
|
|
44
|
+
template: `
|
|
45
|
+
<button @click="configuration?.setActiveThreadId('app-picked')">select</button>
|
|
46
|
+
<button @click="configuration?.startNewThread()">new</button>
|
|
47
|
+
`,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
function renderChat(
|
|
51
|
+
agent: TrackingAgent,
|
|
52
|
+
options: { threadId?: string; withControls?: boolean } = {},
|
|
53
|
+
) {
|
|
54
|
+
agent.agentId = DEFAULT_AGENT_ID;
|
|
55
|
+
const Root = defineComponent({
|
|
56
|
+
components: {
|
|
57
|
+
CopilotChat,
|
|
58
|
+
CopilotChatConfigurationProvider,
|
|
59
|
+
CopilotKitProvider,
|
|
60
|
+
ThreadControls,
|
|
61
|
+
},
|
|
62
|
+
setup() {
|
|
63
|
+
return {
|
|
64
|
+
agents: { [DEFAULT_AGENT_ID]: agent },
|
|
65
|
+
threadId: options.threadId,
|
|
66
|
+
withControls: options.withControls ?? false,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
template: `
|
|
70
|
+
<CopilotKitProvider :agents__unsafe_dev_only="agents">
|
|
71
|
+
<CopilotChatConfigurationProvider v-if="withControls" :agent-id="'default'">
|
|
72
|
+
<CopilotChat :thread-id="threadId" />
|
|
73
|
+
<ThreadControls />
|
|
74
|
+
</CopilotChatConfigurationProvider>
|
|
75
|
+
<CopilotChat v-else :thread-id="threadId" />
|
|
76
|
+
</CopilotKitProvider>
|
|
77
|
+
`,
|
|
78
|
+
});
|
|
79
|
+
return render(Root);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function viewRequest(requestId: string, threadId = "saved-thread") {
|
|
83
|
+
return emitInspectorViewThread({
|
|
84
|
+
requestId,
|
|
85
|
+
threadId,
|
|
86
|
+
agentId: DEFAULT_AGENT_ID,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
describe("inspector thread override", () => {
|
|
91
|
+
const cleanups: Array<() => void> = [];
|
|
92
|
+
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
cleanups.length = 0;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
afterEach(() => {
|
|
98
|
+
for (const unsubscribe of cleanups.splice(0)) unsubscribe();
|
|
99
|
+
cleanup();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("switches a pinned threadId when view-thread matches the agent", async () => {
|
|
103
|
+
const agent = new TrackingAgent();
|
|
104
|
+
renderChat(agent, { threadId: "pinned-thread" });
|
|
105
|
+
await waitFor(() => expect(agent.threadId).toBe("pinned-thread"));
|
|
106
|
+
|
|
107
|
+
expect(viewRequest("request-1")).toBe(true);
|
|
108
|
+
|
|
109
|
+
await waitFor(() => expect(agent.threadId).toBe("saved-thread"));
|
|
110
|
+
expect(agent.connectCalls).toContain("saved-thread");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("ignores view-thread for a different agent", async () => {
|
|
114
|
+
const agent = new TrackingAgent();
|
|
115
|
+
renderChat(agent, { threadId: "pinned-thread" });
|
|
116
|
+
await waitFor(() => expect(agent.threadId).toBe("pinned-thread"));
|
|
117
|
+
|
|
118
|
+
expect(
|
|
119
|
+
emitInspectorViewThread({
|
|
120
|
+
requestId: "request-2",
|
|
121
|
+
threadId: "other-thread",
|
|
122
|
+
agentId: "other-agent",
|
|
123
|
+
}),
|
|
124
|
+
).toBe(false);
|
|
125
|
+
|
|
126
|
+
expect(agent.threadId).toBe("pinned-thread");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("restores the previous thread on stop-viewing", async () => {
|
|
130
|
+
const agent = new TrackingAgent();
|
|
131
|
+
renderChat(agent, { threadId: "pinned-thread" });
|
|
132
|
+
viewRequest("request-3");
|
|
133
|
+
await waitFor(() => expect(agent.threadId).toBe("saved-thread"));
|
|
134
|
+
|
|
135
|
+
emitInspectorStopViewing({
|
|
136
|
+
requestId: "request-3",
|
|
137
|
+
agentId: DEFAULT_AGENT_ID,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await waitFor(() => expect(agent.threadId).toBe("pinned-thread"));
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("keeps the original previous thread after a second view-thread", async () => {
|
|
144
|
+
const agent = new TrackingAgent();
|
|
145
|
+
renderChat(agent, { threadId: "pinned-thread" });
|
|
146
|
+
viewRequest("request-4a", "first-saved");
|
|
147
|
+
await waitFor(() => expect(agent.threadId).toBe("first-saved"));
|
|
148
|
+
viewRequest("request-4b", "second-saved");
|
|
149
|
+
await waitFor(() => expect(agent.threadId).toBe("second-saved"));
|
|
150
|
+
|
|
151
|
+
emitInspectorStopViewing({
|
|
152
|
+
requestId: "request-4b",
|
|
153
|
+
agentId: DEFAULT_AGENT_ID,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await waitFor(() => expect(agent.threadId).toBe("pinned-thread"));
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("ends the override when the app picks a thread", async () => {
|
|
160
|
+
const agent = new TrackingAgent();
|
|
161
|
+
renderChat(agent, { withControls: true });
|
|
162
|
+
viewRequest("request-5");
|
|
163
|
+
await waitFor(() => expect(agent.threadId).toBe("saved-thread"));
|
|
164
|
+
|
|
165
|
+
await fireEvent.click(screen.getByText("select"));
|
|
166
|
+
|
|
167
|
+
await waitFor(() => expect(agent.threadId).toBe("app-picked"));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("ends the override when the app starts a new thread", async () => {
|
|
171
|
+
const agent = new TrackingAgent();
|
|
172
|
+
renderChat(agent, { withControls: true });
|
|
173
|
+
viewRequest("request-6");
|
|
174
|
+
await waitFor(() => expect(agent.threadId).toBe("saved-thread"));
|
|
175
|
+
|
|
176
|
+
await fireEvent.click(screen.getByText("new"));
|
|
177
|
+
|
|
178
|
+
await waitFor(() => expect(agent.threadId).not.toBe("saved-thread"));
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("rolls back when connect fails", async () => {
|
|
182
|
+
const agent = new TrackingAgent();
|
|
183
|
+
agent.failThreadId = "saved-thread";
|
|
184
|
+
renderChat(agent, { threadId: "pinned-thread" });
|
|
185
|
+
const results: boolean[] = [];
|
|
186
|
+
cleanups.push(
|
|
187
|
+
onInspectorViewThreadResult((payload) => {
|
|
188
|
+
if (payload.requestId === "request-7") results.push(payload.ok);
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
viewRequest("request-7");
|
|
193
|
+
|
|
194
|
+
await waitFor(() => expect(results).toContain(false));
|
|
195
|
+
await waitFor(() => expect(agent.threadId).toBe("pinned-thread"));
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("emits applied result and active-thread on view", async () => {
|
|
199
|
+
const agent = new TrackingAgent();
|
|
200
|
+
const results: boolean[] = [];
|
|
201
|
+
const activeSources: string[] = [];
|
|
202
|
+
cleanups.push(
|
|
203
|
+
onInspectorViewThreadResult((payload) => results.push(payload.ok)),
|
|
204
|
+
onInspectorActiveThread((payload) => activeSources.push(payload.source)),
|
|
205
|
+
);
|
|
206
|
+
renderChat(agent, { threadId: "pinned-thread" });
|
|
207
|
+
|
|
208
|
+
expect(viewRequest("request-8")).toBe(true);
|
|
209
|
+
|
|
210
|
+
await waitFor(() => expect(agent.threadId).toBe("saved-thread"));
|
|
211
|
+
expect(results).toContain(true);
|
|
212
|
+
expect(activeSources).toContain("override");
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
|
2
|
+
import type { Ref } from "vue";
|
|
3
|
+
import {
|
|
4
|
+
emitInspectorActiveThread,
|
|
5
|
+
emitInspectorViewThreadResult,
|
|
6
|
+
isInspectorThreadBridgeEnabled,
|
|
7
|
+
onInspectorStopViewing,
|
|
8
|
+
onInspectorViewThread,
|
|
9
|
+
} from "@copilotkit/core";
|
|
10
|
+
|
|
11
|
+
type InspectorOverride = {
|
|
12
|
+
requestId: string;
|
|
13
|
+
threadId: string;
|
|
14
|
+
agentId: string;
|
|
15
|
+
previousThreadId: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function useInspectorThreadOverride(args: {
|
|
19
|
+
agentId: Ref<string>;
|
|
20
|
+
baseThreadId: Ref<string>;
|
|
21
|
+
}): {
|
|
22
|
+
inspectorThreadId: Ref<string | null>;
|
|
23
|
+
inspectorRequestId: Ref<string | null>;
|
|
24
|
+
failInspectorOverride: (requestId: string) => void;
|
|
25
|
+
} {
|
|
26
|
+
const override = ref<InspectorOverride | null>(null);
|
|
27
|
+
|
|
28
|
+
const restoreOverride = (
|
|
29
|
+
current: InspectorOverride,
|
|
30
|
+
nextThreadId: string,
|
|
31
|
+
) => {
|
|
32
|
+
override.value = null;
|
|
33
|
+
emitInspectorActiveThread({
|
|
34
|
+
requestId: current.requestId,
|
|
35
|
+
threadId: nextThreadId,
|
|
36
|
+
agentId: current.agentId,
|
|
37
|
+
source: "app",
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const failInspectorOverride = (requestId: string) => {
|
|
42
|
+
const current = override.value;
|
|
43
|
+
if (!current || current.requestId !== requestId) return;
|
|
44
|
+
emitInspectorViewThreadResult({
|
|
45
|
+
requestId,
|
|
46
|
+
threadId: current.threadId,
|
|
47
|
+
agentId: current.agentId,
|
|
48
|
+
ok: false,
|
|
49
|
+
reason: "connect-failed",
|
|
50
|
+
});
|
|
51
|
+
restoreOverride(current, current.previousThreadId);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (isInspectorThreadBridgeEnabled()) {
|
|
55
|
+
const offView = onInspectorViewThread((payload) => {
|
|
56
|
+
if (payload.agentId !== args.agentId.value) return false;
|
|
57
|
+
override.value = override.value
|
|
58
|
+
? { ...override.value, ...payload }
|
|
59
|
+
: {
|
|
60
|
+
...payload,
|
|
61
|
+
previousThreadId: args.baseThreadId.value,
|
|
62
|
+
};
|
|
63
|
+
emitInspectorViewThreadResult({ ...payload, ok: true });
|
|
64
|
+
emitInspectorActiveThread({ ...payload, source: "override" });
|
|
65
|
+
return true;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const offStop = onInspectorStopViewing((payload) => {
|
|
69
|
+
const current = override.value;
|
|
70
|
+
if (!current) return;
|
|
71
|
+
if (payload.requestId !== current.requestId) return;
|
|
72
|
+
if (payload.agentId !== current.agentId) return;
|
|
73
|
+
restoreOverride(current, current.previousThreadId);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
onBeforeUnmount(() => {
|
|
77
|
+
offView();
|
|
78
|
+
offStop();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
watch([args.agentId, args.baseThreadId], ([agentId, baseThreadId]) => {
|
|
83
|
+
const current = override.value;
|
|
84
|
+
if (!current) return;
|
|
85
|
+
if (agentId !== current.agentId) {
|
|
86
|
+
restoreOverride(current, current.previousThreadId);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (baseThreadId === current.previousThreadId) return;
|
|
90
|
+
if (baseThreadId === current.threadId) return;
|
|
91
|
+
restoreOverride(current, baseThreadId);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
inspectorThreadId: computed(() => override.value?.threadId ?? null),
|
|
96
|
+
inspectorRequestId: computed(() => override.value?.requestId ?? null),
|
|
97
|
+
failInspectorOverride,
|
|
98
|
+
};
|
|
99
|
+
}
|