@polpo-ai/server 0.15.81 → 0.15.83
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/dist/channels/conversation-bridge.js.map +1 -1
- package/dist/routes/completions/chat-handler.d.ts.map +1 -1
- package/dist/routes/completions/chat-handler.js +3 -2
- package/dist/routes/completions/chat-handler.js.map +1 -1
- package/dist/routes/completions/chat-via-run-handler.test.js +97 -0
- package/dist/routes/completions/chat-via-run-handler.test.js.map +1 -1
- package/dist/routes/completions/client-tools.d.ts +46 -0
- package/dist/routes/completions/client-tools.d.ts.map +1 -0
- package/dist/routes/completions/client-tools.js +60 -0
- package/dist/routes/completions/client-tools.js.map +1 -0
- package/dist/routes/completions/client-tools.test.d.ts +2 -0
- package/dist/routes/completions/client-tools.test.d.ts.map +1 -0
- package/dist/routes/completions/client-tools.test.js +105 -0
- package/dist/routes/completions/client-tools.test.js.map +1 -0
- package/dist/routes/completions/continuation.d.ts +10 -0
- package/dist/routes/completions/continuation.d.ts.map +1 -0
- package/dist/routes/completions/continuation.js +18 -0
- package/dist/routes/completions/continuation.js.map +1 -0
- package/dist/routes/completions/continuation.test.d.ts +2 -0
- package/dist/routes/completions/continuation.test.d.ts.map +1 -0
- package/dist/routes/completions/continuation.test.js +39 -0
- package/dist/routes/completions/continuation.test.js.map +1 -0
- package/dist/routes/completions/conversation-turn.d.ts +4 -0
- package/dist/routes/completions/conversation-turn.d.ts.map +1 -1
- package/dist/routes/completions/conversation-turn.js +119 -7
- package/dist/routes/completions/conversation-turn.js.map +1 -1
- package/dist/routes/completions/message-mapping.d.ts +3 -0
- package/dist/routes/completions/message-mapping.d.ts.map +1 -1
- package/dist/routes/completions/message-mapping.js +50 -2
- package/dist/routes/completions/message-mapping.js.map +1 -1
- package/dist/routes/completions/message-mapping.test.js +70 -13
- package/dist/routes/completions/message-mapping.test.js.map +1 -1
- package/dist/routes/completions/runtime-plan-hook.test.js +102 -0
- package/dist/routes/completions/runtime-plan-hook.test.js.map +1 -1
- package/dist/routes/completions/schemas.d.ts +89 -2
- package/dist/routes/completions/schemas.d.ts.map +1 -1
- package/dist/routes/completions/schemas.js +205 -2
- package/dist/routes/completions/schemas.js.map +1 -1
- package/dist/routes/completions/schemas.test.js +196 -0
- package/dist/routes/completions/schemas.test.js.map +1 -1
- package/dist/routes/completions-durable-delivery.test.js +112 -0
- package/dist/routes/completions-durable-delivery.test.js.map +1 -1
- package/dist/routes/completions.d.ts.map +1 -1
- package/dist/routes/completions.js +38 -1
- package/dist/routes/completions.js.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsonSchema } from "ai";
|
|
2
|
+
type RequestClientTool = {
|
|
3
|
+
type: "function";
|
|
4
|
+
function: {
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
parameters?: Record<string, unknown>;
|
|
8
|
+
strict?: boolean;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
type RequestToolChoice = "auto" | "none" | "required" | {
|
|
12
|
+
type: "function";
|
|
13
|
+
function: {
|
|
14
|
+
name: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
type ModelToolCall = {
|
|
18
|
+
toolCallId?: string;
|
|
19
|
+
toolName?: string;
|
|
20
|
+
input?: unknown;
|
|
21
|
+
};
|
|
22
|
+
export declare class RequestClientToolError extends Error {
|
|
23
|
+
readonly code: "tool_name_conflict" | "parallel_client_tool_calls_returned";
|
|
24
|
+
readonly toolName?: string;
|
|
25
|
+
constructor(code: RequestClientToolError["code"], message: string, toolName?: string);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Convert request-declared OpenAI function tools into AI SDK tools without an
|
|
29
|
+
* execute handler. They can be selected by the model and returned to the
|
|
30
|
+
* caller, but can never cross the server execution boundary.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createRequestClientTools(tools: RequestClientTool[] | undefined): Record<string, {
|
|
33
|
+
description?: string;
|
|
34
|
+
strict?: boolean;
|
|
35
|
+
inputSchema: ReturnType<typeof jsonSchema>;
|
|
36
|
+
}>;
|
|
37
|
+
export declare function assertRequestClientToolNamesAvailable(requestTools: RequestClientTool[] | undefined, occupiedNames: Iterable<string>): void;
|
|
38
|
+
export declare function requestToolChoiceToAI(choice: RequestToolChoice | undefined): unknown | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* A request-scoped client tool ends the server turn. Executing any sibling
|
|
41
|
+
* call before returning would make the result order-dependent, so mixed or
|
|
42
|
+
* parallel calls fail before a server tool is dispatched.
|
|
43
|
+
*/
|
|
44
|
+
export declare function selectRequestClientToolCall<T extends ModelToolCall>(calls: T[], clientToolNames: ReadonlySet<string>): T | undefined;
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=client-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-tools.d.ts","sourceRoot":"","sources":["../../../src/routes/completions/client-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAEhC,KAAK,iBAAiB,GAAG;IACvB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;CACH,CAAC;AAEF,KAAK,iBAAiB,GAClB,MAAM,GACN,MAAM,GACN,UAAU,GACV;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAErD,KAAK,aAAa,GAAG;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAQF,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,IAAI,EAAE,oBAAoB,GAAG,qCAAqC,CAAC;IAC5E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;gBAGzB,IAAI,EAAE,sBAAsB,CAAC,MAAM,CAAC,EACpC,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM;CAOpB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,iBAAiB,EAAE,GAAG,SAAS,GACrC,MAAM,CAAC,MAAM,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;CAAE,CAAC,CASxG;AAED,wBAAgB,qCAAqC,CACnD,YAAY,EAAE,iBAAiB,EAAE,GAAG,SAAS,EAC7C,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,GAC9B,IAAI,CAaN;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,iBAAiB,GAAG,SAAS,GACpC,OAAO,GAAG,SAAS,CAGrB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,aAAa,EACjE,KAAK,EAAE,CAAC,EAAE,EACV,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,GACnC,CAAC,GAAG,SAAS,CAYf"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsonSchema } from "ai";
|
|
2
|
+
const EMPTY_PARAMETERS = {
|
|
3
|
+
type: "object",
|
|
4
|
+
properties: {},
|
|
5
|
+
additionalProperties: false,
|
|
6
|
+
};
|
|
7
|
+
export class RequestClientToolError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
toolName;
|
|
10
|
+
constructor(code, message, toolName) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "RequestClientToolError";
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.toolName = toolName;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Convert request-declared OpenAI function tools into AI SDK tools without an
|
|
19
|
+
* execute handler. They can be selected by the model and returned to the
|
|
20
|
+
* caller, but can never cross the server execution boundary.
|
|
21
|
+
*/
|
|
22
|
+
export function createRequestClientTools(tools) {
|
|
23
|
+
return Object.fromEntries((tools ?? []).map((tool) => [
|
|
24
|
+
tool.function.name,
|
|
25
|
+
{
|
|
26
|
+
...(tool.function.description ? { description: tool.function.description } : {}),
|
|
27
|
+
...(tool.function.strict !== undefined ? { strict: tool.function.strict } : {}),
|
|
28
|
+
inputSchema: jsonSchema(tool.function.parameters ?? EMPTY_PARAMETERS),
|
|
29
|
+
},
|
|
30
|
+
]));
|
|
31
|
+
}
|
|
32
|
+
export function assertRequestClientToolNamesAvailable(requestTools, occupiedNames) {
|
|
33
|
+
const occupied = new Set([...occupiedNames].map((name) => name.toLocaleLowerCase("en-US")));
|
|
34
|
+
for (const tool of requestTools ?? []) {
|
|
35
|
+
const name = tool.function.name;
|
|
36
|
+
if (!occupied.has(name.toLocaleLowerCase("en-US")))
|
|
37
|
+
continue;
|
|
38
|
+
throw new RequestClientToolError("tool_name_conflict", `Request tool name conflicts with an effective runtime tool: ${name}`, name);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function requestToolChoiceToAI(choice) {
|
|
42
|
+
if (choice === undefined || typeof choice === "string")
|
|
43
|
+
return choice;
|
|
44
|
+
return { type: "tool", toolName: choice.function.name };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A request-scoped client tool ends the server turn. Executing any sibling
|
|
48
|
+
* call before returning would make the result order-dependent, so mixed or
|
|
49
|
+
* parallel calls fail before a server tool is dispatched.
|
|
50
|
+
*/
|
|
51
|
+
export function selectRequestClientToolCall(calls, clientToolNames) {
|
|
52
|
+
const clientCalls = calls.filter((call) => typeof call.toolName === "string" && clientToolNames.has(call.toolName));
|
|
53
|
+
if (clientCalls.length === 0)
|
|
54
|
+
return undefined;
|
|
55
|
+
if (calls.length !== 1 || clientCalls.length !== 1) {
|
|
56
|
+
throw new RequestClientToolError("parallel_client_tool_calls_returned", "A client-side tool call cannot be combined with another tool call in the same turn");
|
|
57
|
+
}
|
|
58
|
+
return clientCalls[0];
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=client-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-tools.js","sourceRoot":"","sources":["../../../src/routes/completions/client-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-tools.test.d.ts","sourceRoot":"","sources":["../../../src/routes/completions/client-tools.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { RequestClientToolError, assertRequestClientToolNamesAvailable, createRequestClientTools, requestToolChoiceToAI, selectRequestClientToolCall, } from "./client-tools.js";
|
|
3
|
+
const requestTool = (name = "configure_site_module") => ({
|
|
4
|
+
type: "function",
|
|
5
|
+
function: {
|
|
6
|
+
name,
|
|
7
|
+
description: "Open the module configuration UI.",
|
|
8
|
+
parameters: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
module: { type: "string" },
|
|
12
|
+
},
|
|
13
|
+
required: ["module"],
|
|
14
|
+
additionalProperties: false,
|
|
15
|
+
},
|
|
16
|
+
strict: true,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
function capturedError(run) {
|
|
20
|
+
try {
|
|
21
|
+
run();
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error instanceof RequestClientToolError)
|
|
25
|
+
return error;
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
throw new Error("Expected RequestClientToolError");
|
|
29
|
+
}
|
|
30
|
+
describe("request-scoped client tools", () => {
|
|
31
|
+
it("maps OpenAI function tools to return-only AI SDK tools", () => {
|
|
32
|
+
const tools = createRequestClientTools([requestTool()]);
|
|
33
|
+
expect(Object.keys(tools)).toEqual(["configure_site_module"]);
|
|
34
|
+
expect(tools.configure_site_module).toMatchObject({
|
|
35
|
+
description: "Open the module configuration UI.",
|
|
36
|
+
strict: true,
|
|
37
|
+
inputSchema: {
|
|
38
|
+
jsonSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: { module: { type: "string" } },
|
|
41
|
+
required: ["module"],
|
|
42
|
+
additionalProperties: false,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
expect(tools.configure_site_module).not.toHaveProperty("execute");
|
|
47
|
+
});
|
|
48
|
+
it("uses a closed empty object schema for parameterless functions", () => {
|
|
49
|
+
const tools = createRequestClientTools([{
|
|
50
|
+
type: "function",
|
|
51
|
+
function: { name: "open_dialog" },
|
|
52
|
+
}]);
|
|
53
|
+
expect(tools.open_dialog.inputSchema).toMatchObject({
|
|
54
|
+
jsonSchema: {
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: {},
|
|
57
|
+
additionalProperties: false,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
it("rejects collisions with any effective tool name case-insensitively", () => {
|
|
62
|
+
const error = capturedError(() => assertRequestClientToolNamesAvailable([requestTool("Configure_Site_Module")], ["configure_site_module", "bash"]));
|
|
63
|
+
expect(error).toMatchObject({
|
|
64
|
+
code: "tool_name_conflict",
|
|
65
|
+
toolName: "Configure_Site_Module",
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
it("accepts request tools that do not overlap the effective palette", () => {
|
|
69
|
+
expect(() => assertRequestClientToolNamesAvailable([requestTool()], ["bash", "tool_search", "ask_user_question"])).not.toThrow();
|
|
70
|
+
});
|
|
71
|
+
it.each([
|
|
72
|
+
["auto", "auto"],
|
|
73
|
+
["none", "none"],
|
|
74
|
+
["required", "required"],
|
|
75
|
+
[
|
|
76
|
+
{ type: "function", function: { name: "configure_site_module" } },
|
|
77
|
+
{ type: "tool", toolName: "configure_site_module" },
|
|
78
|
+
],
|
|
79
|
+
])("maps OpenAI tool_choice %# to AI SDK tool choice", (choice, expected) => {
|
|
80
|
+
expect(requestToolChoiceToAI(choice)).toEqual(expected);
|
|
81
|
+
});
|
|
82
|
+
it("returns the only client-side call without executing it", () => {
|
|
83
|
+
const call = {
|
|
84
|
+
toolCallId: "call_1",
|
|
85
|
+
toolName: "configure_site_module",
|
|
86
|
+
input: { module: "commerce" },
|
|
87
|
+
};
|
|
88
|
+
expect(selectRequestClientToolCall([call], new Set([call.toolName]))).toBe(call);
|
|
89
|
+
});
|
|
90
|
+
it("returns undefined when the turn contains no client-side call", () => {
|
|
91
|
+
expect(selectRequestClientToolCall([
|
|
92
|
+
{ toolCallId: "call_1", toolName: "bash", input: {} },
|
|
93
|
+
], new Set(["configure_site_module"]))).toBeUndefined();
|
|
94
|
+
});
|
|
95
|
+
it("fails closed when a client-side call is mixed with another call", () => {
|
|
96
|
+
const error = capturedError(() => selectRequestClientToolCall([
|
|
97
|
+
{ toolCallId: "call_1", toolName: "configure_site_module", input: {} },
|
|
98
|
+
{ toolCallId: "call_2", toolName: "bash", input: {} },
|
|
99
|
+
], new Set(["configure_site_module"])));
|
|
100
|
+
expect(error).toMatchObject({
|
|
101
|
+
code: "parallel_client_tool_calls_returned",
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=client-tools.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-tools.test.js","sourceRoot":"","sources":["../../../src/routes/completions/client-tools.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function continuationFingerprint(input: {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
agent: string;
|
|
4
|
+
loop: string;
|
|
5
|
+
user?: string;
|
|
6
|
+
toolCallId: string;
|
|
7
|
+
expectedSessionVersion: number;
|
|
8
|
+
result: unknown;
|
|
9
|
+
}): string;
|
|
10
|
+
//# sourceMappingURL=continuation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continuation.d.ts","sourceRoot":"","sources":["../../../src/routes/completions/continuation.ts"],"names":[],"mappings":"AAaA,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,MAAM,EAAE,OAAO,CAAC;CACjB,GAAG,MAAM,CAET"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
function stableJson(value) {
|
|
3
|
+
if (value === undefined)
|
|
4
|
+
return "null";
|
|
5
|
+
if (value === null || typeof value !== "object")
|
|
6
|
+
return JSON.stringify(value);
|
|
7
|
+
if (Array.isArray(value))
|
|
8
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
9
|
+
return `{${Object.entries(value)
|
|
10
|
+
.filter(([, entry]) => entry !== undefined)
|
|
11
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
12
|
+
.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
|
|
13
|
+
.join(",")}}`;
|
|
14
|
+
}
|
|
15
|
+
export function continuationFingerprint(input) {
|
|
16
|
+
return createHash("sha256").update(stableJson(input)).digest("hex");
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=continuation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continuation.js","sourceRoot":"","sources":["../../../src/routes/completions/continuation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,SAAS,UAAU,CAAC,KAAc;IAChC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACvC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC9E,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACxE,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC;SACxD,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC;SAC1C,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;SACpD,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;SACpE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAQvC;IACC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continuation.test.d.ts","sourceRoot":"","sources":["../../../src/routes/completions/continuation.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { continuationFingerprint } from "./continuation.js";
|
|
3
|
+
describe("continuationFingerprint", () => {
|
|
4
|
+
it("is stable across nested JSON key ordering", () => {
|
|
5
|
+
const base = {
|
|
6
|
+
sessionId: "session-1",
|
|
7
|
+
agent: "leo",
|
|
8
|
+
loop: "build",
|
|
9
|
+
toolCallId: "call-1",
|
|
10
|
+
expectedSessionVersion: 2,
|
|
11
|
+
};
|
|
12
|
+
expect(continuationFingerprint({
|
|
13
|
+
...base,
|
|
14
|
+
result: { site: { id: "1", mode: "edit" }, accepted: true },
|
|
15
|
+
})).toBe(continuationFingerprint({
|
|
16
|
+
...base,
|
|
17
|
+
result: { accepted: true, site: { mode: "edit", id: "1" } },
|
|
18
|
+
}));
|
|
19
|
+
});
|
|
20
|
+
it("changes for a different session boundary or result", () => {
|
|
21
|
+
const base = {
|
|
22
|
+
sessionId: "session-1",
|
|
23
|
+
agent: "leo",
|
|
24
|
+
loop: "build",
|
|
25
|
+
toolCallId: "call-1",
|
|
26
|
+
expectedSessionVersion: 2,
|
|
27
|
+
result: "ok",
|
|
28
|
+
};
|
|
29
|
+
expect(continuationFingerprint(base)).not.toBe(continuationFingerprint({
|
|
30
|
+
...base,
|
|
31
|
+
sessionId: "session-2",
|
|
32
|
+
}));
|
|
33
|
+
expect(continuationFingerprint(base)).not.toBe(continuationFingerprint({
|
|
34
|
+
...base,
|
|
35
|
+
result: "different",
|
|
36
|
+
}));
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
//# sourceMappingURL=continuation.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continuation.test.js","sourceRoot":"","sources":["../../../src/routes/completions/continuation.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,IAAI,GAAG;YACX,SAAS,EAAE,WAAW;YACtB,KAAK,EAAE,KAAK;YACZ,IAAI,EAAE,OAAO;YACb,UAAU,EAAE,QAAQ;YACpB,sBAAsB,EAAE,CAAC;SAC1B,CAAC;QACF,MAAM,CAAC,uBAAuB,CAAC;YAC7B,GAAG,IAAI;YACP,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;SAC5D,CAAC,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC;YAC/B,GAAG,IAAI;YACP,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE;SAC5D,CAAC,CAAC,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,IAAI,GAAG;YACX,SAAS,EAAE,WAAW;YACtB,KAAK,EAAE,KAAK;YACZ,IAAI,EAAE,OAAO;YACb,UAAU,EAAE,QAAQ;YACpB,sBAAsB,EAAE,CAAC;YACzB,MAAM,EAAE,IAAI;SACb,CAAC;QACF,MAAM,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,uBAAuB,CAAC;YACrE,GAAG,IAAI;YACP,SAAS,EAAE,WAAW;SACvB,CAAC,CAAC,CAAC;QACJ,MAAM,CAAC,uBAAuB,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,uBAAuB,CAAC;YACrE,GAAG,IAAI;YACP,MAAM,EAAE,WAAW;SACpB,CAAC,CAAC,CAAC;IACN,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -45,6 +45,10 @@ export interface PrepareConversationTurnOptions {
|
|
|
45
45
|
/** Trusted surface identity. Omit for ordinary HTTP/API agent calls. */
|
|
46
46
|
runtime?: CompletionRuntimeInvocation;
|
|
47
47
|
signal?: AbortSignal;
|
|
48
|
+
continuation?: {
|
|
49
|
+
idempotencyKey: string;
|
|
50
|
+
fingerprint: string;
|
|
51
|
+
};
|
|
48
52
|
}
|
|
49
53
|
export interface RunConversationTurnInput extends PrepareConversationTurnOptions {
|
|
50
54
|
body: CompletionRequestBody;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-turn.d.ts","sourceRoot":"","sources":["../../../src/routes/completions/conversation-turn.ts"],"names":[],"mappings":"AACA,OAAO,EAuBL,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAE3B,MAAM,gBAAgB,CAAC;AAKxB,OAAO,KAAK,EACV,mBAAmB,EACnB,2BAA2B,EAE5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"conversation-turn.d.ts","sourceRoot":"","sources":["../../../src/routes/completions/conversation-turn.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { nanoid } from "nanoid";
|
|
2
2
|
import { agentMemoryScope, createToolInvocationContext, createRuntimePromptContextSegment, compileExecutionRouteManifest, createExecutionRouteResolvedEvent, createExplicitExecutionRoute, createRuntimePlanResolvedEvent, executionRouteRuntimePlanFields, normalizeRuntimeContextTrustMode, normalizeRuntimePlan, renderRuntimePromptContextSegment, renderRuntimePromptContextSegments, renderRuntimeContextPrompt, replacesLegacyAgentMemory, replacesLegacySharedMemory, resolveRuntimeContext, resolveExecutionRoute, resolveLoopSelection, validateExecutionRouterConfig, } from "@polpo-ai/core";
|
|
3
3
|
import { normalizeChatInteractionSettings, resolveChatInteractionCapabilities, } from "@polpo-ai/core/chat-interactions";
|
|
4
|
-
import { convertMessages, extractText } from "./message-mapping.js";
|
|
4
|
+
import { convertMessages, extractText, sessionMessagesToCompletionMessages, } from "./message-mapping.js";
|
|
5
|
+
import { SessionContinuationError } from "@polpo-ai/core/session-continuation";
|
|
5
6
|
import { clientSideToolsForCapabilities, toAIToolChoice, } from "./tool-mapping.js";
|
|
7
|
+
import { RequestClientToolError, assertRequestClientToolNamesAvailable, createRequestClientTools, requestToolChoiceToAI, } from "./client-tools.js";
|
|
6
8
|
import { createGuardedCompletionToolExecutor, } from "./tool-guardrails.js";
|
|
7
9
|
import { assertCompletionMessageContent, assertModelPreflightValue, assertRuntimeContextResolution, } from "./preflight-validation.js";
|
|
8
10
|
import { agentConfigForModelPrimary, modelSelectionForAgent, modelSelectionForResolvedModel, } from "./agent-step-runner.js";
|
|
@@ -23,6 +25,16 @@ function completionError(message, status, code, type = "invalid_request_error")
|
|
|
23
25
|
},
|
|
24
26
|
};
|
|
25
27
|
}
|
|
28
|
+
async function cleanupFailedPreparation(cleanup) {
|
|
29
|
+
if (!cleanup)
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
await cleanup();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Preserve the deterministic preflight error; cleanup is best-effort here.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
26
38
|
function preflightError(error) {
|
|
27
39
|
const guardrail = guardrailErrorEnvelope(error);
|
|
28
40
|
if (guardrail) {
|
|
@@ -313,7 +325,8 @@ export async function prepareChatCompletionExecution(deps, body, options = {}) {
|
|
|
313
325
|
let executionRoute;
|
|
314
326
|
let activatedSkills = [];
|
|
315
327
|
let deferredAgentTools = false;
|
|
316
|
-
|
|
328
|
+
let completionId = options.completionId ?? `chatcmpl-${nanoid(24)}`;
|
|
329
|
+
const sessionStore = deps.getSessionStore();
|
|
317
330
|
const invocation = completionInvocation(options.runtime);
|
|
318
331
|
const interactionSettings = normalizeChatInteractionSettings(initialAgentConfig?.chat);
|
|
319
332
|
const interactionCapabilities = resolveChatInteractionCapabilities({
|
|
@@ -321,12 +334,26 @@ export async function prepareChatCompletionExecution(deps, body, options = {}) {
|
|
|
321
334
|
settings: interactionSettings,
|
|
322
335
|
client: body.polpo?.capabilities,
|
|
323
336
|
});
|
|
324
|
-
const
|
|
337
|
+
const builtInClientSideTools = clientSideToolsForCapabilities(interactionCapabilities);
|
|
338
|
+
try {
|
|
339
|
+
assertRequestClientToolNamesAvailable(body.tools, Object.keys(builtInClientSideTools));
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
if (error instanceof RequestClientToolError) {
|
|
343
|
+
return completionError(error.message, 400, error.code);
|
|
344
|
+
}
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
const requestClientSideTools = createRequestClientTools(body.tools);
|
|
348
|
+
const clientSideTools = {
|
|
349
|
+
...builtInClientSideTools,
|
|
350
|
+
...requestClientSideTools,
|
|
351
|
+
};
|
|
325
352
|
const clientSideToolNames = new Set(Object.keys(clientSideTools));
|
|
326
353
|
const contextTrust = normalizeRuntimeContextTrustMode(deps.getConfig()?.settings?.contextTrust);
|
|
327
354
|
const converted = convertMessages(body.messages, contextTrust);
|
|
328
355
|
let { aiMessages, extraSystemParts, promptContextSegments, } = converted;
|
|
329
|
-
|
|
356
|
+
let callerSystemParts = contextTrust === "enforce" && promptContextSegments.length > 0
|
|
330
357
|
? [renderRuntimePromptContextSegments(promptContextSegments)]
|
|
331
358
|
: extraSystemParts;
|
|
332
359
|
if (agentMode) {
|
|
@@ -388,6 +415,56 @@ export async function prepareChatCompletionExecution(deps, body, options = {}) {
|
|
|
388
415
|
}
|
|
389
416
|
}
|
|
390
417
|
resolvedAgentConfig = agentConfig;
|
|
418
|
+
const continuation = body.polpo?.continuation;
|
|
419
|
+
if (continuation) {
|
|
420
|
+
if (!projectLoopRuntime) {
|
|
421
|
+
return completionError("The selected loop is not available as a Project Loop", 422, "loop_not_available");
|
|
422
|
+
}
|
|
423
|
+
if (!options.sessionId || !options.continuation) {
|
|
424
|
+
return completionError("Client-tool continuation requires an existing session and Idempotency-Key", 400, "invalid_continuation_request");
|
|
425
|
+
}
|
|
426
|
+
if (!sessionStore?.prepareContinuation) {
|
|
427
|
+
return completionError("Session continuation is not supported by the configured store", 501, "session_continuation_unavailable");
|
|
428
|
+
}
|
|
429
|
+
const toolResult = body.messages[0];
|
|
430
|
+
try {
|
|
431
|
+
const prepared = await sessionStore.prepareContinuation({
|
|
432
|
+
sessionId: options.sessionId,
|
|
433
|
+
agent: body.agent,
|
|
434
|
+
user: options.runtime?.user ?? body.user,
|
|
435
|
+
...(options.runtime?.scope ? { scope: options.runtime.scope } : {}),
|
|
436
|
+
toolCallId: continuation.tool_call_id,
|
|
437
|
+
result: toolResult.content,
|
|
438
|
+
expectedSessionVersion: continuation.expected_session_version,
|
|
439
|
+
idempotencyKey: options.continuation.idempotencyKey,
|
|
440
|
+
fingerprint: options.continuation.fingerprint,
|
|
441
|
+
runId: completionId,
|
|
442
|
+
});
|
|
443
|
+
completionId = prepared.runId;
|
|
444
|
+
const canonicalMessages = sessionMessagesToCompletionMessages(prepared.messages);
|
|
445
|
+
body = { ...body, messages: canonicalMessages };
|
|
446
|
+
const canonical = convertMessages(body.messages, contextTrust);
|
|
447
|
+
aiMessages = canonical.aiMessages;
|
|
448
|
+
extraSystemParts = canonical.extraSystemParts;
|
|
449
|
+
promptContextSegments = canonical.promptContextSegments;
|
|
450
|
+
callerSystemParts = contextTrust === "enforce" && promptContextSegments.length > 0
|
|
451
|
+
? [renderRuntimePromptContextSegments(promptContextSegments)]
|
|
452
|
+
: extraSystemParts;
|
|
453
|
+
options.setHeader?.("x-polpo-run-id", completionId);
|
|
454
|
+
options.setHeader?.("x-session-version", String(prepared.sessionVersion + 1));
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
if (error instanceof SessionContinuationError) {
|
|
458
|
+
const status = error.code === "session_not_found"
|
|
459
|
+
? 404
|
|
460
|
+
: error.code === "continuation_scope_mismatch"
|
|
461
|
+
? 403
|
|
462
|
+
: 409;
|
|
463
|
+
return completionError(error.message, status, error.code);
|
|
464
|
+
}
|
|
465
|
+
throw error;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
391
468
|
if (requestedSkills.length > 0) {
|
|
392
469
|
const assignedSkills = new Set(Array.isArray(agentConfig.skills)
|
|
393
470
|
? agentConfig.skills.filter((skill) => typeof skill === "string")
|
|
@@ -555,7 +632,6 @@ export async function prepareChatCompletionExecution(deps, body, options = {}) {
|
|
|
555
632
|
if (projectLoopRuntime && isStructuredResponseFormat(body.response_format)) {
|
|
556
633
|
return completionError("response_format is not supported with project loop execution", 400, "unsupported_response_format");
|
|
557
634
|
}
|
|
558
|
-
const sessionStore = deps.getSessionStore();
|
|
559
635
|
let sessionId = options.sessionId ?? null;
|
|
560
636
|
if (sessionStore) {
|
|
561
637
|
if (!sessionId) {
|
|
@@ -565,14 +641,20 @@ export async function prepareChatCompletionExecution(deps, body, options = {}) {
|
|
|
565
641
|
sessionId = await sessionStore.create({
|
|
566
642
|
title: sessionTitle,
|
|
567
643
|
agent: agentScope ?? undefined,
|
|
568
|
-
user: body.user,
|
|
644
|
+
user: options.runtime?.user ?? body.user,
|
|
569
645
|
metadata: body.metadata,
|
|
646
|
+
...(options.runtime?.scope ? { scope: options.runtime.scope } : {}),
|
|
570
647
|
});
|
|
571
648
|
}
|
|
572
649
|
const lastUserMsg = [...body.messages].reverse().find((message) => message.role === "user");
|
|
573
|
-
if (lastUserMsg && sessionId) {
|
|
650
|
+
if (!body.polpo?.continuation && lastUserMsg && sessionId) {
|
|
574
651
|
await sessionStore.addMessage(sessionId, "user", lastUserMsg.content);
|
|
575
652
|
}
|
|
653
|
+
if (!body.polpo?.continuation && sessionId && sessionStore.getSession) {
|
|
654
|
+
const current = await sessionStore.getSession(sessionId);
|
|
655
|
+
if (current)
|
|
656
|
+
options.setHeader?.("x-session-version", String(Number(current.version ?? 0) + 1));
|
|
657
|
+
}
|
|
576
658
|
}
|
|
577
659
|
if (deferredAgentTools) {
|
|
578
660
|
const toolInvocation = createCompletionToolInvocation({
|
|
@@ -589,6 +671,21 @@ export async function prepareChatCompletionExecution(deps, body, options = {}) {
|
|
|
589
671
|
extraAiTools = resolvedTools.extraAiTools;
|
|
590
672
|
}
|
|
591
673
|
if (!projectLoopRuntime) {
|
|
674
|
+
try {
|
|
675
|
+
assertRequestClientToolNamesAvailable(body.tools, [
|
|
676
|
+
...effectiveTools
|
|
677
|
+
.map((tool) => tool?.name)
|
|
678
|
+
.filter((name) => typeof name === "string"),
|
|
679
|
+
...Object.keys(extraAiTools ?? {}),
|
|
680
|
+
]);
|
|
681
|
+
}
|
|
682
|
+
catch (error) {
|
|
683
|
+
if (error instanceof RequestClientToolError) {
|
|
684
|
+
await cleanupFailedPreparation(onResponseFinished);
|
|
685
|
+
return completionError(error.message, 400, error.code);
|
|
686
|
+
}
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
592
689
|
effectiveToolExecutor = createGuardedCompletionToolExecutor({
|
|
593
690
|
executor: effectiveToolExecutor,
|
|
594
691
|
tools: effectiveTools,
|
|
@@ -615,6 +712,18 @@ export async function prepareChatCompletionExecution(deps, body, options = {}) {
|
|
|
615
712
|
maxLoadBatch: toolDisclosure.maxLoadBatch,
|
|
616
713
|
maxSearchResults: toolDisclosure.maxSearchResults,
|
|
617
714
|
});
|
|
715
|
+
try {
|
|
716
|
+
assertRequestClientToolNamesAvailable(body.tools, pool.tools
|
|
717
|
+
.map((tool) => tool?.name)
|
|
718
|
+
.filter((name) => typeof name === "string"));
|
|
719
|
+
}
|
|
720
|
+
catch (error) {
|
|
721
|
+
if (error instanceof RequestClientToolError) {
|
|
722
|
+
await cleanupFailedPreparation(onResponseFinished);
|
|
723
|
+
return completionError(error.message, 400, error.code);
|
|
724
|
+
}
|
|
725
|
+
throw error;
|
|
726
|
+
}
|
|
618
727
|
const alwaysActive = [
|
|
619
728
|
...Object.keys(extraAiTools ?? {}),
|
|
620
729
|
...clientSideToolNames,
|
|
@@ -649,6 +758,9 @@ export async function prepareChatCompletionExecution(deps, body, options = {}) {
|
|
|
649
758
|
activatedSkills,
|
|
650
759
|
};
|
|
651
760
|
}
|
|
761
|
+
if (body.tool_choice !== undefined) {
|
|
762
|
+
modelToolChoice = requestToolChoiceToAI(body.tool_choice);
|
|
763
|
+
}
|
|
652
764
|
const execution = {
|
|
653
765
|
deps,
|
|
654
766
|
body,
|