okami.mom/albedo

git clone https://git.nkp.pet/okami.mom/albedo

dawnkernel: a turn end no longer parks into a swap, and replacements and reattaches tell the model what rana88d329

main
11.1 KiB258 linesraw
1"""Provider changes preserve history and confirmed configuration across restart."""
2
3import json
4import time
5import urllib.error
6import urllib.parse
7
8from harness import exclusive
9
10from integration_support import IntegrationScenario, contents
11
12
13class ProviderBindingTests(IntegrationScenario):
14    # exclusive: changes global provider settings and restarts the daemon
15    @exclusive
16    def test_provider_switch_projects_history_across_protocols_and_restart(self):
17        for protocol in ("responses", "chat_completions"):
18            with self.subTest(protocol=protocol):
19                app = self.app_for(protocol)
20                self.settings(app, protocol)
21                session = self.default_session(app)
22                self.change(
23                    app,
24                    session,
25                    {"provider_profile": "alpha", "model": "initial-alpha"},
26                )
27                self.send(app, session, "build switch history")
28                other = (
29                    "responses"
30                    if protocol == "chat_completions"
31                    else "chat_completions"
32                )
33                for fields in (
34                    {"provider_profile": "gamma", "model": "chosen-gamma"},
35                    {"model": "renamed-gamma"},
36                ):
37                    self.change(app, session, fields)
38                selected = self.read(app, f"/sessions/{session}")
39                self.assertEqual(
40                    (selected["provider_profile"], selected["model"]),
41                    ("gamma", "renamed-gamma"),
42                )
43                with self.assertRaises(urllib.error.HTTPError) as caught:
44                    self.change(
45                        app, session, {"provider_profile": "unknown", "model": "wrong"}
46                    )
47                self.assertIn(caught.exception.code, (400, 409))
48                start = len(self.provider.requests)
49                self.send(app, session, "after first switch")
50                gamma = self.records(start)
51                self.assertTrue(gamma)
52                self.assertTrue(
53                    all(
54                        "/gamma/v1/" in r["path"]
55                        and r["model"] == "renamed-gamma"
56                        and r["authorization"] == "Bearer gamma-1"
57                        for r in gamma
58                    )
59                )
60                self.assert_projected(
61                    gamma[0], other, ["build switch history", "after first switch"]
62                )
63                # Per-session selection does not silently rewrite global defaults.
64                default = self.read(app, f"/sessions/{self.default_session(app)}")
65                self.assertEqual(
66                    (default["provider_profile"], default["model"]),
67                    ("alpha", "fixture-alpha"),
68                )
69                self.change(
70                    app,
71                    session,
72                    {"provider_profile": "alpha", "model": "returned-alpha"},
73                )
74                start = len(self.provider.requests)
75                self.send(app, session, "after second switch")
76                returned = self.records(start)
77                self.assertTrue(
78                    all(
79                        "/alpha/v1/" in r["path"] and r["model"] == "returned-alpha"
80                        for r in returned
81                    )
82                )
83                users = [
84                    "build switch history",
85                    "after first switch",
86                    "after second switch",
87                ]
88                self.assert_projected(returned[0], protocol, users)
89                self.restart(app)
90                start = len(self.provider.requests)
91                self.send(app, session, "after persisted restart")
92                restarted = self.records(start)
93                self.assertTrue(
94                    all(
95                        "/alpha/v1/" in r["path"] and r["model"] == "returned-alpha"
96                        for r in restarted
97                    )
98                )
99                self.assert_projected(
100                    restarted[0], protocol, users + ["after persisted restart"]
101                )
102
103    # exclusive: changes global provider settings and restarts the daemon
104    @exclusive
105    def test_provider_config_reload_and_interrupt_recovery(self):
106        for protocol in ("responses", "chat_completions"):
107            with self.subTest(protocol=protocol):
108                app = self.app_for(protocol)
109                self.settings(app, protocol)
110                initial = json.loads((app.home / "config.json").read_text())
111                initial["providers"]["alpha"]["apiKey"] = "alpha-1"
112                (app.home / "config.json").write_text(json.dumps(initial))
113                session = self.default_session(app)
114                self.settings(app, protocol, active="beta")
115                start = len(self.provider.requests)
116                self.send(app, session, "write and inspect a file")
117                alpha = self.records(start)
118                self.assertTrue(alpha)
119                self.assertTrue(
120                    all(
121                        "/alpha/v1/" in r["path"]
122                        and r["authorization"] == "Bearer alpha-2"
123                        and r["model"] == "fixture-alpha"
124                        for r in alpha
125                    )
126                )
127                second = self.default_session(app)
128                info = next(
129                    s
130                    for s in self.read(app, "/sessions?scope=all")["items"]
131                    if s["id"] == second
132                )
133                self.assertEqual(
134                    (info["provider_profile"], info["model"]), ("beta", "fixture-beta")
135                )
136                start = len(self.provider.requests)
137                app.prompt(second, "hang").close()
138                self.wait_for_request(start)
139                for fields in (
140                    {"workspace": str(app.root)},
141                    {"provider_profile": "gamma", "model": "busy-rejected"},
142                ):
143                    with self.assertRaises(urllib.error.HTTPError) as caught:
144                        self.change(app, second, fields)
145                    self.assertEqual(caught.exception.code, 409)
146                info = next(
147                    s
148                    for s in self.read(app, "/sessions?scope=all")["items"]
149                    if s["id"] == second
150                )
151                self.assertEqual(
152                    (info["workspace"], info["provider_profile"]),
153                    (str(app.workspace), "beta"),
154                )
155                captured = self.read(app, f"/sessions/{second}?tail=0")
156                app.api(
157                    f"/sessions/{second}/interrupt",
158                    {
159                        "run_id": captured["status"]["run_id"],
160                        "through_input_order": captured["input_order"],
161                    },
162                ).close()
163                app.idle(second)
164                beta = self.records(start)
165                self.assertTrue(beta)
166                self.assertTrue(
167                    all(
168                        "/beta/v1/" in r["path"]
169                        and r["authorization"] == "Bearer beta-1"
170                        for r in beta
171                    )
172                )
173                self.assertTrue(
174                    any(e["kind"] == "user" for e in self.history(app, second))
175                )
176                third = self.default_session(app)
177                app.prompt(third, "hang then recover").close()
178                ready = app.workspace / "recovery-started"
179                deadline = time.monotonic() + 15
180                while not ready.exists() and time.monotonic() < deadline:
181                    time.sleep(0.05)
182                self.assertTrue(ready.exists(), "recovery tool did not start")
183                stamp = (app.workspace / "example.txt").stat().st_mtime_ns
184                self.settings(
185                    app, protocol, active="alpha", beta="changed-beta-default"
186                )
187                config = json.loads((app.home / "config.json").read_text())
188                config["providers"]["beta"]["apiKey"] = "beta-2"
189                (app.home / "config.json").write_text(json.dumps(config))
190                start = len(self.provider.requests)
191                self.restart(app)
192                app.idle(third)
193                restored = self.history(app, third)
194                self.assertTrue(
195                    any(
196                        e["kind"] == "assistant" and self.entry_text(e) == "finished"
197                        for e in restored
198                    ),
199                    {
200                        "history": restored,
201                        "session": self.read(app, f"/sessions/{third}?tail=0"),
202                    },
203                )
204                resumed = self.records(start)
205                self.assertTrue(resumed)
206                self.assertTrue(
207                    all(
208                        "/beta/v1/" in r["path"]
209                        and r["authorization"] == "Bearer beta-2"
210                        and r["model"] == "fixture-beta"
211                        for r in resumed
212                    )
213                )
214                inputs = resumed[0]["request"][
215                    "messages" if protocol == "chat_completions" else "input"
216                ]
217                note = [contents(i) for i in inputs if i.get("role") == "user"][-1]
218                self.assertTrue(
219                    note.startswith(
220                        '<system-note origin="daemon restart">albedo restarted'
221                    )
222                )
223                # The detached kernel outlives the crash: it reattached with
224                # its namespace, so the notice says that, not a reset.
225                self.assertIn("<system-note>The python kernel reattached", note)
226                self.assertNotIn("<system-note>The python kernel got reset", note)
227                markers = [
228                    e
229                    for e in restored
230                    if e["kind"] == "note" and "albedo restarted" in self.entry_text(e)
231                ]
232                self.assertEqual(len(markers), 1)
233                self.assertTrue(
234                    self.entry_text(markers[0]).startswith("albedo restarted")
235                )
236                sessions = {
237                    s["id"]: s for s in self.read(app, "/sessions?scope=all")["items"]
238                }
239                self.assertEqual(
240                    (
241                        sessions[session]["provider_profile"],
242                        sessions[session]["automatic_name"],
243                    ),
244                    ("alpha", "write and inspect a file"),
245                )
246                self.assertEqual(
247                    (
248                        sessions[third]["provider_profile"],
249                        sessions[third]["automatic_name"],
250                    ),
251                    ("beta", "hang then recover"),
252                )
253                self.assertEqual(
254                    (app.workspace / "example.txt").stat().st_mtime_ns, stamp
255                )
256                self.assertTrue(
257                    any(e["kind"] == "assistant" for e in self.history(app, session))
258                )