fix: self-heal executor credential resolution for custom providers
Stop synthesizing credentialInstanceId "default" into executor sessions, soft-fail unresolved instances to the legacy unscoped auth path, and collapse-match renamed custom-provider auth slugs so task execute matches chat.
This commit is contained in:
7
.changeset/credential-instance-self-heal.md
Normal file
7
.changeset/credential-instance-self-heal.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Self-heal executor credential resolution so custom providers and renames match chat.
|
||||||
|
category: fix
|
||||||
|
dev: Stop synthesizing credentialInstanceId "default" into executor sessions; soft-fail unresolved instances to the legacy unscoped auth path (customProviders.apiKey); collapse-match renamed custom-provider auth slugs when unique.
|
||||||
@@ -1016,6 +1016,73 @@ describe("createResolvedAgentSession", () => {
|
|||||||
const passedTools = createSessionMock.mock.calls[0][0].customTools;
|
const passedTools = createSessionMock.mock.calls[0][0].customTools;
|
||||||
expect(passedTools[0]).toBe(rawTool);
|
expect(passedTools[0]).toBe(rawTool);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNXC:ProviderAuth 2026-08-03-17:35:
|
||||||
|
Executor used to synthesize credentialInstanceId "default" and hard-fail when auth.json
|
||||||
|
had no default instance for a custom provider. Chat omits the field and works via
|
||||||
|
customProviders.apiKey. Session create must self-heal: continue without a scoped ref.
|
||||||
|
*/
|
||||||
|
it("self-heals missing credential instances instead of failing the session", async () => {
|
||||||
|
const createSessionMock = vi.fn().mockResolvedValue({
|
||||||
|
session: { prompt: vi.fn() },
|
||||||
|
sessionFile: "session.json",
|
||||||
|
});
|
||||||
|
resolveRuntimeMock.mockResolvedValue({
|
||||||
|
runtime: {
|
||||||
|
id: "pi",
|
||||||
|
name: "Default PI Runtime",
|
||||||
|
createSession: createSessionMock,
|
||||||
|
promptWithFallback: vi.fn(),
|
||||||
|
describeModel: vi.fn(() => "umansapi/model"),
|
||||||
|
},
|
||||||
|
runtimeId: "pi",
|
||||||
|
wasConfigured: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emptyAuth = {
|
||||||
|
reload() {},
|
||||||
|
get: () => undefined,
|
||||||
|
getAll: () => ({}),
|
||||||
|
list: () => [],
|
||||||
|
has: () => false,
|
||||||
|
hasAuth: () => false,
|
||||||
|
listInstances: () => [],
|
||||||
|
getInstance: () => undefined,
|
||||||
|
setInstance: async () => {},
|
||||||
|
removeInstance: async () => {},
|
||||||
|
getDefaultInstance: () => undefined,
|
||||||
|
setDefaultInstance: async () => {},
|
||||||
|
set: async () => {},
|
||||||
|
remove: async () => {},
|
||||||
|
logout: async () => {},
|
||||||
|
getApiKey: async () => undefined,
|
||||||
|
getOAuthProviders: () => [],
|
||||||
|
login: async () => {},
|
||||||
|
modify: async () => undefined,
|
||||||
|
setModelRuntime: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const { createResolvedAgentSession } = await import("../agents/agent-session-helpers.js");
|
||||||
|
await expect(createResolvedAgentSession({
|
||||||
|
sessionPurpose: "executor",
|
||||||
|
cwd: "/tmp/project",
|
||||||
|
systemPrompt: "system",
|
||||||
|
defaultProvider: "umansapi",
|
||||||
|
defaultModelId: "model",
|
||||||
|
credentialInstanceId: "default",
|
||||||
|
authStorage: emptyAuth as any,
|
||||||
|
})).resolves.toMatchObject({ runtimeId: "pi" });
|
||||||
|
|
||||||
|
expect(createSessionMock).toHaveBeenCalledWith(expect.not.objectContaining({
|
||||||
|
credentialInstanceId: expect.anything(),
|
||||||
|
resolvedCredentialInstance: expect.anything(),
|
||||||
|
}));
|
||||||
|
// Unscoped legacy path: neither scoped field is set.
|
||||||
|
const passed = createSessionMock.mock.calls[0][0];
|
||||||
|
expect(passed.credentialInstanceId).toBeUndefined();
|
||||||
|
expect(passed.resolvedCredentialInstance).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("resolveMergerSessionModel", () => {
|
describe("resolveMergerSessionModel", () => {
|
||||||
|
|||||||
@@ -1,20 +1,52 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import type { FusionAuthStorage } from "../auth/auth-storage.js";
|
import type { FusionAuthStorage } from "../auth/auth-storage.js";
|
||||||
import { CredentialInstanceResolutionError, createFusionCredentialStore, resolveCredentialInstanceRef } from "../auth/auth-storage.js";
|
import {
|
||||||
|
CredentialInstanceResolutionError,
|
||||||
|
createFusionCredentialStore,
|
||||||
|
findRenamedProviderDefaultInstance,
|
||||||
|
resolveCredentialInstanceRef,
|
||||||
|
} from "../auth/auth-storage.js";
|
||||||
|
|
||||||
function storage(): FusionAuthStorage {
|
function storage(overrides?: Partial<FusionAuthStorage> & {
|
||||||
const values = new Map([
|
values?: Map<string, { type: string; key: string }>;
|
||||||
|
listProviders?: string[];
|
||||||
|
defaults?: Record<string, { providerId: string; instanceId: string } | undefined>;
|
||||||
|
}): FusionAuthStorage {
|
||||||
|
const values = overrides?.values ?? new Map([
|
||||||
["openai[work]", { type: "api_key", key: "work-key" }],
|
["openai[work]", { type: "api_key", key: "work-key" }],
|
||||||
["openai[personal]", { type: "api_key", key: "personal-key" }],
|
["openai[personal]", { type: "api_key", key: "personal-key" }],
|
||||||
["fallback", { type: "api_key", key: "fallback-key" }],
|
["fallback", { type: "api_key", key: "fallback-key" }],
|
||||||
]);
|
]);
|
||||||
|
const listProviders = overrides?.listProviders ?? ["openai", "fallback"];
|
||||||
|
const defaults = overrides?.defaults ?? { openai: { providerId: "openai", instanceId: "work" } };
|
||||||
const ref = (providerId: string, instanceId: string) => ({ providerId, instanceId });
|
const ref = (providerId: string, instanceId: string) => ({ providerId, instanceId });
|
||||||
return {
|
return {
|
||||||
reload() {}, get: provider => values.get(provider), getAll: () => ({}), list: () => ["openai", "fallback"], has: () => true, hasAuth: () => true,
|
reload() {},
|
||||||
|
get: provider => values.get(provider) as never,
|
||||||
|
getAll: () => ({}),
|
||||||
|
list: () => listProviders,
|
||||||
|
has: () => true,
|
||||||
|
hasAuth: () => true,
|
||||||
listInstances: provider => provider === "openai" ? [ref("openai", "work"), ref("openai", "personal")] : [],
|
listInstances: provider => provider === "openai" ? [ref("openai", "work"), ref("openai", "personal")] : [],
|
||||||
getInstance: item => values.get(`${item.providerId}[${item.instanceId}]`), setInstance: async (item, credential) => { values.set(`${item.providerId}[${item.instanceId}]`, credential); }, removeInstance: async () => {},
|
getInstance: item => {
|
||||||
getDefaultInstance: provider => provider === "openai" ? ref("openai", "work") : undefined, setDefaultInstance: async () => {},
|
const named = values.get(`${item.providerId}[${item.instanceId}]`);
|
||||||
set: async () => {}, remove: async () => {}, logout: async () => {}, getApiKey: async () => undefined, getOAuthProviders: () => [], login: async () => {}, modify: async () => undefined, setModelRuntime: () => {},
|
if (named) return named as never;
|
||||||
|
if (item.instanceId === "default") return values.get(item.providerId) as never;
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
setInstance: async (item, credential) => { values.set(`${item.providerId}[${item.instanceId}]`, credential as never); },
|
||||||
|
removeInstance: async () => {},
|
||||||
|
getDefaultInstance: provider => defaults[provider] ?? (values.has(provider) ? ref(provider, "default") : undefined),
|
||||||
|
setDefaultInstance: async () => {},
|
||||||
|
set: async () => {},
|
||||||
|
remove: async () => {},
|
||||||
|
logout: async () => {},
|
||||||
|
getApiKey: async () => undefined,
|
||||||
|
getOAuthProviders: () => [],
|
||||||
|
login: async () => {},
|
||||||
|
modify: async () => undefined,
|
||||||
|
setModelRuntime: () => {},
|
||||||
|
...overrides,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +75,59 @@ describe("credential instance resolution", () => {
|
|||||||
const auth = storage();
|
const auth = storage();
|
||||||
expect(resolveCredentialInstanceRef(auth, "openai", "deleted")).toMatchObject({ ref: { instanceId: "work" }, missing: true });
|
expect(resolveCredentialInstanceRef(auth, "openai", "deleted")).toMatchObject({ ref: { instanceId: "work" }, missing: true });
|
||||||
expect(resolveCredentialInstanceRef(auth, "openai", "bad name")).toMatchObject({ ref: { instanceId: "work" }, missing: true });
|
expect(resolveCredentialInstanceRef(auth, "openai", "bad name")).toMatchObject({ ref: { instanceId: "work" }, missing: true });
|
||||||
const noDefault = { ...auth, getDefaultInstance: () => undefined };
|
const noDefault = storage({
|
||||||
|
getDefaultInstance: () => undefined,
|
||||||
|
listProviders: ["openai"],
|
||||||
|
defaults: {},
|
||||||
|
});
|
||||||
expect(() => resolveCredentialInstanceRef(noDefault, "openai", "deleted")).toThrow(CredentialInstanceResolutionError);
|
expect(() => resolveCredentialInstanceRef(noDefault, "openai", "deleted")).toThrow(CredentialInstanceResolutionError);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNXC:ProviderAuth 2026-08-03-17:35:
|
||||||
|
Custom-provider rename leaves auth under a punctuation-variant slug while the model
|
||||||
|
catalog uses the collapsed form. Exact alphanumeric collapse must self-heal when unique.
|
||||||
|
*/
|
||||||
|
it("self-heals slug punctuation renames (umans-api ↔ umansapi)", () => {
|
||||||
|
const auth = storage({
|
||||||
|
values: new Map([["umans-api", { type: "api_key", key: "k" }]]),
|
||||||
|
listProviders: ["umans-api"],
|
||||||
|
getDefaultInstance: (provider) => (provider === "umans-api" ? { providerId: "umans-api", instanceId: "default" } : undefined),
|
||||||
|
});
|
||||||
|
expect(findRenamedProviderDefaultInstance(auth, "umansapi")).toEqual({ providerId: "umans-api", instanceId: "default" });
|
||||||
|
expect(resolveCredentialInstanceRef(auth, "umansapi", "default")).toMatchObject({
|
||||||
|
ref: { providerId: "umans-api", instanceId: "default" },
|
||||||
|
missing: true,
|
||||||
|
renamedProvider: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not cross-wire unrelated providers that share a prefix", () => {
|
||||||
|
const auth = storage({
|
||||||
|
values: new Map([
|
||||||
|
["openai", { type: "api_key", key: "a" }],
|
||||||
|
["openai-codex", { type: "api_key", key: "b" }],
|
||||||
|
]),
|
||||||
|
listProviders: ["openai", "openai-codex"],
|
||||||
|
// No default for a missing sibling slug — only the two real providers exist.
|
||||||
|
getDefaultInstance: (provider) => (
|
||||||
|
provider === "openai" || provider === "openai-codex"
|
||||||
|
? { providerId: provider, instanceId: "default" }
|
||||||
|
: undefined
|
||||||
|
),
|
||||||
|
});
|
||||||
|
expect(findRenamedProviderDefaultInstance(auth, "openai-extra")).toBeUndefined();
|
||||||
|
expect(() => resolveCredentialInstanceRef(auth, "openai-extra", "default")).toThrow(CredentialInstanceResolutionError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("throws when no auth default and no unique collapse rename match exist", () => {
|
||||||
|
const auth = storage({
|
||||||
|
values: new Map([["umans", { type: "api_key", key: "umans-key" }]]),
|
||||||
|
listProviders: ["umans"],
|
||||||
|
getDefaultInstance: (provider) => (provider === "umans" ? { providerId: "umans", instanceId: "default" } : undefined),
|
||||||
|
});
|
||||||
|
// umans vs umansapi do not collapse equal — session soft-heal covers this case via legacy path
|
||||||
|
expect(findRenamedProviderDefaultInstance(auth, "umansapi")).toBeUndefined();
|
||||||
|
expect(() => resolveCredentialInstanceRef(auth, "umansapi", "default")).toThrow(CredentialInstanceResolutionError);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -881,16 +881,39 @@ export async function createResolvedAgentSession(
|
|||||||
const storage = injectedAuthStorage ?? createFusionAuthStorage();
|
const storage = injectedAuthStorage ?? createFusionAuthStorage();
|
||||||
credentialResolution = resolveCredentialInstanceRef(storage, runtimeOptionsRaw.defaultProvider ?? "", requestedCredentialInstanceId);
|
credentialResolution = resolveCredentialInstanceRef(storage, runtimeOptionsRaw.defaultProvider ?? "", requestedCredentialInstanceId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if ((error as Error).name === "CredentialInstanceResolutionError") throw error;
|
/*
|
||||||
sessionLog.warn(`[${sessionPurpose}] credential instance resolution unavailable; using provider default`);
|
FNXC:ProviderAuth 2026-08-03-17:35:
|
||||||
|
CredentialInstanceResolutionError used to hard-fail the lane. Chat never passes a
|
||||||
|
credentialInstanceId and succeeds via customProviders.apiKey registration; executor
|
||||||
|
previously synthesized "default" and died when auth.json had no bare/default instance
|
||||||
|
for that provider (custom providers, renames). Self-heal: drop the scoped selection and
|
||||||
|
continue on the legacy unscoped path so the session matches chat. Still warn for audit.
|
||||||
|
Other resolution errors remain soft as before.
|
||||||
|
*/
|
||||||
|
if ((error as Error).name === "CredentialInstanceResolutionError") {
|
||||||
|
sessionLog.warn(
|
||||||
|
`[${sessionPurpose}] credential instance "${requestedCredentialInstanceId}" for provider "${runtimeOptionsRaw.defaultProvider ?? ""}" unresolved; continuing with legacy provider auth (custom provider apiKey / unscoped default)`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
sessionLog.warn(`[${sessionPurpose}] credential instance resolution unavailable; using provider default`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
FNXC:ProviderAuth 2026-08-01-08:10:
|
FNXC:ProviderAuth 2026-08-01-08:10:
|
||||||
A dangling named instance must be auditable: silent substitution spends the wrong account quota under an identity the operator did not select.
|
A dangling named instance must be auditable: silent substitution spends the wrong account quota under an identity the operator did not select.
|
||||||
|
|
||||||
|
FNXC:ProviderAuth 2026-08-03-17:35:
|
||||||
|
renamedProvider is the auth-key slug self-heal (custom provider rename left credentials under the previous registry key).
|
||||||
*/
|
*/
|
||||||
if (credentialResolution?.missing) sessionLog.warn(`[${sessionPurpose}] requested credential instance is missing; using provider default`);
|
if (credentialResolution?.missing) {
|
||||||
|
sessionLog.warn(
|
||||||
|
credentialResolution.renamedProvider
|
||||||
|
? `[${sessionPurpose}] requested credential instance is missing; self-healed via renamed provider default "${credentialResolution.ref.providerId}/${credentialResolution.ref.instanceId}"`
|
||||||
|
: `[${sessionPurpose}] requested credential instance is missing; using provider default`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const skillNamesFromSelection = extractSkillNamesFromSelection(runtimeOptionsRaw.skillSelection);
|
const skillNamesFromSelection = extractSkillNamesFromSelection(runtimeOptionsRaw.skillSelection);
|
||||||
const mergedSkillNames = runtimeOptionsRaw.skills && runtimeOptionsRaw.skills.length > 0
|
const mergedSkillNames = runtimeOptionsRaw.skills && runtimeOptionsRaw.skills.length > 0
|
||||||
|
|||||||
@@ -251,11 +251,48 @@ export type CredentialInstanceResolution = {
|
|||||||
ref: ProviderInstanceRef;
|
ref: ProviderInstanceRef;
|
||||||
requestedInstanceId: string;
|
requestedInstanceId: string;
|
||||||
missing: boolean;
|
missing: boolean;
|
||||||
|
/**
|
||||||
|
* True when the resolved provider id differs from the requested one after
|
||||||
|
* rename/slug self-heal (ids/outcomes only — never credential material).
|
||||||
|
*/
|
||||||
|
renamedProvider?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNXC:ProviderAuth 2026-08-03-17:35:
|
||||||
|
Custom-provider renames change the registry slug (e.g. "Umans API" → umans-api vs umansapi)
|
||||||
|
while auth.json may still hold the previous bare key. Collapse alphanumerics so punctuation-
|
||||||
|
only slug drift still resolves when exactly one auth provider collapses to the same id.
|
||||||
|
Prefix matching is intentionally NOT used — openai vs openai-codex would cross-wire accounts.
|
||||||
|
*/
|
||||||
|
function collapseProviderIdForRenameMatch(providerId: string): string {
|
||||||
|
return providerId.toLowerCase().replace(/[^a-z0-9]/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findRenamedProviderDefaultInstance(
|
||||||
|
authStorage: FusionAuthStorage,
|
||||||
|
providerId: string,
|
||||||
|
): ProviderInstanceRef | undefined {
|
||||||
|
if (!providerId || !isValidProviderId(providerId)) return undefined;
|
||||||
|
const requestedCollapsed = collapseProviderIdForRenameMatch(providerId);
|
||||||
|
if (!requestedCollapsed) return undefined;
|
||||||
|
const candidates = authStorage.list().filter((candidateId) => {
|
||||||
|
if (candidateId === providerId) return false;
|
||||||
|
return collapseProviderIdForRenameMatch(candidateId) === requestedCollapsed;
|
||||||
|
});
|
||||||
|
if (candidates.length !== 1) return undefined;
|
||||||
|
return authStorage.getDefaultInstance(candidates[0]!);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
FNXC:ProviderAuth 2026-08-01-08:10:
|
FNXC:ProviderAuth 2026-08-01-08:10:
|
||||||
A selected instance is resolved once before runtime dispatch and its concrete ref is passed forward. Re-resolving downstream could select a different default after audit, silently running work on an account the operator did not choose.
|
A selected instance is resolved once before runtime dispatch and its concrete ref is passed forward. Re-resolving downstream could select a different default after audit, silently running work on an account the operator did not choose.
|
||||||
|
|
||||||
|
FNXC:ProviderAuth 2026-08-03-17:35:
|
||||||
|
When the requested provider has no default instance, attempt rename self-heal before
|
||||||
|
throwing. Executor lanes previously synthesized credentialInstanceId "default" for
|
||||||
|
rotation bookkeeping; without a heal, custom providers authenticated only via
|
||||||
|
customProviders.apiKey (chat parity) or renamed auth keys died at session create.
|
||||||
*/
|
*/
|
||||||
export function resolveCredentialInstanceRef(
|
export function resolveCredentialInstanceRef(
|
||||||
authStorage: FusionAuthStorage,
|
authStorage: FusionAuthStorage,
|
||||||
@@ -267,8 +304,14 @@ export function resolveCredentialInstanceRef(
|
|||||||
return { ref: requested, requestedInstanceId, missing: false };
|
return { ref: requested, requestedInstanceId, missing: false };
|
||||||
}
|
}
|
||||||
const ref = authStorage.getDefaultInstance(providerId);
|
const ref = authStorage.getDefaultInstance(providerId);
|
||||||
if (!ref) throw new CredentialInstanceResolutionError(providerId, requestedInstanceId);
|
if (ref) {
|
||||||
return { ref, requestedInstanceId, missing: true };
|
return { ref, requestedInstanceId, missing: true };
|
||||||
|
}
|
||||||
|
const renamed = findRenamedProviderDefaultInstance(authStorage, providerId);
|
||||||
|
if (renamed) {
|
||||||
|
return { ref: renamed, requestedInstanceId, missing: true, renamedProvider: true };
|
||||||
|
}
|
||||||
|
throw new CredentialInstanceResolutionError(providerId, requestedInstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -14420,9 +14420,20 @@ export class TaskExecutor {
|
|||||||
overrideColumnGovernsInitialSession ? undefined : activeAgentInstanceRef?.instanceId ?? detail.credentialInstanceId,
|
overrideColumnGovernsInitialSession ? undefined : activeAgentInstanceRef?.instanceId ?? detail.credentialInstanceId,
|
||||||
);
|
);
|
||||||
const { provider: executorProvider, modelId: executorModelId } = executorSessionModel;
|
const { provider: executorProvider, modelId: executorModelId } = executorSessionModel;
|
||||||
|
/*
|
||||||
|
FNXC:ProviderAuth 2026-08-03-17:35:
|
||||||
|
Keep a synthetic "default" ref only for credential-rotation bookkeeping (startingInstanceId).
|
||||||
|
Never force that synthetic id into createResolvedAgentSession: chat omits unset instance ids
|
||||||
|
and custom providers authenticate via customProviders.apiKey. Passing "default" required an
|
||||||
|
auth.json default instance and failed step-execute while chat with the same model worked.
|
||||||
|
After a usage-limit rotation, agentDispatchedRotation is true and the offered instance is real.
|
||||||
|
*/
|
||||||
activeAgentInstanceRef ??= executorProvider
|
activeAgentInstanceRef ??= executorProvider
|
||||||
? { providerId: executorProvider, instanceId: executorSessionModel.credentialInstanceId ?? DEFAULT_PROVIDER_INSTANCE_ID }
|
? { providerId: executorProvider, instanceId: executorSessionModel.credentialInstanceId ?? DEFAULT_PROVIDER_INSTANCE_ID }
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const sessionCredentialInstanceId = agentDispatchedRotation
|
||||||
|
? activeAgentInstanceRef?.instanceId
|
||||||
|
: executorSessionModel.credentialInstanceId;
|
||||||
const { provider: executorFallbackProvider, modelId: executorFallbackModelId } = resolveExecutorFallbackModel(settings);
|
const { provider: executorFallbackProvider, modelId: executorFallbackModelId } = resolveExecutorFallbackModel(settings);
|
||||||
const executorSessionThinkingSource = this.graphSeamThinkingLevel.get(task.id) ?? detail.thinkingLevel;
|
const executorSessionThinkingSource = this.graphSeamThinkingLevel.get(task.id) ?? detail.thinkingLevel;
|
||||||
const executorThinkingLevel = resolveExecutorThinkingLevel(executorSessionThinkingSource, settings);
|
const executorThinkingLevel = resolveExecutorThinkingLevel(executorSessionThinkingSource, settings);
|
||||||
@@ -14523,7 +14534,7 @@ export class TaskExecutor {
|
|||||||
onToolEnd: agentLogger.onToolEnd,
|
onToolEnd: agentLogger.onToolEnd,
|
||||||
defaultProvider: executorProvider,
|
defaultProvider: executorProvider,
|
||||||
defaultModelId: executorModelId,
|
defaultModelId: executorModelId,
|
||||||
...(activeAgentInstanceRef ? { credentialInstanceId: activeAgentInstanceRef.instanceId } : {}),
|
...(sessionCredentialInstanceId ? { credentialInstanceId: sessionCredentialInstanceId } : {}),
|
||||||
fallbackProvider: executorFallbackProvider,
|
fallbackProvider: executorFallbackProvider,
|
||||||
fallbackModelId: executorFallbackModelId,
|
fallbackModelId: executorFallbackModelId,
|
||||||
fallbackThinkingLevel: executorFallbackThinkingLevel,
|
fallbackThinkingLevel: executorFallbackThinkingLevel,
|
||||||
|
|||||||
Reference in New Issue
Block a user