1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! The model-executor seam: the general injection point a host supplies so the
//! server can perform a model call on a client-driven run's behalf.
//!
//! # Why it is a seam, not baked in
//!
//! This mirrors the [`AgentFactory`](crate::AgentFactory) decision exactly. The
//! server owns the *mechanism* of a durable model step (append the write-ahead
//! intent, perform the call, append the completion, answer retries from the
//! log), but it must not own the *policy* of which provider runs, where its
//! credential comes from, or how the request is shaped. Salvor is for anyone
//! building on it, browser or backend; aarg is only the first consumer. So the
//! executor is a trait the embedding binary implements and injects, never a
//! hard-wired provider.
//!
//! `salvor serve` wires a default [`LlmModelExecutor`] from its own
//! client-construction path, so the feature works out of the box; another host
//! (a future `aarg serve`) injects its own executor resolving a keychain
//! credential, and nothing consumer-specific leaks into salvor-server.
//!
//! # The two methods
//!
//! - [`ModelExecutor::execute`] performs a single call and returns the assembled
//! [`MessageResponse`]. This backs the non-streaming model step.
//! - [`ModelExecutor::open_stream`] opens the provider stream and returns a
//! [`ModelStream`] the server pumps: each event feeds a live ticker frame and
//! a [`MessageAccumulator`], and the assembled completion is recorded once at
//! the end. This backs the server-sent-events model step.
//!
//! The request crosses the seam as a raw [`Value`] (the caller's canonical
//! request JSON), not a typed `MessageRequest`, because the server hashes and
//! records exactly those bytes: the hash it recorded is the hash it sent.
//!
//! An executor error is a plain `String`, the same human-message convention the
//! agent factory uses. A provider failure maps to the server error envelope
//! without a completion being recorded, so the intent is left dangling (legal,
//! the crash story) and the run stays drivable.
//!
//! # Naming the key on a 401, the way the CLI already does
//!
//! A raw 401 from `salvor_llm::Error::Api` says the request was rejected; it
//! says nothing about where to fix it. The CLI's `contextualize_auth_error`
//! (`salvor-cli/src/commands.rs`) solves this for the agent-driven run/resume
//! path because it still has the agent's own `[llm] api_key_env` in scope. This
//! executor has no such per-agent config to read: a client-driven run's model
//! step is served by the one [`LlmModelExecutor`] `salvor serve` wires for the
//! whole process, built by `Config::from_env`, which only ever reads
//! `ANTHROPIC_API_KEY` (see `salvor-llm/src/config.rs`). So `contextualize_401`
//! names that fixed variable directly, and points at the machine running the
//! server rather than the client's own environment: for a client-driven run the
//! key lives with the server, not the caller driving it over HTTP.
use async_trait;
use ;
use Value;
/// The environment variable `Config::from_env` reads for the client-driven
/// model step's executor. Fixed, not per-agent: see the module doc above.
const API_KEY_ENV: &str = "ANTHROPIC_API_KEY";
/// Turns a 401 from the Messages API into a message naming `ANTHROPIC_API_KEY`
/// and where to set it; every other error passes through as `to_string()`.
/// Performs a model call on behalf of a client-driven run. Injected by the
/// embedding binary, exactly like [`AgentFactory`](crate::AgentFactory).
/// A stream of provider events opened by [`ModelExecutor::open_stream`].
///
/// It mirrors `salvor_llm::MessageStream`: pull one typed event at a time until
/// `None`. The server feeds each event to a [`MessageAccumulator`] so the
/// recorded completion is byte-identical to the non-streaming path.
/// The default [`ModelExecutor`], wrapping a general `salvor_llm::Client`.
///
/// This is not consumer-specific: it wraps the general model transport, so any
/// host that reaches a provider through `salvor-llm` gets a working executor by
/// constructing a [`Client`] and handing it here. `salvor serve` builds one from
/// its environment client-construction path.
/// The [`ModelStream`] over a `salvor_llm::MessageStream`.