@framers/agentos 0.12.3 → 0.12.5
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/dist/api/agent.d.ts +7 -5
- package/dist/api/agent.d.ts.map +1 -1
- package/dist/api/agent.js.map +1 -1
- package/dist/api/generateObject.d.ts +5 -5
- package/dist/api/generateText.d.ts +173 -50
- package/dist/api/generateText.d.ts.map +1 -1
- package/dist/api/generateText.js +272 -81
- package/dist/api/generateText.js.map +1 -1
- package/dist/api/index.d.ts +6 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +5 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/runtime/GMIChunkTransformer.d.ts +1 -1
- package/dist/api/runtime/GMIChunkTransformer.d.ts.map +1 -1
- package/dist/api/runtime/GMIChunkTransformer.js +7 -2
- package/dist/api/runtime/GMIChunkTransformer.js.map +1 -1
- package/dist/api/runtime/completionGateway.d.ts +98 -0
- package/dist/api/runtime/completionGateway.d.ts.map +1 -0
- package/dist/api/runtime/completionGateway.js +347 -0
- package/dist/api/runtime/completionGateway.js.map +1 -0
- package/dist/api/runtime/contextWindowFit.d.ts +49 -0
- package/dist/api/runtime/contextWindowFit.d.ts.map +1 -0
- package/dist/api/runtime/contextWindowFit.js +109 -0
- package/dist/api/runtime/contextWindowFit.js.map +1 -0
- package/dist/api/runtime/gatewayProviderManager.d.ts +37 -0
- package/dist/api/runtime/gatewayProviderManager.d.ts.map +1 -0
- package/dist/api/runtime/gatewayProviderManager.js +49 -0
- package/dist/api/runtime/gatewayProviderManager.js.map +1 -0
- package/dist/api/streamText.d.ts.map +1 -1
- package/dist/api/streamText.js +90 -6
- package/dist/api/streamText.js.map +1 -1
- package/dist/cognition/emergent/ComposableToolBuilder.d.ts.map +1 -1
- package/dist/cognition/emergent/ComposableToolBuilder.js +16 -8
- package/dist/cognition/emergent/ComposableToolBuilder.js.map +1 -1
- package/dist/cognition/emergent/EffectsStore.d.ts +58 -0
- package/dist/cognition/emergent/EffectsStore.d.ts.map +1 -0
- package/dist/cognition/emergent/EffectsStore.js +96 -0
- package/dist/cognition/emergent/EffectsStore.js.map +1 -0
- package/dist/cognition/emergent/EmergentCapabilityEngine.d.ts +22 -2
- package/dist/cognition/emergent/EmergentCapabilityEngine.d.ts.map +1 -1
- package/dist/cognition/emergent/EmergentCapabilityEngine.js +128 -19
- package/dist/cognition/emergent/EmergentCapabilityEngine.js.map +1 -1
- package/dist/cognition/emergent/EmergentJudge.d.ts +1 -1
- package/dist/cognition/emergent/EmergentJudge.d.ts.map +1 -1
- package/dist/cognition/emergent/EmergentToolRegistry.d.ts +12 -3
- package/dist/cognition/emergent/EmergentToolRegistry.d.ts.map +1 -1
- package/dist/cognition/emergent/EmergentToolRegistry.js +51 -4
- package/dist/cognition/emergent/EmergentToolRegistry.js.map +1 -1
- package/dist/cognition/emergent/ForgeToolMetaTool.d.ts.map +1 -1
- package/dist/cognition/emergent/ForgeToolMetaTool.js +4 -1
- package/dist/cognition/emergent/ForgeToolMetaTool.js.map +1 -1
- package/dist/cognition/emergent/SandboxedToolForge.d.ts +55 -51
- package/dist/cognition/emergent/SandboxedToolForge.d.ts.map +1 -1
- package/dist/cognition/emergent/SandboxedToolForge.js +103 -109
- package/dist/cognition/emergent/SandboxedToolForge.js.map +1 -1
- package/dist/cognition/emergent/ToolPackage.d.ts +2 -2
- package/dist/cognition/emergent/ToolPackage.d.ts.map +1 -1
- package/dist/cognition/emergent/broker/CapabilityBroker.d.ts +58 -0
- package/dist/cognition/emergent/broker/CapabilityBroker.d.ts.map +1 -0
- package/dist/cognition/emergent/broker/CapabilityBroker.js +288 -0
- package/dist/cognition/emergent/broker/CapabilityBroker.js.map +1 -0
- package/dist/cognition/emergent/broker/fetch.d.ts +44 -0
- package/dist/cognition/emergent/broker/fetch.d.ts.map +1 -0
- package/dist/cognition/emergent/broker/fetch.js +197 -0
- package/dist/cognition/emergent/broker/fetch.js.map +1 -0
- package/dist/cognition/emergent/broker/fs-read.d.ts +63 -0
- package/dist/cognition/emergent/broker/fs-read.d.ts.map +1 -0
- package/dist/cognition/emergent/broker/fs-read.js +133 -0
- package/dist/cognition/emergent/broker/fs-read.js.map +1 -0
- package/dist/cognition/emergent/broker/refusal.d.ts +10 -0
- package/dist/cognition/emergent/broker/refusal.d.ts.map +1 -0
- package/dist/cognition/emergent/broker/refusal.js +13 -0
- package/dist/cognition/emergent/broker/refusal.js.map +1 -0
- package/dist/cognition/emergent/ceiling.d.ts +75 -0
- package/dist/cognition/emergent/ceiling.d.ts.map +1 -0
- package/dist/cognition/emergent/ceiling.js +166 -0
- package/dist/cognition/emergent/ceiling.js.map +1 -0
- package/dist/cognition/emergent/index.d.ts +2 -0
- package/dist/cognition/emergent/index.d.ts.map +1 -1
- package/dist/cognition/emergent/index.js +1 -0
- package/dist/cognition/emergent/index.js.map +1 -1
- package/dist/cognition/emergent/types.d.ts +75 -2
- package/dist/cognition/emergent/types.d.ts.map +1 -1
- package/dist/cognition/emergent/types.js.map +1 -1
- package/dist/cognition/substrate/GMI.d.ts +21 -0
- package/dist/cognition/substrate/GMI.d.ts.map +1 -1
- package/dist/cognition/substrate/GMI.js +286 -88
- package/dist/cognition/substrate/GMI.js.map +1 -1
- package/dist/cognition/substrate/IGMI.d.ts +48 -1
- package/dist/cognition/substrate/IGMI.d.ts.map +1 -1
- package/dist/cognition/substrate/IGMI.js +4 -0
- package/dist/cognition/substrate/IGMI.js.map +1 -1
- package/dist/core/llm/providers/errors/ContextWindowExceededError.d.ts +22 -0
- package/dist/core/llm/providers/errors/ContextWindowExceededError.d.ts.map +1 -0
- package/dist/core/llm/providers/errors/ContextWindowExceededError.js +21 -0
- package/dist/core/llm/providers/errors/ContextWindowExceededError.js.map +1 -0
- package/dist/core/llm/providers/implementations/OpenRouterProvider.d.ts.map +1 -1
- package/dist/core/llm/providers/implementations/OpenRouterProvider.js +34 -13
- package/dist/core/llm/providers/implementations/OpenRouterProvider.js.map +1 -1
- package/dist/core/llm/routing/IModelRouter.d.ts +6 -0
- package/dist/core/llm/routing/IModelRouter.d.ts.map +1 -1
- package/dist/core/llm/routing/PolicyAwareRouter.d.ts +7 -0
- package/dist/core/llm/routing/PolicyAwareRouter.d.ts.map +1 -1
- package/dist/core/llm/routing/PolicyAwareRouter.js +12 -0
- package/dist/core/llm/routing/PolicyAwareRouter.js.map +1 -1
- package/dist/core/llm/routing/UncensoredModelCatalog.d.ts +37 -0
- package/dist/core/llm/routing/UncensoredModelCatalog.d.ts.map +1 -1
- package/dist/core/llm/routing/UncensoredModelCatalog.js +64 -74
- package/dist/core/llm/routing/UncensoredModelCatalog.js.map +1 -1
- package/dist/core/tools/ITool.d.ts +45 -0
- package/dist/core/tools/ITool.d.ts.map +1 -1
- package/dist/core/tools/ToolOrchestrator.d.ts.map +1 -1
- package/dist/core/tools/ToolOrchestrator.js +4 -8
- package/dist/core/tools/ToolOrchestrator.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/api/generateText.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* the system prompt so the tool loop executes with awareness of the strategy.
|
|
13
13
|
*/
|
|
14
14
|
import { randomUUID } from 'node:crypto';
|
|
15
|
-
import { resolveModelOption, resolveProvider, createProviderManager } from './model.js';
|
|
15
|
+
import { resolveModelOption, resolveProvider, createProviderManager, knownProviderPrefixOf } from './model.js';
|
|
16
16
|
import { attachGenAiAttributes, attachUsageAttributes, toTurnMetricUsage } from './observability.js';
|
|
17
17
|
import { fireLlmUsageObserver } from './observers.js';
|
|
18
18
|
import { hostPolicyToRouteParams, mergeRequiredCapabilities, } from './runtime/hostPolicy.js';
|
|
@@ -24,6 +24,9 @@ import { recordAgentOSTurnMetrics, withAgentOSSpan } from '../safety/evaluation/
|
|
|
24
24
|
import { createLogger } from '../core/logging/loggerFactory.js';
|
|
25
25
|
import { globalLLMProviderHealth } from '../core/safety/LLMProviderHealthRegistry.js';
|
|
26
26
|
import { CONTEXT_WINDOW_EXCEEDED_CODE } from '../core/llm/providers/errors/errorCodes.js';
|
|
27
|
+
import { ContextWindowExceededError } from '../core/llm/providers/errors/ContextWindowExceededError.js';
|
|
28
|
+
import { catalogEntryHasCapability, createUncensoredModelCatalog, findCatalogTextModel, } from '../core/llm/routing/UncensoredModelCatalog.js';
|
|
29
|
+
import { checkContextFit } from './runtime/contextWindowFit.js';
|
|
27
30
|
import { describeResponseFormatShape } from './runtime/responseFormatForProvider.js';
|
|
28
31
|
const fallbackLogger = createLogger('fallback');
|
|
29
32
|
/**
|
|
@@ -324,7 +327,7 @@ export function addModelUsage(target, usage) {
|
|
|
324
327
|
* The usage a provider error reports for the request it ended, such as the
|
|
325
328
|
* billed tokens of a refused turn (`details.usage`).
|
|
326
329
|
*
|
|
327
|
-
* @internal Shared with streamText.
|
|
330
|
+
* @internal Shared with streamText and the completion gateway.
|
|
328
331
|
*/
|
|
329
332
|
export function usageOfError(error) {
|
|
330
333
|
return error?.details?.usage;
|
|
@@ -662,81 +665,193 @@ export function fallbackHopOverrides(opts, entry) {
|
|
|
662
665
|
};
|
|
663
666
|
}
|
|
664
667
|
/**
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
*
|
|
668
|
+
* The policy tier a call's fallback chain is built for: the explicit route
|
|
669
|
+
* tier, the call's tier, the host policy's tier (`standard` when a host
|
|
670
|
+
* policy names none), then the router's default. The primary's route block
|
|
671
|
+
* sends the first three terms only, so a delegated base router still sees no
|
|
672
|
+
* tier when no explicit source set one.
|
|
673
|
+
*/
|
|
674
|
+
export function resolvePolicyTier(opts) {
|
|
675
|
+
return (opts.routerParams?.policyTier ??
|
|
676
|
+
opts.policyTier ??
|
|
677
|
+
hostPolicyToRouteParams(opts.hostPolicy).policyTier ??
|
|
678
|
+
opts.router?.policyTier);
|
|
679
|
+
}
|
|
680
|
+
/** @internal */
|
|
681
|
+
export const INITIAL_FALLBACK_WALK = Object.freeze({
|
|
682
|
+
refillsOwed: 0,
|
|
683
|
+
refusalSeen: false,
|
|
684
|
+
});
|
|
685
|
+
/** Claude, direct or through OpenRouter. */
|
|
686
|
+
function isClaudeEntry(entry) {
|
|
687
|
+
return (entry.provider === 'anthropic' ||
|
|
688
|
+
(entry.provider === 'openrouter' && (entry.model ?? '').startsWith('anthropic/')));
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* The capabilities a call names explicitly (host policy and route params).
|
|
692
|
+
* Native tool calling is not inferred from the presence of tools: a model
|
|
693
|
+
* without it serves a tool-carrying call through the prompt shim.
|
|
670
694
|
*
|
|
671
|
-
*
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
*
|
|
678
|
-
*
|
|
679
|
-
*
|
|
680
|
-
*
|
|
681
|
-
* headroom band when Hermes 3 is rate-limited or down.
|
|
682
|
-
* 3. The standard {@link buildFallbackChain} suffix: keeps
|
|
683
|
-
* availability fallback on top of the policy fallback so a
|
|
684
|
-
* mature request that hits a Hermes 3 outage AND a Sonnet
|
|
685
|
-
* outage still has gpt-4o-mini etc. to fall back to (which
|
|
686
|
-
* will refuse on the explicit case but at least surfaces a
|
|
687
|
-
* moderation error rather than a network error).
|
|
695
|
+
* @internal
|
|
696
|
+
*/
|
|
697
|
+
export function explicitRequiredCapabilities(opts) {
|
|
698
|
+
return (mergeRequiredCapabilities(hostPolicyToRouteParams(opts.hostPolicy).requiredCapabilities, opts.routerParams?.requiredCapabilities) ?? []);
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* The provider and model a fallback entry is sent as: resolved the way the
|
|
702
|
+
* leg's own call resolves it (a provider's default model when the entry
|
|
703
|
+
* names none, a `provider:model` id split), or the entry as written when it
|
|
704
|
+
* does not resolve.
|
|
688
705
|
*
|
|
689
|
-
*
|
|
690
|
-
|
|
691
|
-
|
|
706
|
+
* @internal Shared with streamText.
|
|
707
|
+
*/
|
|
708
|
+
export function fallbackEntrySentAs(entry) {
|
|
709
|
+
try {
|
|
710
|
+
const { providerId, modelId } = resolveModelOption({ provider: entry.provider, model: entry.model }, 'text');
|
|
711
|
+
return { provider: providerId, model: modelId };
|
|
712
|
+
}
|
|
713
|
+
catch {
|
|
714
|
+
return { provider: entry.provider, model: entry.model };
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Resolve a call's fallback chain once, at the start of the top-level walk.
|
|
719
|
+
* Policy-chain entries (`origin: 'policy-default'`) naming the failed first
|
|
720
|
+
* model are dropped; every entry is checked, as the provider and model it
|
|
721
|
+
* will be sent as, against the call's explicitly required capabilities and
|
|
722
|
+
* excluded models (a model outside the catalog counts as capable); the
|
|
723
|
+
* uncensored group's first two remaining entries become standing legs and
|
|
724
|
+
* the rest refills. Caller-written entries keep their order.
|
|
692
725
|
*
|
|
693
|
-
*
|
|
694
|
-
|
|
695
|
-
|
|
726
|
+
* @internal
|
|
727
|
+
*/
|
|
728
|
+
export function resolveFallbackChain(chain, ctx) {
|
|
729
|
+
const required = ctx.requiredCapabilities ?? [];
|
|
730
|
+
// An exclusion names a model as written or as a `provider:model` id; a leg
|
|
731
|
+
// matches by its model as written and as it is sent.
|
|
732
|
+
const excluded = new Set();
|
|
733
|
+
for (const id of ctx.excludedModelIds ?? []) {
|
|
734
|
+
excluded.add(id);
|
|
735
|
+
const prefix = knownProviderPrefixOf(id);
|
|
736
|
+
if (prefix)
|
|
737
|
+
excluded.add(id.slice(prefix.length + 1));
|
|
738
|
+
}
|
|
739
|
+
const isExcluded = (model) => model !== undefined && excluded.has(model);
|
|
740
|
+
const resolved = [];
|
|
741
|
+
let uncensoredLegs = 0;
|
|
742
|
+
for (const entry of chain) {
|
|
743
|
+
if (entry.origin === 'policy-default' &&
|
|
744
|
+
entry.provider === ctx.primary.provider &&
|
|
745
|
+
entry.model === ctx.primary.model) {
|
|
746
|
+
ctx.onSkip?.(entry, 'failed_primary');
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
const sentAs = fallbackEntrySentAs(entry);
|
|
750
|
+
if (isExcluded(entry.model) || isExcluded(sentAs.model)) {
|
|
751
|
+
ctx.onSkip?.(entry, 'excluded_model');
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
const catalogEntry = sentAs.model !== undefined ? findCatalogTextModel(sentAs.model, sentAs.provider) : undefined;
|
|
755
|
+
if (catalogEntry && required.some((capability) => !catalogEntryHasCapability(catalogEntry, capability))) {
|
|
756
|
+
ctx.onSkip?.(entry, 'missing_capability');
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (entry.origin === 'policy-default' && entry.group === 'uncensored') {
|
|
760
|
+
resolved.push({ ...entry, walkRole: uncensoredLegs < 2 ? 'standing' : 'refill' });
|
|
761
|
+
uncensoredLegs += 1;
|
|
762
|
+
}
|
|
763
|
+
else {
|
|
764
|
+
resolved.push(entry);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return resolved;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Whether the walk runs `entry` now: a refill only while a standing leg is
|
|
771
|
+
* owed one, and none of the policy chain's Claude legs after a refusal.
|
|
696
772
|
*
|
|
697
|
-
* @
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
773
|
+
* @internal
|
|
774
|
+
*/
|
|
775
|
+
export function gateFallbackEntry(state, entry) {
|
|
776
|
+
if (state.refusalSeen && entry.origin === 'policy-default' && isClaudeEntry(entry)) {
|
|
777
|
+
return { run: false, reason: 'claude_after_refusal' };
|
|
778
|
+
}
|
|
779
|
+
if (entry.walkRole === 'refill') {
|
|
780
|
+
if (state.refillsOwed < 1)
|
|
781
|
+
return { run: false, reason: 'refill_not_owed' };
|
|
782
|
+
return { run: true, state: { ...state, refillsOwed: state.refillsOwed - 1 } };
|
|
783
|
+
}
|
|
784
|
+
return { run: true, state };
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* The walk's state after an attempt failed with `error`: a standing leg that
|
|
788
|
+
* failed for anything but a content decline (a timeout, an open breaker and
|
|
789
|
+
* the context check's refusal included) is owed a refill, and a model
|
|
790
|
+
* refusal (code `content_filter`; a filter's `content_policy_violation`
|
|
791
|
+
* does not count) is recorded for the rest of the walk.
|
|
792
|
+
*
|
|
793
|
+
* @internal
|
|
794
|
+
*/
|
|
795
|
+
export function advanceFallbackWalk(state, role, error) {
|
|
796
|
+
const owed = role === 'standing' && !isContentPolicyRefusal(error) ? 1 : 0;
|
|
797
|
+
const refusal = error?.code === 'content_filter';
|
|
798
|
+
return {
|
|
799
|
+
refillsOwed: state.refillsOwed + owed,
|
|
800
|
+
refusalSeen: state.refusalSeen || refusal,
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
/** The catalog the policy chain reads its ladders from. */
|
|
804
|
+
const POLICY_CATALOG = createUncensoredModelCatalog();
|
|
805
|
+
/** A catalog model never used as a fallback leg: too weak for a rescue reply. */
|
|
806
|
+
const NEVER_A_LEG_MODEL = 'meta-llama/llama-3.1-8b-instruct';
|
|
807
|
+
/**
|
|
808
|
+
* Build a policy-tier-aware fallback chain, for callers that pass
|
|
809
|
+
* `policyTier: 'mature' | 'private-adult'`: a refusal or an outage of the
|
|
810
|
+
* primary walks to an uncensored model before the availability legs.
|
|
811
|
+
*
|
|
812
|
+
* Mature and private-adult: the tier's catalog ladder
|
|
813
|
+
* (the catalog's `getFallbackLadder`; private-adult keeps the
|
|
814
|
+
* models that permit `erotic`) as OpenRouter legs in the `uncensored`
|
|
815
|
+
* group, llama-3.1-8b left out, then the {@link buildFallbackChain} suffix.
|
|
816
|
+
* Every entry is tagged `origin: 'policy-default'`, which lets the walk drop
|
|
817
|
+
* the leg naming the failed first model, keep two standing uncensored legs,
|
|
818
|
+
* and pass over Claude legs after a refusal. Safe, standard and an absent
|
|
819
|
+
* tier get {@link buildFallbackChain} unchanged and untagged.
|
|
820
|
+
*
|
|
821
|
+
* Each leg pins `cache: false`. A missing key drops its legs instead of
|
|
822
|
+
* throwing, so a partial deploy still gets a shorter usable chain.
|
|
823
|
+
*
|
|
824
|
+
* @param tier - The call's content tier.
|
|
825
|
+
* @param excludeProvider - Provider to leave out of the availability suffix
|
|
826
|
+
* (typically the failed primary's). The ladder legs ignore it: another
|
|
827
|
+
* model on the same provider is a valid rescue, and the walk drops the
|
|
828
|
+
* exact failed model.
|
|
704
829
|
*/
|
|
705
830
|
export function buildPolicyAwareFallbackChain(tier, excludeProvider) {
|
|
706
|
-
|
|
707
|
-
if (!isMatureTier) {
|
|
831
|
+
if (tier !== 'mature' && tier !== 'private-adult') {
|
|
708
832
|
return buildFallbackChain(excludeProvider);
|
|
709
833
|
}
|
|
710
834
|
const chain = [];
|
|
711
|
-
// Hermes 3 405B leads: uncensored, large, instruction-following
|
|
712
|
-
// proven on the wilds-ai identity-generation path. Skipped when
|
|
713
|
-
// OPENROUTER_API_KEY is absent rather than throwing; the suffix
|
|
714
|
-
// chain may still produce a usable fallback.
|
|
715
835
|
if (process.env.OPENROUTER_API_KEY) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
cache: false,
|
|
731
|
-
});
|
|
836
|
+
const ladder = tier === 'private-adult'
|
|
837
|
+
? POLICY_CATALOG.getFallbackLadder('private-adult', { contentIntent: 'erotic' })
|
|
838
|
+
: POLICY_CATALOG.getFallbackLadder('mature');
|
|
839
|
+
for (const entry of ladder) {
|
|
840
|
+
if (entry.modelId === NEVER_A_LEG_MODEL)
|
|
841
|
+
continue;
|
|
842
|
+
chain.push({
|
|
843
|
+
provider: entry.providerId,
|
|
844
|
+
model: entry.modelId,
|
|
845
|
+
cache: false,
|
|
846
|
+
origin: 'policy-default',
|
|
847
|
+
group: 'uncensored',
|
|
848
|
+
});
|
|
849
|
+
}
|
|
732
850
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
const alreadyInChain = chain.some((existing) => existing.provider === entry.provider && existing.model === entry.model);
|
|
738
|
-
if (!alreadyInChain)
|
|
739
|
-
chain.push(entry);
|
|
851
|
+
for (const entry of buildFallbackChain(excludeProvider)) {
|
|
852
|
+
const listed = chain.some((e) => e.provider === entry.provider && e.model === entry.model);
|
|
853
|
+
if (!listed)
|
|
854
|
+
chain.push({ ...entry, origin: 'policy-default' });
|
|
740
855
|
}
|
|
741
856
|
return chain;
|
|
742
857
|
}
|
|
@@ -947,6 +1062,28 @@ export async function generateText(opts) {
|
|
|
947
1062
|
function: { name: t.name, description: t.description, parameters: t.inputSchema },
|
|
948
1063
|
}))
|
|
949
1064
|
: undefined;
|
|
1065
|
+
// A catalog model is sent only a request it can hold: otherwise the
|
|
1066
|
+
// call throws in place of the send and the walk moves on. Checked on
|
|
1067
|
+
// the first native send and on the prompt shim's first send.
|
|
1068
|
+
const assertFitsContextWindow = (sent) => {
|
|
1069
|
+
const fit = checkContextFit({
|
|
1070
|
+
provider: resolved.providerId,
|
|
1071
|
+
model: resolved.modelId,
|
|
1072
|
+
messages: sent.messages,
|
|
1073
|
+
tools: sent.tools,
|
|
1074
|
+
maxTokens: opts.maxTokens,
|
|
1075
|
+
customModelParams: opts.customModelParams,
|
|
1076
|
+
});
|
|
1077
|
+
if (!fit.fits && fit.contextWindow !== undefined) {
|
|
1078
|
+
throw new ContextWindowExceededError({
|
|
1079
|
+
provider: resolved.providerId,
|
|
1080
|
+
model: resolved.modelId,
|
|
1081
|
+
contextWindow: fit.contextWindow,
|
|
1082
|
+
estimatedInputTokens: fit.estimatedInputTokens,
|
|
1083
|
+
outputTokens: fit.outputTokens,
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
950
1087
|
const allToolCalls = [];
|
|
951
1088
|
const totalUsage = attemptUsage;
|
|
952
1089
|
// Provider-reported model id of the final step (spec batch-1 C1);
|
|
@@ -1003,6 +1140,7 @@ export async function generateText(opts) {
|
|
|
1003
1140
|
// provider's tool-unsupported error (see the catch after the loop).
|
|
1004
1141
|
const toolMode = opts.toolMode ?? 'auto';
|
|
1005
1142
|
const shimMaxRoundtrips = opts.maxSteps ?? 5;
|
|
1143
|
+
let shimSendChecked = false;
|
|
1006
1144
|
const runShim = async () => {
|
|
1007
1145
|
const loopResult = await runEmulatedToolLoop({
|
|
1008
1146
|
tools: Array.from(toolMap.values()),
|
|
@@ -1018,6 +1156,12 @@ export async function generateText(opts) {
|
|
|
1018
1156
|
messages: toShimMessages(messages),
|
|
1019
1157
|
maxRoundtrips: shimMaxRoundtrips,
|
|
1020
1158
|
callModel: async (msgs) => {
|
|
1159
|
+
// The shim sends rendered tool text in place of native schemas,
|
|
1160
|
+
// so its first send is checked on its own.
|
|
1161
|
+
if (!shimSendChecked) {
|
|
1162
|
+
shimSendChecked = true;
|
|
1163
|
+
assertFitsContextWindow({ messages: msgs });
|
|
1164
|
+
}
|
|
1021
1165
|
const r = await provider.generateCompletion(resolved.modelId, msgs, {
|
|
1022
1166
|
temperature: opts.temperature,
|
|
1023
1167
|
...(opts.topP !== undefined ? { topP: opts.topP } : {}),
|
|
@@ -1176,6 +1320,10 @@ export async function generateText(opts) {
|
|
|
1176
1320
|
console.warn('[agentos] onBeforeGeneration hook error:', hookErr);
|
|
1177
1321
|
}
|
|
1178
1322
|
}
|
|
1323
|
+
// A continuation leg's first send carries its completed tool rounds.
|
|
1324
|
+
// Later steps are not checked again.
|
|
1325
|
+
if (step === 0)
|
|
1326
|
+
assertFitsContextWindow({ messages: effectiveMessages, tools: toolSchemas });
|
|
1179
1327
|
const response = await withAgentOSSpan('agentos.api.generate_text.step', async (stepSpan) => {
|
|
1180
1328
|
stepSpan?.setAttribute('llm.provider', resolved.providerId);
|
|
1181
1329
|
stepSpan?.setAttribute('llm.model', resolved.modelId);
|
|
@@ -1668,40 +1816,76 @@ export async function generateText(opts) {
|
|
|
1668
1816
|
{ provider: primaryProviderId ?? 'unknown', model: primaryModelId, ok: false },
|
|
1669
1817
|
];
|
|
1670
1818
|
// ── Fallback chain ────────────────────────────────────────────────
|
|
1671
|
-
//
|
|
1672
|
-
//
|
|
1673
|
-
//
|
|
1674
|
-
// the policy
|
|
1675
|
-
//
|
|
1676
|
-
//
|
|
1677
|
-
//
|
|
1678
|
-
const
|
|
1679
|
-
|
|
1819
|
+
// Caller-supplied wins, undefined auto-builds from env keys for the
|
|
1820
|
+
// call's resolved tier (the policy-aware chain on mature and
|
|
1821
|
+
// private-adult), an empty array opts out. The top-level walk resolves
|
|
1822
|
+
// the chain once: it drops the policy chain's entry for the failed first
|
|
1823
|
+
// model, applies the call's explicit capability and exclusion
|
|
1824
|
+
// requirements, and marks the uncensored group's standing legs and
|
|
1825
|
+
// refills. A leg receives its resolved slice and never resolves it again.
|
|
1826
|
+
const walkTier = resolvePolicyTier(opts);
|
|
1827
|
+
const chainEntries = opts.fallbackProviders === undefined
|
|
1828
|
+
? buildPolicyAwareFallbackChain(walkTier, metricProviderId)
|
|
1680
1829
|
: opts.fallbackProviders;
|
|
1830
|
+
const logLegSkip = (entry, reason) => fallbackLogger.info('provider fallback skipped', {
|
|
1831
|
+
event: 'fallback_leg_skipped',
|
|
1832
|
+
api: 'generateText',
|
|
1833
|
+
reason,
|
|
1834
|
+
primaryProvider: metricProviderId,
|
|
1835
|
+
fallbackProvider: entry.provider,
|
|
1836
|
+
fallbackModel: entry.model,
|
|
1837
|
+
});
|
|
1838
|
+
// Resolved only when the walk runs, so its skip lines describe a walk.
|
|
1681
1839
|
// A call whose prompt-shim tools already ran cannot be continued, and a
|
|
1682
1840
|
// restart would run them again; it surfaces the error instead.
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1841
|
+
const walks = isRetryableError(error) && !toolProgress.shimRanTool;
|
|
1842
|
+
const effectiveFallbacks = !walks
|
|
1843
|
+
? []
|
|
1844
|
+
: opts.__fallbackWalk
|
|
1845
|
+
? chainEntries
|
|
1846
|
+
: resolveFallbackChain(chainEntries, {
|
|
1847
|
+
primary: { provider: metricProviderId, model: metricModelId },
|
|
1848
|
+
requiredCapabilities: explicitRequiredCapabilities(opts),
|
|
1849
|
+
excludedModelIds: opts.routerParams?.excludedModelIds,
|
|
1850
|
+
onSkip: logLegSkip,
|
|
1851
|
+
});
|
|
1852
|
+
// This attempt's own failure updates the walk: a refusal is recorded,
|
|
1853
|
+
// and a standing leg that failed on availability is owed a refill.
|
|
1854
|
+
let walkState = advanceFallbackWalk(opts.__fallbackWalk?.state ?? INITIAL_FALLBACK_WALK, opts.__fallbackWalk?.role, error);
|
|
1855
|
+
if (walks && effectiveFallbacks.length) {
|
|
1686
1856
|
let lastError = error;
|
|
1687
1857
|
let attempt = 0;
|
|
1688
1858
|
for (const fb of effectiveFallbacks) {
|
|
1689
1859
|
attempt += 1;
|
|
1860
|
+
// A refill runs only while a standing leg is owed one, and the
|
|
1861
|
+
// policy chain's Claude legs are passed over after a refusal.
|
|
1862
|
+
const gate = gateFallbackEntry(walkState, fb);
|
|
1863
|
+
if (!gate.run) {
|
|
1864
|
+
logLegSkip(fb, gate.reason);
|
|
1865
|
+
continue;
|
|
1866
|
+
}
|
|
1867
|
+
walkState = gate.state;
|
|
1690
1868
|
// Skip fallback entries whose breaker is already open. Without
|
|
1691
1869
|
// this check, the loop would still spend a full network round-
|
|
1692
1870
|
// trip on every dead fallback in the chain before reaching the
|
|
1693
1871
|
// next healthy one. The recursive `generateText` call below would
|
|
1694
1872
|
// also short-circuit at the same isOpen() check, but the outer
|
|
1695
1873
|
// skip avoids the recursion overhead + the extra log line.
|
|
1696
|
-
|
|
1874
|
+
// The breaker read is the provider the leg is sent to: an
|
|
1875
|
+
// `openrouter:` id under another provider goes to OpenRouter.
|
|
1876
|
+
const legProvider = fallbackEntrySentAs(fb).provider;
|
|
1877
|
+
if (globalLLMProviderHealth.isOpen(legProvider)) {
|
|
1697
1878
|
fallbackLogger.info('provider fallback skipped (circuit open)', {
|
|
1698
1879
|
event: 'fallback_skipped_circuit_open',
|
|
1699
1880
|
api: 'generateText',
|
|
1700
1881
|
primaryProvider: metricProviderId,
|
|
1701
|
-
fallbackProvider:
|
|
1882
|
+
fallbackProvider: legProvider,
|
|
1702
1883
|
fallbackModel: fb.model,
|
|
1703
1884
|
attempt,
|
|
1704
1885
|
});
|
|
1886
|
+
// An open breaker is an availability failure: a standing leg is
|
|
1887
|
+
// owed a refill.
|
|
1888
|
+
walkState = advanceFallbackWalk(walkState, fb.walkRole, undefined);
|
|
1705
1889
|
continue;
|
|
1706
1890
|
}
|
|
1707
1891
|
try {
|
|
@@ -1735,6 +1919,12 @@ export async function generateText(opts) {
|
|
|
1735
1919
|
...opts,
|
|
1736
1920
|
provider: fb.provider,
|
|
1737
1921
|
model: fb.model,
|
|
1922
|
+
// Legs run as named: no router re-picks the model, and the call's
|
|
1923
|
+
// resolved tier travels with them.
|
|
1924
|
+
router: undefined,
|
|
1925
|
+
policyTier: walkTier,
|
|
1926
|
+
// The walk's state before this leg, and its role.
|
|
1927
|
+
__fallbackWalk: { state: walkState, role: fb.walkRole },
|
|
1738
1928
|
// When a builder exists, ALWAYS override _responseFormat —
|
|
1739
1929
|
// including with an explicit `undefined` on builder failure or
|
|
1740
1930
|
// decline. Merely omitting the key would let the `...opts`
|
|
@@ -1764,7 +1954,7 @@ export async function generateText(opts) {
|
|
|
1764
1954
|
// fallback provider rather than the primary's overrides.
|
|
1765
1955
|
apiKey: undefined,
|
|
1766
1956
|
baseUrl: undefined,
|
|
1767
|
-
// Preserve the REMAINING
|
|
1957
|
+
// Preserve the REMAINING resolved chain (entries AFTER the current
|
|
1768
1958
|
// fb; `attempt` is 1-indexed so slice(attempt) drops fb and all
|
|
1769
1959
|
// already-tried entries). This stops the recursion from rebuilding
|
|
1770
1960
|
// the default cheap chain (which includes gpt-4o-mini) when a
|
|
@@ -1844,6 +2034,7 @@ export async function generateText(opts) {
|
|
|
1844
2034
|
// The leg walked every entry after it and all of them failed.
|
|
1845
2035
|
if (chainWalkedBefore(fbError))
|
|
1846
2036
|
break;
|
|
2037
|
+
walkState = advanceFallbackWalk(walkState, fb.walkRole, fbError);
|
|
1847
2038
|
}
|
|
1848
2039
|
}
|
|
1849
2040
|
// All fallbacks exhausted: fall through to throw
|