@librechat/agents 4.0.1 → 4.0.3
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/agents/AgentContext.cjs +3 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/decisions/deadline.cjs +52 -0
- package/dist/cjs/decisions/deadline.cjs.map +1 -0
- package/dist/cjs/decisions/dialect.cjs +88 -0
- package/dist/cjs/decisions/dialect.cjs.map +1 -0
- package/dist/cjs/decisions/http.cjs +77 -0
- package/dist/cjs/decisions/http.cjs.map +1 -0
- package/dist/cjs/decisions/index.cjs +7 -0
- package/dist/cjs/decisions/presets.cjs +82 -0
- package/dist/cjs/decisions/presets.cjs.map +1 -0
- package/dist/cjs/decisions/questions.cjs +70 -0
- package/dist/cjs/decisions/questions.cjs.map +1 -0
- package/dist/cjs/decisions/structuredChat.cjs +172 -0
- package/dist/cjs/decisions/structuredChat.cjs.map +1 -0
- package/dist/cjs/decisions/structuredOutput.cjs +18 -0
- package/dist/cjs/decisions/structuredOutput.cjs.map +1 -0
- package/dist/cjs/decisions/traceMarker.cjs +6 -0
- package/dist/cjs/decisions/traceMarker.cjs.map +1 -0
- package/dist/cjs/decisions/transport.cjs +166 -0
- package/dist/cjs/decisions/transport.cjs.map +1 -0
- package/dist/cjs/decisions/types.cjs +44 -0
- package/dist/cjs/decisions/types.cjs.map +1 -0
- package/dist/cjs/graphs/Graph.cjs +28 -9
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs +6 -1
- package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs +1 -1
- package/dist/cjs/llm/invoke.cjs +34 -12
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +7 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/providerTextBoundary.cjs +355 -0
- package/dist/cjs/llm/providerTextBoundary.cjs.map +1 -0
- package/dist/cjs/llm/providerTextChunk.cjs +14 -0
- package/dist/cjs/llm/providerTextChunk.cjs.map +1 -0
- package/dist/cjs/llm/providerTextControls.cjs +236 -0
- package/dist/cjs/llm/providerTextControls.cjs.map +1 -0
- package/dist/cjs/llm/stream/smoother.cjs +2 -1
- package/dist/cjs/llm/stream/smoother.cjs.map +1 -1
- package/dist/cjs/main.cjs +49 -1
- package/dist/cjs/protection/providerText.cjs +224 -0
- package/dist/cjs/protection/providerText.cjs.map +1 -0
- package/dist/cjs/protection/providerTextInput.cjs +25 -0
- package/dist/cjs/protection/providerTextInput.cjs.map +1 -0
- package/dist/cjs/rerank/index.cjs +2 -0
- package/dist/cjs/rerank/search.cjs +45 -0
- package/dist/cjs/rerank/search.cjs.map +1 -0
- package/dist/cjs/rerank/systemone.cjs +70 -0
- package/dist/cjs/rerank/systemone.cjs.map +1 -0
- package/dist/cjs/run.cjs +11 -1
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +42 -14
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +26 -8
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/SubagentTool.cjs +11 -3
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +6 -5
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/search/rerankers.cjs +1 -0
- package/dist/cjs/tools/search/search.cjs +3 -3
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +4 -3
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +13 -2
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +149 -26
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs +4 -2
- package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs +3 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
- package/dist/cjs/tools/subagent/diagnostics.cjs +88 -2
- package/dist/cjs/tools/subagent/diagnostics.cjs.map +1 -1
- package/dist/cjs/tools/subagent/hostArgs.cjs +214 -0
- package/dist/cjs/tools/subagent/hostArgs.cjs.map +1 -0
- package/dist/cjs/tools/subagent/index.cjs +2 -0
- package/dist/cjs/tools/toolErrorContent.cjs +1 -1
- package/dist/esm/agents/AgentContext.mjs +3 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/decisions/deadline.mjs +52 -0
- package/dist/esm/decisions/deadline.mjs.map +1 -0
- package/dist/esm/decisions/dialect.mjs +87 -0
- package/dist/esm/decisions/dialect.mjs.map +1 -0
- package/dist/esm/decisions/http.mjs +75 -0
- package/dist/esm/decisions/http.mjs.map +1 -0
- package/dist/esm/decisions/index.mjs +8 -0
- package/dist/esm/decisions/presets.mjs +77 -0
- package/dist/esm/decisions/presets.mjs.map +1 -0
- package/dist/esm/decisions/questions.mjs +67 -0
- package/dist/esm/decisions/questions.mjs.map +1 -0
- package/dist/esm/decisions/structuredChat.mjs +172 -0
- package/dist/esm/decisions/structuredChat.mjs.map +1 -0
- package/dist/esm/decisions/structuredOutput.mjs +18 -0
- package/dist/esm/decisions/structuredOutput.mjs.map +1 -0
- package/dist/esm/decisions/traceMarker.mjs +6 -0
- package/dist/esm/decisions/traceMarker.mjs.map +1 -0
- package/dist/esm/decisions/transport.mjs +164 -0
- package/dist/esm/decisions/transport.mjs.map +1 -0
- package/dist/esm/decisions/types.mjs +39 -0
- package/dist/esm/decisions/types.mjs.map +1 -0
- package/dist/esm/graphs/Graph.mjs +28 -9
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuseToolOutputTracing.mjs +6 -1
- package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +34 -12
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +7 -0
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/providerTextBoundary.mjs +355 -0
- package/dist/esm/llm/providerTextBoundary.mjs.map +1 -0
- package/dist/esm/llm/providerTextChunk.mjs +13 -0
- package/dist/esm/llm/providerTextChunk.mjs.map +1 -0
- package/dist/esm/llm/providerTextControls.mjs +230 -0
- package/dist/esm/llm/providerTextControls.mjs.map +1 -0
- package/dist/esm/llm/stream/smoother.mjs +2 -1
- package/dist/esm/llm/stream/smoother.mjs.map +1 -1
- package/dist/esm/main.mjs +16 -2
- package/dist/esm/protection/providerText.mjs +218 -0
- package/dist/esm/protection/providerText.mjs.map +1 -0
- package/dist/esm/protection/providerTextInput.mjs +24 -0
- package/dist/esm/protection/providerTextInput.mjs.map +1 -0
- package/dist/esm/rerank/index.mjs +3 -0
- package/dist/esm/rerank/search.mjs +45 -0
- package/dist/esm/rerank/search.mjs.map +1 -0
- package/dist/esm/rerank/systemone.mjs +70 -0
- package/dist/esm/rerank/systemone.mjs.map +1 -0
- package/dist/esm/run.mjs +11 -1
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +42 -14
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +26 -8
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/SubagentTool.mjs +11 -3
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +6 -5
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/search/rerankers.mjs +1 -1
- package/dist/esm/tools/search/search.mjs +3 -3
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +4 -3
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +13 -2
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +149 -26
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs +4 -2
- package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs +3 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
- package/dist/esm/tools/subagent/diagnostics.mjs +84 -3
- package/dist/esm/tools/subagent/diagnostics.mjs.map +1 -1
- package/dist/esm/tools/subagent/hostArgs.mjs +207 -0
- package/dist/esm/tools/subagent/hostArgs.mjs.map +1 -0
- package/dist/esm/tools/subagent/index.mjs +2 -0
- package/dist/esm/tools/toolErrorContent.mjs +1 -1
- package/dist/types/decisions/deadline.d.ts +3 -0
- package/dist/types/decisions/dialect.d.ts +11 -0
- package/dist/types/decisions/http.d.ts +22 -0
- package/dist/types/decisions/index.d.ts +10 -0
- package/dist/types/decisions/presets.d.ts +17 -0
- package/dist/types/decisions/questions.d.ts +8 -0
- package/dist/types/decisions/structuredChat.d.ts +17 -0
- package/dist/types/decisions/structuredOutput.d.ts +4 -0
- package/dist/types/decisions/traceMarker.d.ts +2 -0
- package/dist/types/decisions/transport.d.ts +29 -0
- package/dist/types/decisions/types.d.ts +119 -0
- package/dist/types/graphs/Graph.d.ts +7 -3
- package/dist/types/index.d.ts +4 -0
- package/dist/types/llm/invoke.d.ts +4 -1
- package/dist/types/llm/providerTextBoundary.d.ts +4 -0
- package/dist/types/llm/providerTextChunk.d.ts +7 -0
- package/dist/types/llm/providerTextControls.d.ts +18 -0
- package/dist/types/protection/providerText.d.ts +77 -0
- package/dist/types/protection/providerTextInput.d.ts +3 -0
- package/dist/types/rerank/index.d.ts +4 -0
- package/dist/types/rerank/search.d.ts +7 -0
- package/dist/types/rerank/systemone.d.ts +13 -0
- package/dist/types/rerank/types.d.ts +24 -0
- package/dist/types/run.d.ts +2 -0
- package/dist/types/tools/BashExecutor.d.ts +4 -0
- package/dist/types/tools/CodeExecutor.d.ts +4 -0
- package/dist/types/tools/ReadFile.d.ts +4 -0
- package/dist/types/tools/SkillTool.d.ts +4 -0
- package/dist/types/tools/SubagentTool.d.ts +2 -0
- package/dist/types/tools/ToolSearch.d.ts +4 -0
- package/dist/types/tools/search/schema.d.ts +4 -0
- package/dist/types/tools/search/types.d.ts +3 -1
- package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +6 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +22 -1
- package/dist/types/tools/subagent/SubagentReplay.d.ts +3 -1
- package/dist/types/tools/subagent/diagnostics.d.ts +56 -8
- package/dist/types/tools/subagent/hostArgs.d.ts +57 -0
- package/dist/types/tools/subagent/index.d.ts +3 -0
- package/dist/types/types/graph.d.ts +39 -0
- package/dist/types/types/run.d.ts +6 -2
- package/dist/types/types/tools.d.ts +2 -0
- package/package.json +4 -4
- package/src/agents/AgentContext.ts +3 -2
- package/src/decisions/deadline.ts +92 -0
- package/src/decisions/dialect.ts +200 -0
- package/src/decisions/http.ts +175 -0
- package/src/decisions/index.ts +14 -0
- package/src/decisions/presets.ts +111 -0
- package/src/decisions/questions.ts +128 -0
- package/src/decisions/structuredChat.ts +358 -0
- package/src/decisions/structuredOutput.ts +25 -0
- package/src/decisions/traceMarker.ts +2 -0
- package/src/decisions/transport.ts +335 -0
- package/src/decisions/types.ts +193 -0
- package/src/graphs/Graph.ts +57 -9
- package/src/index.ts +9 -0
- package/src/langfuseToolOutputTracing.ts +8 -1
- package/src/llm/invoke.ts +48 -7
- package/src/llm/openai/index.ts +8 -0
- package/src/llm/providerTextBoundary.ts +446 -0
- package/src/llm/providerTextChunk.ts +15 -0
- package/src/llm/providerTextControls.ts +173 -0
- package/src/llm/stream/smoother.ts +4 -2
- package/src/protection/providerText.ts +325 -0
- package/src/protection/providerTextInput.ts +21 -0
- package/src/rerank/index.ts +4 -0
- package/src/rerank/search.ts +85 -0
- package/src/rerank/systemone.ts +140 -0
- package/src/rerank/types.ts +28 -0
- package/src/run.ts +13 -0
- package/src/stream.ts +33 -7
- package/src/summarization/node.ts +32 -14
- package/src/tools/SubagentTool.ts +14 -3
- package/src/tools/ToolNode.ts +7 -3
- package/src/tools/search/search.ts +4 -4
- package/src/tools/search/tool.ts +27 -25
- package/src/tools/search/types.ts +4 -1
- package/src/tools/subagent/SubagentExecutionRegistry.ts +27 -2
- package/src/tools/subagent/SubagentExecutor.ts +323 -28
- package/src/tools/subagent/SubagentReplay.ts +10 -3
- package/src/tools/subagent/childGraphConfig.ts +12 -1
- package/src/tools/subagent/diagnostics.ts +180 -9
- package/src/tools/subagent/hostArgs.ts +413 -0
- package/src/tools/subagent/index.ts +20 -0
- package/src/types/graph.ts +44 -0
- package/src/types/run.ts +6 -2
- package/src/types/tools.ts +6 -1
|
@@ -24,8 +24,9 @@ export type LegacyGraphConfig = BaseGraphConfig & {
|
|
|
24
24
|
} & Omit<g.StandardGraphInput,
|
|
25
25
|
/** `streamLimits` is excluded because legacy graphs receive limits only
|
|
26
26
|
* via the top-level `RunConfig.streamLimits`; accepting the field here
|
|
27
|
-
* would type-check but be silently ignored by `createLegacyGraph`.
|
|
28
|
-
|
|
27
|
+
* would type-check but be silently ignored by `createLegacyGraph`.
|
|
28
|
+
* Required provider-text protection also belongs only to RunConfig. */
|
|
29
|
+
'provider' | 'clientOptions' | 'agents' | 'streamLimits' | 'providerTextProtection'> & Omit<g.AgentInputs, 'provider' | 'clientOptions' | 'agentId'>;
|
|
29
30
|
export type SupervisedGraphConfig = BaseGraphConfig & {
|
|
30
31
|
type: 'supervised';
|
|
31
32
|
/** Enable supervised router; when false, fall back to standard loop */
|
|
@@ -281,6 +282,8 @@ export type RunConfig = {
|
|
|
281
282
|
subagentTasks?: SubagentTaskConfig;
|
|
282
283
|
/** Authorizes and supplies host-owned context to isolated child runs. */
|
|
283
284
|
subagentContext?: g.SubagentContextAdapter;
|
|
285
|
+
/** Host diagnostic sink for foreground, detached, and nested startup failures. */
|
|
286
|
+
onSubagentResolutionFailure?: g.StandardGraphInput['onSubagentResolutionFailure'];
|
|
284
287
|
/**
|
|
285
288
|
* Pre-constructed hook registry for this run. Hooks fire at lifecycle
|
|
286
289
|
* points in `processStream` (RunStart, UserPromptSubmit, Stop,
|
|
@@ -315,6 +318,7 @@ export type RunConfig = {
|
|
|
315
318
|
* tool-call argument byte cap is ON by default, the per-turn event cap is
|
|
316
319
|
* opt-in. See {@link StreamLimits}.
|
|
317
320
|
*/
|
|
321
|
+
providerTextProtection?: g.StandardGraphInput['providerTextProtection'];
|
|
318
322
|
streamLimits?: StreamLimits;
|
|
319
323
|
returnContent?: boolean;
|
|
320
324
|
tokenCounter?: TokenCounter;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@librechat/agents",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.3",
|
|
4
4
|
"reova": {
|
|
5
5
|
"enabled": true,
|
|
6
6
|
"endpoint": "https://telemetry.reo.dev/data"
|
|
@@ -329,7 +329,7 @@
|
|
|
329
329
|
"uuid": "^11.1.1"
|
|
330
330
|
},
|
|
331
331
|
"peerDependencies": {
|
|
332
|
-
"@anthropic-ai/sandbox-runtime": "^0.0.
|
|
332
|
+
"@anthropic-ai/sandbox-runtime": "^0.0.78"
|
|
333
333
|
},
|
|
334
334
|
"peerDependenciesMeta": {
|
|
335
335
|
"@anthropic-ai/sandbox-runtime": {
|
|
@@ -337,7 +337,7 @@
|
|
|
337
337
|
}
|
|
338
338
|
},
|
|
339
339
|
"devDependencies": {
|
|
340
|
-
"@anthropic-ai/sandbox-runtime": "
|
|
340
|
+
"@anthropic-ai/sandbox-runtime": "0.0.78",
|
|
341
341
|
"@anthropic-ai/vertex-sdk": "^0.12.0",
|
|
342
342
|
"@eslint/js": "^10.0.1",
|
|
343
343
|
"@langchain/langgraph-checkpoint-mongodb": "^1.4.1",
|
|
@@ -360,7 +360,7 @@
|
|
|
360
360
|
"mongodb-memory-server-core": "^11.2.0",
|
|
361
361
|
"prettier": "^3.6.2",
|
|
362
362
|
"ts-jest": "^29.4.12",
|
|
363
|
-
"tsc-alias": "^1.
|
|
363
|
+
"tsc-alias": "^1.9.7",
|
|
364
364
|
"tsdown": "^0.22.2",
|
|
365
365
|
"tslib": "^2.6.3",
|
|
366
366
|
"tsx": "^4.23.12",
|
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
import { isTokenCounterCacheCompatible } from '@/llm/tokenCounterCacheCompatibility';
|
|
59
59
|
import { snapshotCompactionSemanticIndex } from '@/summarization/semanticIndex';
|
|
60
60
|
import { createExactTokenCountCache } from '@/llm/contextPressureMeter';
|
|
61
|
+
import { markProviderTextInput } from '@/protection/providerTextInput';
|
|
61
62
|
import { buildSummaryCarrierText } from '@/summarization/shared';
|
|
62
63
|
import { createSchemaOnlyTools } from '@/tools/schema';
|
|
63
64
|
import { apportionTokenCounts } from '@/utils/tokens';
|
|
@@ -960,7 +961,7 @@ export class AgentContext {
|
|
|
960
961
|
: 0;
|
|
961
962
|
}
|
|
962
963
|
|
|
963
|
-
return RunnableLambda.from((messages: BaseMessage[]) => {
|
|
964
|
+
return markProviderTextInput(RunnableLambda.from((messages: BaseMessage[]) => {
|
|
964
965
|
const prefix: BaseMessage[] = systemMessage ? [systemMessage] : [];
|
|
965
966
|
|
|
966
967
|
// Build the non-system portion (summary + conversation), then apply
|
|
@@ -998,7 +999,7 @@ export class AgentContext {
|
|
|
998
999
|
);
|
|
999
1000
|
}
|
|
1000
1001
|
return [...prefix, ...body];
|
|
1001
|
-
}).withConfig({ runName: 'prompt' });
|
|
1002
|
+
})).withConfig({ runName: 'prompt' });
|
|
1002
1003
|
}
|
|
1003
1004
|
|
|
1004
1005
|
private buildSummaryHumanMessage(
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { DecisionError } from './types';
|
|
2
|
+
|
|
3
|
+
export type AwaitWithinDeadline = <T>(task: Promise<T>) => Promise<T>;
|
|
4
|
+
|
|
5
|
+
/** Bounds even non-cooperative credential minters, fetches, body reads, and model calls. */
|
|
6
|
+
export async function withDecisionDeadline<T>(
|
|
7
|
+
provider: string,
|
|
8
|
+
timeoutMs: number,
|
|
9
|
+
callerSignal: AbortSignal | undefined,
|
|
10
|
+
operation: (signal: AbortSignal, waitFor: AwaitWithinDeadline) => Promise<T>
|
|
11
|
+
): Promise<T> {
|
|
12
|
+
if (
|
|
13
|
+
!Number.isSafeInteger(timeoutMs) ||
|
|
14
|
+
timeoutMs <= 0 ||
|
|
15
|
+
timeoutMs > 3_600_000
|
|
16
|
+
) {
|
|
17
|
+
throw new DecisionError('bad_request', 'invalid decision model timeout', {
|
|
18
|
+
provider,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
if (callerSignal?.aborted === true) {
|
|
22
|
+
throw new DecisionError('aborted', 'caller aborted the request', {
|
|
23
|
+
provider,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const expiresAt = performance.now() + timeoutMs;
|
|
28
|
+
const deadline = new AbortController();
|
|
29
|
+
const signal = callerSignal
|
|
30
|
+
? AbortSignal.any([callerSignal, deadline.signal])
|
|
31
|
+
: deadline.signal;
|
|
32
|
+
const timer = setTimeout(() => deadline.abort(), timeoutMs);
|
|
33
|
+
const expired = (): boolean => {
|
|
34
|
+
if (performance.now() >= expiresAt) {
|
|
35
|
+
deadline.abort();
|
|
36
|
+
}
|
|
37
|
+
return signal.aborted;
|
|
38
|
+
};
|
|
39
|
+
const abortError = (): DecisionError => {
|
|
40
|
+
if (callerSignal?.aborted === true) {
|
|
41
|
+
return new DecisionError('aborted', 'caller aborted the request', {
|
|
42
|
+
provider,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return new DecisionError('timeout', 'decision model deadline exceeded', {
|
|
46
|
+
provider,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const waitFor: AwaitWithinDeadline = <U>(task: Promise<U>): Promise<U> =>
|
|
51
|
+
new Promise<U>((resolve, reject) => {
|
|
52
|
+
if (expired()) {
|
|
53
|
+
void task.catch(() => {});
|
|
54
|
+
reject(abortError());
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const onAbort = (): void => {
|
|
58
|
+
signal.removeEventListener('abort', onAbort);
|
|
59
|
+
reject(abortError());
|
|
60
|
+
};
|
|
61
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
62
|
+
if (expired()) {
|
|
63
|
+
onAbort();
|
|
64
|
+
}
|
|
65
|
+
task.then(
|
|
66
|
+
(value) => {
|
|
67
|
+
signal.removeEventListener('abort', onAbort);
|
|
68
|
+
if (expired()) {
|
|
69
|
+
reject(abortError());
|
|
70
|
+
} else {
|
|
71
|
+
resolve(value);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
(error: unknown) => {
|
|
75
|
+
signal.removeEventListener('abort', onAbort);
|
|
76
|
+
reject(expired() ? abortError() : error);
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
return await waitFor(operation(signal, waitFor));
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (expired()) {
|
|
85
|
+
throw abortError();
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
88
|
+
} finally {
|
|
89
|
+
expired();
|
|
90
|
+
clearTimeout(timer);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DecisionAnswer,
|
|
3
|
+
DecisionDialect,
|
|
4
|
+
DecisionQuestion,
|
|
5
|
+
} from './types';
|
|
6
|
+
import { isDecisionObject } from './types';
|
|
7
|
+
|
|
8
|
+
interface WireQuestion {
|
|
9
|
+
type: string;
|
|
10
|
+
instructions: DecisionQuestion['instructions'];
|
|
11
|
+
criteria?: DecisionQuestion['criteria'];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface WireAnswer {
|
|
15
|
+
type?: unknown;
|
|
16
|
+
probability?: unknown;
|
|
17
|
+
noul?: unknown;
|
|
18
|
+
choice?: unknown;
|
|
19
|
+
score?: unknown;
|
|
20
|
+
confidence?: unknown;
|
|
21
|
+
probabilities?: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** System One calls a yes/no question a `noul`; both dialects use a `{true, false}` criterion. */
|
|
25
|
+
export function toWireQuestion(
|
|
26
|
+
question: DecisionQuestion,
|
|
27
|
+
dialect: DecisionDialect
|
|
28
|
+
): WireQuestion {
|
|
29
|
+
const type =
|
|
30
|
+
dialect === 'systemone' && question.type === 'boolean'
|
|
31
|
+
? 'noul'
|
|
32
|
+
: question.type;
|
|
33
|
+
if (question.criteria == null) {
|
|
34
|
+
return { type, instructions: question.instructions };
|
|
35
|
+
}
|
|
36
|
+
const criteria =
|
|
37
|
+
question.type === 'boolean' && typeof question.criteria === 'string'
|
|
38
|
+
? { true: question.criteria }
|
|
39
|
+
: question.criteria;
|
|
40
|
+
return { type, instructions: question.instructions, criteria };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isProbability(value: unknown): value is number {
|
|
44
|
+
return (
|
|
45
|
+
typeof value === 'number' &&
|
|
46
|
+
Number.isFinite(value) &&
|
|
47
|
+
value >= 0 &&
|
|
48
|
+
value <= 1
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** `undefined` means malformed; `null` means no distribution was reported. */
|
|
53
|
+
function probabilitiesOf(
|
|
54
|
+
raw: unknown,
|
|
55
|
+
answerType: 'choice' | 'score',
|
|
56
|
+
question?: DecisionQuestion,
|
|
57
|
+
selectedChoice?: string
|
|
58
|
+
): Record<string, number> | null | undefined {
|
|
59
|
+
if (raw == null) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
if (!isDecisionObject(raw)) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const entries = Object.entries(raw);
|
|
66
|
+
if (entries.length === 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
(question?.type === 'choice' &&
|
|
71
|
+
entries.length !== Object.keys(question.criteria).length) ||
|
|
72
|
+
(question?.type === 'score' && entries.length !== question.criteria.length)
|
|
73
|
+
) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
const probabilities: Record<string, number> = Object.create(null);
|
|
77
|
+
let total = 0;
|
|
78
|
+
let maximum = 0;
|
|
79
|
+
for (const [key, value] of entries) {
|
|
80
|
+
if (!isProbability(value)) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
if (question?.type === 'choice' && !Object.hasOwn(question.criteria, key)) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
if (
|
|
87
|
+
answerType === 'score' &&
|
|
88
|
+
(!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= entries.length)
|
|
89
|
+
) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
probabilities[key] = value;
|
|
93
|
+
total += value;
|
|
94
|
+
maximum = Math.max(maximum, value);
|
|
95
|
+
}
|
|
96
|
+
if (
|
|
97
|
+
selectedChoice !== undefined &&
|
|
98
|
+
(!Object.hasOwn(probabilities, selectedChoice) ||
|
|
99
|
+
probabilities[selectedChoice] < maximum)
|
|
100
|
+
) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
const roundingTolerance = Math.min(
|
|
104
|
+
0.02,
|
|
105
|
+
Math.max(0.01, entries.length * 0.00005 + 0.001)
|
|
106
|
+
);
|
|
107
|
+
return Math.abs(total - 1) <= roundingTolerance ? probabilities : undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** A validated answer, or `null` for one that cannot safely be read. */
|
|
111
|
+
export function readAnswer(
|
|
112
|
+
answer: unknown,
|
|
113
|
+
dialect: DecisionDialect,
|
|
114
|
+
question?: DecisionQuestion
|
|
115
|
+
): DecisionAnswer | null {
|
|
116
|
+
if (!isDecisionObject(answer)) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const record: WireAnswer = answer;
|
|
120
|
+
const booleanType = dialect === 'systemone' ? 'noul' : 'boolean';
|
|
121
|
+
const probability =
|
|
122
|
+
dialect === 'systemone' ? record.noul : record.probability;
|
|
123
|
+
if (record.type === booleanType) {
|
|
124
|
+
if (
|
|
125
|
+
!isProbability(probability) ||
|
|
126
|
+
(question != null && question.type !== 'boolean')
|
|
127
|
+
) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return { type: 'boolean', probability };
|
|
131
|
+
}
|
|
132
|
+
if (record.type !== 'choice' && record.type !== 'score') {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const confidence = record.confidence;
|
|
136
|
+
if (confidence != null && !isProbability(confidence)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const probabilities = probabilitiesOf(
|
|
140
|
+
record.probabilities,
|
|
141
|
+
record.type,
|
|
142
|
+
question,
|
|
143
|
+
record.type === 'choice' && typeof record.choice === 'string'
|
|
144
|
+
? record.choice
|
|
145
|
+
: undefined
|
|
146
|
+
);
|
|
147
|
+
if (probabilities === undefined) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
if (record.type === 'choice') {
|
|
151
|
+
if (
|
|
152
|
+
typeof record.choice !== 'string' ||
|
|
153
|
+
(question != null &&
|
|
154
|
+
(question.type !== 'choice' ||
|
|
155
|
+
!Object.hasOwn(question.criteria, record.choice)))
|
|
156
|
+
) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
type: 'choice',
|
|
161
|
+
choice: record.choice,
|
|
162
|
+
confidence: confidence ?? null,
|
|
163
|
+
probabilities,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (
|
|
167
|
+
typeof record.score !== 'number' ||
|
|
168
|
+
!Number.isFinite(record.score) ||
|
|
169
|
+
record.score < 0 ||
|
|
170
|
+
(question != null &&
|
|
171
|
+
(question.type !== 'score' ||
|
|
172
|
+
record.score > question.criteria.length - 1))
|
|
173
|
+
) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
if (probabilities !== null) {
|
|
177
|
+
let weightedScore = 0;
|
|
178
|
+
let levels = 0;
|
|
179
|
+
for (const [level, probability] of Object.entries(probabilities)) {
|
|
180
|
+
weightedScore += Number(level) * probability;
|
|
181
|
+
levels += 1;
|
|
182
|
+
}
|
|
183
|
+
const roundingTolerance = Math.min(
|
|
184
|
+
0.02,
|
|
185
|
+
0.00005 * (1 + (levels * (levels - 1)) / 2) + 0.001
|
|
186
|
+
);
|
|
187
|
+
if (
|
|
188
|
+
!Number.isFinite(weightedScore) ||
|
|
189
|
+
Math.abs(record.score - weightedScore) > roundingTolerance
|
|
190
|
+
) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
type: 'score',
|
|
196
|
+
score: record.score,
|
|
197
|
+
confidence: confidence ?? null,
|
|
198
|
+
probabilities,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DecisionModel,
|
|
3
|
+
DecisionAnswer,
|
|
4
|
+
DecisionResult,
|
|
5
|
+
DecisionRequest,
|
|
6
|
+
DecisionDialect,
|
|
7
|
+
DecisionCredential,
|
|
8
|
+
DecisionQuestion,
|
|
9
|
+
} from './types';
|
|
10
|
+
import type { DecisionFetch } from './transport';
|
|
11
|
+
import { DecisionError, isDecisionObject, readDecisionUsage } from './types';
|
|
12
|
+
import { validateDecisionQuestions } from './questions';
|
|
13
|
+
import { toWireQuestion, readAnswer } from './dialect';
|
|
14
|
+
import { createTransport } from './transport';
|
|
15
|
+
|
|
16
|
+
export const HTTP_PROVIDER_ID = 'http';
|
|
17
|
+
|
|
18
|
+
export interface HttpDecisionModelOptions {
|
|
19
|
+
providerId?: string;
|
|
20
|
+
apiKey?: DecisionCredential;
|
|
21
|
+
requiresAuth?: boolean;
|
|
22
|
+
/** Full URL, not a base path. */
|
|
23
|
+
endpoint: string;
|
|
24
|
+
model?: string;
|
|
25
|
+
dialect?: DecisionDialect;
|
|
26
|
+
requestKey?: string;
|
|
27
|
+
responseKey?: string;
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
maxRetries?: number;
|
|
30
|
+
fetch?: DecisionFetch;
|
|
31
|
+
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
32
|
+
onAnswered?: (label: string, ms: number) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function parseEnvelope(
|
|
36
|
+
body: string,
|
|
37
|
+
providerId: string,
|
|
38
|
+
readOne: (
|
|
39
|
+
answer: unknown,
|
|
40
|
+
question?: DecisionQuestion
|
|
41
|
+
) => DecisionAnswer | null,
|
|
42
|
+
responseKey?: string,
|
|
43
|
+
expected?: Record<string, DecisionQuestion>
|
|
44
|
+
): DecisionResult {
|
|
45
|
+
let parsed: unknown;
|
|
46
|
+
try {
|
|
47
|
+
parsed = JSON.parse(body);
|
|
48
|
+
} catch {
|
|
49
|
+
throw new DecisionError('malformed_response', 'response was not JSON', {
|
|
50
|
+
provider: providerId,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (!isDecisionObject(parsed)) {
|
|
54
|
+
throw new DecisionError(
|
|
55
|
+
'malformed_response',
|
|
56
|
+
'response was not an object',
|
|
57
|
+
{
|
|
58
|
+
provider: providerId,
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const unwrapped =
|
|
63
|
+
responseKey != null && responseKey !== '' ? parsed[responseKey] : parsed;
|
|
64
|
+
if (
|
|
65
|
+
!isDecisionObject(unwrapped) ||
|
|
66
|
+
!isDecisionObject(unwrapped.answers) ||
|
|
67
|
+
Object.keys(unwrapped.answers).length === 0
|
|
68
|
+
) {
|
|
69
|
+
throw new DecisionError(
|
|
70
|
+
'malformed_response',
|
|
71
|
+
'response carried no answers',
|
|
72
|
+
{
|
|
73
|
+
provider: providerId,
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const answers: DecisionResult['answers'] = Object.create(null);
|
|
79
|
+
for (const [id, answer] of Object.entries(unwrapped.answers)) {
|
|
80
|
+
if (expected && !Object.hasOwn(expected, id)) {
|
|
81
|
+
throw new DecisionError(
|
|
82
|
+
'malformed_response',
|
|
83
|
+
'response carried an unknown answer',
|
|
84
|
+
{
|
|
85
|
+
provider: providerId,
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const mapped = readOne(answer, expected?.[id]);
|
|
90
|
+
if (!mapped) {
|
|
91
|
+
throw new DecisionError(
|
|
92
|
+
'malformed_response',
|
|
93
|
+
'response carried an invalid answer',
|
|
94
|
+
{
|
|
95
|
+
provider: providerId,
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
answers[id] = mapped;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
model: typeof unwrapped.model === 'string' ? unwrapped.model : 'unknown',
|
|
104
|
+
answers,
|
|
105
|
+
usage: readDecisionUsage(unwrapped.usage),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Jev and Laya use the same System One HTTP dialect, but may report different confidence metrics. */
|
|
110
|
+
export function createHttpDecisionModel(
|
|
111
|
+
options: HttpDecisionModelOptions
|
|
112
|
+
): DecisionModel {
|
|
113
|
+
const providerId = options.providerId ?? HTTP_PROVIDER_ID;
|
|
114
|
+
const send = createTransport({ ...options, providerId });
|
|
115
|
+
const model = options.model ?? '';
|
|
116
|
+
const dialect: DecisionDialect = options.dialect ?? 'port';
|
|
117
|
+
const { requestKey, responseKey } = options;
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
id: providerId,
|
|
121
|
+
model,
|
|
122
|
+
async decide(request: DecisionRequest): Promise<DecisionResult> {
|
|
123
|
+
return send(
|
|
124
|
+
() => {
|
|
125
|
+
const entries = validateDecisionQuestions(
|
|
126
|
+
request.questions,
|
|
127
|
+
providerId
|
|
128
|
+
);
|
|
129
|
+
const questions: Record<
|
|
130
|
+
string,
|
|
131
|
+
ReturnType<typeof toWireQuestion>
|
|
132
|
+
> = Object.create(null);
|
|
133
|
+
const expected: Record<string, DecisionQuestion> =
|
|
134
|
+
Object.create(null);
|
|
135
|
+
for (const [id, question] of entries) {
|
|
136
|
+
questions[id] = toWireQuestion(question, dialect);
|
|
137
|
+
expected[id] = question;
|
|
138
|
+
}
|
|
139
|
+
const inner = { state: request.state, questions };
|
|
140
|
+
let payload: string;
|
|
141
|
+
try {
|
|
142
|
+
payload = JSON.stringify({
|
|
143
|
+
...(model !== '' ? { model } : {}),
|
|
144
|
+
...(requestKey != null && requestKey !== ''
|
|
145
|
+
? { [requestKey]: inner }
|
|
146
|
+
: inner),
|
|
147
|
+
});
|
|
148
|
+
} catch {
|
|
149
|
+
throw new DecisionError(
|
|
150
|
+
'bad_request',
|
|
151
|
+
'invalid decision model request',
|
|
152
|
+
{
|
|
153
|
+
provider: providerId,
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
payload,
|
|
159
|
+
parse: (body: string) =>
|
|
160
|
+
parseEnvelope(
|
|
161
|
+
body,
|
|
162
|
+
providerId,
|
|
163
|
+
(answer, question) => readAnswer(answer, dialect, question),
|
|
164
|
+
responseKey,
|
|
165
|
+
expected
|
|
166
|
+
),
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
request.signal,
|
|
170
|
+
request.label ?? 'decide',
|
|
171
|
+
request.timeoutMs
|
|
172
|
+
);
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './types';
|
|
2
|
+
export * from './questions';
|
|
3
|
+
export * from './presets';
|
|
4
|
+
export {
|
|
5
|
+
createHttpDecisionModel,
|
|
6
|
+
parseEnvelope,
|
|
7
|
+
HTTP_PROVIDER_ID,
|
|
8
|
+
} from './http';
|
|
9
|
+
export { createStructuredChatDecisionModel } from './structuredChat';
|
|
10
|
+
export type { HttpDecisionModelOptions } from './http';
|
|
11
|
+
export type { StructuredChatDecisionModelOptions } from './structuredChat';
|
|
12
|
+
export { toWireQuestion, readAnswer } from './dialect';
|
|
13
|
+
export { createTransport } from './transport';
|
|
14
|
+
export type { DecisionFetch, Transport, TransportOptions } from './transport';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DecisionModel,
|
|
3
|
+
DecisionCredential,
|
|
4
|
+
DecisionProviderSettings,
|
|
5
|
+
} from './types';
|
|
6
|
+
import type { DecisionFetch } from './transport';
|
|
7
|
+
import { createHttpDecisionModel } from './http';
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_API_KEY_ENV = 'DECISION_API_KEY';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Known HTTP hosts as settings, not new decision model implementations. Jev and Laya speak the
|
|
13
|
+
* same wire protocol, but their confidence measures and checkpoint calibration differ.
|
|
14
|
+
*/
|
|
15
|
+
export const DECISION_PRESETS: Readonly<
|
|
16
|
+
Record<string, Readonly<DecisionProviderSettings>>
|
|
17
|
+
> = Object.freeze({
|
|
18
|
+
http: Object.freeze({
|
|
19
|
+
dialect: 'port',
|
|
20
|
+
requiresAuth: true,
|
|
21
|
+
apiKeyEnv: DEFAULT_API_KEY_ENV,
|
|
22
|
+
}),
|
|
23
|
+
typesafe: Object.freeze({
|
|
24
|
+
baseURL: 'https://api.typesafe.ai/v1/systemone',
|
|
25
|
+
model: 'jev-latest',
|
|
26
|
+
dialect: 'systemone',
|
|
27
|
+
requiresAuth: true,
|
|
28
|
+
apiKeyEnv: 'TYPESAFE_API_KEY',
|
|
29
|
+
}),
|
|
30
|
+
laya: Object.freeze({
|
|
31
|
+
/** Supply the server's full /v1/systemone URL; no model lets Laya route by language. */
|
|
32
|
+
dialect: 'systemone',
|
|
33
|
+
requiresAuth: false,
|
|
34
|
+
}),
|
|
35
|
+
clickhouse: Object.freeze({
|
|
36
|
+
/** ClickHouse's inference gateway; the token is what `dataplanectl chai auth token` prints, hourly. */
|
|
37
|
+
baseURL: 'https://inference-internal.clickhouse.cloud/v1/systemone',
|
|
38
|
+
model: 'jev-latest',
|
|
39
|
+
dialect: 'systemone',
|
|
40
|
+
requiresAuth: true,
|
|
41
|
+
apiKeyEnv: 'CHAI_AUTH_TOKEN',
|
|
42
|
+
}),
|
|
43
|
+
openrouter: Object.freeze({
|
|
44
|
+
baseURL: 'https://openrouter.ai/api/alpha/decisions',
|
|
45
|
+
model: '~typesafe/jev-latest',
|
|
46
|
+
dialect: 'systemone',
|
|
47
|
+
requiresAuth: true,
|
|
48
|
+
apiKeyEnv: 'OPENROUTER_KEY',
|
|
49
|
+
}),
|
|
50
|
+
cloudflare: Object.freeze({
|
|
51
|
+
/** No default URL: the account id is part of it. */
|
|
52
|
+
model: 'typesafe/jev',
|
|
53
|
+
dialect: 'systemone',
|
|
54
|
+
requestKey: 'input',
|
|
55
|
+
responseKey: 'result',
|
|
56
|
+
requiresAuth: true,
|
|
57
|
+
apiKeyEnv: 'CLOUDFLARE_API_TOKEN',
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export function decisionPreset(
|
|
62
|
+
name: string | undefined
|
|
63
|
+
): DecisionProviderSettings | null {
|
|
64
|
+
if (name == null || name === '') {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return Object.hasOwn(DECISION_PRESETS, name)
|
|
68
|
+
? { ...DECISION_PRESETS[name] }
|
|
69
|
+
: null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Caller settings win except that required preset authentication cannot be weakened. */
|
|
73
|
+
export function mergeDecisionSettings(
|
|
74
|
+
preset: DecisionProviderSettings | null,
|
|
75
|
+
configured: DecisionProviderSettings | undefined
|
|
76
|
+
): DecisionProviderSettings {
|
|
77
|
+
return {
|
|
78
|
+
...(preset ?? {}),
|
|
79
|
+
...(configured ?? {}),
|
|
80
|
+
...(preset?.requiresAuth === true ? { requiresAuth: true } : {}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function decisionProviderNames(): string[] {
|
|
85
|
+
return Object.keys(DECISION_PRESETS);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function createDecisionModel(
|
|
89
|
+
settings: DecisionProviderSettings,
|
|
90
|
+
apiKey?: DecisionCredential,
|
|
91
|
+
options?: {
|
|
92
|
+
fetch?: DecisionFetch;
|
|
93
|
+
providerId?: string;
|
|
94
|
+
onAnswered?: (label: string, ms: number) => void;
|
|
95
|
+
}
|
|
96
|
+
): DecisionModel {
|
|
97
|
+
return createHttpDecisionModel({
|
|
98
|
+
providerId: options?.providerId,
|
|
99
|
+
apiKey,
|
|
100
|
+
requiresAuth: settings.requiresAuth,
|
|
101
|
+
endpoint: settings.baseURL ?? '',
|
|
102
|
+
model: settings.model,
|
|
103
|
+
dialect: settings.dialect,
|
|
104
|
+
requestKey: settings.requestKey,
|
|
105
|
+
responseKey: settings.responseKey,
|
|
106
|
+
timeoutMs: settings.timeoutMs,
|
|
107
|
+
maxRetries: settings.maxRetries,
|
|
108
|
+
fetch: options?.fetch,
|
|
109
|
+
onAnswered: options?.onAnswered,
|
|
110
|
+
});
|
|
111
|
+
}
|