@stigmer/runner 3.38.2 → 3.39.0
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,8 +7,39 @@
|
|
|
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
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* How a model reasons, as its registry row declares it (the proto's
|
|
15
|
+
* `ModelCapabilities`):
|
|
16
|
+
*
|
|
17
|
+
* - `shape` is `"adaptive"` when the row declares `adaptiveThinking` (depth
|
|
18
|
+
* adapts; Anthropic `{type: "adaptive"}`), `"budget"` when it declares only
|
|
19
|
+
* `thinking` (a fixed token budget; `{type: "enabled", budget_tokens}`),
|
|
20
|
+
* and `"none"` otherwise. Adaptive wins where a row declares both: it is
|
|
21
|
+
* the current form, and the budget form is deprecated on models that take
|
|
22
|
+
* both.
|
|
23
|
+
* - `required` is the row's `thinkingRequired`, tri-state: `true` for a
|
|
24
|
+
* model that always thinks and refuses to be told not to, `false` for one
|
|
25
|
+
* that accepts an explicit `{type: "disabled"}`, `undefined` for a row that
|
|
26
|
+
* was never assessed. The absence matters: a runner that read it as
|
|
27
|
+
* `false` would send `disabled` to a model that refuses it.
|
|
28
|
+
*/
|
|
29
|
+
export interface NativeThinkingProfile {
|
|
30
|
+
readonly shape: "budget" | "adaptive" | "none";
|
|
31
|
+
readonly required: boolean | undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The request facts a native-harness model's registry row states, read by
|
|
35
|
+
* the native execution turn when it builds its model client. One home for
|
|
36
|
+
* what the provider needs to be told about a model (the runner never names
|
|
37
|
+
* models in code); a field is `undefined` when the row does not state it.
|
|
38
|
+
*/
|
|
39
|
+
export interface NativeRequestProfile {
|
|
40
|
+
readonly maxOutputTokens: number | undefined;
|
|
41
|
+
readonly thinking: NativeThinkingProfile;
|
|
42
|
+
}
|
|
12
43
|
/**
|
|
13
44
|
* Check whether a model identifier is known to the registry.
|
|
14
45
|
*
|
|
@@ -68,5 +99,15 @@ export declare function resolveToApiModelId(registryId: string): Promise<string>
|
|
|
68
99
|
* carries the registry id, so both forms arrive here.
|
|
69
100
|
*/
|
|
70
101
|
export declare function getModelVisionCapability(modelName: string): Promise<boolean | undefined>;
|
|
102
|
+
/**
|
|
103
|
+
* The native-harness row's request facts for a model, matched by registry
|
|
104
|
+
* `id` or `apiModelId` (the execution config carries the former, the
|
|
105
|
+
* registry default the latter) and filtered to `harness: native`: ids such
|
|
106
|
+
* as `claude-sonnet-5` exist on both harnesses, and only the native row
|
|
107
|
+
* describes the native request. `undefined` when the registry is
|
|
108
|
+
* unreachable, the name is empty or Auto, or no native row matches — the
|
|
109
|
+
* caller then sends what it sent before.
|
|
110
|
+
*/
|
|
111
|
+
export declare function getNativeRequestProfile(modelName: string): Promise<NativeRequestProfile | undefined>;
|
|
71
112
|
/** Exposed for testing — resets the in-memory cache. */
|
|
72
113
|
export declare function _resetRegistryCache(): void;
|
|
@@ -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
|
import { resolveModelRegistryUrl, buildRegistryHeaders, REGISTRY_RETRY_POLICY, } from "./registry-endpoint.js";
|
|
13
14
|
import { fetchWithRetry } from "./http-retry.js";
|
|
@@ -34,8 +35,26 @@ function parseRegistry(json) {
|
|
|
34
35
|
harness: m.harness ?? "native",
|
|
35
36
|
featured: !!m.featured,
|
|
36
37
|
visionCapability: parseVisionCapability(m.capabilities),
|
|
38
|
+
maxOutputTokens: parsePositiveInteger(m.maxOutputTokens),
|
|
39
|
+
thinking: parseThinkingProfile(m.capabilities),
|
|
37
40
|
}));
|
|
38
41
|
}
|
|
42
|
+
function parsePositiveInteger(value) {
|
|
43
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The row's thinking form. A missing or malformed `capabilities` block
|
|
47
|
+
* declares no shape and leaves `required` unknown, the same tri-state
|
|
48
|
+
* posture as {@link parseVisionCapability}.
|
|
49
|
+
*/
|
|
50
|
+
function parseThinkingProfile(capabilities) {
|
|
51
|
+
if (!capabilities || typeof capabilities !== "object")
|
|
52
|
+
return { shape: "none", required: undefined };
|
|
53
|
+
const flags = capabilities;
|
|
54
|
+
const shape = flags.adaptiveThinking === true ? "adaptive" : flags.thinking === true ? "budget" : "none";
|
|
55
|
+
const required = typeof flags.thinkingRequired === "boolean" ? flags.thinkingRequired : undefined;
|
|
56
|
+
return { shape, required };
|
|
57
|
+
}
|
|
39
58
|
/**
|
|
40
59
|
* Extract `capabilities.vision` preserving the tri-state: a missing or
|
|
41
60
|
* malformed `capabilities` block stays `undefined` (never coerced to false).
|
|
@@ -207,6 +226,24 @@ export async function getModelVisionCapability(modelName) {
|
|
|
207
226
|
const entry = registry.find((m) => m.id === modelName || m.apiModelId === modelName);
|
|
208
227
|
return entry?.visionCapability;
|
|
209
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* The native-harness row's request facts for a model, matched by registry
|
|
231
|
+
* `id` or `apiModelId` (the execution config carries the former, the
|
|
232
|
+
* registry default the latter) and filtered to `harness: native`: ids such
|
|
233
|
+
* as `claude-sonnet-5` exist on both harnesses, and only the native row
|
|
234
|
+
* describes the native request. `undefined` when the registry is
|
|
235
|
+
* unreachable, the name is empty or Auto, or no native row matches — the
|
|
236
|
+
* caller then sends what it sent before.
|
|
237
|
+
*/
|
|
238
|
+
export async function getNativeRequestProfile(modelName) {
|
|
239
|
+
if (!modelName || modelName === "default")
|
|
240
|
+
return undefined;
|
|
241
|
+
const registry = await getRegistry();
|
|
242
|
+
const entry = registry.find((m) => m.harness === "native" && (m.id === modelName || m.apiModelId === modelName));
|
|
243
|
+
if (!entry)
|
|
244
|
+
return undefined;
|
|
245
|
+
return { maxOutputTokens: entry.maxOutputTokens, thinking: entry.thinking };
|
|
246
|
+
}
|
|
210
247
|
/** Exposed for testing — resets the in-memory cache. */
|
|
211
248
|
export function _resetRegistryCache() {
|
|
212
249
|
cache = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-registry.js","sourceRoot":"","sources":["../../src/shared/model-registry.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"model-registry.js","sourceRoot":"","sources":["../../src/shared/model-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,12 +16,23 @@
|
|
|
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
|
*/
|
|
33
|
+
import type { ThinkingConfigParam } from "@anthropic-ai/sdk/resources/messages";
|
|
24
34
|
import { ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
35
|
+
import type { NativeThinkingProfile } from "./model-registry.js";
|
|
25
36
|
/**
|
|
26
37
|
* The effective mode after platform-default resolution: never UNSPECIFIED.
|
|
27
38
|
*/
|
|
@@ -33,3 +44,39 @@ export type EffectiveThinkingMode = ThinkingMode.DISABLED | ThinkingMode.ENABLED
|
|
|
33
44
|
export declare function resolveEffectiveThinkingMode(configured: ThinkingMode | undefined): EffectiveThinkingMode;
|
|
34
45
|
/** Human-readable mode label for logs and error messages. */
|
|
35
46
|
export declare function thinkingModeLabel(mode: ThinkingMode): string;
|
|
47
|
+
/**
|
|
48
|
+
* The thinking budget sent to a model whose row declares the fixed-budget
|
|
49
|
+
* form. One platform constant, not a user choice (per-execution budgets are
|
|
50
|
+
* a later proto change): 16,000 tokens is room for a real plan while
|
|
51
|
+
* leaving every budget row's output ceiling (64,000 on the smallest) most
|
|
52
|
+
* of its length for the answer. The provider requires at least 1,024 and
|
|
53
|
+
* less than `max_tokens`. No interleaved-thinking beta rides with it: the
|
|
54
|
+
* hosted proxy forwards no `anthropic-beta` header, so these models think
|
|
55
|
+
* at the start of each model call, not between tool calls.
|
|
56
|
+
*/
|
|
57
|
+
export declare const BUDGET_THINKING_TOKENS = 16000;
|
|
58
|
+
/**
|
|
59
|
+
* The native request's `thinking` parameter for an execution's effective
|
|
60
|
+
* mode on a model's native row, or `undefined` to send none.
|
|
61
|
+
*
|
|
62
|
+
* - A row that requires thinking gets its thinking form whatever the mode;
|
|
63
|
+
* the model thinks either way, and `display: "summarized"` is what makes
|
|
64
|
+
* its reasoning readable (the newest models default to `"omitted"`).
|
|
65
|
+
* - ENABLED gets the row's form: adaptive with `display: "summarized"`, or
|
|
66
|
+
* the fixed budget. A row that declares no form gets nothing; create-time
|
|
67
|
+
* validation refuses ENABLED there, so this is the fail-safe.
|
|
68
|
+
* - DISABLED gets an explicit `{type: "disabled"}` only where the row
|
|
69
|
+
* declares a thinking form and states `thinkingRequired: false`. Where the
|
|
70
|
+
* flag was never assessed nothing is sent (the request as it was before),
|
|
71
|
+
* because sending `disabled` to a model that requires thinking fails every
|
|
72
|
+
* turn; a row with no thinking form has no parameter to pin.
|
|
73
|
+
* - No row (an unreachable registry, an unknown model) sends nothing.
|
|
74
|
+
*/
|
|
75
|
+
export declare function toAnthropicThinking(mode: EffectiveThinkingMode, profile: NativeThinkingProfile | undefined): ThinkingConfigParam | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Whether a graph built on this row reasons natively: true exactly when
|
|
78
|
+
* {@link toAnthropicThinking} asks for thinking. Derived from the same
|
|
79
|
+
* mapping, never decided separately, so the `think` tool and the structured
|
|
80
|
+
* output strategy can never disagree with the wire.
|
|
81
|
+
*/
|
|
82
|
+
export declare function graphThinks(mode: EffectiveThinkingMode, profile: NativeThinkingProfile | undefined): boolean;
|
|
@@ -16,10 +16,19 @@
|
|
|
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
|
import { ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
25
34
|
/**
|
|
@@ -40,4 +49,57 @@ export function thinkingModeLabel(mode) {
|
|
|
40
49
|
return "unspecified";
|
|
41
50
|
}
|
|
42
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The thinking budget sent to a model whose row declares the fixed-budget
|
|
54
|
+
* form. One platform constant, not a user choice (per-execution budgets are
|
|
55
|
+
* a later proto change): 16,000 tokens is room for a real plan while
|
|
56
|
+
* leaving every budget row's output ceiling (64,000 on the smallest) most
|
|
57
|
+
* of its length for the answer. The provider requires at least 1,024 and
|
|
58
|
+
* less than `max_tokens`. No interleaved-thinking beta rides with it: the
|
|
59
|
+
* hosted proxy forwards no `anthropic-beta` header, so these models think
|
|
60
|
+
* at the start of each model call, not between tool calls.
|
|
61
|
+
*/
|
|
62
|
+
export const BUDGET_THINKING_TOKENS = 16_000;
|
|
63
|
+
/**
|
|
64
|
+
* The native request's `thinking` parameter for an execution's effective
|
|
65
|
+
* mode on a model's native row, or `undefined` to send none.
|
|
66
|
+
*
|
|
67
|
+
* - A row that requires thinking gets its thinking form whatever the mode;
|
|
68
|
+
* the model thinks either way, and `display: "summarized"` is what makes
|
|
69
|
+
* its reasoning readable (the newest models default to `"omitted"`).
|
|
70
|
+
* - ENABLED gets the row's form: adaptive with `display: "summarized"`, or
|
|
71
|
+
* the fixed budget. A row that declares no form gets nothing; create-time
|
|
72
|
+
* validation refuses ENABLED there, so this is the fail-safe.
|
|
73
|
+
* - DISABLED gets an explicit `{type: "disabled"}` only where the row
|
|
74
|
+
* declares a thinking form and states `thinkingRequired: false`. Where the
|
|
75
|
+
* flag was never assessed nothing is sent (the request as it was before),
|
|
76
|
+
* because sending `disabled` to a model that requires thinking fails every
|
|
77
|
+
* turn; a row with no thinking form has no parameter to pin.
|
|
78
|
+
* - No row (an unreachable registry, an unknown model) sends nothing.
|
|
79
|
+
*/
|
|
80
|
+
export function toAnthropicThinking(mode, profile) {
|
|
81
|
+
if (profile === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
if (profile.required === true || mode === ThinkingMode.ENABLED) {
|
|
84
|
+
switch (profile.shape) {
|
|
85
|
+
case "adaptive":
|
|
86
|
+
return { type: "adaptive", display: "summarized" };
|
|
87
|
+
case "budget":
|
|
88
|
+
return { type: "enabled", budget_tokens: BUDGET_THINKING_TOKENS };
|
|
89
|
+
case "none":
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return profile.required === false && profile.shape !== "none" ? { type: "disabled" } : undefined;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Whether a graph built on this row reasons natively: true exactly when
|
|
97
|
+
* {@link toAnthropicThinking} asks for thinking. Derived from the same
|
|
98
|
+
* mapping, never decided separately, so the `think` tool and the structured
|
|
99
|
+
* output strategy can never disagree with the wire.
|
|
100
|
+
*/
|
|
101
|
+
export function graphThinks(mode, profile) {
|
|
102
|
+
const thinking = toAnthropicThinking(mode, profile);
|
|
103
|
+
return thinking !== undefined && thinking.type !== "disabled";
|
|
104
|
+
}
|
|
43
105
|
//# sourceMappingURL=thinking-mode.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thinking-mode.js","sourceRoot":"","sources":["../../src/shared/thinking-mode.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"thinking-mode.js","sourceRoot":"","sources":["../../src/shared/thinking-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,8DAA8D,CAAC;AAS5F;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAC1C,UAAoC;IAEpC,OAAO,UAAU,KAAK,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC;AAC5F,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,iBAAiB,CAAC,IAAkB;IAClD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,YAAY,CAAC,OAAO;YACvB,OAAO,SAAS,CAAC;QACnB,KAAK,YAAY,CAAC,QAAQ;YACxB,OAAO,UAAU,CAAC;QACpB;YACE,OAAO,aAAa,CAAC;IACzB,CAAC;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE7C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAA2B,EAC3B,OAA0C;IAE1C,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC5C,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,KAAK,YAAY,CAAC,OAAO,EAAE,CAAC;QAC/D,QAAQ,OAAO,CAAC,KAAK,EAAE,CAAC;YACtB,KAAK,UAAU;gBACb,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;YACrD,KAAK,QAAQ;gBACX,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,sBAAsB,EAAE,CAAC;YACpE,KAAK,MAAM;gBACT,OAAO,SAAS,CAAC;QACrB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC,QAAQ,KAAK,KAAK,IAAI,OAAO,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AACnG,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,IAA2B,EAC3B,OAA0C;IAE1C,MAAM,QAAQ,GAAG,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACpD,OAAO,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,IAAI,KAAK,UAAU,CAAC;AAChE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stigmer/runner",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.39.0",
|
|
4
4
|
"description": "Embeddable Temporal worker for the Stigmer AI agent platform — handles agent execution, workflow orchestration, and MCP server management",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
"dependencies": {
|
|
88
88
|
"@anthropic-ai/bedrock-sdk": "^0.32.1",
|
|
89
89
|
"@anthropic-ai/foundry-sdk": "^0.4.1",
|
|
90
|
+
"@anthropic-ai/sdk": "0.116.0",
|
|
90
91
|
"@anthropic-ai/vertex-sdk": "^0.19.1",
|
|
91
92
|
"@azure/identity": "^4.13.1",
|
|
92
93
|
"@bufbuild/protobuf": "2.12.0",
|
|
@@ -108,10 +109,10 @@
|
|
|
108
109
|
"@opentelemetry/sdk-metrics": "^2.0.0",
|
|
109
110
|
"@opentelemetry/sdk-trace-base": "^2.0.0",
|
|
110
111
|
"@opentelemetry/sdk-trace-node": "^2.0.0",
|
|
111
|
-
"@stigmer/outbound": "3.
|
|
112
|
-
"@stigmer/protos": "3.
|
|
113
|
-
"@stigmer/temporal-codecs": "3.
|
|
114
|
-
"@stigmer/zip-structure": "3.
|
|
112
|
+
"@stigmer/outbound": "3.39.0",
|
|
113
|
+
"@stigmer/protos": "3.39.0",
|
|
114
|
+
"@stigmer/temporal-codecs": "3.39.0",
|
|
115
|
+
"@stigmer/zip-structure": "3.39.0",
|
|
115
116
|
"@temporalio/activity": "1.16.2",
|
|
116
117
|
"@temporalio/common": "1.16.2",
|
|
117
118
|
"@temporalio/interceptors-opentelemetry": "1.16.2",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import type { StructuredTool } from "@langchain/core/tools";
|
|
3
3
|
import type { McpServerUsage } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/spec_pb";
|
|
4
|
+
import { ThinkingMode } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
5
|
+
import type { EffectiveThinkingMode } from "../../../shared/thinking-mode.js";
|
|
4
6
|
|
|
5
7
|
import {
|
|
6
8
|
BUILTIN_SUBAGENT_TYPES,
|
|
@@ -12,12 +14,15 @@ import {
|
|
|
12
14
|
transformAndCompileSubagents,
|
|
13
15
|
type TransformedSubagent,
|
|
14
16
|
} from "../subagent-transformer.js";
|
|
17
|
+
import { _resetRegistryCache } from "../../../shared/model-registry.js";
|
|
15
18
|
import { mockWorkspaceBackend } from "../../../__test-utils__/mock-workspace.js";
|
|
16
19
|
|
|
17
20
|
// =========================================================================
|
|
18
21
|
// Test helpers
|
|
19
22
|
// =========================================================================
|
|
20
23
|
|
|
24
|
+
const NOT_THINKING: EffectiveThinkingMode = ThinkingMode.DISABLED;
|
|
25
|
+
|
|
21
26
|
function mockTool(name: string): StructuredTool {
|
|
22
27
|
return { name, description: `Mock tool: ${name}` } as unknown as StructuredTool;
|
|
23
28
|
}
|
|
@@ -145,7 +150,8 @@ describe("transformSingleSubagent", () => {
|
|
|
145
150
|
parentMcpTools: [] as StructuredTool[],
|
|
146
151
|
parentMcpServerToolMap: new Map<string, StructuredTool[]>(),
|
|
147
152
|
parentMcpUsages: [] as McpServerUsage[],
|
|
148
|
-
|
|
153
|
+
parentThinks: true,
|
|
154
|
+
thinkingMode: NOT_THINKING,
|
|
149
155
|
parentModelName: "claude-sonnet-4-6",
|
|
150
156
|
webFetchPosture: "strict" as const,
|
|
151
157
|
};
|
|
@@ -197,11 +203,11 @@ describe("transformSingleSubagent", () => {
|
|
|
197
203
|
expect(result!.systemPrompt).toContain("## Response rules");
|
|
198
204
|
});
|
|
199
205
|
|
|
200
|
-
it("does not inject think tool when parent
|
|
206
|
+
it("does not inject think tool when it inherits a parent that thinks", async () => {
|
|
201
207
|
const proto = mockSubAgentProto();
|
|
202
208
|
const result = await transformSingleSubagent(proto, {
|
|
203
209
|
...baseOpts,
|
|
204
|
-
|
|
210
|
+
parentThinks: true,
|
|
205
211
|
});
|
|
206
212
|
|
|
207
213
|
const hasThinkTool = result!.tools.some(
|
|
@@ -211,24 +217,65 @@ describe("transformSingleSubagent", () => {
|
|
|
211
217
|
});
|
|
212
218
|
|
|
213
219
|
it("always injects web_fetch, regardless of thinking support", async () => {
|
|
214
|
-
for (const
|
|
220
|
+
for (const parentThinks of [true, false]) {
|
|
215
221
|
const result = await transformSingleSubagent(mockSubAgentProto(), {
|
|
216
222
|
...baseOpts,
|
|
217
|
-
|
|
223
|
+
parentThinks,
|
|
218
224
|
});
|
|
219
225
|
const hasWebFetch = result!.tools.some((t) => t.name === "web_fetch");
|
|
220
226
|
expect(hasWebFetch).toBe(true);
|
|
221
227
|
}
|
|
222
228
|
});
|
|
223
229
|
|
|
224
|
-
it("injects think tool when parent
|
|
230
|
+
it("injects think tool when it inherits a parent that does not think", async () => {
|
|
225
231
|
const proto = mockSubAgentProto();
|
|
226
232
|
const result = await transformSingleSubagent(proto, {
|
|
227
233
|
...baseOpts,
|
|
228
|
-
|
|
234
|
+
parentThinks: false,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
expect(result!.tools.map((t) => t.name)).toContain("think");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("a sub-agent with its own model answers from its own native row", () => {
|
|
241
|
+
beforeEach(() => {
|
|
242
|
+
_resetRegistryCache();
|
|
243
|
+
vi.restoreAllMocks();
|
|
244
|
+
vi.spyOn(global, "fetch").mockResolvedValue({
|
|
245
|
+
ok: true,
|
|
246
|
+
json: () => Promise.resolve({
|
|
247
|
+
models: [
|
|
248
|
+
{ id: "claude-sonnet-5", provider: "anthropic", harness: "native", capabilities: { adaptiveThinking: true, thinkingRequired: false } },
|
|
249
|
+
{ id: "claude-haiku-4.5", provider: "anthropic", harness: "native", capabilities: { thinking: true, thinkingRequired: false } },
|
|
250
|
+
{ id: "claude-fable-5", provider: "anthropic", harness: "native", capabilities: { adaptiveThinking: true, thinkingRequired: true } },
|
|
251
|
+
{ id: "composer-2.5", provider: "cursor", harness: "cursor" },
|
|
252
|
+
],
|
|
253
|
+
}),
|
|
254
|
+
} as Response);
|
|
229
255
|
});
|
|
230
256
|
|
|
231
|
-
|
|
257
|
+
afterEach(() => {
|
|
258
|
+
_resetRegistryCache();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const thinkBound = async (modelOverride: string, thinkingMode: ThinkingMode.DISABLED | ThinkingMode.ENABLED, parentThinks: boolean) => {
|
|
262
|
+
const result = await transformSingleSubagent(mockSubAgentProto({ modelOverride }), { ...baseOpts, thinkingMode, parentThinks });
|
|
263
|
+
return result!.tools.some((t) => t.name === "think");
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
it("a thinking execution drops think on a model that can think, whatever the parent does", async () => {
|
|
267
|
+
expect(await thinkBound("claude-sonnet-5", ThinkingMode.ENABLED, false)).toBe(false);
|
|
268
|
+
expect(await thinkBound("claude-haiku-4.5", ThinkingMode.ENABLED, false)).toBe(false);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("a non-thinking execution binds think, unless the sub-agent's model always thinks", async () => {
|
|
272
|
+
expect(await thinkBound("claude-sonnet-5", ThinkingMode.DISABLED, true)).toBe(true);
|
|
273
|
+
expect(await thinkBound("claude-fable-5", ThinkingMode.DISABLED, false)).toBe(false);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("a model with no native row binds think: its request carries no thinking", async () => {
|
|
277
|
+
expect(await thinkBound("composer-2.5", ThinkingMode.ENABLED, true)).toBe(true);
|
|
278
|
+
});
|
|
232
279
|
});
|
|
233
280
|
|
|
234
281
|
it("returns null for invalid model override", async () => {
|
|
@@ -451,7 +498,8 @@ describe("transformAndCompileSubagents", () => {
|
|
|
451
498
|
workspaceBackend: mockWorkspaceBackend(),
|
|
452
499
|
approvalGate: null,
|
|
453
500
|
parentModelName: "claude-sonnet-4-6",
|
|
454
|
-
|
|
501
|
+
parentThinks: true,
|
|
502
|
+
thinkingMode: NOT_THINKING,
|
|
455
503
|
webFetchPosture: "strict" as const,
|
|
456
504
|
costCap: undefined,
|
|
457
505
|
};
|
|
@@ -622,7 +670,8 @@ describe("subagent-transformer edge cases", () => {
|
|
|
622
670
|
parentMcpTools: [],
|
|
623
671
|
parentMcpServerToolMap: new Map(),
|
|
624
672
|
parentMcpUsages: [],
|
|
625
|
-
|
|
673
|
+
parentThinks: true,
|
|
674
|
+
thinkingMode: NOT_THINKING,
|
|
626
675
|
parentModelName: "claude-sonnet-4-6",
|
|
627
676
|
webFetchPosture: "strict",
|
|
628
677
|
});
|
|
@@ -649,7 +698,8 @@ describe("subagent-transformer edge cases", () => {
|
|
|
649
698
|
workspaceBackend: mockWorkspaceBackend({ rootDir: "" }),
|
|
650
699
|
approvalGate: null,
|
|
651
700
|
parentModelName: "claude-sonnet-4-6",
|
|
652
|
-
|
|
701
|
+
parentThinks: true,
|
|
702
|
+
thinkingMode: NOT_THINKING,
|
|
653
703
|
webFetchPosture: "strict",
|
|
654
704
|
costCap: undefined,
|
|
655
705
|
} as Parameters<typeof transformAndCompileSubagents>[0]);
|
|
@@ -673,7 +723,8 @@ describe("subagent-transformer edge cases", () => {
|
|
|
673
723
|
workspaceBackend: mockWorkspaceBackend(),
|
|
674
724
|
approvalGate: null,
|
|
675
725
|
parentModelName: "claude-sonnet-4-6",
|
|
676
|
-
|
|
726
|
+
parentThinks: true,
|
|
727
|
+
thinkingMode: NOT_THINKING,
|
|
677
728
|
webFetchPosture: "strict",
|
|
678
729
|
costCap: undefined,
|
|
679
730
|
} as Parameters<typeof transformAndCompileSubagents>[0]);
|
|
@@ -39,7 +39,8 @@ import type { CostAdvisoryMiddleware, StigmerMiddleware } from "../../middleware
|
|
|
39
39
|
import { createThinkTool, createWebFetchTool, type GuardPosture } from "../../tools/index.js";
|
|
40
40
|
import { buildSubAgentMiddleware } from "./subagent-wiring.js";
|
|
41
41
|
import { SubAgentGate } from "../../shared/subagent-gate.js";
|
|
42
|
-
import { isModelRegistered } from "../../shared/model-registry.js";
|
|
42
|
+
import { getNativeRequestProfile, isModelRegistered } from "../../shared/model-registry.js";
|
|
43
|
+
import { graphThinks, type EffectiveThinkingMode } from "../../shared/thinking-mode.js";
|
|
43
44
|
import type { SkillMetadata } from "../../shared/skill-resolver.js";
|
|
44
45
|
import { renderSkillsSection } from "./prompt-builder.js";
|
|
45
46
|
import { ENGINE_TOOL } from "./engine-tools.js";
|
|
@@ -175,7 +176,15 @@ export interface SubagentTransformOptions {
|
|
|
175
176
|
*/
|
|
176
177
|
readonly casObserver?: CasCaptureObserver;
|
|
177
178
|
readonly parentModelName: string;
|
|
178
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Whether the parent's graph reasons natively (thinking-mode.ts
|
|
181
|
+
* `graphThinks` on its row). A sub-agent that names no model of its own
|
|
182
|
+
* runs on the parent's, so it inherits this answer; a sub-agent with a
|
|
183
|
+
* `model_override` is answered from its own model's row.
|
|
184
|
+
*/
|
|
185
|
+
readonly parentThinks: boolean;
|
|
186
|
+
/** The execution's effective thinking mode, read against a sub-agent's own row. */
|
|
187
|
+
readonly thinkingMode: EffectiveThinkingMode;
|
|
179
188
|
/**
|
|
180
189
|
* URL-guard posture for the native `web_fetch` tool, inherited from the
|
|
181
190
|
* parent (resolveGuardPosture(config.mode) in turn-setup.ts) so a sub-agent
|
|
@@ -303,7 +312,8 @@ export async function transformSingleSubagent(
|
|
|
303
312
|
readonly parentMcpTools: readonly StructuredTool[];
|
|
304
313
|
readonly parentMcpServerToolMap: ReadonlyMap<string, readonly StructuredTool[]>;
|
|
305
314
|
readonly parentMcpUsages: readonly McpServerUsage[];
|
|
306
|
-
readonly
|
|
315
|
+
readonly parentThinks: boolean;
|
|
316
|
+
readonly thinkingMode: EffectiveThinkingMode;
|
|
307
317
|
readonly parentModelName: string;
|
|
308
318
|
readonly webFetchPosture: GuardPosture;
|
|
309
319
|
},
|
|
@@ -330,12 +340,14 @@ export async function transformSingleSubagent(
|
|
|
330
340
|
// Build tools list — start with filtered MCP tools (populated by caller)
|
|
331
341
|
const tools: StructuredTool[] = [];
|
|
332
342
|
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
343
|
+
// `think` is a reasoning aid for a graph that does not reason natively.
|
|
344
|
+
// Whether this one does is the same answer its model's request carries
|
|
345
|
+
// (the model factory maps the same mode against the same row).
|
|
346
|
+
const saThinks = model
|
|
347
|
+
? graphThinks(opts.thinkingMode, (await getNativeRequestProfile(model))?.thinking)
|
|
348
|
+
: opts.parentThinks;
|
|
337
349
|
|
|
338
|
-
if (!
|
|
350
|
+
if (!saThinks) {
|
|
339
351
|
tools.push(createThinkTool() as unknown as StructuredTool);
|
|
340
352
|
}
|
|
341
353
|
|
|
@@ -444,30 +456,6 @@ export function resolveSubagentSkillPrompt(
|
|
|
444
456
|
return renderSkillsSection(skills.get(subAgent.name) ?? []);
|
|
445
457
|
}
|
|
446
458
|
|
|
447
|
-
/**
|
|
448
|
-
* Heuristic check for native extended thinking support. Models with thinking
|
|
449
|
-
* support don't need the explicit think tool. Read for the parent (does its
|
|
450
|
-
* graph carry `think`?) and inherited by every sub-agent that names no model
|
|
451
|
-
* of its own. Moved from `setup.ts` in #1096: the compile step is where it
|
|
452
|
-
* is consumed, and the adapter's setup reads it from here too.
|
|
453
|
-
*/
|
|
454
|
-
export function modelHasNativeThinking(modelId: string): boolean {
|
|
455
|
-
const lower = modelId.toLowerCase();
|
|
456
|
-
|
|
457
|
-
if (lower.includes("haiku")) return false;
|
|
458
|
-
if (lower.includes("gpt-4o-mini")) return false;
|
|
459
|
-
|
|
460
|
-
if (lower.includes("claude") && (
|
|
461
|
-
lower.includes("sonnet") || lower.includes("opus")
|
|
462
|
-
)) return true;
|
|
463
|
-
|
|
464
|
-
if (lower.includes("o1") || lower.includes("o3") || lower.includes("o4")) {
|
|
465
|
-
return true;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return false;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
459
|
// =========================================================================
|
|
472
460
|
// Compilation pipeline
|
|
473
461
|
// =========================================================================
|
|
@@ -654,7 +642,8 @@ export async function transformAndCompileSubagents(
|
|
|
654
642
|
approvalGate,
|
|
655
643
|
casObserver,
|
|
656
644
|
parentModelName,
|
|
657
|
-
|
|
645
|
+
parentThinks,
|
|
646
|
+
thinkingMode,
|
|
658
647
|
webFetchPosture,
|
|
659
648
|
costAdvisory,
|
|
660
649
|
modelFactory,
|
|
@@ -694,7 +683,8 @@ export async function transformAndCompileSubagents(
|
|
|
694
683
|
parentMcpTools,
|
|
695
684
|
parentMcpServerToolMap,
|
|
696
685
|
parentMcpUsages,
|
|
697
|
-
|
|
686
|
+
parentThinks,
|
|
687
|
+
thinkingMode,
|
|
698
688
|
parentModelName,
|
|
699
689
|
webFetchPosture,
|
|
700
690
|
});
|
|
@@ -780,32 +770,3 @@ export async function transformAndCompileSubagents(
|
|
|
780
770
|
);
|
|
781
771
|
return compiled;
|
|
782
772
|
}
|
|
783
|
-
|
|
784
|
-
// =========================================================================
|
|
785
|
-
// Private helpers
|
|
786
|
-
// =========================================================================
|
|
787
|
-
|
|
788
|
-
/**
|
|
789
|
-
* Check if a model supports native extended thinking.
|
|
790
|
-
*
|
|
791
|
-
* Heuristic: models with "claude" in the name that are NOT haiku-class
|
|
792
|
-
* support thinking. Models with "o1", "o3", "o4" support reasoning.
|
|
793
|
-
* This is a conservative heuristic — err on the side of injecting the
|
|
794
|
-
* think tool (it's harmless if the model already thinks natively).
|
|
795
|
-
*/
|
|
796
|
-
async function _modelSupportsThinking(modelId: string): Promise<boolean> {
|
|
797
|
-
const lower = modelId.toLowerCase();
|
|
798
|
-
|
|
799
|
-
if (lower.includes("haiku")) return false;
|
|
800
|
-
if (lower.includes("gpt-4o-mini")) return false;
|
|
801
|
-
|
|
802
|
-
if (lower.includes("claude") && (
|
|
803
|
-
lower.includes("sonnet") || lower.includes("opus")
|
|
804
|
-
)) return true;
|
|
805
|
-
|
|
806
|
-
if (lower.includes("o1") || lower.includes("o3") || lower.includes("o4")) {
|
|
807
|
-
return true;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
return false;
|
|
811
|
-
}
|