@librechat/agents 3.3.5 → 3.3.7
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/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
- package/dist/cjs/llm/openai/index.cjs +51 -6
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +16 -0
- package/dist/cjs/session/JsonlSessionStore.cjs +1 -1
- package/dist/cjs/stream.cjs +6 -1
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +3 -1
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -0
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +19 -16
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +2 -0
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ReadFile.cjs +8 -4
- package/dist/cjs/tools/ReadFile.cjs.map +1 -1
- package/dist/cjs/tools/SkillTool.cjs +2 -0
- package/dist/cjs/tools/SkillTool.cjs.map +1 -1
- package/dist/cjs/tools/SubagentTool.cjs +3 -0
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +88 -27
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +5 -1
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/intentArg.cjs +256 -0
- package/dist/cjs/tools/intentArg.cjs.map +1 -0
- package/dist/cjs/tools/local/CompileCheckTool.cjs +3 -2
- package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalCodingTools.cjs +13 -12
- package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +2 -2
- package/dist/cjs/tools/ptcTimeout.cjs.map +1 -1
- package/dist/cjs/tools/search/schema.cjs +2 -0
- package/dist/cjs/tools/search/schema.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +31 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +41 -7
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
- package/dist/esm/llm/openai/index.mjs +51 -6
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +4 -3
- package/dist/esm/session/JsonlSessionStore.mjs +1 -1
- package/dist/esm/stream.mjs +6 -1
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -1
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +2 -0
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +19 -16
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +2 -0
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ReadFile.mjs +8 -4
- package/dist/esm/tools/ReadFile.mjs.map +1 -1
- package/dist/esm/tools/SkillTool.mjs +2 -0
- package/dist/esm/tools/SkillTool.mjs.map +1 -1
- package/dist/esm/tools/SubagentTool.mjs +3 -0
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +88 -28
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +5 -1
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/intentArg.mjs +244 -0
- package/dist/esm/tools/intentArg.mjs.map +1 -0
- package/dist/esm/tools/local/CompileCheckTool.mjs +3 -2
- package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
- package/dist/esm/tools/local/LocalCodingTools.mjs +13 -12
- package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +2 -2
- package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
- package/dist/esm/tools/search/schema.mjs +2 -0
- package/dist/esm/tools/search/schema.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +31 -2
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +41 -7
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/llm/openai/index.d.ts +0 -11
- package/dist/types/tools/BashExecutor.d.ts +18 -0
- package/dist/types/tools/CodeExecutor.d.ts +18 -0
- package/dist/types/tools/ReadFile.d.ts +18 -0
- package/dist/types/tools/SkillTool.d.ts +18 -0
- package/dist/types/tools/SubagentTool.d.ts +9 -0
- package/dist/types/tools/ToolNode.d.ts +37 -5
- package/dist/types/tools/ToolSearch.d.ts +18 -0
- package/dist/types/tools/intentArg.d.ts +181 -0
- package/dist/types/tools/ptcTimeout.d.ts +2 -0
- package/dist/types/tools/search/schema.d.ts +18 -0
- package/dist/types/tools/search/tool.d.ts +18 -0
- package/dist/types/tools/toolOutputReferences.d.ts +12 -2
- package/dist/types/types/stream.d.ts +18 -0
- package/dist/types/types/tools.d.ts +27 -2
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/llm/openai/index.ts +74 -10
- package/src/llm/openai/llm.spec.ts +105 -0
- package/src/stream.ts +12 -1
- package/src/tools/BashExecutor.ts +6 -0
- package/src/tools/BashProgrammaticToolCalling.ts +2 -0
- package/src/tools/CodeExecutor.ts +6 -0
- package/src/tools/ProgrammaticToolCalling.ts +2 -0
- package/src/tools/ReadFile.ts +2 -0
- package/src/tools/SkillTool.ts +2 -0
- package/src/tools/SubagentTool.ts +3 -0
- package/src/tools/ToolNode.ts +190 -43
- package/src/tools/ToolSearch.ts +3 -0
- package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +174 -0
- package/src/tools/__tests__/intentArg.test.ts +357 -0
- package/src/tools/__tests__/intentCoverage.test.ts +139 -0
- package/src/tools/__tests__/toolOutputReferences.test.ts +83 -0
- package/src/tools/intentArg.ts +357 -0
- package/src/tools/local/CompileCheckTool.ts +4 -3
- package/src/tools/local/LocalCodingTools.ts +14 -13
- package/src/tools/ptcTimeout.ts +2 -0
- package/src/tools/search/outcome.test.ts +90 -0
- package/src/tools/search/schema.ts +3 -0
- package/src/tools/search/tool.ts +47 -1
- package/src/tools/subagent/SubagentExecutor.ts +2 -1
- package/src/tools/toolOutputReferences.ts +77 -9
- package/src/types/stream.ts +18 -0
- package/src/types/tools.ts +24 -2
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tool intent labels.
|
|
3
|
+
*
|
|
4
|
+
* Lets a tool declare, as the FIRST property of its input schema, an `intent`
|
|
5
|
+
* string: one model-authored sentence stating what that specific call is about
|
|
6
|
+
* to do ("Searching for OAuth handling in the callback router"). Because the
|
|
7
|
+
* property is first, it is the first key providers stream in the tool-call
|
|
8
|
+
* args, so a host UI can render it as the call's live status label before the
|
|
9
|
+
* rest of the args exist. When the call settles, {@link applyOutcome} edits
|
|
10
|
+
* the sentence in place into its outcome form — a tool-supplied replacement
|
|
11
|
+
* (`outcome`) or a tool-supplied span edit (`outcome_patch`). Absent either,
|
|
12
|
+
* the label is left exactly as the model wrote it: completion is a UI state
|
|
13
|
+
* (the shimmer stopping, the icon settling), not a tense change.
|
|
14
|
+
*
|
|
15
|
+
* The arg is always optional (never listed in `required`): the same schemas
|
|
16
|
+
* are callable from programmatic tool calling, where no UI renders a label
|
|
17
|
+
* and forcing generated code to fabricate one would be pure cost. Tool bodies
|
|
18
|
+
* must call {@link stripIntent} before using their args so no tool receives a
|
|
19
|
+
* parameter it did not declare.
|
|
20
|
+
*/
|
|
21
|
+
import type { JsonSchemaType, OutcomePatch } from '@/types';
|
|
22
|
+
/** Argument carrying the model-authored label for a tool call. */
|
|
23
|
+
export declare const INTENT_ARG = "intent";
|
|
24
|
+
/**
|
|
25
|
+
* Opening words of {@link INTENT_DESCRIPTION}, and the discriminator that
|
|
26
|
+
* tells the injected LABEL apart from a tool's own business parameter that
|
|
27
|
+
* merely shares the name `intent`.
|
|
28
|
+
*
|
|
29
|
+
* Exported because host applications reimplement the same strip/sanitize
|
|
30
|
+
* passes and would otherwise duplicate this as a string literal: if the two
|
|
31
|
+
* copies drift, the host silently stops recognizing SDK-native labels and
|
|
32
|
+
* fails OPEN (labels stay in schemas, opt-outs stop working) with no error.
|
|
33
|
+
* Any edit to the description must preserve this prefix verbatim.
|
|
34
|
+
*/
|
|
35
|
+
export declare const INTENT_LABEL_MARKER = "ALWAYS write this field FIRST";
|
|
36
|
+
/**
|
|
37
|
+
* Model-facing instruction for the injected `intent` property.
|
|
38
|
+
*
|
|
39
|
+
* Deliberately terse — it is repeated on every opted-in tool schema, on every
|
|
40
|
+
* request, so each sentence is paid for many times over. What remains is
|
|
41
|
+
* load-bearing: first-position placement (the entire streaming mechanism),
|
|
42
|
+
* the one-sentence present-progressive form, who reads it, and the sibling
|
|
43
|
+
* rule, without which models emit identical labels for parallel calls to one
|
|
44
|
+
* tool and defeat the feature's headline case.
|
|
45
|
+
*/
|
|
46
|
+
export declare const INTENT_DESCRIPTION: string;
|
|
47
|
+
/**
|
|
48
|
+
* Canonical (frozen) shape of the injected property. Always embed a COPY
|
|
49
|
+
* (`{ ...INTENT_PROPERTY }`): LangChain's JSON-schema validator stamps a
|
|
50
|
+
* `__absolute_uri__` marker onto every subschema it dereferences, which
|
|
51
|
+
* throws on a frozen object — and a single shared instance would be stamped
|
|
52
|
+
* with one schema's URI while embedded in many.
|
|
53
|
+
*/
|
|
54
|
+
export declare const INTENT_PROPERTY: JsonSchemaType;
|
|
55
|
+
/**
|
|
56
|
+
* Discriminates the intent LABEL property from a tool's own business
|
|
57
|
+
* parameter that merely shares the name: the label contract always opens
|
|
58
|
+
* with the same instruction. Removal/sanitize passes must never strip a
|
|
59
|
+
* parameter the tool actually needs.
|
|
60
|
+
*/
|
|
61
|
+
export declare function isIntentLabelProperty(property: unknown): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Schema shape accepted by {@link withoutIntent}.
|
|
64
|
+
*
|
|
65
|
+
* `required` is widened to `readonly string[]` because the SDK's own native
|
|
66
|
+
* schemas are declared `as const` — their `required` is a readonly tuple, and
|
|
67
|
+
* a mutable `string[]` parameter would reject the very schemas this helper
|
|
68
|
+
* exists for (TS2345), forcing embedders to cast to use the advertised API.
|
|
69
|
+
*/
|
|
70
|
+
export type IntentStrippableSchema = Omit<JsonSchemaType, 'required'> & {
|
|
71
|
+
required?: readonly string[];
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Returns a copy of `parameters` without the injected intent LABEL — the
|
|
75
|
+
* opt-out for consumers that render no status label and should not pay for
|
|
76
|
+
* the property.
|
|
77
|
+
*
|
|
78
|
+
* The SDK's native schemas carry the label unconditionally, so without this
|
|
79
|
+
* an embedder has no lever at all: `withIntent` is applied at module scope.
|
|
80
|
+
* Marker-guarded, so a tool's own business parameter named `intent` is never
|
|
81
|
+
* removed. Returns the input unchanged when there is nothing to strip.
|
|
82
|
+
*
|
|
83
|
+
* `required` is pruned alongside the property: a schema that lists `intent`
|
|
84
|
+
* as required (strict-mode normalization does exactly that, since OpenAI
|
|
85
|
+
* strict function schemas require every property to appear in `required`)
|
|
86
|
+
* would otherwise be left naming a property it no longer declares, which is
|
|
87
|
+
* invalid JSON Schema and gets rejected by the provider instead of quietly
|
|
88
|
+
* opting out.
|
|
89
|
+
*/
|
|
90
|
+
export declare function withoutIntent(parameters?: IntentStrippableSchema): JsonSchemaType | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Returns a copy of the parameters schema with `intent` prepended as the
|
|
93
|
+
* FIRST property (object key order is insertion order and every provider
|
|
94
|
+
* serializer preserves it — first key in the schema means first key in the
|
|
95
|
+
* streamed input). Never mutates the input; no-op when the schema already
|
|
96
|
+
* declares `intent`. The property is not added to `required`.
|
|
97
|
+
*/
|
|
98
|
+
export declare function withIntent(parameters?: JsonSchemaType): JsonSchemaType;
|
|
99
|
+
/**
|
|
100
|
+
* Reads the model-authored intent from tool-call args (handles stringified
|
|
101
|
+
* args). Returns undefined when absent, empty, or not a string.
|
|
102
|
+
*/
|
|
103
|
+
export declare function readIntent(args: unknown): string | undefined;
|
|
104
|
+
/**
|
|
105
|
+
* Returns the args without the `intent` key so downstream consumers that did
|
|
106
|
+
* not declare it never receive it. Parses stringified JSON object args;
|
|
107
|
+
* returns the value unchanged when the key is absent.
|
|
108
|
+
*/
|
|
109
|
+
export declare function stripIntent(args: unknown): unknown;
|
|
110
|
+
/**
|
|
111
|
+
* Resolves the settled label for a call from its model-authored `intent` and
|
|
112
|
+
* the tool's result fields, in precedence order:
|
|
113
|
+
*
|
|
114
|
+
* 1. `outcome` — full replacement authored by the tool.
|
|
115
|
+
* 2. `outcome_patch` — first occurrence of `from` in the intent replaced
|
|
116
|
+
* with `to` (case-sensitive); no-op when `from` is absent or empty.
|
|
117
|
+
* 3. Otherwise the intent is returned UNCHANGED.
|
|
118
|
+
*
|
|
119
|
+
* There is deliberately no mechanical present-progressive→past-tense rewrite.
|
|
120
|
+
* Such a transform can only be a closed list of English verbs, which makes it
|
|
121
|
+
* wrong in three ways at once: it never fires for the non-English labels this
|
|
122
|
+
* feature expects (the model answers in the user's language), it fires for
|
|
123
|
+
* some sibling calls and not others inside one group — "Searched…" beside
|
|
124
|
+
* "Recording…" — and it quietly enumerates a vocabulary in a feature whose
|
|
125
|
+
* premise is that the sentence is free-form. Completion is conveyed by UI
|
|
126
|
+
* state (the shimmer stopping, the icon settling), which is language-neutral
|
|
127
|
+
* and always consistent; a tool that wants past tense says so explicitly via
|
|
128
|
+
* `outcome` or `outcome_patch`.
|
|
129
|
+
*
|
|
130
|
+
* Returns undefined when there is neither an intent nor an outcome, so
|
|
131
|
+
* callers fall back to their default label. Pure and dependency-free — host
|
|
132
|
+
* UIs needing identical logic can import or mirror it.
|
|
133
|
+
*/
|
|
134
|
+
export declare function applyOutcome(intent: string | undefined, result?: {
|
|
135
|
+
outcome?: string;
|
|
136
|
+
outcome_patch?: OutcomePatch;
|
|
137
|
+
}): string | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Resolves the settled label to emit on a completion event: only when the
|
|
140
|
+
* tool actually authored `outcome`/`outcome_patch` fields. Returns undefined
|
|
141
|
+
* otherwise, so the wire never carries a label the host already has — a bare
|
|
142
|
+
* intent needs no settled form, because it is displayed unchanged and the UI
|
|
143
|
+
* conveys completion through its own state. Hosts must NOT rewrite it (see
|
|
144
|
+
* {@link applyOutcome} for why a tense transform is deliberately absent). The
|
|
145
|
+
* result is collapsed to a bounded single line before emission.
|
|
146
|
+
*
|
|
147
|
+
* For failed calls (`isError`), only tool-AUTHORED text may label the call:
|
|
148
|
+
* an explicit `outcome`, or a patch whose `from` actually matches the intent.
|
|
149
|
+
* An unmatched patch resolves to undefined rather than silently reusing the
|
|
150
|
+
* in-flight intent, so a failure is never labelled as though it succeeded.
|
|
151
|
+
*/
|
|
152
|
+
export declare function resolveToolOutcome(args: unknown, fields?: {
|
|
153
|
+
outcome?: string;
|
|
154
|
+
outcome_patch?: OutcomePatch;
|
|
155
|
+
} | null, options?: {
|
|
156
|
+
isError?: boolean;
|
|
157
|
+
}): string | undefined;
|
|
158
|
+
/**
|
|
159
|
+
* Reads the outcome fields off a tool-execution result: the typed
|
|
160
|
+
* `outcome`/`outcome_patch` fields when present, else the artifact channel
|
|
161
|
+
* (see {@link readOutcomeFields}) — so a `content_and_artifact` tool authors
|
|
162
|
+
* its label the same way on the direct and event-driven paths.
|
|
163
|
+
*/
|
|
164
|
+
export declare function outcomeFieldsFromResult(result: {
|
|
165
|
+
outcome?: string;
|
|
166
|
+
outcome_patch?: OutcomePatch;
|
|
167
|
+
artifact?: unknown;
|
|
168
|
+
}): {
|
|
169
|
+
outcome?: string;
|
|
170
|
+
outcome_patch?: OutcomePatch;
|
|
171
|
+
} | undefined;
|
|
172
|
+
/**
|
|
173
|
+
* Extracts validated `outcome`/`outcome_patch` fields from an arbitrary
|
|
174
|
+
* value — the artifact channel through which an in-process
|
|
175
|
+
* `content_and_artifact` tool authors its settled label. Returns undefined
|
|
176
|
+
* when neither field is usable.
|
|
177
|
+
*/
|
|
178
|
+
export declare function readOutcomeFields(source: unknown): {
|
|
179
|
+
outcome?: string;
|
|
180
|
+
outcome_patch?: OutcomePatch;
|
|
181
|
+
} | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { JsonSchemaType } from '@/types';
|
|
1
2
|
export declare const DEFAULT_CODE_API_RUN_TIMEOUT_MS = 15000;
|
|
2
3
|
export declare const MIN_CODE_API_RUN_TIMEOUT_MS = 1000;
|
|
3
4
|
export declare const MAX_CODE_API_RUN_TIMEOUT_SCHEMA_MS = 300000;
|
|
@@ -11,6 +12,7 @@ type TimeoutSchema = {
|
|
|
11
12
|
export type ProgrammaticToolCallingJsonSchema = {
|
|
12
13
|
type: 'object';
|
|
13
14
|
properties: {
|
|
15
|
+
intent: JsonSchemaType;
|
|
14
16
|
code: {
|
|
15
17
|
type: 'string';
|
|
16
18
|
minLength: number;
|
|
@@ -36,6 +36,15 @@ export declare const newsSchema: {
|
|
|
36
36
|
export declare const WebSearchToolSchema: {
|
|
37
37
|
readonly type: "object";
|
|
38
38
|
readonly properties: {
|
|
39
|
+
readonly intent: {
|
|
40
|
+
readonly type: "string" | "number" | "integer" | "float" | "boolean" | "array" | "object";
|
|
41
|
+
readonly enum?: string[];
|
|
42
|
+
readonly items?: import("../..").JsonSchemaType;
|
|
43
|
+
readonly properties?: Record<string, import("../..").JsonSchemaType>;
|
|
44
|
+
readonly required?: string[];
|
|
45
|
+
readonly description?: string;
|
|
46
|
+
readonly additionalProperties?: boolean | import("../..").JsonSchemaType;
|
|
47
|
+
};
|
|
39
48
|
readonly query: {
|
|
40
49
|
readonly type: "string";
|
|
41
50
|
readonly description: string;
|
|
@@ -72,6 +81,15 @@ export declare const WebSearchToolDefinition: {
|
|
|
72
81
|
readonly schema: {
|
|
73
82
|
readonly type: "object";
|
|
74
83
|
readonly properties: {
|
|
84
|
+
readonly intent: {
|
|
85
|
+
readonly type: "string" | "number" | "integer" | "float" | "boolean" | "array" | "object";
|
|
86
|
+
readonly enum?: string[];
|
|
87
|
+
readonly items?: import("../..").JsonSchemaType;
|
|
88
|
+
readonly properties?: Record<string, import("../..").JsonSchemaType>;
|
|
89
|
+
readonly required?: string[];
|
|
90
|
+
readonly description?: string;
|
|
91
|
+
readonly additionalProperties?: boolean | import("../..").JsonSchemaType;
|
|
92
|
+
};
|
|
75
93
|
readonly query: {
|
|
76
94
|
readonly type: "string";
|
|
77
95
|
readonly description: string;
|
|
@@ -2,6 +2,24 @@ import { DynamicStructuredTool } from '@langchain/core/tools';
|
|
|
2
2
|
import type * as t from './types';
|
|
3
3
|
import { DATE_RANGE } from './schema';
|
|
4
4
|
import { createSearchAPI } from './search';
|
|
5
|
+
/**
|
|
6
|
+
* Settled label for a `web_search` call's intent (see `intentArg.ts`).
|
|
7
|
+
*
|
|
8
|
+
* Counts the result kinds `formatResultsForLLM` actually renders —
|
|
9
|
+
* `references` only tracks links embedded in extracted highlights, so it
|
|
10
|
+
* undercounts ordinary results and can overcount when one highlight embeds
|
|
11
|
+
* several links.
|
|
12
|
+
*
|
|
13
|
+
* A caught provider or processing failure is reported through `data.error`
|
|
14
|
+
* while the tool still returns NORMALLY, so that case must author its own
|
|
15
|
+
* label: the `ToolMessage` carries success status, so without an authored
|
|
16
|
+
* outcome the in-flight intent ("Searching…") would stand as the settled
|
|
17
|
+
* label and present a failed search as an ordinary one.
|
|
18
|
+
*
|
|
19
|
+
* Returns undefined for a genuine zero-result search, leaving the
|
|
20
|
+
* model-authored intent to stand unchanged as the label.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveSearchOutcome(data: t.SearchResultData, query: string): string | undefined;
|
|
5
23
|
/**
|
|
6
24
|
* Executes parallel searches and merges the results,
|
|
7
25
|
* deduplicating top stories by link
|
|
@@ -69,8 +69,18 @@ export type ResolveResult<T> = {
|
|
|
69
69
|
* point in time, ignoring any subsequent registrations.
|
|
70
70
|
*/
|
|
71
71
|
export interface ToolOutputResolveView {
|
|
72
|
-
resolve<T>(args: T): ResolveResult<T>;
|
|
72
|
+
resolve<T>(args: T, options?: ResolveOptions): ResolveResult<T>;
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Per-call resolution options. `substituteIntentKey` opts the top-level
|
|
76
|
+
* `intent` key back INTO placeholder substitution: the exemption protects
|
|
77
|
+
* the injected display label, but a tool whose own schema declares a
|
|
78
|
+
* business parameter named `intent` (the injectors skip such tools) still
|
|
79
|
+
* needs references piped into it like any other argument.
|
|
80
|
+
*/
|
|
81
|
+
export type ResolveOptions = {
|
|
82
|
+
substituteIntentKey?: boolean;
|
|
83
|
+
};
|
|
74
84
|
/**
|
|
75
85
|
* Pre-resolved arg map keyed by `toolCallId`. Used by the mixed
|
|
76
86
|
* direct+event dispatch path to feed event calls' resolved args
|
|
@@ -166,7 +176,7 @@ export declare class ToolOutputReferenceRegistry {
|
|
|
166
176
|
* the serialized args, the original input is returned without
|
|
167
177
|
* walking the tree.
|
|
168
178
|
*/
|
|
169
|
-
resolve<T>(runId: string | undefined, args: T): ResolveResult<T>;
|
|
179
|
+
resolve<T>(runId: string | undefined, args: T, options?: ResolveOptions): ResolveResult<T>;
|
|
170
180
|
/**
|
|
171
181
|
* Captures a frozen snapshot of `runId`'s current entries and
|
|
172
182
|
* returns a view that resolves placeholders against *only* that
|
|
@@ -98,6 +98,19 @@ export type ProcessedToolCall = {
|
|
|
98
98
|
id: string;
|
|
99
99
|
output: string;
|
|
100
100
|
progress: number;
|
|
101
|
+
/**
|
|
102
|
+
* Settled label for the call, resolved from the tool-supplied
|
|
103
|
+
* `outcome`/`outcome_patch` result fields against the model-authored
|
|
104
|
+
* `intent` arg. Present ONLY when the tool authored one.
|
|
105
|
+
*
|
|
106
|
+
* When absent, display the `intent` arg unchanged — do NOT rewrite its
|
|
107
|
+
* tense. A gerund→past-tense rewrite can only be a closed list of English
|
|
108
|
+
* verbs, so it never fires for the non-English labels this feature expects
|
|
109
|
+
* and fires for some sibling calls but not others within one group.
|
|
110
|
+
* Completion belongs to UI state (the shimmer stopping, the icon settling),
|
|
111
|
+
* which is language-neutral and always consistent.
|
|
112
|
+
*/
|
|
113
|
+
outcome?: string;
|
|
101
114
|
};
|
|
102
115
|
export type ProcessedContent = {
|
|
103
116
|
type: ContentType;
|
|
@@ -265,6 +278,11 @@ export type ToolCallPart = {
|
|
|
265
278
|
id?: string;
|
|
266
279
|
/** If provided, the output of the tool call */
|
|
267
280
|
output?: ToolResultContent['content'];
|
|
281
|
+
/**
|
|
282
|
+
* Tool-authored settled label for the call (see `ProcessedToolCall.outcome`),
|
|
283
|
+
* preserved through aggregation so it survives persistence/reload.
|
|
284
|
+
*/
|
|
285
|
+
outcome?: string;
|
|
268
286
|
/** Auth URL */
|
|
269
287
|
auth?: string;
|
|
270
288
|
/** Expiration time */
|
|
@@ -224,8 +224,16 @@ export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
|
|
|
224
224
|
export type ToolEndEvent = {
|
|
225
225
|
/** The Step Id of the Tool Call */
|
|
226
226
|
id: string;
|
|
227
|
-
/**
|
|
228
|
-
|
|
227
|
+
/**
|
|
228
|
+
* The Completed Tool Call. Carries the tool-authored `outcome` label when
|
|
229
|
+
* present (see `ProcessedToolCall.outcome`) so `ON_RUN_STEP_COMPLETED`
|
|
230
|
+
* consumers can read it without an unsafe cast.
|
|
231
|
+
*/
|
|
232
|
+
tool_call: ToolCall & {
|
|
233
|
+
output?: string;
|
|
234
|
+
progress?: number;
|
|
235
|
+
outcome?: string;
|
|
236
|
+
};
|
|
229
237
|
/** The content index of the tool call */
|
|
230
238
|
index: number;
|
|
231
239
|
type?: 'tool_call';
|
|
@@ -486,6 +494,16 @@ export type InjectedMessage = {
|
|
|
486
494
|
/** Only set when source is 'skill', for compaction preservation */
|
|
487
495
|
skillName?: string;
|
|
488
496
|
};
|
|
497
|
+
/**
|
|
498
|
+
* In-place edit of a call's model-authored `intent` label: the first
|
|
499
|
+
* occurrence of `from` in the intent is replaced with `to` (case-sensitive).
|
|
500
|
+
* Lets a tool settle the label while preserving the model's own phrasing,
|
|
501
|
+
* e.g. `{ from: 'Searching', to: 'Searched' }`.
|
|
502
|
+
*/
|
|
503
|
+
export type OutcomePatch = {
|
|
504
|
+
from: string;
|
|
505
|
+
to: string;
|
|
506
|
+
};
|
|
489
507
|
/** Result for a single tool call in event-driven execution */
|
|
490
508
|
export type ToolExecuteResult = {
|
|
491
509
|
/** Matches ToolCallRequest.id */
|
|
@@ -498,6 +516,13 @@ export type ToolExecuteResult = {
|
|
|
498
516
|
status: 'success' | 'error';
|
|
499
517
|
/** Error message if status is 'error' */
|
|
500
518
|
errorMessage?: string;
|
|
519
|
+
/**
|
|
520
|
+
* Settled human-readable label for this call, replacing the model-authored
|
|
521
|
+
* `intent` arg in the UI. Full replacement; wins over `outcome_patch`.
|
|
522
|
+
*/
|
|
523
|
+
outcome?: string;
|
|
524
|
+
/** In-place edit of the model-authored `intent` label (see {@link OutcomePatch}). */
|
|
525
|
+
outcome_patch?: OutcomePatch;
|
|
501
526
|
/**
|
|
502
527
|
* Messages to inject into graph state after the ToolMessage for this call.
|
|
503
528
|
* Placed after tool results to respect provider message ordering (tool_call -> tool_result adjacency).
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ export * from './tools/ReadFile';
|
|
|
26
26
|
export * from './tools/skillCatalog';
|
|
27
27
|
export * from './tools/ToolSearch';
|
|
28
28
|
export * from './tools/ToolNode';
|
|
29
|
+
export * from './tools/intentArg';
|
|
29
30
|
export * from './tools/schema';
|
|
30
31
|
export * from './tools/handlers';
|
|
31
32
|
export * from './tools/local';
|
package/src/llm/openai/index.ts
CHANGED
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
projectOpenAIResponsesToolMessageContent,
|
|
52
52
|
projectToolStreamContentForProvider,
|
|
53
53
|
} from '@/messages/core';
|
|
54
|
+
import { INTENT_ARG, isIntentLabelProperty } from '@/tools/intentArg';
|
|
54
55
|
import { isReasoningModel, _convertMessagesToOpenAIParams } from './utils';
|
|
55
56
|
import { dropRepeatedScalarMetadata } from './streamMetadata';
|
|
56
57
|
|
|
@@ -1003,6 +1004,65 @@ function createAbortHandler(controller: AbortController): () => void {
|
|
|
1003
1004
|
* @param {Object} [fields] Additional fields to add to the OpenAI tool.
|
|
1004
1005
|
* @returns {ToolDefinition} The inputted tool in OpenAI tool format.
|
|
1005
1006
|
*/
|
|
1007
|
+
/**
|
|
1008
|
+
* OpenAI strict function schemas require every property to appear in
|
|
1009
|
+
* `required`. The optional `intent` label (see `tools/intentArg.ts`) is
|
|
1010
|
+
* deliberately NOT required — the same schema is callable from programmatic
|
|
1011
|
+
* tool calling — so a tool auto-marked `strict: true` (the non-streaming
|
|
1012
|
+
* `json_schema` structured-output path) would be rejected as invalid before
|
|
1013
|
+
* execution. That path never streams a live label anyway, so the
|
|
1014
|
+
* marker-identified property is dropped there; every other path keeps it.
|
|
1015
|
+
*/
|
|
1016
|
+
function stripIntentFromStrictTools<T extends object>(params: T): T {
|
|
1017
|
+
const record = params as { tools?: unknown[] };
|
|
1018
|
+
const tools = record.tools;
|
|
1019
|
+
if (!Array.isArray(tools) || tools.length === 0) {
|
|
1020
|
+
return params;
|
|
1021
|
+
}
|
|
1022
|
+
const nextTools = tools.map((tool) => {
|
|
1023
|
+
const candidate = tool as {
|
|
1024
|
+
strict?: boolean;
|
|
1025
|
+
parameters?: { properties?: Record<string, unknown>; required?: unknown };
|
|
1026
|
+
function?: {
|
|
1027
|
+
strict?: boolean;
|
|
1028
|
+
parameters?: {
|
|
1029
|
+
properties?: Record<string, unknown>;
|
|
1030
|
+
required?: unknown;
|
|
1031
|
+
};
|
|
1032
|
+
};
|
|
1033
|
+
};
|
|
1034
|
+
/** Chat-completions tools nest under `function`; responses-API tools are flat. */
|
|
1035
|
+
const holder = candidate.function ?? candidate;
|
|
1036
|
+
if (holder.strict !== true) {
|
|
1037
|
+
return tool;
|
|
1038
|
+
}
|
|
1039
|
+
const parameters = holder.parameters;
|
|
1040
|
+
const properties = parameters?.properties;
|
|
1041
|
+
if (properties == null || !isIntentLabelProperty(properties[INTENT_ARG])) {
|
|
1042
|
+
return tool;
|
|
1043
|
+
}
|
|
1044
|
+
const required = Array.isArray(parameters?.required)
|
|
1045
|
+
? (parameters.required as unknown[])
|
|
1046
|
+
: [];
|
|
1047
|
+
if (required.includes(INTENT_ARG)) {
|
|
1048
|
+
return tool;
|
|
1049
|
+
}
|
|
1050
|
+
const { [INTENT_ARG]: _omit, ...restProps } = properties;
|
|
1051
|
+
const nextParams = { ...parameters, properties: restProps };
|
|
1052
|
+
if (candidate.function != null) {
|
|
1053
|
+
return {
|
|
1054
|
+
...candidate,
|
|
1055
|
+
function: { ...candidate.function, parameters: nextParams },
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
return { ...candidate, parameters: nextParams };
|
|
1059
|
+
});
|
|
1060
|
+
if (nextTools.every((tool, index) => tool === tools[index])) {
|
|
1061
|
+
return params;
|
|
1062
|
+
}
|
|
1063
|
+
return { ...params, tools: nextTools } as T;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1006
1066
|
export function _convertToOpenAITool(
|
|
1007
1067
|
tool: BindToolsInput,
|
|
1008
1068
|
fields?: {
|
|
@@ -1184,10 +1244,12 @@ class LibreChatOpenAICompletions extends OriginalChatOpenAICompletions {
|
|
|
1184
1244
|
options?: this['ParsedCallOptions'],
|
|
1185
1245
|
extra?: { streaming?: boolean }
|
|
1186
1246
|
): ReturnType<OriginalChatOpenAICompletions['invocationParams']> {
|
|
1187
|
-
return
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1247
|
+
return stripIntentFromStrictTools(
|
|
1248
|
+
applyManagedRequestParams(super.invocationParams(options, extra), {
|
|
1249
|
+
promptCacheExplicit: this.promptCacheExplicit,
|
|
1250
|
+
safetyIdentifier: this.safetyIdentifier,
|
|
1251
|
+
})
|
|
1252
|
+
);
|
|
1191
1253
|
}
|
|
1192
1254
|
|
|
1193
1255
|
protected _getReasoningParams(
|
|
@@ -1640,7 +1702,7 @@ class LibreChatOpenAIResponses extends OriginalChatOpenAIResponses {
|
|
|
1640
1702
|
]),
|
|
1641
1703
|
];
|
|
1642
1704
|
}
|
|
1643
|
-
return params;
|
|
1705
|
+
return stripIntentFromStrictTools(params);
|
|
1644
1706
|
}
|
|
1645
1707
|
|
|
1646
1708
|
async completionWithRetry(
|
|
@@ -1741,10 +1803,12 @@ class LibreChatAzureOpenAICompletions extends OriginalAzureChatOpenAICompletions
|
|
|
1741
1803
|
options?: this['ParsedCallOptions'],
|
|
1742
1804
|
extra?: { streaming?: boolean }
|
|
1743
1805
|
): ReturnType<OriginalAzureChatOpenAICompletions['invocationParams']> {
|
|
1744
|
-
return
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1806
|
+
return stripIntentFromStrictTools(
|
|
1807
|
+
applyManagedRequestParams(super.invocationParams(options, extra), {
|
|
1808
|
+
promptCacheExplicit: this.promptCacheExplicit,
|
|
1809
|
+
safetyIdentifier: this.safetyIdentifier,
|
|
1810
|
+
})
|
|
1811
|
+
);
|
|
1748
1812
|
}
|
|
1749
1813
|
|
|
1750
1814
|
protected _getReasoningParams(
|
|
@@ -1888,7 +1952,7 @@ class LibreChatAzureOpenAIResponses extends OriginalAzureChatOpenAIResponses {
|
|
|
1888
1952
|
]),
|
|
1889
1953
|
];
|
|
1890
1954
|
}
|
|
1891
|
-
return params;
|
|
1955
|
+
return stripIntentFromStrictTools(params);
|
|
1892
1956
|
}
|
|
1893
1957
|
|
|
1894
1958
|
async completionWithRetry(
|
|
@@ -1206,6 +1206,111 @@ describe('ChatOpenAICompletions strict tools for structured output', () => {
|
|
|
1206
1206
|
toolStrict({ response_format: { type: 'json_object' } })
|
|
1207
1207
|
).toBeUndefined();
|
|
1208
1208
|
});
|
|
1209
|
+
|
|
1210
|
+
describe('optional intent labels under strict mode', () => {
|
|
1211
|
+
const intentTool = {
|
|
1212
|
+
type: 'function' as const,
|
|
1213
|
+
function: {
|
|
1214
|
+
name: 'search_mcp_docs',
|
|
1215
|
+
description: 'Search docs',
|
|
1216
|
+
parameters: {
|
|
1217
|
+
type: 'object',
|
|
1218
|
+
properties: {
|
|
1219
|
+
intent: {
|
|
1220
|
+
type: 'string',
|
|
1221
|
+
description:
|
|
1222
|
+
'ALWAYS write this field FIRST, before any other argument. One short sentence…',
|
|
1223
|
+
},
|
|
1224
|
+
query: { type: 'string' },
|
|
1225
|
+
},
|
|
1226
|
+
required: ['query'],
|
|
1227
|
+
},
|
|
1228
|
+
},
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
function toolProperties(options: Record<string, unknown>): string[] {
|
|
1232
|
+
const model = new ChatOpenAI({ model: 'gpt-4', apiKey: 'test-key' });
|
|
1233
|
+
const completions = completionsOf<InvocationParamsDelegate>(model);
|
|
1234
|
+
const params = completions.invocationParams({
|
|
1235
|
+
tools: [intentTool],
|
|
1236
|
+
...options,
|
|
1237
|
+
}) as unknown as {
|
|
1238
|
+
tools?: { function: { parameters: { properties: object } } }[];
|
|
1239
|
+
};
|
|
1240
|
+
return Object.keys(params.tools?.[0]?.function.parameters.properties ?? {});
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
it('drops the optional label when strict is auto-enabled (invalid otherwise)', () => {
|
|
1244
|
+
expect(toolProperties({ response_format: jsonSchemaResponseFormat })).toEqual(['query']);
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
it('keeps the label on non-strict requests', () => {
|
|
1248
|
+
expect(toolProperties({})).toEqual(['intent', 'query']);
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* The Responses API flattens tools to `{type, name, parameters, strict}`
|
|
1253
|
+
* and does NOT infer strict from a `json_schema` response_format the way
|
|
1254
|
+
* Completions does — an explicit `strict` (per-call option or on the tool)
|
|
1255
|
+
* is what turns it on there.
|
|
1256
|
+
*/
|
|
1257
|
+
function responsesToolProperties(
|
|
1258
|
+
options: Record<string, unknown>
|
|
1259
|
+
): string[] {
|
|
1260
|
+
const model = new ChatOpenAI({ model: 'gpt-4', apiKey: 'test-key' });
|
|
1261
|
+
const responses = (
|
|
1262
|
+
model as unknown as {
|
|
1263
|
+
responses: {
|
|
1264
|
+
invocationParams: (o: Record<string, unknown>) => {
|
|
1265
|
+
tools?: { parameters?: { properties?: object } }[];
|
|
1266
|
+
};
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
).responses;
|
|
1270
|
+
const params = responses.invocationParams({
|
|
1271
|
+
tools: [intentTool],
|
|
1272
|
+
...options,
|
|
1273
|
+
});
|
|
1274
|
+
return Object.keys(params.tools?.[0]?.parameters?.properties ?? {});
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
it('drops the label on the RESPONSES delegate under strict too', () => {
|
|
1278
|
+
expect(responsesToolProperties({ strict: true })).toEqual(['query']);
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
it('keeps the label on a non-strict RESPONSES request', () => {
|
|
1282
|
+
expect(responsesToolProperties({})).toEqual(['intent', 'query']);
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
it('spares a business `intent` param even under strict', () => {
|
|
1286
|
+
const businessTool = {
|
|
1287
|
+
type: 'function' as const,
|
|
1288
|
+
function: {
|
|
1289
|
+
name: 'create_record',
|
|
1290
|
+
parameters: {
|
|
1291
|
+
type: 'object',
|
|
1292
|
+
properties: {
|
|
1293
|
+
intent: { type: 'string', description: 'CRM intent category' },
|
|
1294
|
+
title: { type: 'string' },
|
|
1295
|
+
},
|
|
1296
|
+
required: ['intent', 'title'],
|
|
1297
|
+
},
|
|
1298
|
+
},
|
|
1299
|
+
};
|
|
1300
|
+
const model = new ChatOpenAI({ model: 'gpt-4', apiKey: 'test-key' });
|
|
1301
|
+
const completions = completionsOf<InvocationParamsDelegate>(model);
|
|
1302
|
+
const params = completions.invocationParams({
|
|
1303
|
+
tools: [businessTool],
|
|
1304
|
+
response_format: jsonSchemaResponseFormat,
|
|
1305
|
+
}) as unknown as {
|
|
1306
|
+
tools?: { function: { parameters: { properties: object } } }[];
|
|
1307
|
+
};
|
|
1308
|
+
expect(Object.keys(params.tools?.[0]?.function.parameters.properties ?? {})).toEqual([
|
|
1309
|
+
'intent',
|
|
1310
|
+
'title',
|
|
1311
|
+
]);
|
|
1312
|
+
});
|
|
1313
|
+
});
|
|
1209
1314
|
});
|
|
1210
1315
|
|
|
1211
1316
|
describe('ChatOpenAI._streamChatModelEvents (native, fork)', () => {
|
package/src/stream.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
calculateMaxToolResultChars,
|
|
40
40
|
truncateToolResultContent,
|
|
41
41
|
} from '@/utils/truncation';
|
|
42
|
+
import { resolveToolOutcome, outcomeFieldsFromResult } from '@/tools/intentArg';
|
|
42
43
|
import { TOOL_OUTPUT_REF_PATTERN } from '@/tools/toolOutputReferences';
|
|
43
44
|
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
44
45
|
import { isGoogleLike } from '@/utils/llm';
|
|
@@ -859,6 +860,11 @@ async function dispatchEagerToolCompletions(args: {
|
|
|
859
860
|
maxToolResultChars
|
|
860
861
|
).content;
|
|
861
862
|
}
|
|
863
|
+
const outcome = resolveToolOutcome(
|
|
864
|
+
record.request.args,
|
|
865
|
+
outcomeFieldsFromResult(result),
|
|
866
|
+
{ isError: result.status === 'error' }
|
|
867
|
+
);
|
|
862
868
|
|
|
863
869
|
try {
|
|
864
870
|
const dispatched = await safeDispatchCustomEvent(
|
|
@@ -878,6 +884,7 @@ async function dispatchEagerToolCompletions(args: {
|
|
|
878
884
|
id: result.toolCallId,
|
|
879
885
|
output,
|
|
880
886
|
progress: 1,
|
|
887
|
+
...(outcome != null && { outcome }),
|
|
881
888
|
} as t.ProcessedToolCall,
|
|
882
889
|
},
|
|
883
890
|
},
|
|
@@ -2160,7 +2167,7 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
2160
2167
|
toolCallContentIndexMap.delete(existingToolCallId);
|
|
2161
2168
|
}
|
|
2162
2169
|
|
|
2163
|
-
const newToolCall: ToolCall & t.PartMetadata = {
|
|
2170
|
+
const newToolCall: ToolCall & t.PartMetadata & { outcome?: string } = {
|
|
2164
2171
|
id,
|
|
2165
2172
|
name,
|
|
2166
2173
|
args,
|
|
@@ -2180,6 +2187,10 @@ export function createContentAggregator(): t.ContentAggregatorResult {
|
|
|
2180
2187
|
if (finalUpdate) {
|
|
2181
2188
|
newToolCall.progress = 1;
|
|
2182
2189
|
newToolCall.output = contentPart.tool_call.output;
|
|
2190
|
+
const outcome = (contentPart.tool_call as t.ToolCallPart).outcome;
|
|
2191
|
+
if (outcome != null) {
|
|
2192
|
+
newToolCall.outcome = outcome;
|
|
2193
|
+
}
|
|
2183
2194
|
}
|
|
2184
2195
|
|
|
2185
2196
|
contentParts[index] = {
|