@copilotkit/runtime-client-gql 1.10.0 → 1.10.1-next.1
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/CHANGELOG.md +20 -0
- package/dist/{chunk-PAQ6AHHC.mjs → chunk-5V6B3OXS.mjs} +2 -2
- package/dist/{chunk-PAQ6AHHC.mjs.map → chunk-5V6B3OXS.mjs.map} +1 -1
- package/dist/{chunk-YNQMTL2P.mjs → chunk-SCACOKQX.mjs} +38 -8
- package/dist/chunk-SCACOKQX.mjs.map +1 -0
- package/dist/{chunk-MTD2RJDJ.mjs → chunk-ZYA32QXZ.mjs} +44 -29
- package/dist/chunk-ZYA32QXZ.mjs.map +1 -0
- package/dist/client/CopilotRuntimeClient.js +1 -1
- package/dist/client/CopilotRuntimeClient.js.map +1 -1
- package/dist/client/CopilotRuntimeClient.mjs +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +82 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -3
- package/dist/message-conversion/agui-to-gql.js +37 -7
- package/dist/message-conversion/agui-to-gql.js.map +1 -1
- package/dist/message-conversion/agui-to-gql.mjs +2 -2
- package/dist/message-conversion/agui-to-gql.test.js +529 -7
- package/dist/message-conversion/agui-to-gql.test.js.map +1 -1
- package/dist/message-conversion/agui-to-gql.test.mjs +494 -2
- package/dist/message-conversion/agui-to-gql.test.mjs.map +1 -1
- package/dist/message-conversion/gql-to-agui.d.ts +3 -2
- package/dist/message-conversion/gql-to-agui.js +44 -28
- package/dist/message-conversion/gql-to-agui.js.map +1 -1
- package/dist/message-conversion/gql-to-agui.mjs +4 -2
- package/dist/message-conversion/gql-to-agui.test.js +622 -28
- package/dist/message-conversion/gql-to-agui.test.js.map +1 -1
- package/dist/message-conversion/gql-to-agui.test.mjs +583 -2
- package/dist/message-conversion/gql-to-agui.test.mjs.map +1 -1
- package/dist/message-conversion/index.d.ts +1 -1
- package/dist/message-conversion/index.js +81 -35
- package/dist/message-conversion/index.js.map +1 -1
- package/dist/message-conversion/index.mjs +5 -3
- package/dist/message-conversion/roundtrip-conversion.test.js +250 -35
- package/dist/message-conversion/roundtrip-conversion.test.js.map +1 -1
- package/dist/message-conversion/roundtrip-conversion.test.mjs +174 -3
- package/dist/message-conversion/roundtrip-conversion.test.mjs.map +1 -1
- package/package.json +3 -3
- package/src/message-conversion/agui-to-gql.test.ts +566 -0
- package/src/message-conversion/agui-to-gql.ts +56 -9
- package/src/message-conversion/gql-to-agui.test.ts +663 -0
- package/src/message-conversion/gql-to-agui.ts +62 -35
- package/src/message-conversion/roundtrip-conversion.test.ts +228 -0
- package/dist/chunk-MTD2RJDJ.mjs.map +0 -1
- package/dist/chunk-YNQMTL2P.mjs.map +0 -1
|
@@ -19013,8 +19013,14 @@ function aguiToGQL(messages, actions, coAgentStateRenders) {
|
|
|
19013
19013
|
const actionExecMsg = aguiToolCallToGQLActionExecution(toolCall, message.id);
|
|
19014
19014
|
if ("generativeUI" in message && message.generativeUI && actions) {
|
|
19015
19015
|
const actionName = toolCall.function.name;
|
|
19016
|
-
|
|
19017
|
-
|
|
19016
|
+
const specificAction = Object.values(actions).find(
|
|
19017
|
+
(action) => action.name === actionName
|
|
19018
|
+
);
|
|
19019
|
+
const wildcardAction = Object.values(actions).find((action) => action.name === "*");
|
|
19020
|
+
if (specificAction) {
|
|
19021
|
+
specificAction.render = message.generativeUI;
|
|
19022
|
+
} else if (wildcardAction) {
|
|
19023
|
+
wildcardAction.render = message.generativeUI;
|
|
19018
19024
|
}
|
|
19019
19025
|
}
|
|
19020
19026
|
gqlMessages.push(actionExecMsg);
|
|
@@ -19060,10 +19066,20 @@ function aguiToolCallToGQLActionExecution(toolCall, parentMessageId) {
|
|
|
19060
19066
|
throw new Error(`Unsupported tool call type: ${toolCall.type}`);
|
|
19061
19067
|
}
|
|
19062
19068
|
let argumentsObj;
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
|
|
19066
|
-
|
|
19069
|
+
if (typeof toolCall.function.arguments === "string") {
|
|
19070
|
+
try {
|
|
19071
|
+
argumentsObj = JSON.parse(toolCall.function.arguments);
|
|
19072
|
+
} catch (error) {
|
|
19073
|
+
console.warn(`Failed to parse tool call arguments for ${toolCall.function.name}:`, error);
|
|
19074
|
+
argumentsObj = {};
|
|
19075
|
+
}
|
|
19076
|
+
} else if (typeof toolCall.function.arguments === "object" && toolCall.function.arguments !== null) {
|
|
19077
|
+
argumentsObj = toolCall.function.arguments;
|
|
19078
|
+
} else {
|
|
19079
|
+
console.warn(
|
|
19080
|
+
`Invalid tool call arguments type for ${toolCall.function.name}:`,
|
|
19081
|
+
typeof toolCall.function.arguments
|
|
19082
|
+
);
|
|
19067
19083
|
argumentsObj = {};
|
|
19068
19084
|
}
|
|
19069
19085
|
return new ActionExecutionMessage({
|
|
@@ -19081,9 +19097,23 @@ function aguiToolMessageToGQLResultMessage(message, toolCallNames) {
|
|
|
19081
19097
|
throw new Error("Tool message must have a toolCallId");
|
|
19082
19098
|
}
|
|
19083
19099
|
const actionName = toolCallNames[message.toolCallId] || "unknown";
|
|
19100
|
+
let resultContent;
|
|
19101
|
+
const messageContent = message.content || "";
|
|
19102
|
+
if (typeof messageContent === "string") {
|
|
19103
|
+
resultContent = messageContent;
|
|
19104
|
+
} else if (typeof messageContent === "object" && messageContent !== null) {
|
|
19105
|
+
try {
|
|
19106
|
+
resultContent = JSON.stringify(messageContent);
|
|
19107
|
+
} catch (error) {
|
|
19108
|
+
console.warn(`Failed to stringify tool result for ${actionName}:`, error);
|
|
19109
|
+
resultContent = String(messageContent);
|
|
19110
|
+
}
|
|
19111
|
+
} else {
|
|
19112
|
+
resultContent = String(messageContent);
|
|
19113
|
+
}
|
|
19084
19114
|
return new ResultMessage({
|
|
19085
19115
|
id: message.id,
|
|
19086
|
-
result:
|
|
19116
|
+
result: resultContent,
|
|
19087
19117
|
actionExecutionId: message.toolCallId,
|
|
19088
19118
|
actionName: message.toolName || actionName
|
|
19089
19119
|
});
|
|
@@ -19141,45 +19171,59 @@ function gqlToAGUI(messages, actions, coAgentStateRenders) {
|
|
|
19141
19171
|
return aguiMessages;
|
|
19142
19172
|
}
|
|
19143
19173
|
function gqlActionExecutionMessageToAGUIMessage(message, actions, actionResults) {
|
|
19144
|
-
|
|
19145
|
-
|
|
19146
|
-
|
|
19147
|
-
if (!originalRender)
|
|
19148
|
-
return void 0;
|
|
19149
|
-
return (props) => {
|
|
19150
|
-
var _a3;
|
|
19151
|
-
const actionResult = actionResults == null ? void 0 : actionResults.get(message.id);
|
|
19152
|
-
let status = "inProgress";
|
|
19153
|
-
if (actionResult !== void 0) {
|
|
19154
|
-
status = "complete";
|
|
19155
|
-
} else if (((_a3 = message.status) == null ? void 0 : _a3.code) !== "Pending" /* Pending */) {
|
|
19156
|
-
status = "executing";
|
|
19157
|
-
}
|
|
19158
|
-
const renderProps = {
|
|
19159
|
-
status: (props == null ? void 0 : props.status) || status,
|
|
19160
|
-
args: message.arguments || {},
|
|
19161
|
-
result: (props == null ? void 0 : props.result) || actionResult || void 0,
|
|
19162
|
-
respond: (props == null ? void 0 : props.respond) || (() => {
|
|
19163
|
-
}),
|
|
19164
|
-
messageId: message.id,
|
|
19165
|
-
...props
|
|
19166
|
-
};
|
|
19167
|
-
return originalRender(renderProps);
|
|
19168
|
-
};
|
|
19169
|
-
};
|
|
19174
|
+
const hasSpecificAction = actions && Object.values(actions).some((action2) => action2.name === message.name);
|
|
19175
|
+
const hasWildcardAction = actions && Object.values(actions).some((action2) => action2.name === "*");
|
|
19176
|
+
if (!actions || !hasSpecificAction && !hasWildcardAction) {
|
|
19170
19177
|
return {
|
|
19171
19178
|
id: message.id,
|
|
19172
19179
|
role: "assistant",
|
|
19173
|
-
content: "",
|
|
19174
19180
|
toolCalls: [actionExecutionMessageToAGUIMessage(message)],
|
|
19175
|
-
generativeUI: createRenderWrapper(action.render),
|
|
19176
19181
|
name: message.name
|
|
19177
19182
|
};
|
|
19178
19183
|
}
|
|
19184
|
+
const action = Object.values(actions).find((action2) => action2.name === message.name) || Object.values(actions).find((action2) => action2.name === "*");
|
|
19185
|
+
const createRenderWrapper = (originalRender) => {
|
|
19186
|
+
if (!originalRender)
|
|
19187
|
+
return void 0;
|
|
19188
|
+
return (props) => {
|
|
19189
|
+
var _a3;
|
|
19190
|
+
let actionResult = actionResults == null ? void 0 : actionResults.get(message.id);
|
|
19191
|
+
let status = "inProgress";
|
|
19192
|
+
if (actionResult !== void 0) {
|
|
19193
|
+
status = "complete";
|
|
19194
|
+
} else if (((_a3 = message.status) == null ? void 0 : _a3.code) !== "Pending" /* Pending */) {
|
|
19195
|
+
status = "executing";
|
|
19196
|
+
}
|
|
19197
|
+
if (typeof (props == null ? void 0 : props.result) === "string") {
|
|
19198
|
+
try {
|
|
19199
|
+
props.result = JSON.parse(props.result);
|
|
19200
|
+
} catch (e) {
|
|
19201
|
+
}
|
|
19202
|
+
}
|
|
19203
|
+
if (typeof actionResult === "string") {
|
|
19204
|
+
try {
|
|
19205
|
+
actionResult = JSON.parse(actionResult);
|
|
19206
|
+
} catch (e) {
|
|
19207
|
+
}
|
|
19208
|
+
}
|
|
19209
|
+
const renderProps = {
|
|
19210
|
+
status: (props == null ? void 0 : props.status) || status,
|
|
19211
|
+
args: message.arguments || {},
|
|
19212
|
+
result: (props == null ? void 0 : props.result) || actionResult || void 0,
|
|
19213
|
+
respond: (props == null ? void 0 : props.respond) || (() => {
|
|
19214
|
+
}),
|
|
19215
|
+
messageId: message.id,
|
|
19216
|
+
...props
|
|
19217
|
+
};
|
|
19218
|
+
return originalRender(renderProps);
|
|
19219
|
+
};
|
|
19220
|
+
};
|
|
19179
19221
|
return {
|
|
19180
19222
|
id: message.id,
|
|
19181
19223
|
role: "assistant",
|
|
19224
|
+
content: "",
|
|
19182
19225
|
toolCalls: [actionExecutionMessageToAGUIMessage(message)],
|
|
19226
|
+
generativeUI: createRenderWrapper(action.render),
|
|
19183
19227
|
name: message.name
|
|
19184
19228
|
};
|
|
19185
19229
|
}
|
|
@@ -19473,6 +19517,177 @@ describe("roundtrip message conversion", () => {
|
|
|
19473
19517
|
globalExpect(aguiUserMsgs2[0].image.format).toBe("png");
|
|
19474
19518
|
globalExpect(aguiUserMsgs2[0].image.bytes).toBe("userbase64data");
|
|
19475
19519
|
});
|
|
19520
|
+
test3("wild card action roundtrip conversion", () => {
|
|
19521
|
+
const mockRender = vi.fn((props) => `Wildcard rendered: ${props.args.test}`);
|
|
19522
|
+
const aguiMsg = {
|
|
19523
|
+
id: "assistant-wildcard-1",
|
|
19524
|
+
role: "assistant",
|
|
19525
|
+
content: "Running wild card action",
|
|
19526
|
+
toolCalls: [
|
|
19527
|
+
{
|
|
19528
|
+
id: "tool-call-wildcard-1",
|
|
19529
|
+
type: "function",
|
|
19530
|
+
function: {
|
|
19531
|
+
name: "unknownAction",
|
|
19532
|
+
arguments: JSON.stringify({ test: "wildcard-value" })
|
|
19533
|
+
}
|
|
19534
|
+
}
|
|
19535
|
+
],
|
|
19536
|
+
generativeUI: mockRender
|
|
19537
|
+
};
|
|
19538
|
+
const actions = {
|
|
19539
|
+
"*": { name: "*" }
|
|
19540
|
+
};
|
|
19541
|
+
const gqlMsgs = aguiToGQL(aguiMsg, actions);
|
|
19542
|
+
const aguiMsgs2 = gqlToAGUI(gqlMsgs, actions);
|
|
19543
|
+
globalExpect(typeof actions["*"].render).toBe("function");
|
|
19544
|
+
globalExpect(actions["*"].render).toBe(mockRender);
|
|
19545
|
+
globalExpect(aguiMsgs2).toHaveLength(2);
|
|
19546
|
+
globalExpect(aguiMsgs2[0].role).toBe("assistant");
|
|
19547
|
+
globalExpect(aguiMsgs2[1].role).toBe("assistant");
|
|
19548
|
+
if ("toolCalls" in aguiMsgs2[1]) {
|
|
19549
|
+
globalExpect(aguiMsgs2[1].toolCalls[0].function.name).toBe("unknownAction");
|
|
19550
|
+
globalExpect(aguiMsgs2[1].toolCalls[0].function.arguments).toBe(
|
|
19551
|
+
'{"test":"wildcard-value"}'
|
|
19552
|
+
);
|
|
19553
|
+
}
|
|
19554
|
+
});
|
|
19555
|
+
test3("wild card action with specific action priority roundtrip", () => {
|
|
19556
|
+
const mockRender = vi.fn((props) => `Specific action rendered: ${props.args.test}`);
|
|
19557
|
+
const aguiMsg = {
|
|
19558
|
+
id: "assistant-priority-1",
|
|
19559
|
+
role: "assistant",
|
|
19560
|
+
content: "Running specific action",
|
|
19561
|
+
toolCalls: [
|
|
19562
|
+
{
|
|
19563
|
+
id: "tool-call-priority-1",
|
|
19564
|
+
type: "function",
|
|
19565
|
+
function: {
|
|
19566
|
+
name: "specificAction",
|
|
19567
|
+
arguments: JSON.stringify({ test: "specific-value" })
|
|
19568
|
+
}
|
|
19569
|
+
}
|
|
19570
|
+
],
|
|
19571
|
+
generativeUI: mockRender
|
|
19572
|
+
};
|
|
19573
|
+
const actions = {
|
|
19574
|
+
specificAction: { name: "specificAction" },
|
|
19575
|
+
"*": { name: "*" }
|
|
19576
|
+
};
|
|
19577
|
+
const gqlMsgs = aguiToGQL(aguiMsg, actions);
|
|
19578
|
+
const aguiMsgs2 = gqlToAGUI(gqlMsgs, actions);
|
|
19579
|
+
globalExpect(typeof actions.specificAction.render).toBe("function");
|
|
19580
|
+
globalExpect(actions.specificAction.render).toBe(mockRender);
|
|
19581
|
+
globalExpect(actions["*"].render).toBeUndefined();
|
|
19582
|
+
globalExpect(aguiMsgs2).toHaveLength(2);
|
|
19583
|
+
globalExpect(aguiMsgs2[0].role).toBe("assistant");
|
|
19584
|
+
globalExpect(aguiMsgs2[1].role).toBe("assistant");
|
|
19585
|
+
if ("toolCalls" in aguiMsgs2[1]) {
|
|
19586
|
+
globalExpect(aguiMsgs2[1].toolCalls[0].function.name).toBe("specificAction");
|
|
19587
|
+
globalExpect(aguiMsgs2[1].toolCalls[0].function.arguments).toBe(
|
|
19588
|
+
'{"test":"specific-value"}'
|
|
19589
|
+
);
|
|
19590
|
+
}
|
|
19591
|
+
});
|
|
19592
|
+
test3("wild card action GQL -> AGUI -> GQL roundtrip", () => {
|
|
19593
|
+
const actionExecMsg = new ActionExecutionMessage({
|
|
19594
|
+
id: "wildcard-action-1",
|
|
19595
|
+
name: "unknownAction",
|
|
19596
|
+
arguments: { test: "wildcard-gql-value" },
|
|
19597
|
+
parentMessageId: "assistant-1"
|
|
19598
|
+
});
|
|
19599
|
+
const actions = {
|
|
19600
|
+
"*": {
|
|
19601
|
+
name: "*",
|
|
19602
|
+
render: vi.fn((props) => `GQL wildcard rendered: ${props.args.test}`)
|
|
19603
|
+
}
|
|
19604
|
+
};
|
|
19605
|
+
const aguiMsgs = gqlToAGUI([actionExecMsg], actions);
|
|
19606
|
+
const gqlMsgs2 = aguiToGQL(aguiMsgs, actions);
|
|
19607
|
+
globalExpect(gqlMsgs2).toHaveLength(2);
|
|
19608
|
+
globalExpect(gqlMsgs2[0].id).toBe("wildcard-action-1");
|
|
19609
|
+
globalExpect(gqlMsgs2[0].role).toBe(Role.Assistant);
|
|
19610
|
+
globalExpect(gqlMsgs2[1].id).toBe("wildcard-action-1");
|
|
19611
|
+
globalExpect(gqlMsgs2[1].name).toBe("unknownAction");
|
|
19612
|
+
globalExpect(gqlMsgs2[1].arguments).toEqual({ test: "wildcard-gql-value" });
|
|
19613
|
+
});
|
|
19614
|
+
test3("roundtrip conversion with result parsing edge cases", () => {
|
|
19615
|
+
const toolResultMsg = {
|
|
19616
|
+
id: "tool-result-json",
|
|
19617
|
+
role: "tool",
|
|
19618
|
+
content: '{"status": "success", "data": {"value": 42}}',
|
|
19619
|
+
toolCallId: "tool-call-json",
|
|
19620
|
+
toolName: "jsonAction"
|
|
19621
|
+
};
|
|
19622
|
+
const gqlMsgs = aguiToGQL(toolResultMsg);
|
|
19623
|
+
const aguiMsgs = gqlToAGUI(gqlMsgs);
|
|
19624
|
+
globalExpect(gqlMsgs).toHaveLength(1);
|
|
19625
|
+
globalExpect(gqlMsgs[0]).toBeInstanceOf(ResultMessage);
|
|
19626
|
+
globalExpect(gqlMsgs[0].result).toBe('{"status": "success", "data": {"value": 42}}');
|
|
19627
|
+
globalExpect(aguiMsgs).toHaveLength(1);
|
|
19628
|
+
globalExpect(aguiMsgs[0].role).toBe("tool");
|
|
19629
|
+
globalExpect(aguiMsgs[0].content).toBe('{"status": "success", "data": {"value": 42}}');
|
|
19630
|
+
});
|
|
19631
|
+
test3("roundtrip conversion with object content in tool results", () => {
|
|
19632
|
+
const toolResultMsg = {
|
|
19633
|
+
id: "tool-result-object",
|
|
19634
|
+
role: "tool",
|
|
19635
|
+
content: { status: "success", data: { value: 42 } },
|
|
19636
|
+
toolCallId: "tool-call-object",
|
|
19637
|
+
toolName: "objectAction"
|
|
19638
|
+
};
|
|
19639
|
+
const gqlMsgs = aguiToGQL(toolResultMsg);
|
|
19640
|
+
const aguiMsgs = gqlToAGUI(gqlMsgs);
|
|
19641
|
+
globalExpect(gqlMsgs).toHaveLength(1);
|
|
19642
|
+
globalExpect(gqlMsgs[0]).toBeInstanceOf(ResultMessage);
|
|
19643
|
+
globalExpect(gqlMsgs[0].result).toBe('{"status":"success","data":{"value":42}}');
|
|
19644
|
+
globalExpect(aguiMsgs).toHaveLength(1);
|
|
19645
|
+
globalExpect(aguiMsgs[0].role).toBe("tool");
|
|
19646
|
+
globalExpect(aguiMsgs[0].content).toBe('{"status":"success","data":{"value":42}}');
|
|
19647
|
+
});
|
|
19648
|
+
test3("roundtrip conversion with action execution and result parsing", () => {
|
|
19649
|
+
const mockRender = vi.fn((props) => `Rendered: ${JSON.stringify(props.result)}`);
|
|
19650
|
+
const actionExecMsg = new ActionExecutionMessage({
|
|
19651
|
+
id: "action-with-result",
|
|
19652
|
+
name: "testAction",
|
|
19653
|
+
arguments: { input: "test-value" },
|
|
19654
|
+
parentMessageId: "parent-result"
|
|
19655
|
+
});
|
|
19656
|
+
const resultMsg = new ResultMessage({
|
|
19657
|
+
id: "result-with-json",
|
|
19658
|
+
result: '{"output": "processed", "count": 5}',
|
|
19659
|
+
actionExecutionId: "action-with-result",
|
|
19660
|
+
actionName: "testAction"
|
|
19661
|
+
});
|
|
19662
|
+
const actions = {
|
|
19663
|
+
testAction: {
|
|
19664
|
+
name: "testAction",
|
|
19665
|
+
render: mockRender
|
|
19666
|
+
}
|
|
19667
|
+
};
|
|
19668
|
+
const aguiMsgs = gqlToAGUI([actionExecMsg, resultMsg], actions);
|
|
19669
|
+
globalExpect(aguiMsgs).toHaveLength(2);
|
|
19670
|
+
globalExpect(aguiMsgs[0].role).toBe("assistant");
|
|
19671
|
+
globalExpect("generativeUI" in aguiMsgs[0]).toBe(true);
|
|
19672
|
+
globalExpect(aguiMsgs[1].role).toBe("tool");
|
|
19673
|
+
globalExpect(aguiMsgs[1].content).toBe('{"output": "processed", "count": 5}');
|
|
19674
|
+
if ("generativeUI" in aguiMsgs[0] && aguiMsgs[0].generativeUI) {
|
|
19675
|
+
aguiMsgs[0].generativeUI({ result: '{"parsed": true}' });
|
|
19676
|
+
globalExpect(mockRender).toHaveBeenCalledWith(
|
|
19677
|
+
globalExpect.objectContaining({
|
|
19678
|
+
result: { parsed: true }
|
|
19679
|
+
// Should be parsed from string
|
|
19680
|
+
})
|
|
19681
|
+
);
|
|
19682
|
+
}
|
|
19683
|
+
const gqlMsgs2 = aguiToGQL(aguiMsgs, actions);
|
|
19684
|
+
globalExpect(gqlMsgs2).toHaveLength(3);
|
|
19685
|
+
globalExpect(gqlMsgs2[0]).toBeInstanceOf(TextMessage);
|
|
19686
|
+
globalExpect(gqlMsgs2[1]).toBeInstanceOf(ActionExecutionMessage);
|
|
19687
|
+
globalExpect(gqlMsgs2[2]).toBeInstanceOf(ResultMessage);
|
|
19688
|
+
globalExpect(gqlMsgs2[1].arguments).toEqual({ input: "test-value" });
|
|
19689
|
+
globalExpect(gqlMsgs2[2].result).toBe('{"output": "processed", "count": 5}');
|
|
19690
|
+
});
|
|
19476
19691
|
});
|
|
19477
19692
|
/*! Bundled license information:
|
|
19478
19693
|
|