@xandreed/plugin-models 0.13.0 → 0.15.0
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/README.md +1 -1
- package/dist/auth/anthropicOAuth.d.ts +45 -11
- package/dist/auth/anthropicOAuth.js +23 -23
- package/dist/auth/auth-store.conformance.d.ts +63 -0
- package/dist/auth/auth-store.conformance.js +152 -0
- package/dist/auth/credential-input.entity.d.ts +52 -0
- package/dist/auth/credential-input.entity.functions.d.ts +18 -0
- package/dist/auth/credential-input.entity.functions.js +12 -0
- package/dist/auth/credential-input.entity.js +22 -0
- package/dist/auth/credential-refresh.entity.d.ts +17 -0
- package/dist/auth/credential-refresh.entity.functions.d.ts +57 -0
- package/dist/auth/credential-refresh.entity.functions.js +21 -0
- package/dist/auth/credential-refresh.entity.js +2 -0
- package/dist/auth/localAuth.d.ts +12 -2
- package/dist/auth/localAuth.js +58 -86
- package/dist/auth/memory-auth.adapter.d.ts +35 -0
- package/dist/auth/memory-auth.adapter.js +35 -0
- package/dist/auth/openAiCodexOAuth.d.ts +28 -6
- package/dist/auth/openAiCodexOAuth.js +22 -32
- package/dist/auth/token-response.entity.d.ts +21 -0
- package/dist/auth/token-response.entity.functions.d.ts +20 -0
- package/dist/auth/token-response.entity.functions.js +10 -0
- package/dist/auth/token-response.entity.js +10 -0
- package/dist/compat/index.d.ts +2 -0
- package/dist/compat/index.js +1 -0
- package/dist/conformance/index.d.ts +6 -0
- package/dist/conformance/index.js +3 -0
- package/dist/index.d.ts +30 -13
- package/dist/index.js +25 -50
- package/dist/llm/chat-wire.entity.d.ts +123 -0
- package/dist/llm/chat-wire.entity.functions.d.ts +28 -0
- package/dist/llm/chat-wire.entity.functions.js +10 -0
- package/dist/llm/chat-wire.entity.js +37 -0
- package/dist/llm/compat-telemetry.adapter.d.ts +6 -0
- package/dist/llm/compat-telemetry.adapter.js +21 -0
- package/dist/llm/compat.d.ts +84 -33
- package/dist/llm/compat.js +188 -121
- package/dist/llm/model-completion.adapter.d.ts +49 -0
- package/dist/llm/model-completion.adapter.js +77 -0
- package/dist/llm/model-completion.entity.d.ts +4 -0
- package/dist/llm/model-completion.entity.functions.d.ts +3 -0
- package/dist/llm/model-completion.entity.functions.js +7 -0
- package/dist/llm/model-completion.entity.js +7 -0
- package/dist/llm/model-retry-policy.entity.d.ts +37 -0
- package/dist/llm/model-retry-policy.entity.functions.d.ts +16 -0
- package/dist/llm/model-retry-policy.entity.functions.js +33 -0
- package/dist/llm/model-retry-policy.entity.js +23 -0
- package/dist/llm/model-settings.entity.d.ts +85 -0
- package/dist/llm/model-settings.entity.functions.d.ts +11 -0
- package/dist/llm/model-settings.entity.functions.js +49 -0
- package/dist/llm/model-settings.entity.js +28 -0
- package/dist/llm/model-usage.entity.d.ts +61 -0
- package/dist/llm/model-usage.entity.functions.d.ts +11 -0
- package/dist/llm/model-usage.entity.functions.js +24 -0
- package/dist/llm/model-usage.entity.js +19 -0
- package/dist/llm/modelCatalog.d.ts +47 -7
- package/dist/llm/modelCatalog.js +31 -57
- package/dist/llm/openAiCodex.d.ts +18 -7
- package/dist/llm/openAiCodex.js +57 -45
- package/dist/llm/openAiCodexWebSocket.d.ts +60 -7
- package/dist/llm/openAiCodexWebSocket.js +113 -105
- package/dist/llm/prompt-cache-frame.entity.d.ts +18 -0
- package/dist/llm/prompt-cache-frame.entity.js +10 -0
- package/dist/llm/prompt-cache.entity.d.ts +57 -0
- package/dist/llm/prompt-cache.entity.functions.d.ts +8 -0
- package/dist/llm/prompt-cache.entity.functions.js +73 -0
- package/dist/llm/prompt-cache.entity.js +27 -0
- package/dist/llm/provider-client.adapter.d.ts +9 -0
- package/dist/llm/provider-client.adapter.js +60 -0
- package/dist/llm/provider-failure.entity.d.ts +42 -0
- package/dist/llm/provider-failure.entity.functions.d.ts +8 -0
- package/dist/llm/provider-failure.entity.functions.js +67 -0
- package/dist/llm/provider-failure.entity.js +20 -0
- package/dist/llm/provider-profile.entity.d.ts +79 -0
- package/dist/llm/provider-profile.entity.functions.d.ts +23 -0
- package/dist/llm/provider-profile.entity.functions.js +83 -0
- package/dist/llm/provider-profile.entity.js +10 -0
- package/dist/llm/providers.d.ts +125 -29
- package/dist/llm/providers.js +311 -117
- package/dist/llm/retry.d.ts +11 -47
- package/dist/llm/retry.js +33 -52
- package/dist/llm/router.d.ts +56 -28
- package/dist/llm/router.js +125 -88
- package/dist/llm/sse.d.ts +58 -29
- package/dist/llm/sse.js +47 -39
- package/dist/llm/thinking-binding.entity.d.ts +49 -0
- package/dist/llm/thinking-binding.entity.functions.d.ts +21 -0
- package/dist/llm/thinking-binding.entity.functions.js +16 -0
- package/dist/llm/thinking-binding.entity.js +11 -0
- package/dist/llm/utilityLlm.d.ts +14 -5
- package/dist/llm/utilityLlm.js +18 -13
- package/dist/plugin.adapter.d.ts +154 -6
- package/dist/plugin.adapter.js +53 -10
- package/dist/ports/auth-store.port.d.ts +73 -0
- package/dist/ports/auth-store.port.js +24 -0
- package/dist/ports/host-credentials.port.d.ts +22 -0
- package/dist/ports/host-credentials.port.js +4 -0
- package/dist/ports/model-call-completion.port.d.ts +9 -0
- package/dist/ports/model-call-completion.port.js +2 -0
- package/dist/ports/model-endpoints.port.d.ts +66 -19
- package/dist/ports/model-endpoints.port.js +22 -4
- package/dist/ports/model-retry-policy.port.d.ts +17 -0
- package/dist/ports/model-retry-policy.port.js +3 -0
- package/dist/ports/model-settings.port.d.ts +32 -0
- package/dist/ports/model-settings.port.js +4 -0
- package/dist/ports/models-transport.port.d.ts +53 -0
- package/dist/ports/models-transport.port.js +7 -0
- package/dist/ports/prompt-cache-frame.port.d.ts +21 -0
- package/dist/ports/prompt-cache-frame.port.js +4 -0
- package/dist/ports/settings-store.port.d.ts +70 -0
- package/dist/ports/settings-store.port.js +33 -0
- package/dist/ports/thinking-binding.port.d.ts +7 -0
- package/dist/ports/thinking-binding.port.js +4 -0
- package/dist/settings/localSettings.d.ts +2 -1
- package/dist/settings/localSettings.js +18 -61
- package/dist/settings/memory-settings.adapter.d.ts +12 -0
- package/dist/settings/memory-settings.adapter.js +11 -0
- package/dist/settings/roleView.d.ts +3 -1
- package/dist/settings/roleView.js +1 -1
- package/dist/settings/settings-store.conformance.d.ts +47 -0
- package/dist/settings/settings-store.conformance.js +100 -0
- package/dist/settings/settings-value.entity.d.ts +15 -0
- package/dist/settings/settings-value.entity.functions.d.ts +51 -0
- package/dist/settings/settings-value.entity.functions.js +42 -0
- package/dist/settings/settings-value.entity.js +2 -0
- package/dist/storage/json-file.d.ts +18 -0
- package/dist/storage/json-file.js +26 -0
- package/dist/transport/http-proxy.entity.d.ts +104 -0
- package/dist/transport/http-proxy.entity.functions.d.ts +51 -0
- package/dist/transport/http-proxy.entity.functions.js +112 -0
- package/dist/transport/http-proxy.entity.js +42 -0
- package/dist/transport/model-request.conformance.d.ts +13 -0
- package/dist/transport/model-request.conformance.js +36 -0
- package/dist/transport/models-transport.adapter.d.ts +49 -0
- package/dist/transport/models-transport.adapter.js +99 -0
- package/dist/transport/resilient-http-client.adapter.d.ts +73 -0
- package/dist/transport/resilient-http-client.adapter.js +177 -0
- package/dist/transport/undici-fetch.adapter.d.ts +13 -0
- package/dist/transport/undici-fetch.adapter.js +1 -0
- package/package.json +13 -6
- package/dist/auth/anthropicOAuth.test.d.ts +0 -1
- package/dist/auth/localAuth.test.d.ts +0 -1
- package/dist/llm/compat.test.d.ts +0 -1
- package/dist/llm/modelCatalog.test.d.ts +0 -1
- package/dist/llm/providers.test.d.ts +0 -1
- package/dist/llm/retry.test.d.ts +0 -1
- package/dist/llm/router.test.d.ts +0 -1
- package/dist/llm/sse.test.d.ts +0 -1
- package/dist/plugin.adapter.test.d.ts +0 -1
- package/dist/settings/localSettings.test.d.ts +0 -1
- package/dist/settings/roleView.test.d.ts +0 -1
package/dist/llm/sse.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { AiError } from "effect/ai";
|
|
2
|
-
import { Effect, Option, Result, Stream } from "effect";
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import { Cause, Effect, Exit, Match, Option, Result, Schema, Stream } from "effect";
|
|
3
|
+
import { isTurnError, reasonOf } from "@xandreed/core";
|
|
4
|
+
import { completeModelAttempt, modelOutcomeOfExit, observeModelAttempt } from "./model-completion.adapter.js";
|
|
5
|
+
import { chunkErrorOf, decodeStreamChunk, decodeToolArguments, isQuotaExhausted } from "./chat-wire.entity.functions.js";
|
|
6
|
+
import { redactCredentials } from "../transport/http-proxy.entity.functions.js";
|
|
7
|
+
import { classifyAiError } from "./provider-failure.entity.functions.js";
|
|
8
|
+
export const usageFromCompletion = (usage, reading = {}) => {
|
|
9
|
+
const input = usage?.prompt_tokens ?? 0, cached = usage?.prompt_cache_hit_tokens ?? usage?.cached_tokens ?? usage?.prompt_tokens_details?.cached_tokens ?? 0, written = usage?.prompt_tokens_details?.cache_write_tokens ?? 0, output = usage?.completion_tokens ?? 0, reasoning = reading.reasoning === !1 ? 0 : usage?.completion_tokens_details?.reasoning_tokens ?? 0;
|
|
5
10
|
return {
|
|
6
|
-
inputTokens: { total: input, uncached: Math.max(0, input - cached), cacheRead: cached },
|
|
7
|
-
outputTokens: { total:
|
|
11
|
+
inputTokens: { total: input, uncached: Math.max(0, input - cached - written), cacheRead: cached, cacheWrite: written },
|
|
12
|
+
outputTokens: { total: output, text: Math.max(0, output - reasoning), reasoning }
|
|
8
13
|
};
|
|
9
|
-
}, finishReasonFromWire = (raw) => {
|
|
10
|
-
if (raw === "tool_calls")
|
|
11
|
-
return "tool-calls";
|
|
12
|
-
if (raw === "length")
|
|
13
|
-
return "length";
|
|
14
|
-
if (raw === "content_filter")
|
|
15
|
-
return "content-filter";
|
|
16
|
-
return "stop";
|
|
17
14
|
};
|
|
15
|
+
const endOfWord = (word) => Match.value(word.trim().toLowerCase()).pipe(Match.whenOr("stop", "tool_calls", "function_call", "tool_use", "tool_call", "end_turn", "stop_sequence", "eos", () => "settled"), Match.whenOr("length", "max_tokens", () => "length"), Match.when("content_filter", () => "content-filter"), Match.whenOr("error", "insufficient_system_resource", () => "error"), Match.orElse(() => "other")), namedReason = (raw) => Option.filter(Option.fromNullishOr(raw), (word) => word.trim().length > 0);
|
|
16
|
+
export const finishReasonFromWire = (raw, hasToolCalls) => {
|
|
17
|
+
const end = Option.match(Option.flatMap(raw, namedReason), { onNone: () => "settled", onSome: endOfWord });
|
|
18
|
+
return end === "settled" ? hasToolCalls ? "tool-calls" : "stop" : end;
|
|
19
|
+
}, toolCallParams = (args, reason, invalid) => decodeToolArguments(args).pipe(Effect.catchTag("SchemaError", () => reason === "stop" || reason === "tool-calls" ? Effect.fail(invalid()) : Effect.succeed({})));
|
|
18
20
|
const initialState = {
|
|
19
21
|
textOpen: Option.none(),
|
|
20
22
|
reasoningOpen: Option.none(),
|
|
@@ -68,51 +70,57 @@ const initialState = {
|
|
|
68
70
|
args: call.args + (fragment.function?.arguments ?? "")
|
|
69
71
|
} : call);
|
|
70
72
|
}, malformed = (moduleName, description) => AiError.make({ module: moduleName, method: "streamText", reason: new AiError.InvalidOutputError({ description }) }), applyChunk = (moduleName, state, chunk) => {
|
|
71
|
-
if (chunk.error !== void 0 && chunk.error !== null)
|
|
72
|
-
|
|
73
|
+
if (chunk.error !== void 0 && chunk.error !== null) {
|
|
74
|
+
const error = chunkErrorOf(chunk.error);
|
|
75
|
+
return Effect.fail(classifyAiError(AiError.make({
|
|
73
76
|
module: moduleName,
|
|
74
77
|
method: "streamText",
|
|
75
|
-
reason: new AiError.UnknownError({
|
|
76
|
-
description: `the stream carried an error chunk: ${JSON.stringify(chunk.error).slice(0, 500)}
|
|
78
|
+
reason: isQuotaExhausted(Option.none(), error) ? new AiError.QuotaExhaustedError({ metadata: { [moduleName]: { ...Option.getOrElse(error, () => ({})) } } }) : new AiError.UnknownError({
|
|
79
|
+
description: `the stream carried an error chunk: ${JSON.stringify(chunk.error).slice(0, 500)}`,
|
|
80
|
+
metadata: { [moduleName]: { ...Option.getOrElse(error, () => ({})) } }
|
|
77
81
|
})
|
|
78
|
-
}));
|
|
82
|
+
}), "compat"));
|
|
83
|
+
}
|
|
79
84
|
const choice = chunk.choices?.[0], delta = choice?.delta, reasoning = delta?.reasoning_content ?? delta?.reasoning, content = delta?.content, afterReasoning = typeof reasoning === "string" && reasoning.length > 0 ? emitReasoning(state, reasoning) : [state, []], afterText = emitThen(afterReasoning, (s) => typeof content === "string" && content.length > 0 ? emitText(s, content) : [s, []]), [folded, parts] = afterText, next = {
|
|
80
85
|
...folded,
|
|
81
86
|
toolCalls: (delta?.tool_calls ?? []).reduce(mergeToolFragment, folded.toolCalls),
|
|
82
|
-
finishReason:
|
|
87
|
+
finishReason: Option.orElse(namedReason(choice?.finish_reason), () => folded.finishReason),
|
|
83
88
|
usage: chunk.usage !== null && chunk.usage !== void 0 ? Option.some(chunk.usage) : folded.usage
|
|
84
89
|
};
|
|
85
90
|
return Effect.succeed([next, parts]);
|
|
86
|
-
}, flush = (moduleName, state) => state.flushed ? Effect.succeed([state, []]) : Effect.gen(function* () {
|
|
87
|
-
const [closed, closeParts] = emitThen(closeText(state), closeReasoning), toolParts = yield* Effect.forEach(closed.toolCalls, (call,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}),
|
|
94
|
-
catch: () => malformed(moduleName, `tool call ${call.name.length > 0 ? call.name : "?"} carried unparseable JSON arguments`)
|
|
95
|
-
})), finish = {
|
|
91
|
+
}, flush = (moduleName, reading, state, end) => state.flushed ? Effect.succeed([state, []]) : Effect.gen(function* () {
|
|
92
|
+
const [closed, closeParts] = emitThen(closeText(state), closeReasoning), reason = end === "eof" && Option.isNone(closed.finishReason) ? "unknown" : finishReasonFromWire(closed.finishReason, closed.toolCalls.length > 0), toolParts = yield* Effect.forEach(closed.toolCalls, (call) => toolCallParams(call.args.length > 0 ? call.args : "{}", reason, () => malformed(moduleName, `tool call ${call.name.length > 0 ? call.name : "?"} carried unparseable JSON arguments`)).pipe(Effect.map((params) => ({
|
|
93
|
+
type: "tool-call",
|
|
94
|
+
id: call.id,
|
|
95
|
+
name: call.name,
|
|
96
|
+
params
|
|
97
|
+
}))), { concurrency: 1 }), finish = {
|
|
96
98
|
type: "finish",
|
|
97
|
-
reason
|
|
98
|
-
usage: usageFromCompletion(Option.
|
|
99
|
+
reason,
|
|
100
|
+
usage: usageFromCompletion(Option.getOrElse(closed.usage, () => ({})), reading)
|
|
99
101
|
};
|
|
100
102
|
return [
|
|
101
103
|
{ ...closed, flushed: !0 },
|
|
102
104
|
[...closeParts, ...toolParts, finish]
|
|
103
105
|
];
|
|
104
|
-
}), DONE = "[DONE]", step = (moduleName, state, payload
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
}), DONE = "[DONE]", step = (moduleName, reading, state, payload, attempt, observeUsage) => {
|
|
107
|
+
const ended = (end) => Option.match(attempt, { onNone: () => Effect.void, onSome: (value) => completeModelAttempt(value, { _tag: "Completed" }) }).pipe(Effect.andThen(flush(moduleName, reading, state, end)));
|
|
108
|
+
return Match.valueTags(payload, {
|
|
109
|
+
End: () => ended("eof"),
|
|
110
|
+
Data: ({ data }) => data === DONE ? ended("done") : decodeStreamChunk(moduleName, data).pipe(Effect.tap((chunk) => {
|
|
111
|
+
const final = Option.isSome(state.finishReason) || (chunk.choices ?? []).some((choice) => choice.finish_reason != null);
|
|
112
|
+
return Option.match(attempt, { onNone: () => Effect.void, onSome: (value) => observeModelAttempt(value, chunk.usage, final, reading.reasoning ?? !0) }).pipe(Effect.andThen(observeUsage?.(chunk.usage, final) ?? Effect.void));
|
|
113
|
+
}), Effect.flatMap((chunk) => applyChunk(moduleName, state, chunk)))
|
|
114
|
+
});
|
|
115
|
+
};
|
|
108
116
|
export const sseStreamParts = (options) => Stream.fromReadableStream({
|
|
109
117
|
evaluate: () => options.body,
|
|
110
|
-
onError: (cause) => AiError.make({
|
|
118
|
+
onError: (cause) => AiError.isAiError(cause) || isTurnError(cause) ? cause : AiError.make({
|
|
111
119
|
module: options.moduleName,
|
|
112
120
|
method: "streamText",
|
|
113
|
-
reason: new AiError.UnknownError({ description: `the response body stream failed: ${
|
|
121
|
+
reason: new AiError.UnknownError({ description: `the response body stream failed: ${redactCredentials(reasonOf(Cause.die(cause)))}` })
|
|
114
122
|
})
|
|
115
123
|
}).pipe(Stream.decodeText(), Stream.splitLines, Stream.filterMap((line) => {
|
|
116
124
|
const trimmed = line.trim();
|
|
117
|
-
return trimmed.startsWith("data:") ? Result.succeed(trimmed.slice(5).trim()) : Result.fail(line);
|
|
118
|
-
}), Stream.concat(Stream.succeed(
|
|
125
|
+
return trimmed.startsWith("data:") ? Result.succeed({ _tag: "Data", data: trimmed.slice(5).trim() }) : Result.fail(line);
|
|
126
|
+
}), Stream.concat(Stream.succeed({ _tag: "End" })), Stream.mapAccumEffect(() => initialState, (state, payload) => step(options.moduleName, options.usage ?? {}, state, payload, options.attempt ?? Option.none(), options.observeUsage)), Stream.onExit((exit) => Option.match(options.attempt ?? Option.none(), { onNone: () => Effect.void, onSome: (attempt) => completeModelAttempt(attempt, Exit.isSuccess(exit) ? { _tag: "Interrupted" } : modelOutcomeOfExit(exit)).pipe(Effect.catch((error) => Effect.die(error))) })), Stream.catch((error) => isTurnError(error) ? Stream.die(error) : Stream.fail(error)));
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* What a request tells a model that binds each thinking block to the
|
|
4
|
+
* conversation before it (`ModelProfile.bindsThinking`) to do with a block
|
|
5
|
+
* whose prefix changed: `provider-default` sends nothing and leaves it to
|
|
6
|
+
* the account (an account that enforces the binding refuses the request,
|
|
7
|
+
* one that does not passes the block on); `drop_block` drops that block and
|
|
8
|
+
* the later thinking blocks for the request; `error` refuses the request.
|
|
9
|
+
* Any value but `provider-default` turns the binding on for that request.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ThinkingPrefixMismatch: Schema.Literals<readonly ["provider-default", "drop_block", "error"]>;
|
|
12
|
+
export type ThinkingPrefixMismatch = typeof ThinkingPrefixMismatch.Type;
|
|
13
|
+
/** The beta a request needs to carry `thinking.block_binding` (refused without it) and to be told what was dropped. */
|
|
14
|
+
export declare const THINKING_BINDING_BETA = "thinking-binding-controls-2026-08-01";
|
|
15
|
+
/** One change the provider made to a request's input before the model read it (`input_transformations`). */
|
|
16
|
+
export declare const InputTransformation: Schema.Struct<{
|
|
17
|
+
/** `thinking_dropped`, `thinking_mismatch_allowed`, or another the provider adds later. */
|
|
18
|
+
readonly type: Schema.String;
|
|
19
|
+
/** Why: `prefix_binding_mismatch`, `model_binding_mismatch`, `organization_binding_mismatch`. */
|
|
20
|
+
readonly reason: Schema.optionalKey<Schema.String>;
|
|
21
|
+
}>;
|
|
22
|
+
export type InputTransformation = typeof InputTransformation.Type;
|
|
23
|
+
/** What a settled response's body says it changed, its other fields left unread. */
|
|
24
|
+
export declare const TransformedResponse: Schema.Struct<{
|
|
25
|
+
readonly input_transformations: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
26
|
+
/** `thinking_dropped`, `thinking_mismatch_allowed`, or another the provider adds later. */
|
|
27
|
+
readonly type: Schema.String;
|
|
28
|
+
/** Why: `prefix_binding_mismatch`, `model_binding_mismatch`, `organization_binding_mismatch`. */
|
|
29
|
+
readonly reason: Schema.optionalKey<Schema.String>;
|
|
30
|
+
}>>>;
|
|
31
|
+
}>;
|
|
32
|
+
/** A stream's `message_start` event: its message carries the changes (the event itself too, read either way). */
|
|
33
|
+
export declare const TransformedMessageStart: Schema.Struct<{
|
|
34
|
+
readonly type: Schema.Literal<"message_start">;
|
|
35
|
+
readonly input_transformations: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
36
|
+
/** `thinking_dropped`, `thinking_mismatch_allowed`, or another the provider adds later. */
|
|
37
|
+
readonly type: Schema.String;
|
|
38
|
+
/** Why: `prefix_binding_mismatch`, `model_binding_mismatch`, `organization_binding_mismatch`. */
|
|
39
|
+
readonly reason: Schema.optionalKey<Schema.String>;
|
|
40
|
+
}>>>;
|
|
41
|
+
readonly message: Schema.optionalKey<Schema.Struct<{
|
|
42
|
+
readonly input_transformations: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
43
|
+
/** `thinking_dropped`, `thinking_mismatch_allowed`, or another the provider adds later. */
|
|
44
|
+
readonly type: Schema.String;
|
|
45
|
+
/** Why: `prefix_binding_mismatch`, `model_binding_mismatch`, `organization_binding_mismatch`. */
|
|
46
|
+
readonly reason: Schema.optionalKey<Schema.String>;
|
|
47
|
+
}>>>;
|
|
48
|
+
}>>;
|
|
49
|
+
}>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Option } from "effect";
|
|
2
|
+
import type { InputTransformation, ThinkingPrefixMismatch } from "./thinking-binding.entity.js";
|
|
3
|
+
/**
|
|
4
|
+
* An `anthropic-beta` header with `betas` added: the values it holds first,
|
|
5
|
+
* in order, then each new one once. Never a replacement: the client's own
|
|
6
|
+
* betas (structured outputs, the subscription's) stay.
|
|
7
|
+
*/
|
|
8
|
+
export declare const mergeBetas: (existing: Option.Option<string>, betas: ReadonlyArray<string>) => string;
|
|
9
|
+
/** The binding a request to a model carries: none for a model that does not bind, or with `provider-default`. */
|
|
10
|
+
export declare const bindingFor: (bindsThinking: boolean, mismatch: ThinkingPrefixMismatch) => Option.Option<Exclude<ThinkingPrefixMismatch, "provider-default">>;
|
|
11
|
+
/** The reasons of the thinking blocks the provider dropped, one per block, in order. */
|
|
12
|
+
export declare const droppedReasonsOf: (transformations: ReadonlyArray<InputTransformation>) => ReadonlyArray<string>;
|
|
13
|
+
/** What a settled response's JSON body says the provider dropped; nothing when it is not that JSON. */
|
|
14
|
+
export declare const droppedInBody: (body: string) => ReadonlyArray<string>;
|
|
15
|
+
/**
|
|
16
|
+
* The first event of a server-sent stream, when `text` (the stream so far)
|
|
17
|
+
* holds it whole: its `data` lines joined. None until its blank line came.
|
|
18
|
+
*/
|
|
19
|
+
export declare const firstEventData: (text: string) => Option.Option<string>;
|
|
20
|
+
/** What a stream's `message_start` data says the provider dropped (on the event or its message); nothing for another event. */
|
|
21
|
+
export declare const droppedInMessageStart: (data: string) => ReadonlyArray<string>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Option, Schema } from "effect";
|
|
2
|
+
import { TransformedMessageStart, TransformedResponse } from "./thinking-binding.entity.js";
|
|
3
|
+
export const mergeBetas = (existing, betas) => {
|
|
4
|
+
return [...Option.match(existing, { onNone: () => [], onSome: (value) => value.split(",").map((beta) => beta.trim()).filter((beta) => beta.length > 0) }), ...betas].filter((beta, index, all) => all.indexOf(beta) === index).join(",");
|
|
5
|
+
}, bindingFor = (bindsThinking, mismatch) => bindsThinking && mismatch !== "provider-default" ? Option.some(mismatch) : Option.none(), droppedReasonsOf = (transformations) => transformations.filter((change) => change.type === "thinking_dropped").map((change) => change.reason ?? "unknown");
|
|
6
|
+
const decodeResponse = Schema.decodeUnknownOption(Schema.fromJsonString(TransformedResponse)), decodeMessageStart = Schema.decodeUnknownOption(Schema.fromJsonString(TransformedMessageStart));
|
|
7
|
+
export const droppedInBody = (body) => Option.match(decodeResponse(body), { onNone: () => [], onSome: (response) => droppedReasonsOf(response.input_transformations ?? []) }), firstEventData = (text) => {
|
|
8
|
+
const end = text.search(/\r?\n\r?\n/);
|
|
9
|
+
if (end < 0)
|
|
10
|
+
return Option.none();
|
|
11
|
+
return Option.some(text.slice(0, end).split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join(`
|
|
12
|
+
`));
|
|
13
|
+
}, droppedInMessageStart = (data) => Option.match(decodeMessageStart(data), {
|
|
14
|
+
onNone: () => [],
|
|
15
|
+
onSome: (event) => droppedReasonsOf([...event.input_transformations ?? [], ...event.message?.input_transformations ?? []])
|
|
16
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
export const ThinkingPrefixMismatch = Schema.Literals(["provider-default", "drop_block", "error"]), THINKING_BINDING_BETA = "thinking-binding-controls-2026-08-01", InputTransformation = Schema.Struct({
|
|
3
|
+
type: Schema.String,
|
|
4
|
+
reason: Schema.optionalKey(Schema.String)
|
|
5
|
+
}), TransformedResponse = Schema.Struct({
|
|
6
|
+
input_transformations: Schema.optionalKey(Schema.Array(InputTransformation))
|
|
7
|
+
}), TransformedMessageStart = Schema.Struct({
|
|
8
|
+
type: Schema.Literal("message_start"),
|
|
9
|
+
input_transformations: Schema.optionalKey(Schema.Array(InputTransformation)),
|
|
10
|
+
message: Schema.optionalKey(TransformedResponse)
|
|
11
|
+
});
|
package/dist/llm/utilityLlm.d.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { Layer } from "effect";
|
|
2
|
-
import {
|
|
2
|
+
import { UtilityLlm } from "@xandreed/core";
|
|
3
|
+
import { AuthStore } from "../ports/auth-store.port.js";
|
|
4
|
+
import { SettingsStore } from "../ports/settings-store.port.js";
|
|
3
5
|
/**
|
|
4
|
-
* The fast helper tier: one-shot completions on `fastModel ?? model
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* The fast helper tier: one-shot completions on `fastModel ?? model`, at
|
|
7
|
+
* `fastReasoningEffort ?? reasoningEffort` (the fast role, as
|
|
8
|
+
* `roleModelView("fast")` reads it; a call policy the caller pinned wins).
|
|
9
|
+
* No toolkit or loop; a Messages helper prompt carries no automatic cache
|
|
10
|
+
* breakpoint, since a one-shot prompt is never read back.
|
|
11
|
+
* A helper completion cannot park a turn. Like the routed model, it reads
|
|
12
|
+
* its provider services once, where it is built: over a `ModelsTransport`,
|
|
13
|
+
* its calls go through it.
|
|
14
|
+
*
|
|
15
|
+
* @public
|
|
7
16
|
*/
|
|
8
|
-
export declare const UtilityLlmLive: Layer.Layer<UtilityLlm, never,
|
|
17
|
+
export declare const UtilityLlmLive: Layer.Layer<UtilityLlm, never, AuthStore | SettingsStore>;
|
package/dist/llm/utilityLlm.js
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
|
-
import { Prompt } from "effect/ai";
|
|
1
|
+
import { Prompt, Response } from "effect/ai";
|
|
2
2
|
import { FetchHttpClient, HttpClient } from "effect/http";
|
|
3
|
-
import { Effect, Layer, Option } from "effect";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { Cause, Context, Effect, Layer, Option } from "effect";
|
|
4
|
+
import { isTurnError, parseModelSelection, reasonOf, responseModelOf, UtilityCompletion, UtilityError, UtilityLlm } from "@xandreed/core";
|
|
5
|
+
import { AuthStore } from "../ports/auth-store.port.js";
|
|
6
|
+
import { SettingsStore } from "../ports/settings-store.port.js";
|
|
7
|
+
import { extractUsage } from "@xandreed/core/internal";
|
|
8
|
+
import { generateOver, layerProviderServices, throughTransport, withConfiguredEffort } from "./router.js";
|
|
9
|
+
import { CurrentModelCallPurpose } from "../ports/model-call-completion.port.js";
|
|
6
10
|
export const UtilityLlmLive = Layer.effect(UtilityLlm, Effect.gen(function* () {
|
|
7
|
-
const
|
|
11
|
+
const stores = Context.pick(AuthStore)(yield* Effect.context()), services = yield* layerProviderServices, http = yield* HttpClient.HttpClient, settings = yield* SettingsStore;
|
|
8
12
|
return {
|
|
9
|
-
complete:
|
|
10
|
-
const loaded = yield* settings.load.pipe(Effect.mapError((e) => new UtilityError({ message: e.message }))), raw = Option.getOrElse(Option.orElse(loaded.fastModel, () => loaded.model), () => ""), selection = yield* Option.match(parseModelSelection(raw), {
|
|
13
|
+
complete: Effect.fn("models.utility")(function* (prompt) {
|
|
14
|
+
const loaded = yield* settings.load.pipe(Effect.mapError((e) => new UtilityError({ message: e.message, cause: e }))), raw = Option.getOrElse(Option.orElse(loaded.fastModel, () => loaded.model), () => ""), selection = yield* Option.match(parseModelSelection(raw), {
|
|
11
15
|
onNone: () => Effect.fail(new UtilityError({ message: "no fast/general model configured" })),
|
|
12
16
|
onSome: Effect.succeed
|
|
13
|
-
}), res = yield*
|
|
17
|
+
}), res = yield* withConfiguredEffort(generateOver(services)(selection, {
|
|
14
18
|
prompt: Prompt.make(prompt),
|
|
15
19
|
tools: [],
|
|
16
20
|
toolChoice: "none",
|
|
17
21
|
responseFormat: { type: "text" }
|
|
18
|
-
}).pipe(Effect.
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
}), Option.orElse(loaded.fastReasoningEffort, () => loaded.reasoningEffort)).pipe(Effect.provideService(CurrentModelCallPurpose, "Helper"), Effect.catch((error) => isTurnError(error) ? Effect.die(error) : Effect.fail(new UtilityError({ message: reasonOf(Cause.fail(error)), cause: error }))), throughTransport(services), Effect.provide(stores), Effect.provideService(HttpClient.HttpClient, http)), parts = res.content.filter(Response.isPart), text = parts.flatMap((part) => part.type === "text" ? [part.text] : []).join(""), model = Option.flatMap(responseModelOf(parts), parseModelSelection);
|
|
23
|
+
return new UtilityCompletion({
|
|
24
|
+
text,
|
|
25
|
+
usage: extractUsage(res.usage, parts),
|
|
26
|
+
...Option.match(model, { onNone: () => ({}), onSome: (selection) => ({ model: selection }) })
|
|
27
|
+
});
|
|
23
28
|
})
|
|
24
29
|
};
|
|
25
30
|
})).pipe(Layer.provide(FetchHttpClient.layer));
|
package/dist/plugin.adapter.d.ts
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { LanguageModel } from "effect/ai";
|
|
2
|
-
import {
|
|
2
|
+
import { Config as EffectConfig, Redacted } from "effect";
|
|
3
|
+
import { HarnessError, ModelCatalog, SessionEnvironment, UtilityLlm } from "@xandreed/core";
|
|
4
|
+
import { AuthStore } from "./ports/auth-store.port.js";
|
|
5
|
+
import { SettingsStore } from "./ports/settings-store.port.js";
|
|
6
|
+
/**
|
|
7
|
+
* The models plugin: the routed `LanguageModel`, the fast `UtilityLlm`, the
|
|
8
|
+
* `ModelCatalog`, and the `AuthStore` and `SettingsStore` they read.
|
|
9
|
+
* Runtime-scoped: built once per `Agent.define`, so credentials, refreshed
|
|
10
|
+
* tokens and settings edits held in memory last across turns. A turn's own
|
|
11
|
+
* services win over it (a per-turn `LanguageModel` replaces the routed one).
|
|
12
|
+
* When a proxy applies (the `proxy` option, else the environment's
|
|
13
|
+
* `https_proxy` / `HTTPS_PROXY` and `http_proxy` / `HTTP_PROXY`), every call
|
|
14
|
+
* it makes (the providers, the OpenAI-compatible endpoints, OAuth token
|
|
15
|
+
* refreshes, the OpenAI subscription's WebSocket) goes through one
|
|
16
|
+
* `ModelsTransport`, the `noProxy` / `NO_PROXY` hosts and loopback direct;
|
|
17
|
+
* so do they through a host's own transport.
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
3
21
|
export declare const modelsPlugin: import("@xandreed/core").TypedPlugin<{
|
|
4
22
|
readonly model: string;
|
|
5
23
|
readonly fastModel: string;
|
|
@@ -8,11 +26,76 @@ export declare const modelsPlugin: import("@xandreed/core").TypedPlugin<{
|
|
|
8
26
|
readonly endpoints: {
|
|
9
27
|
readonly [x: string]: {
|
|
10
28
|
readonly url: string;
|
|
11
|
-
readonly
|
|
12
|
-
readonly
|
|
29
|
+
readonly images?: boolean;
|
|
30
|
+
readonly apiKey?: string | Redacted.Redacted<string>;
|
|
31
|
+
readonly api?: "chat-completions";
|
|
13
32
|
readonly standardOnly?: boolean;
|
|
33
|
+
readonly apiKeyEnv?: string;
|
|
34
|
+
} | {
|
|
35
|
+
readonly url: string;
|
|
36
|
+
readonly api: "anthropic-messages";
|
|
37
|
+
readonly images?: boolean;
|
|
38
|
+
readonly apiKey?: string | Redacted.Redacted<string>;
|
|
39
|
+
readonly auth?: "x-api-key" | "bearer";
|
|
40
|
+
readonly apiKeyEnv?: string;
|
|
41
|
+
readonly cacheControl?: boolean;
|
|
42
|
+
readonly apiVersion?: string;
|
|
43
|
+
readonly betas?: readonly string[];
|
|
14
44
|
};
|
|
15
45
|
};
|
|
46
|
+
readonly modelSettings: {
|
|
47
|
+
readonly [x: string]: {
|
|
48
|
+
readonly effort?: "none" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
49
|
+
readonly limits?: {
|
|
50
|
+
readonly maxOutputTokens?: number;
|
|
51
|
+
readonly contextWindow?: number;
|
|
52
|
+
};
|
|
53
|
+
readonly thinking?: {
|
|
54
|
+
readonly _tag: "Off";
|
|
55
|
+
} | {
|
|
56
|
+
readonly _tag: "Adaptive";
|
|
57
|
+
readonly display?: "omitted" | "summarized";
|
|
58
|
+
} | {
|
|
59
|
+
readonly _tag: "Budget";
|
|
60
|
+
readonly budgetTokens: number;
|
|
61
|
+
readonly display?: "omitted" | "summarized";
|
|
62
|
+
} | {
|
|
63
|
+
readonly _tag: "BetweenTools";
|
|
64
|
+
};
|
|
65
|
+
readonly promptCache?: {
|
|
66
|
+
readonly _tag: "Auto";
|
|
67
|
+
readonly ttl: "5m" | "1h";
|
|
68
|
+
} | {
|
|
69
|
+
readonly _tag: "TopLevel";
|
|
70
|
+
readonly ttl: "5m" | "1h";
|
|
71
|
+
} | {
|
|
72
|
+
readonly _tag: "Off";
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
readonly storage: "local" | "memory";
|
|
77
|
+
readonly credentials: {
|
|
78
|
+
readonly [x: string]: {
|
|
79
|
+
readonly type: "api_key";
|
|
80
|
+
readonly key: Redacted.Redacted<string>;
|
|
81
|
+
} | {
|
|
82
|
+
readonly type: "oauth";
|
|
83
|
+
readonly access: Redacted.Redacted<string>;
|
|
84
|
+
readonly refresh: Redacted.Redacted<string>;
|
|
85
|
+
readonly expires: number;
|
|
86
|
+
readonly accountId?: string & import("effect/Brand").Brand<"AccountId">;
|
|
87
|
+
readonly installationId?: string & import("effect/Brand").Brand<"InstallationId">;
|
|
88
|
+
} | {
|
|
89
|
+
readonly type: "local";
|
|
90
|
+
readonly baseUrl?: string;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
readonly credentialEnv: {
|
|
94
|
+
readonly [x: string]: string;
|
|
95
|
+
};
|
|
96
|
+
readonly proxy: string | Redacted.Redacted<string>;
|
|
97
|
+
readonly noProxy: string;
|
|
98
|
+
readonly thinkingPrefixMismatch: "error" | "provider-default" | "drop_block";
|
|
16
99
|
}, {
|
|
17
100
|
readonly model: string;
|
|
18
101
|
readonly fastModel: string;
|
|
@@ -21,10 +104,75 @@ export declare const modelsPlugin: import("@xandreed/core").TypedPlugin<{
|
|
|
21
104
|
readonly endpoints: {
|
|
22
105
|
readonly [x: string]: {
|
|
23
106
|
readonly url: string;
|
|
24
|
-
readonly
|
|
25
|
-
readonly
|
|
107
|
+
readonly images?: boolean;
|
|
108
|
+
readonly apiKey?: string | Redacted.Redacted<string>;
|
|
109
|
+
readonly api?: "chat-completions";
|
|
26
110
|
readonly standardOnly?: boolean;
|
|
111
|
+
readonly apiKeyEnv?: string;
|
|
112
|
+
} | {
|
|
113
|
+
readonly url: string;
|
|
114
|
+
readonly api: "anthropic-messages";
|
|
115
|
+
readonly images?: boolean;
|
|
116
|
+
readonly apiKey?: string | Redacted.Redacted<string>;
|
|
117
|
+
readonly auth?: "x-api-key" | "bearer";
|
|
118
|
+
readonly apiKeyEnv?: string;
|
|
119
|
+
readonly cacheControl?: boolean;
|
|
120
|
+
readonly apiVersion?: string;
|
|
121
|
+
readonly betas?: readonly string[];
|
|
27
122
|
};
|
|
28
123
|
};
|
|
29
|
-
|
|
124
|
+
readonly modelSettings: {
|
|
125
|
+
readonly [x: string]: {
|
|
126
|
+
readonly effort?: "none" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
127
|
+
readonly limits?: {
|
|
128
|
+
readonly maxOutputTokens?: number;
|
|
129
|
+
readonly contextWindow?: number;
|
|
130
|
+
};
|
|
131
|
+
readonly thinking?: {
|
|
132
|
+
readonly _tag: "Adaptive";
|
|
133
|
+
readonly display?: "omitted" | "summarized";
|
|
134
|
+
} | {
|
|
135
|
+
readonly _tag: "Budget";
|
|
136
|
+
readonly budgetTokens: number;
|
|
137
|
+
readonly display?: "omitted" | "summarized";
|
|
138
|
+
} | {
|
|
139
|
+
readonly _tag: "BetweenTools";
|
|
140
|
+
} | {
|
|
141
|
+
readonly _tag: "Off";
|
|
142
|
+
};
|
|
143
|
+
readonly promptCache?: {
|
|
144
|
+
readonly _tag: "Off";
|
|
145
|
+
} | {
|
|
146
|
+
readonly _tag: "Auto";
|
|
147
|
+
readonly ttl: "5m" | "1h";
|
|
148
|
+
} | {
|
|
149
|
+
readonly _tag: "TopLevel";
|
|
150
|
+
readonly ttl: "5m" | "1h";
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
readonly storage: "local" | "memory";
|
|
155
|
+
readonly credentials: {
|
|
156
|
+
readonly [x: string]: {
|
|
157
|
+
readonly type: "api_key";
|
|
158
|
+
readonly key: Redacted.Redacted<string>;
|
|
159
|
+
} | {
|
|
160
|
+
readonly type: "oauth";
|
|
161
|
+
readonly access: Redacted.Redacted<string>;
|
|
162
|
+
readonly refresh: Redacted.Redacted<string>;
|
|
163
|
+
readonly expires: number;
|
|
164
|
+
readonly accountId?: string;
|
|
165
|
+
readonly installationId?: string;
|
|
166
|
+
} | {
|
|
167
|
+
readonly type: "local";
|
|
168
|
+
readonly baseUrl?: string;
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
readonly credentialEnv: {
|
|
172
|
+
readonly [x: string]: string;
|
|
173
|
+
};
|
|
174
|
+
readonly proxy: string | Redacted.Redacted<string>;
|
|
175
|
+
readonly noProxy: string;
|
|
176
|
+
readonly thinkingPrefixMismatch: "error" | "provider-default" | "drop_block";
|
|
177
|
+
}, ModelCatalog | UtilityLlm | LanguageModel.LanguageModel | AuthStore | SettingsStore, HarnessError | import("./index.js").AuthError | EffectConfig.ConfigError, SessionEnvironment>;
|
|
30
178
|
export default modelsPlugin;
|
package/dist/plugin.adapter.js
CHANGED
|
@@ -1,27 +1,70 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { LanguageModel } from "effect/ai";
|
|
3
|
-
import { Effect, Layer, Option, Schema } from "effect";
|
|
4
|
-
import {
|
|
3
|
+
import { Config as EffectConfig, Context, Effect, Layer, Match, Option, Redacted, Schema } from "effect";
|
|
4
|
+
import { definePlugin, HarnessError, ModelCatalog, SessionEnvironment, UtilityLlm } from "@xandreed/core";
|
|
5
|
+
import { AuthStore } from "./ports/auth-store.port.js";
|
|
6
|
+
import { EngineSettings, SettingsError, SettingsStore } from "./ports/settings-store.port.js";
|
|
7
|
+
import { CredentialInputs } from "./auth/credential-input.entity.js";
|
|
8
|
+
import { credentialsOf } from "./auth/credential-input.entity.functions.js";
|
|
5
9
|
import { LocalAuthStoreLive } from "./auth/localAuth.js";
|
|
10
|
+
import { MemoryAuthStoreLive } from "./auth/memory-auth.adapter.js";
|
|
6
11
|
import { LanguageModelLive } from "./llm/router.js";
|
|
7
12
|
import { UtilityLlmLive } from "./llm/utilityLlm.js";
|
|
8
13
|
import { ConfiguredModelCatalogLive } from "./llm/modelCatalog.js";
|
|
9
14
|
import { LocalSettingsStoreLive } from "./settings/localSettings.js";
|
|
15
|
+
import { MemorySettingsStoreLive } from "./settings/memory-settings.adapter.js";
|
|
16
|
+
import { HostCredentials } from "./ports/host-credentials.port.js";
|
|
10
17
|
import { CurrentModelEndpoints, ModelEndpoints } from "./ports/model-endpoints.port.js";
|
|
11
|
-
|
|
18
|
+
import { CurrentModelSettings } from "./ports/model-settings.port.js";
|
|
19
|
+
import { ModelSettingsMap } from "./llm/model-settings.entity.js";
|
|
20
|
+
import { CurrentThinkingPrefixMismatch } from "./ports/thinking-binding.port.js";
|
|
21
|
+
import { ThinkingPrefixMismatch } from "./llm/thinking-binding.entity.js";
|
|
22
|
+
import { CurrentManagedModelsTransport, ModelsTransport } from "./ports/models-transport.port.js";
|
|
23
|
+
import { ProxyUrlOption } from "./transport/http-proxy.entity.js";
|
|
24
|
+
import { modelsTransportContextFor } from "./transport/models-transport.adapter.js";
|
|
25
|
+
const Config = Schema.Struct({
|
|
26
|
+
model: Schema.String,
|
|
27
|
+
fastModel: Schema.String,
|
|
28
|
+
fallbackModel: Schema.String,
|
|
29
|
+
inheritPrevious: Schema.Boolean,
|
|
30
|
+
endpoints: ModelEndpoints,
|
|
31
|
+
modelSettings: ModelSettingsMap,
|
|
32
|
+
storage: Schema.Literals(["local", "memory"]),
|
|
33
|
+
credentials: CredentialInputs,
|
|
34
|
+
credentialEnv: Schema.Record(Schema.String, Schema.String),
|
|
35
|
+
proxy: ProxyUrlOption,
|
|
36
|
+
noProxy: Schema.String,
|
|
37
|
+
thinkingPrefixMismatch: ThinkingPrefixMismatch
|
|
38
|
+
}).annotate({ messageUnexpectedKey: "not an option of the models plugin" }).check(Schema.makeFilter((config) => config.storage === "memory" || Object.keys(config.credentials).length === 0 && Object.keys(config.credentialEnv).length === 0 ? void 0 : 'credentials and credentialEnv need storage: "memory"')), optional = (value) => value.length === 0 ? Option.none() : Option.some(value), overlaid = (config) => (previous) => new EngineSettings({
|
|
39
|
+
...previous,
|
|
40
|
+
model: Option.orElse(optional(config.model), () => previous.model),
|
|
41
|
+
fastModel: Option.orElse(optional(config.fastModel), () => previous.fastModel),
|
|
42
|
+
fallbackModel: Option.orElse(optional(config.fallbackModel), () => previous.fallbackModel)
|
|
43
|
+
}), memoryStores = (config) => Layer.unwrap(Effect.gen(function* () {
|
|
44
|
+
const fromEnv = yield* Effect.forEach(Object.entries(config.credentialEnv), ([provider, name]) => EffectConfig.option(EffectConfig.Redacted(name)).pipe(Effect.map(Option.map((key) => [provider, { type: "api_key", key }]))), { concurrency: 1 }), seed = new Map([...fromEnv.flatMap(Option.toArray), ...credentialsOf(config.credentials)]);
|
|
45
|
+
return Layer.mergeAll(MemoryAuthStoreLive(seed), MemorySettingsStoreLive(overlaid(config)(new EngineSettings({}))));
|
|
46
|
+
})), readOnly = (what) => () => Effect.fail(new SettingsError({ message: `Edit the models plugin configuration to change ${what}` })), localStores = (config, workspace) => {
|
|
47
|
+
const home = homedir(), files = Effect.service(SettingsStore).pipe(Effect.provide(LocalSettingsStoreLive(workspace, home))), settings = Layer.effect(SettingsStore, Effect.map(files, (stored) => SettingsStore.of({
|
|
48
|
+
load: config.inheritPrevious ? Effect.map(stored.load, overlaid(config)) : Effect.succeed(overlaid(config)(new EngineSettings({}))),
|
|
49
|
+
setRole: readOnly("a model"),
|
|
50
|
+
set: readOnly("settings")
|
|
51
|
+
})));
|
|
52
|
+
return Layer.mergeAll(settings, LocalAuthStoreLive(workspace, home, ".efferent/runtime", config.inheritPrevious ? ".efferent" : void 0));
|
|
53
|
+
}, optionError = (message) => new HarnessError({ code: "config.options", plugin: "@xandreed/plugin-models", message }), transportLayer = (config) => Effect.flatMap(Effect.serviceOption(ModelsTransport), Option.match({
|
|
54
|
+
onSome: (host) => Redacted.isRedacted(config.proxy) || config.proxy.length > 0 || config.noProxy.length > 0 ? Effect.fail(optionError("the host provides ModelsTransport, so the proxy and noProxy options would not apply: give the proxy to the host's transport")) : Effect.succeed(Layer.succeedContext(Context.make(ModelsTransport, host).pipe(Context.add(CurrentManagedModelsTransport, Option.none())))),
|
|
55
|
+
onNone: () => Effect.succeed(Layer.effectContext(modelsTransportContextFor({ proxy: config.proxy, noProxy: config.noProxy }).pipe(Effect.mapError((error) => optionError(error.message)))))
|
|
56
|
+
}));
|
|
12
57
|
export const modelsPlugin = definePlugin({
|
|
13
58
|
id: "@xandreed/plugin-models",
|
|
14
|
-
version: "0.
|
|
59
|
+
version: "0.15.0",
|
|
60
|
+
scope: "runtime",
|
|
15
61
|
config: Config,
|
|
16
|
-
defaults: { model: "", fastModel: "", fallbackModel: "", inheritPrevious: !0, endpoints: {} },
|
|
62
|
+
defaults: { model: "", fastModel: "", fallbackModel: "", inheritPrevious: !0, endpoints: {}, modelSettings: {}, storage: "local", credentials: {}, credentialEnv: {}, proxy: "", noProxy: "", thinkingPrefixMismatch: "drop_block" },
|
|
17
63
|
requires: [SessionEnvironment],
|
|
64
|
+
optional: [HostCredentials, ModelsTransport],
|
|
18
65
|
provides: [LanguageModel.LanguageModel, UtilityLlm, AuthStore, SettingsStore, ModelCatalog],
|
|
19
66
|
layer: (config) => Layer.unwrap(Effect.gen(function* () {
|
|
20
|
-
const { workspace } = yield* SessionEnvironment,
|
|
21
|
-
load: Effect.succeed(new EngineSettings({ ...previous, model: Option.orElse(optional(config.model), () => previous.model), fastModel: Option.orElse(optional(config.fastModel), () => previous.fastModel), fallbackModel: Option.orElse(optional(config.fallbackModel), () => previous.fallbackModel) })),
|
|
22
|
-
setRole: () => Effect.fail(new SettingsError({ message: "Edit the models plugin configuration to change a model" })),
|
|
23
|
-
set: () => Effect.fail(new SettingsError({ message: "Edit the models plugin configuration to change settings" }))
|
|
24
|
-
}), base = Layer.mergeAll(settings, LocalAuthStoreLive(workspace, homedir(), ".efferent/runtime", config.inheritPrevious ? ".efferent" : void 0), Layer.succeed(CurrentModelEndpoints, config.endpoints));
|
|
67
|
+
const { workspace } = yield* SessionEnvironment, host = yield* Effect.serviceOption(HostCredentials), stores = yield* Match.value(config.storage).pipe(Match.when("memory", () => Effect.succeed(memoryStores(config))), Match.when("local", () => Option.isSome(host) ? Effect.fail(optionError('the host provides HostCredentials, which needs storage: "memory"')) : Effect.succeed(localStores(config, workspace))), Match.exhaustive), transport = yield* transportLayer(config), base = Layer.mergeAll(stores, Layer.succeed(CurrentModelEndpoints, config.endpoints), Layer.succeed(CurrentModelSettings, config.modelSettings), Layer.succeed(CurrentThinkingPrefixMismatch, config.thinkingPrefixMismatch)).pipe(Layer.provideMerge(transport));
|
|
25
68
|
return Layer.mergeAll(LanguageModelLive, UtilityLlmLive, ConfiguredModelCatalogLive).pipe(Layer.provideMerge(base));
|
|
26
69
|
}))
|
|
27
70
|
});
|