File size: 3,503 Bytes
78d0c37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { resolveCliRuntimeExecutionProvider } from "../../agents/model-runtime-aliases.js";
import { isCliProvider } from "../../agents/model-selection.js";
import { resolveProviderIdForAuth } from "../../agents/provider-auth-aliases.js";
import { applySessionEntryReplacements } from "../../config/sessions/session-accessor.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { AgentTurnContext } from "../agent-turn/types.js";
import { updateChatRunProvider } from "../chat-abort.js";

export function createAgentRunModelSelectionHandler(params: {
  context: Pick<AgentTurnContext, "chatAbortControllers">;
  runId: string;
  cfg: OpenClawConfig;
  cfgForAgent?: OpenClawConfig;
  restoredCronContinuationLifecycleRevision?: string;
  resolvedSessionKey?: string;
  lifecycleStorePath: string;
  activeSessionAgentId: string;
  trustedInternalHandoff?: { provider: string; model: string };
}): (selection: { provider: string; model: string }) => Promise<void> {
  return async ({ provider, model }) => {
    if (params.trustedInternalHandoff) {
      // The one-use grant remains tied to this run while its active model falls back.
      params.trustedInternalHandoff.provider = provider.trim().toLowerCase();
      params.trustedInternalHandoff.model = model.trim();
    }
    updateChatRunProvider(params.context.chatAbortControllers, {
      runId: params.runId,
      providerId: provider,
      authProviderId: resolveProviderIdForAuth(provider, {
        config: params.cfgForAgent ?? params.cfg,
      }),
    });
    if (!params.restoredCronContinuationLifecycleRevision || !params.resolvedSessionKey) {
      return;
    }
    const persistedSelectedModel = await applySessionEntryReplacements({
      activeSessionKey: params.resolvedSessionKey,
      requireWriteSuccess: true,
      sessionKeys: [params.resolvedSessionKey],
      skipMaintenance: false,
      storePath: params.lifecycleStorePath,
      update: (entries) => {
        const current = entries.find(
          (entry) => entry.sessionKey === params.resolvedSessionKey,
        )?.entry;
        const marker = current?.cronRunContinuation;
        if (
          !current ||
          marker?.phase !== "continuing" ||
          marker.ownerRunId !== params.runId ||
          marker.lifecycleRevision !== params.restoredCronContinuationLifecycleRevision
        ) {
          return { result: false };
        }
        const executionProvider =
          resolveCliRuntimeExecutionProvider({
            provider,
            cfg: params.cfgForAgent ?? params.cfg,
            agentId: params.activeSessionAgentId,
            modelId: model,
          }) ?? provider;
        const cronRunContinuation = { ...marker };
        if (isCliProvider(executionProvider, params.cfgForAgent ?? params.cfg)) {
          cronRunContinuation.cliExecutionProvider = executionProvider;
        } else {
          delete cronRunContinuation.cliExecutionProvider;
        }
        return {
          replacements: [
            {
              sessionKey: params.resolvedSessionKey!,
              entry: {
                ...current,
                cronRunContinuation,
                modelProvider: provider,
                model,
                updatedAt: Date.now(),
              },
            },
          ],
          result: true,
        };
      },
    });
    if (!persistedSelectedModel) {
      throw new Error("cron run continuation changed before model execution");
    }
  };
}