@stigmer/runner 3.38.2 → 3.38.3
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/.build-fingerprint +1 -1
- package/dist/activities/execute-deep-agent/subagent-transformer.d.ts +12 -10
- package/dist/activities/execute-deep-agent/subagent-transformer.js +12 -52
- package/dist/activities/execute-deep-agent/subagent-transformer.js.map +1 -1
- package/dist/activities/execute-deep-agent/turn-setup.d.ts +5 -4
- package/dist/activities/execute-deep-agent/turn-setup.js +68 -24
- package/dist/activities/execute-deep-agent/turn-setup.js.map +1 -1
- package/dist/shared/model-client.d.ts +24 -2
- package/dist/shared/model-client.js +8 -0
- package/dist/shared/model-client.js.map +1 -1
- package/dist/shared/model-registry.d.ts +42 -1
- package/dist/shared/model-registry.js +38 -1
- package/dist/shared/model-registry.js.map +1 -1
- package/dist/shared/thinking-mode.d.ts +51 -4
- package/dist/shared/thinking-mode.js +66 -4
- package/dist/shared/thinking-mode.js.map +1 -1
- package/package.json +6 -5
- package/src/activities/execute-deep-agent/__tests__/subagent-transformer.test.ts +63 -12
- package/src/activities/execute-deep-agent/subagent-transformer.ts +24 -63
- package/src/activities/execute-deep-agent/turn-setup.ts +70 -19
- package/src/shared/__tests__/model-client-sampling.test.ts +56 -2
- package/src/shared/__tests__/model-client.test.ts +72 -0
- package/src/shared/__tests__/model-registry.test.ts +96 -1
- package/src/shared/__tests__/thinking-mode.test.ts +69 -0
- package/src/shared/model-client.ts +35 -2
- package/src/shared/model-registry.ts +83 -1
- package/src/shared/thinking-mode.ts +77 -4
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* model-pricing-data.ts) and uses `costTier` + `harness` fields to
|
|
8
8
|
* dynamically resolve economy-tier models for extraction/summarization,
|
|
9
9
|
* plus `capabilities` catalog metadata for per-model capability lookups
|
|
10
|
-
* (getModelVisionCapability)
|
|
10
|
+
* (getModelVisionCapability) and the request facts a native row states
|
|
11
|
+
* (getNativeRequestProfile).
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
import {
|
|
@@ -38,6 +39,46 @@ interface RegistryModel {
|
|
|
38
39
|
* Consumers gate only on the explicit `false` (see attachment-vision.ts).
|
|
39
40
|
*/
|
|
40
41
|
visionCapability?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* The model's output ceiling in tokens, as the row states it. `undefined`
|
|
44
|
+
* when the row carries none or a value that is not a positive integer;
|
|
45
|
+
* the caller then keeps its own default.
|
|
46
|
+
*/
|
|
47
|
+
maxOutputTokens?: number;
|
|
48
|
+
/** The thinking form the row declares; see {@link NativeThinkingProfile}. */
|
|
49
|
+
thinking: NativeThinkingProfile;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* How a model reasons, as its registry row declares it (the proto's
|
|
54
|
+
* `ModelCapabilities`):
|
|
55
|
+
*
|
|
56
|
+
* - `shape` is `"adaptive"` when the row declares `adaptiveThinking` (depth
|
|
57
|
+
* adapts; Anthropic `{type: "adaptive"}`), `"budget"` when it declares only
|
|
58
|
+
* `thinking` (a fixed token budget; `{type: "enabled", budget_tokens}`),
|
|
59
|
+
* and `"none"` otherwise. Adaptive wins where a row declares both: it is
|
|
60
|
+
* the current form, and the budget form is deprecated on models that take
|
|
61
|
+
* both.
|
|
62
|
+
* - `required` is the row's `thinkingRequired`, tri-state: `true` for a
|
|
63
|
+
* model that always thinks and refuses to be told not to, `false` for one
|
|
64
|
+
* that accepts an explicit `{type: "disabled"}`, `undefined` for a row that
|
|
65
|
+
* was never assessed. The absence matters: a runner that read it as
|
|
66
|
+
* `false` would send `disabled` to a model that refuses it.
|
|
67
|
+
*/
|
|
68
|
+
export interface NativeThinkingProfile {
|
|
69
|
+
readonly shape: "budget" | "adaptive" | "none";
|
|
70
|
+
readonly required: boolean | undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The request facts a native-harness model's registry row states, read by
|
|
75
|
+
* the native execution turn when it builds its model client. One home for
|
|
76
|
+
* what the provider needs to be told about a model (the runner never names
|
|
77
|
+
* models in code); a field is `undefined` when the row does not state it.
|
|
78
|
+
*/
|
|
79
|
+
export interface NativeRequestProfile {
|
|
80
|
+
readonly maxOutputTokens: number | undefined;
|
|
81
|
+
readonly thinking: NativeThinkingProfile;
|
|
41
82
|
}
|
|
42
83
|
|
|
43
84
|
let cache: { models: readonly RegistryModel[]; expiresAt: number } | null = null;
|
|
@@ -58,9 +99,28 @@ function parseRegistry(json: unknown): RegistryModel[] {
|
|
|
58
99
|
harness: (m.harness as string) ?? "native",
|
|
59
100
|
featured: !!m.featured,
|
|
60
101
|
visionCapability: parseVisionCapability(m.capabilities),
|
|
102
|
+
maxOutputTokens: parsePositiveInteger(m.maxOutputTokens),
|
|
103
|
+
thinking: parseThinkingProfile(m.capabilities),
|
|
61
104
|
}));
|
|
62
105
|
}
|
|
63
106
|
|
|
107
|
+
function parsePositiveInteger(value: unknown): number | undefined {
|
|
108
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The row's thinking form. A missing or malformed `capabilities` block
|
|
113
|
+
* declares no shape and leaves `required` unknown, the same tri-state
|
|
114
|
+
* posture as {@link parseVisionCapability}.
|
|
115
|
+
*/
|
|
116
|
+
function parseThinkingProfile(capabilities: unknown): NativeThinkingProfile {
|
|
117
|
+
if (!capabilities || typeof capabilities !== "object") return { shape: "none", required: undefined };
|
|
118
|
+
const flags = capabilities as Record<string, unknown>;
|
|
119
|
+
const shape = flags.adaptiveThinking === true ? "adaptive" : flags.thinking === true ? "budget" : "none";
|
|
120
|
+
const required = typeof flags.thinkingRequired === "boolean" ? flags.thinkingRequired : undefined;
|
|
121
|
+
return { shape, required };
|
|
122
|
+
}
|
|
123
|
+
|
|
64
124
|
/**
|
|
65
125
|
* Extract `capabilities.vision` preserving the tri-state: a missing or
|
|
66
126
|
* malformed `capabilities` block stays `undefined` (never coerced to false).
|
|
@@ -264,6 +324,28 @@ export async function getModelVisionCapability(
|
|
|
264
324
|
return entry?.visionCapability;
|
|
265
325
|
}
|
|
266
326
|
|
|
327
|
+
/**
|
|
328
|
+
* The native-harness row's request facts for a model, matched by registry
|
|
329
|
+
* `id` or `apiModelId` (the execution config carries the former, the
|
|
330
|
+
* registry default the latter) and filtered to `harness: native`: ids such
|
|
331
|
+
* as `claude-sonnet-5` exist on both harnesses, and only the native row
|
|
332
|
+
* describes the native request. `undefined` when the registry is
|
|
333
|
+
* unreachable, the name is empty or Auto, or no native row matches — the
|
|
334
|
+
* caller then sends what it sent before.
|
|
335
|
+
*/
|
|
336
|
+
export async function getNativeRequestProfile(
|
|
337
|
+
modelName: string,
|
|
338
|
+
): Promise<NativeRequestProfile | undefined> {
|
|
339
|
+
if (!modelName || modelName === "default") return undefined;
|
|
340
|
+
|
|
341
|
+
const registry = await getRegistry();
|
|
342
|
+
const entry = registry.find(
|
|
343
|
+
(m) => m.harness === "native" && (m.id === modelName || m.apiModelId === modelName),
|
|
344
|
+
);
|
|
345
|
+
if (!entry) return undefined;
|
|
346
|
+
return { maxOutputTokens: entry.maxOutputTokens, thinking: entry.thinking };
|
|
347
|
+
}
|
|
348
|
+
|
|
267
349
|
/** Exposed for testing — resets the in-memory cache. */
|
|
268
350
|
export function _resetRegistryCache(): void {
|
|
269
351
|
cache = null;
|
|
@@ -16,14 +16,26 @@
|
|
|
16
16
|
* rather than pricing-gated, and the estimate/billing paths need no
|
|
17
17
|
* thinking-specific rates.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
* `thinking` variant parameter
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* Each harness translates the mode in its own dialect: the Cursor harness
|
|
20
|
+
* as the explicit `thinking` variant parameter
|
|
21
|
+
* (`execute-cursor/service-tier.ts`), the native harness as Anthropic's
|
|
22
|
+
* `thinking` request parameter in the form the model's native registry row
|
|
23
|
+
* declares ({@link toAnthropicThinking}). ENABLED means "the model reasons
|
|
24
|
+
* before it answers", whichever form the row takes.
|
|
25
|
+
*
|
|
26
|
+
* One model family breaks the "always explicit" rule on purpose: a row
|
|
27
|
+
* that states `thinkingRequired` names a model that always thinks and
|
|
28
|
+
* refuses `{type: "disabled"}` with a 400. Create-time validation refuses
|
|
29
|
+
* an explicit DISABLED there, and the runner sends the model's thinking
|
|
30
|
+
* form whatever the mode, so its reasoning streams as text rather than the
|
|
31
|
+
* empty blocks the model's own `display` default produces.
|
|
23
32
|
*/
|
|
24
33
|
|
|
34
|
+
import type { ThinkingConfigParam } from "@anthropic-ai/sdk/resources/messages";
|
|
25
35
|
import { ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
26
36
|
|
|
37
|
+
import type { NativeThinkingProfile } from "./model-registry.js";
|
|
38
|
+
|
|
27
39
|
/**
|
|
28
40
|
* The effective mode after platform-default resolution: never UNSPECIFIED.
|
|
29
41
|
*/
|
|
@@ -50,3 +62,64 @@ export function thinkingModeLabel(mode: ThinkingMode): string {
|
|
|
50
62
|
return "unspecified";
|
|
51
63
|
}
|
|
52
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The thinking budget sent to a model whose row declares the fixed-budget
|
|
68
|
+
* form. One platform constant, not a user choice (per-execution budgets are
|
|
69
|
+
* a later proto change): 16,000 tokens is room for a real plan while
|
|
70
|
+
* leaving every budget row's output ceiling (64,000 on the smallest) most
|
|
71
|
+
* of its length for the answer. The provider requires at least 1,024 and
|
|
72
|
+
* less than `max_tokens`. No interleaved-thinking beta rides with it: the
|
|
73
|
+
* hosted proxy forwards no `anthropic-beta` header, so these models think
|
|
74
|
+
* at the start of each model call, not between tool calls.
|
|
75
|
+
*/
|
|
76
|
+
export const BUDGET_THINKING_TOKENS = 16_000;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The native request's `thinking` parameter for an execution's effective
|
|
80
|
+
* mode on a model's native row, or `undefined` to send none.
|
|
81
|
+
*
|
|
82
|
+
* - A row that requires thinking gets its thinking form whatever the mode;
|
|
83
|
+
* the model thinks either way, and `display: "summarized"` is what makes
|
|
84
|
+
* its reasoning readable (the newest models default to `"omitted"`).
|
|
85
|
+
* - ENABLED gets the row's form: adaptive with `display: "summarized"`, or
|
|
86
|
+
* the fixed budget. A row that declares no form gets nothing; create-time
|
|
87
|
+
* validation refuses ENABLED there, so this is the fail-safe.
|
|
88
|
+
* - DISABLED gets an explicit `{type: "disabled"}` only where the row
|
|
89
|
+
* declares a thinking form and states `thinkingRequired: false`. Where the
|
|
90
|
+
* flag was never assessed nothing is sent (the request as it was before),
|
|
91
|
+
* because sending `disabled` to a model that requires thinking fails every
|
|
92
|
+
* turn; a row with no thinking form has no parameter to pin.
|
|
93
|
+
* - No row (an unreachable registry, an unknown model) sends nothing.
|
|
94
|
+
*/
|
|
95
|
+
export function toAnthropicThinking(
|
|
96
|
+
mode: EffectiveThinkingMode,
|
|
97
|
+
profile: NativeThinkingProfile | undefined,
|
|
98
|
+
): ThinkingConfigParam | undefined {
|
|
99
|
+
if (profile === undefined) return undefined;
|
|
100
|
+
if (profile.required === true || mode === ThinkingMode.ENABLED) {
|
|
101
|
+
switch (profile.shape) {
|
|
102
|
+
case "adaptive":
|
|
103
|
+
return { type: "adaptive", display: "summarized" };
|
|
104
|
+
case "budget":
|
|
105
|
+
return { type: "enabled", budget_tokens: BUDGET_THINKING_TOKENS };
|
|
106
|
+
case "none":
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return profile.required === false && profile.shape !== "none" ? { type: "disabled" } : undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Whether a graph built on this row reasons natively: true exactly when
|
|
115
|
+
* {@link toAnthropicThinking} asks for thinking. Derived from the same
|
|
116
|
+
* mapping, never decided separately, so the `think` tool and the structured
|
|
117
|
+
* output strategy can never disagree with the wire.
|
|
118
|
+
*/
|
|
119
|
+
export function graphThinks(
|
|
120
|
+
mode: EffectiveThinkingMode,
|
|
121
|
+
profile: NativeThinkingProfile | undefined,
|
|
122
|
+
): boolean {
|
|
123
|
+
const thinking = toAnthropicThinking(mode, profile);
|
|
124
|
+
return thinking !== undefined && thinking.type !== "disabled";
|
|
125
|
+
}
|