@deepstrike/sdk 0.2.7 → 0.2.9
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/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/kernel.d.ts +0 -54
- package/dist/kernel.js +0 -8
- package/dist/providers/base.d.ts +6 -0
- package/dist/providers/base.js +10 -1
- package/dist/providers/deepseek.js +3 -0
- package/dist/providers/minimax.js +3 -0
- package/dist/providers/openai-chat.d.ts +12 -0
- package/dist/providers/openai-chat.js +19 -2
- package/dist/providers/openai.d.ts +10 -0
- package/dist/providers/openai.js +17 -0
- package/dist/providers/replay-validator.d.ts +23 -1
- package/dist/providers/replay-validator.js +50 -17
- package/dist/runtime/provider-replay.d.ts +9 -1
- package/dist/runtime/provider-replay.js +10 -0
- package/dist/runtime/runner.js +25 -9
- package/dist/types.d.ts +15 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export { RuntimeRunner, collectText } from "./runtime/runner.js";
|
|
2
2
|
export type { RuntimeOptions, SchedulerBudget } from "./runtime/runner.js";
|
|
3
3
|
export type { MemoryPolicy, MemoryWriteRateLimit, ResourceQuota } from "./kernel.js";
|
|
4
|
-
export { createTournament, createLoopUntilDone } from "./kernel.js";
|
|
5
|
-
export type { TournamentMatch, TournamentAction, StopConditionSpec, RoundReport, LoopAction, } from "./kernel.js";
|
|
6
4
|
export { KernelPrimitivesDashboard } from "./runtime/kernel-primitives-dashboard.js";
|
|
7
5
|
export { FilteredExecutionPlane } from "./runtime/filtered-plane.js";
|
|
8
6
|
export { SubAgentOrchestrator, defaultSubAgentOrchestrator, spawnStandalone } from "./runtime/sub-agent-orchestrator.js";
|
|
@@ -43,6 +41,8 @@ export { endpointProfiles, modelProfiles, getModelProfile } from "./providers/pr
|
|
|
43
41
|
export type { ModelProfileId, ProviderId } from "./providers/profiles.js";
|
|
44
42
|
export { createProvider } from "./providers/catalog.js";
|
|
45
43
|
export type { CreateProviderOptions, EndpointProfileId } from "./providers/catalog.js";
|
|
44
|
+
export { ProviderReplayValidationError, DEGRADED_REASONING_PLACEHOLDER } from "./providers/replay-validator.js";
|
|
45
|
+
export { assessProviderReplayability, peekProviderReplay, seedProviderReplayFromEvents, isReplayCompatibleWithProvider, } from "./runtime/provider-replay.js";
|
|
46
46
|
export { tool, streamingTool, executeTools, readFile, validateToolArguments } from "./tools/index.js";
|
|
47
47
|
export type { RegisteredTool } from "./tools/index.js";
|
|
48
48
|
export { scanSkillDir, readSkillFile } from "./skills/loader.js";
|
|
@@ -59,7 +59,7 @@ export { Governance, governancePolicyToKernelEvent } from "./governance.js";
|
|
|
59
59
|
export type { GovernanceVerdict, GovernancePolicy, GovernanceConstraint } from "./governance.js";
|
|
60
60
|
export { SinglePassHarness, EvalLoopHarness, HarnessLoop } from "./harness/harness.js";
|
|
61
61
|
export type { HarnessRequest, HarnessOutcome, HarnessLoopOptions, QualityGate } from "./harness/harness.js";
|
|
62
|
-
export type { Message, ToolCall, ToolResult, ToolSchema, ContentPart, TextPart, ImagePart, AudioPart, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolChunk, ToolDeltaEvent, ToolSuspendEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, PermissionResolvedEvent, PermissionResponse, LLMProvider, RetryConfig, TokenUsage, ProviderToolSpec, ProviderRunState, ProviderReplay, RenderedContext, } from "./types.js";
|
|
62
|
+
export type { Message, ToolCall, ToolResult, ToolSchema, ContentPart, TextPart, ImagePart, AudioPart, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolChunk, ToolDeltaEvent, ToolSuspendEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, PermissionResolvedEvent, PermissionResponse, LLMProvider, RetryConfig, TokenUsage, ProviderToolSpec, ProviderRunState, ProviderReplay, RenderedContext, ReplayabilityAssessment, } from "./types.js";
|
|
63
63
|
export type { AgentCapabilityFilter, AgentIdentity, AgentIsolation, AgentRunSpec, AgentProcessChangedObservation, ContextInheritance, KernelAgentRole, LoopResult, MilestoneCheckResult, MilestoneContract, MilestonePhase, MilestonePolicy, SubAgentResult, TerminationReason, WorkflowSpec, WorkflowNodeSpec, WorkflowTaskSpec, WorkflowSpawnInfo, } from "./types/agent.js";
|
|
64
64
|
export { agentIdentitySub, agentRunSpecToKernel, milestoneCheckFail, milestoneCheckPass, milestoneCheckResultToKernel, subAgentResultToKernel, workflowSpecToKernel, fanoutSynthesize, generateAndFilter, verifyRules, } from "./types/agent.js";
|
|
65
65
|
export type { AcceptanceCriterion, VerificationContract, ContractCheckResult, } from "./collaboration/contract.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// ── Runtime (Layer 1.5) ────────────────────────────────────────────────────
|
|
2
2
|
export { RuntimeRunner, collectText } from "./runtime/runner.js";
|
|
3
|
-
export { createTournament, createLoopUntilDone } from "./kernel.js";
|
|
4
3
|
export { KernelPrimitivesDashboard } from "./runtime/kernel-primitives-dashboard.js";
|
|
5
4
|
export { FilteredExecutionPlane } from "./runtime/filtered-plane.js";
|
|
6
5
|
export { SubAgentOrchestrator, defaultSubAgentOrchestrator, spawnStandalone } from "./runtime/sub-agent-orchestrator.js";
|
|
@@ -28,6 +27,8 @@ export { OpenAIChatAdapter } from "./providers/openai-chat.js";
|
|
|
28
27
|
export { OpenAIResponsesAdapter, OpenAIResponsesProvider } from "./providers/openai-responses.js";
|
|
29
28
|
export { endpointProfiles, modelProfiles, getModelProfile } from "./providers/profiles.js";
|
|
30
29
|
export { createProvider } from "./providers/catalog.js";
|
|
30
|
+
export { ProviderReplayValidationError, DEGRADED_REASONING_PLACEHOLDER } from "./providers/replay-validator.js";
|
|
31
|
+
export { assessProviderReplayability, peekProviderReplay, seedProviderReplayFromEvents, isReplayCompatibleWithProvider, } from "./runtime/provider-replay.js";
|
|
31
32
|
// ── Tools & Skills ─────────────────────────────────────────────────────────
|
|
32
33
|
export { tool, streamingTool, executeTools, readFile, validateToolArguments } from "./tools/index.js";
|
|
33
34
|
export { scanSkillDir, readSkillFile } from "./skills/loader.js";
|
package/dist/kernel.d.ts
CHANGED
|
@@ -147,54 +147,6 @@ export interface KernelRuntimeInstance {
|
|
|
147
147
|
drainNewMessages(): Message[];
|
|
148
148
|
preservedRefs(): string[];
|
|
149
149
|
}
|
|
150
|
-
/** One pairwise match-up in a tournament round. */
|
|
151
|
-
export interface TournamentMatch {
|
|
152
|
-
id: number;
|
|
153
|
-
left: string;
|
|
154
|
-
right: string;
|
|
155
|
-
}
|
|
156
|
-
/** Discriminated action returned by {@link TournamentInstance} methods. */
|
|
157
|
-
export interface TournamentAction {
|
|
158
|
-
kind: "judgeRound" | "done";
|
|
159
|
-
/** `judgeRound`: 1-based round number. */
|
|
160
|
-
round?: number;
|
|
161
|
-
/** `judgeRound`: run one fresh-context judge per match (parallelisable). */
|
|
162
|
-
matches?: TournamentMatch[];
|
|
163
|
-
/** `done`: the winning entrant id. */
|
|
164
|
-
winner?: string;
|
|
165
|
-
/** `done`: number of rounds played. */
|
|
166
|
-
roundsUsed?: number;
|
|
167
|
-
}
|
|
168
|
-
interface TournamentInstance {
|
|
169
|
-
start(): TournamentAction;
|
|
170
|
-
feedRound(winners: string[]): TournamentAction;
|
|
171
|
-
isDone(): boolean;
|
|
172
|
-
}
|
|
173
|
-
/** A single loop stop predicate. `maxRounds` is required when `kind === "maxRounds"`. */
|
|
174
|
-
export interface StopConditionSpec {
|
|
175
|
-
kind: "noNewFindings" | "noErrors" | "maxRounds";
|
|
176
|
-
maxRounds?: number;
|
|
177
|
-
}
|
|
178
|
-
/** What the SDK reports after running a loop round's worker. */
|
|
179
|
-
export interface RoundReport {
|
|
180
|
-
newFindings: number;
|
|
181
|
-
errors: number;
|
|
182
|
-
}
|
|
183
|
-
/** Discriminated action returned by {@link LoopUntilDoneInstance} methods. */
|
|
184
|
-
export interface LoopAction {
|
|
185
|
-
kind: "spawn" | "done";
|
|
186
|
-
/** `spawn`: 1-based round number to run. */
|
|
187
|
-
round?: number;
|
|
188
|
-
/** `done`: number of rounds run. */
|
|
189
|
-
roundsUsed?: number;
|
|
190
|
-
/** `done`: which condition fired. */
|
|
191
|
-
reason?: "noNewFindings" | "noErrors" | "maxRounds";
|
|
192
|
-
}
|
|
193
|
-
interface LoopUntilDoneInstance {
|
|
194
|
-
start(): LoopAction;
|
|
195
|
-
feed(report: RoundReport): LoopAction;
|
|
196
|
-
isDone(): boolean;
|
|
197
|
-
}
|
|
198
150
|
interface KernelModule {
|
|
199
151
|
Governance: new (defaultAction?: "allow" | "deny" | "ask_user") => GovernanceInstance;
|
|
200
152
|
KernelRuntime: new (policy: {
|
|
@@ -208,12 +160,6 @@ interface KernelModule {
|
|
|
208
160
|
extractSkillOnPass?: boolean;
|
|
209
161
|
}) => EvalPipelineInstance;
|
|
210
162
|
IdlePipeline: new (agentId: string) => IdlePipelineInstance;
|
|
211
|
-
Tournament: new (entrants: string[]) => TournamentInstance;
|
|
212
|
-
LoopUntilDone: new (conditions: StopConditionSpec[]) => LoopUntilDoneInstance;
|
|
213
163
|
}
|
|
214
|
-
/** Create a single-elimination tournament (pairwise comparative judging). Throws if `entrants` is empty. */
|
|
215
|
-
export declare function createTournament(entrants: string[]): TournamentInstance;
|
|
216
|
-
/** Create a loop-until-done state machine. A `maxRounds` backstop is injected if none is given. */
|
|
217
|
-
export declare function createLoopUntilDone(conditions: StopConditionSpec[]): LoopUntilDoneInstance;
|
|
218
164
|
export declare function getKernel(): KernelModule;
|
|
219
165
|
export {};
|
package/dist/kernel.js
CHANGED
|
@@ -2,14 +2,6 @@ import { createRequire } from "module";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
/** Create a single-elimination tournament (pairwise comparative judging). Throws if `entrants` is empty. */
|
|
6
|
-
export function createTournament(entrants) {
|
|
7
|
-
return new (getKernel().Tournament)(entrants);
|
|
8
|
-
}
|
|
9
|
-
/** Create a loop-until-done state machine. A `maxRounds` backstop is injected if none is given. */
|
|
10
|
-
export function createLoopUntilDone(conditions) {
|
|
11
|
-
return new (getKernel().LoopUntilDone)(conditions);
|
|
12
|
-
}
|
|
13
5
|
const cjsRequire = createRequire(import.meta.url);
|
|
14
6
|
let cachedKernel;
|
|
15
7
|
function resolveCoreModule() {
|
package/dist/providers/base.d.ts
CHANGED
|
@@ -9,6 +9,12 @@ export declare class CircuitBreaker {
|
|
|
9
9
|
recordSuccess(): void;
|
|
10
10
|
recordFailure(): void;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Internal control flags that steer DeepStrike's own serialization/validation
|
|
14
|
+
* and must never be forwarded to any provider's wire request, regardless of the
|
|
15
|
+
* per-call omit list.
|
|
16
|
+
*/
|
|
17
|
+
export declare const INTERNAL_EXTENSION_KEYS: readonly string[];
|
|
12
18
|
export declare function omitExtensionKeys(extensions: Record<string, unknown> | undefined, keys: readonly string[]): Record<string, unknown>;
|
|
13
19
|
export declare function normalizeToolCall(id: string, name: string, args: unknown): {
|
|
14
20
|
id: string;
|
package/dist/providers/base.js
CHANGED
|
@@ -26,10 +26,19 @@ export class CircuitBreaker {
|
|
|
26
26
|
this.openedAt = Date.now();
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Internal control flags that steer DeepStrike's own serialization/validation
|
|
31
|
+
* and must never be forwarded to any provider's wire request, regardless of the
|
|
32
|
+
* per-call omit list.
|
|
33
|
+
*/
|
|
34
|
+
export const INTERNAL_EXTENSION_KEYS = [
|
|
35
|
+
"__deepstrikeThinkingEnabled",
|
|
36
|
+
"degradeMissingReasoningReplay",
|
|
37
|
+
];
|
|
29
38
|
export function omitExtensionKeys(extensions, keys) {
|
|
30
39
|
if (!extensions)
|
|
31
40
|
return {};
|
|
32
|
-
const blocked = new Set(keys);
|
|
41
|
+
const blocked = new Set([...keys, ...INTERNAL_EXTENSION_KEYS]);
|
|
33
42
|
return Object.fromEntries(Object.entries(extensions).filter(([key]) => !blocked.has(key)));
|
|
34
43
|
}
|
|
35
44
|
export function normalizeToolCall(id, name, args) {
|
|
@@ -43,6 +43,9 @@ export class DeepSeekProvider extends OpenAIChatProvider {
|
|
|
43
43
|
const requestExtensions = {
|
|
44
44
|
...omitExtensionKeys(extensions, ["thinking", "reasoningEffort", "exposeReasoning", "extra_body", "reasoning_effort"]),
|
|
45
45
|
__deepstrikeThinkingEnabled: thinkingEnabled,
|
|
46
|
+
// Re-thread the degrade control flag (omitExtensionKeys strips internal
|
|
47
|
+
// keys) so buildChatMessages can honor it; the wire-request omit drops it.
|
|
48
|
+
...(extensions?.degradeMissingReasoningReplay === true ? { degradeMissingReasoningReplay: true } : {}),
|
|
46
49
|
reasoning_effort: reasoningEffort,
|
|
47
50
|
extra_body: { thinking: { type: thinking } },
|
|
48
51
|
};
|
|
@@ -70,6 +70,9 @@ export class MiniMaxOpenAIProvider extends OpenAIChatProvider {
|
|
|
70
70
|
return {
|
|
71
71
|
...omitExtensionKeys(extensions, ["reasoning_split", "exposeReasoning"]),
|
|
72
72
|
__deepstrikeThinkingEnabled: reasoningSplit,
|
|
73
|
+
// Re-thread the degrade control flag (omitExtensionKeys strips internal
|
|
74
|
+
// keys) so buildChatMessages can honor it; the wire-request omit drops it.
|
|
75
|
+
...(extensions?.degradeMissingReasoningReplay === true ? { degradeMissingReasoningReplay: true } : {}),
|
|
73
76
|
reasoning_split: reasoningSplit,
|
|
74
77
|
};
|
|
75
78
|
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type OpenAI from "openai";
|
|
2
2
|
import type { Message, ProviderDescriptor, RenderedContext, ToolSchema } from "../types.js";
|
|
3
|
+
import { type ReplayabilityAssessment } from "./replay-validator.js";
|
|
3
4
|
export interface OpenAIChatBuildMessageOptions {
|
|
4
5
|
descriptor?: ProviderDescriptor;
|
|
5
6
|
requireNonEmptyReasoningForToolCalls?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Degrade (rather than throw) when a reasoning-requiring tool-call turn has
|
|
9
|
+
* no stored reasoning replay: a placeholder reasoning is injected so the
|
|
10
|
+
* request still goes out in degraded form.
|
|
11
|
+
*/
|
|
12
|
+
degradeMissingReasoning?: boolean;
|
|
6
13
|
}
|
|
7
14
|
export declare class OpenAIChatAdapter {
|
|
8
15
|
private replayFields;
|
|
@@ -15,6 +22,11 @@ export declare class OpenAIChatAdapter {
|
|
|
15
22
|
};
|
|
16
23
|
}[];
|
|
17
24
|
buildMessages(context: RenderedContext, options?: OpenAIChatBuildMessageOptions): OpenAI.ChatCompletionMessageParam[];
|
|
25
|
+
/**
|
|
26
|
+
* Throw-free pre-flight check: which assistant tool-call turns in `context`
|
|
27
|
+
* lack the non-empty reasoning replay a reasoning-requiring provider needs.
|
|
28
|
+
*/
|
|
29
|
+
assessReasoning(context: RenderedContext): ReplayabilityAssessment;
|
|
18
30
|
normalizeToolCalls(toolCalls?: OpenAI.ChatCompletionMessageToolCall[]): Array<{
|
|
19
31
|
id: string;
|
|
20
32
|
name: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { assistantReplayKey } from "../runtime/provider-replay.js";
|
|
2
2
|
import { normalizeToolCall, toOpenAIMessageParams } from "./base.js";
|
|
3
|
-
import { validateOpenAIChatReplay } from "./replay-validator.js";
|
|
3
|
+
import { DEGRADED_REASONING_PLACEHOLDER, assessReasoningReplay, validateOpenAIChatReplay, } from "./replay-validator.js";
|
|
4
4
|
export class OpenAIChatAdapter {
|
|
5
5
|
replayFields = new Map();
|
|
6
6
|
buildTools(tools) {
|
|
@@ -13,8 +13,10 @@ export class OpenAIChatAdapter {
|
|
|
13
13
|
validateOpenAIChatReplay(context, {
|
|
14
14
|
descriptor: options.descriptor,
|
|
15
15
|
requireNonEmptyReasoningForToolCalls: options.requireNonEmptyReasoningForToolCalls,
|
|
16
|
+
degradeMissingReasoning: options.degradeMissingReasoning,
|
|
16
17
|
replayForAssistant: message => this.replayFields.get(assistantReplayKey(message)),
|
|
17
18
|
});
|
|
19
|
+
const degradeReasoning = Boolean(options.requireNonEmptyReasoningForToolCalls && options.degradeMissingReasoning);
|
|
18
20
|
// toOpenAIMessageParams prepends systemText as messages[0], then turns.
|
|
19
21
|
const serialized = toOpenAIMessageParams(context);
|
|
20
22
|
// Cursor starts at 1 to skip the system message injected by toOpenAIMessageParams.
|
|
@@ -26,7 +28,13 @@ export class OpenAIChatAdapter {
|
|
|
26
28
|
}
|
|
27
29
|
if (source.role === "assistant") {
|
|
28
30
|
const replay = this.replayFields.get(assistantReplayKey(source));
|
|
29
|
-
|
|
31
|
+
let wireReplay = openAIChatWireReplayFields(replay);
|
|
32
|
+
if (!wireReplay && degradeReasoning && source.toolCalls?.length) {
|
|
33
|
+
// Reasoning-requiring provider, no stored reasoning for this tool-call
|
|
34
|
+
// turn, caller opted into degradation: inject a placeholder so the
|
|
35
|
+
// wire message stays well-formed instead of failing the whole request.
|
|
36
|
+
wireReplay = { reasoning_content: DEGRADED_REASONING_PLACEHOLDER };
|
|
37
|
+
}
|
|
30
38
|
if (wireReplay)
|
|
31
39
|
serialized[cursor] = { ...serialized[cursor], ...wireReplay };
|
|
32
40
|
}
|
|
@@ -34,6 +42,15 @@ export class OpenAIChatAdapter {
|
|
|
34
42
|
}
|
|
35
43
|
return serialized;
|
|
36
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Throw-free pre-flight check: which assistant tool-call turns in `context`
|
|
47
|
+
* lack the non-empty reasoning replay a reasoning-requiring provider needs.
|
|
48
|
+
*/
|
|
49
|
+
assessReasoning(context) {
|
|
50
|
+
return assessReasoningReplay(context.turns, {
|
|
51
|
+
replayForAssistant: message => this.replayFields.get(assistantReplayKey(message)),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
37
54
|
normalizeToolCalls(toolCalls = []) {
|
|
38
55
|
return toolCalls
|
|
39
56
|
.filter((tc) => tc.type === "function")
|
|
@@ -2,6 +2,7 @@ import OpenAI from "openai";
|
|
|
2
2
|
import type { Message, ProviderDescriptor, ProviderReplay, RenderedContext, ToolSchema, StreamEvent, LLMProvider, RuntimePolicy } from "../types.js";
|
|
3
3
|
import { CircuitBreaker } from "./base.js";
|
|
4
4
|
import { OpenAIChatAdapter } from "./openai-chat.js";
|
|
5
|
+
import type { ReplayabilityAssessment } from "./replay-validator.js";
|
|
5
6
|
export declare class OpenAIChatProvider implements LLMProvider {
|
|
6
7
|
protected readonly model: string;
|
|
7
8
|
protected client: OpenAI;
|
|
@@ -16,7 +17,16 @@ export declare class OpenAIChatProvider implements LLMProvider {
|
|
|
16
17
|
runtimePolicy(): RuntimePolicy;
|
|
17
18
|
descriptor(): ProviderDescriptor;
|
|
18
19
|
protected requireNonEmptyReasoningReplayForToolTurns(_extensions?: Record<string, unknown>): boolean;
|
|
20
|
+
protected degradeMissingReasoningReplay(extensions?: Record<string, unknown>): boolean;
|
|
19
21
|
protected buildChatMessages(context: RenderedContext, extensions?: Record<string, unknown>): OpenAI.Chat.Completions.ChatCompletionMessageParam[];
|
|
22
|
+
/**
|
|
23
|
+
* Pre-flight query: would this history validate against this provider with the
|
|
24
|
+
* given extensions, without sending the request? Lets an embedder route around
|
|
25
|
+
* a reasoning-replay failure (keep thinking on, disable it, or skip this
|
|
26
|
+
* candidate) before issuing the request. `ok: true` when this provider does
|
|
27
|
+
* not require reasoning replay for the current extensions.
|
|
28
|
+
*/
|
|
29
|
+
assessReplayability(context: RenderedContext, extensions?: Record<string, unknown>): ReplayabilityAssessment;
|
|
20
30
|
peekProviderReplay(message: Pick<Message, "content" | "toolCalls">): ProviderReplay | undefined;
|
|
21
31
|
seedProviderReplay(message: Pick<Message, "content" | "toolCalls">, replay: ProviderReplay): void;
|
|
22
32
|
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
package/dist/providers/openai.js
CHANGED
|
@@ -60,12 +60,29 @@ export class OpenAIChatProvider {
|
|
|
60
60
|
requireNonEmptyReasoningReplayForToolTurns(_extensions) {
|
|
61
61
|
return false;
|
|
62
62
|
}
|
|
63
|
+
degradeMissingReasoningReplay(extensions) {
|
|
64
|
+
return extensions?.degradeMissingReasoningReplay === true;
|
|
65
|
+
}
|
|
63
66
|
buildChatMessages(context, extensions) {
|
|
64
67
|
return this.chat.buildMessages(context, {
|
|
65
68
|
descriptor: this.descriptor(),
|
|
66
69
|
requireNonEmptyReasoningForToolCalls: this.requireNonEmptyReasoningReplayForToolTurns(extensions),
|
|
70
|
+
degradeMissingReasoning: this.degradeMissingReasoningReplay(extensions),
|
|
67
71
|
});
|
|
68
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Pre-flight query: would this history validate against this provider with the
|
|
75
|
+
* given extensions, without sending the request? Lets an embedder route around
|
|
76
|
+
* a reasoning-replay failure (keep thinking on, disable it, or skip this
|
|
77
|
+
* candidate) before issuing the request. `ok: true` when this provider does
|
|
78
|
+
* not require reasoning replay for the current extensions.
|
|
79
|
+
*/
|
|
80
|
+
assessReplayability(context, extensions) {
|
|
81
|
+
if (!this.requireNonEmptyReasoningReplayForToolTurns(extensions)) {
|
|
82
|
+
return { ok: true, offendingCallIds: [] };
|
|
83
|
+
}
|
|
84
|
+
return this.chat.assessReasoning(context);
|
|
85
|
+
}
|
|
69
86
|
peekProviderReplay(message) {
|
|
70
87
|
const fields = this.chat.peekReplayFields(message);
|
|
71
88
|
if (!fields || !("reasoning_content" in fields || "reasoning_details" in fields))
|
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
import type { Message, ProviderDescriptor, ProviderReplay, RenderedContext } from "../types.js";
|
|
1
|
+
import type { Message, ProviderDescriptor, ProviderReplay, RenderedContext, ReplayabilityAssessment } from "../types.js";
|
|
2
|
+
export type { ReplayabilityAssessment };
|
|
2
3
|
export declare class ProviderReplayValidationError extends Error {
|
|
3
4
|
constructor(message: string);
|
|
4
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Placeholder reasoning injected for an assistant tool-call turn that has no
|
|
8
|
+
* stored reasoning replay when the caller opted into graceful degradation
|
|
9
|
+
* (`degradeMissingReasoning`). It keeps the wire message well-formed for a
|
|
10
|
+
* thinking-on provider without fabricating substantive reasoning.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEGRADED_REASONING_PLACEHOLDER = "[reasoning unavailable on replay]";
|
|
5
13
|
export interface OpenAIChatReplayValidationOptions {
|
|
6
14
|
descriptor?: ProviderDescriptor;
|
|
7
15
|
requireNonEmptyReasoningForToolCalls?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* When true, an assistant tool-call turn that lacks reasoning replay is
|
|
18
|
+
* degraded (serialized without/with a placeholder reasoning) instead of
|
|
19
|
+
* throwing. Lets a recovery/fallback request succeed in degraded form
|
|
20
|
+
* rather than fail outright.
|
|
21
|
+
*/
|
|
22
|
+
degradeMissingReasoning?: boolean;
|
|
8
23
|
replayForAssistant?: (message: Pick<Message, "content" | "toolCalls">) => ProviderReplay | Record<string, unknown> | undefined;
|
|
9
24
|
}
|
|
10
25
|
export declare function validateOpenAIChatReplay(context: RenderedContext, options?: OpenAIChatReplayValidationOptions): void;
|
|
26
|
+
/**
|
|
27
|
+
* Pure, throw-free assessment: which assistant tool-call turns lack the
|
|
28
|
+
* non-empty reasoning replay a reasoning-requiring provider needs. Lets an
|
|
29
|
+
* embedder decide per-candidate whether to keep thinking on, disable it, or
|
|
30
|
+
* skip the candidate — before sending.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assessReasoningReplay(turns: Message[], options: Pick<OpenAIChatReplayValidationOptions, "replayForAssistant">): ReplayabilityAssessment;
|
|
@@ -4,12 +4,48 @@ export class ProviderReplayValidationError extends Error {
|
|
|
4
4
|
this.name = "ProviderReplayValidationError";
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Placeholder reasoning injected for an assistant tool-call turn that has no
|
|
9
|
+
* stored reasoning replay when the caller opted into graceful degradation
|
|
10
|
+
* (`degradeMissingReasoning`). It keeps the wire message well-formed for a
|
|
11
|
+
* thinking-on provider without fabricating substantive reasoning.
|
|
12
|
+
*/
|
|
13
|
+
export const DEGRADED_REASONING_PLACEHOLDER = "[reasoning unavailable on replay]";
|
|
7
14
|
export function validateOpenAIChatReplay(context, options = {}) {
|
|
8
15
|
validateStrictToolResultPairing(context.turns);
|
|
9
|
-
if (options.requireNonEmptyReasoningForToolCalls) {
|
|
10
|
-
|
|
16
|
+
if (options.requireNonEmptyReasoningForToolCalls && !options.degradeMissingReasoning) {
|
|
17
|
+
const assessment = assessReasoningReplay(context.turns, options);
|
|
18
|
+
if (!assessment.ok) {
|
|
19
|
+
throw reasoningReplayError(assessment.offendingCallIds, options.descriptor);
|
|
20
|
+
}
|
|
11
21
|
}
|
|
12
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Pure, throw-free assessment: which assistant tool-call turns lack the
|
|
25
|
+
* non-empty reasoning replay a reasoning-requiring provider needs. Lets an
|
|
26
|
+
* embedder decide per-candidate whether to keep thinking on, disable it, or
|
|
27
|
+
* skip the candidate — before sending.
|
|
28
|
+
*/
|
|
29
|
+
export function assessReasoningReplay(turns, options) {
|
|
30
|
+
const offendingCallIds = [];
|
|
31
|
+
for (const message of turns) {
|
|
32
|
+
if (message.role !== "assistant" || !message.toolCalls?.length)
|
|
33
|
+
continue;
|
|
34
|
+
const replay = options.replayForAssistant?.(message);
|
|
35
|
+
const reasoning = typeof replay?.reasoning_content === "string" ? replay.reasoning_content.trim() : "";
|
|
36
|
+
if (!reasoning) {
|
|
37
|
+
for (const tc of message.toolCalls)
|
|
38
|
+
offendingCallIds.push(tc.id);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { ok: offendingCallIds.length === 0, offendingCallIds };
|
|
42
|
+
}
|
|
43
|
+
function reasoningReplayError(callIds, descriptor) {
|
|
44
|
+
const provider = descriptor ? `${descriptor.provider}/${descriptor.model}` : "provider";
|
|
45
|
+
return new ProviderReplayValidationError(`${provider} replay requires non-empty reasoning_content for assistant tool call turn ${callIds.join(", ")}. ` +
|
|
46
|
+
"Disable thinking, rebuild this history with provider replay, switch to a provider that can replay this turn, " +
|
|
47
|
+
"or pass extensions.degradeMissingReasoningReplay to send a degraded turn.");
|
|
48
|
+
}
|
|
13
49
|
function toolResultParts(message) {
|
|
14
50
|
return (message.contentParts ?? [])
|
|
15
51
|
.filter((part) => part.type === "tool_result");
|
|
@@ -17,14 +53,25 @@ function toolResultParts(message) {
|
|
|
17
53
|
function validateStrictToolResultPairing(turns) {
|
|
18
54
|
let pendingIds;
|
|
19
55
|
let completedIds = new Set();
|
|
56
|
+
const assertAllCompleted = () => {
|
|
57
|
+
if (!pendingIds)
|
|
58
|
+
return;
|
|
59
|
+
const missing = [...pendingIds].filter(id => !completedIds.has(id));
|
|
60
|
+
if (missing.length) {
|
|
61
|
+
throw new ProviderReplayValidationError(`OpenAI-compatible replay has assistant tool_calls with no tool result for ${missing.join(", ")}: ` +
|
|
62
|
+
"every tool_call must be answered by a tool message before the next assistant or user turn.");
|
|
63
|
+
}
|
|
64
|
+
};
|
|
20
65
|
for (const message of turns) {
|
|
21
66
|
if (message.role === "assistant") {
|
|
67
|
+
assertAllCompleted();
|
|
22
68
|
const toolCalls = message.toolCalls ?? [];
|
|
23
69
|
pendingIds = toolCalls.length ? new Set(toolCalls.map(tc => tc.id)) : undefined;
|
|
24
70
|
completedIds = new Set();
|
|
25
71
|
continue;
|
|
26
72
|
}
|
|
27
73
|
if (message.role !== "tool") {
|
|
74
|
+
assertAllCompleted();
|
|
28
75
|
pendingIds = undefined;
|
|
29
76
|
completedIds = new Set();
|
|
30
77
|
continue;
|
|
@@ -39,19 +86,5 @@ function validateStrictToolResultPairing(turns) {
|
|
|
39
86
|
completedIds.add(part.callId);
|
|
40
87
|
}
|
|
41
88
|
}
|
|
42
|
-
|
|
43
|
-
function validateReasoningReplayForAssistantToolCalls(turns, options) {
|
|
44
|
-
const descriptor = options.descriptor;
|
|
45
|
-
for (const message of turns) {
|
|
46
|
-
if (message.role !== "assistant" || !message.toolCalls?.length)
|
|
47
|
-
continue;
|
|
48
|
-
const replay = options.replayForAssistant?.(message);
|
|
49
|
-
const reasoning = typeof replay?.reasoning_content === "string" ? replay.reasoning_content.trim() : "";
|
|
50
|
-
if (!reasoning) {
|
|
51
|
-
const callIds = message.toolCalls.map(tc => tc.id).join(", ");
|
|
52
|
-
const provider = descriptor ? `${descriptor.provider}/${descriptor.model}` : "provider";
|
|
53
|
-
throw new ProviderReplayValidationError(`${provider} replay requires non-empty reasoning_content for assistant tool call turn ${callIds}. ` +
|
|
54
|
-
"Disable thinking, rebuild this history with provider replay, or switch to a provider that can replay this turn.");
|
|
55
|
-
}
|
|
56
|
-
}
|
|
89
|
+
assertAllCompleted();
|
|
57
90
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LLMProvider, Message, ProviderDescriptor, ProviderReplay, ToolCall } from "../types.js";
|
|
1
|
+
import type { LLMProvider, Message, ProviderDescriptor, ProviderReplay, RenderedContext, ReplayabilityAssessment, ToolCall } from "../types.js";
|
|
2
2
|
import type { SessionEvent } from "./session-log.js";
|
|
3
3
|
export declare function assistantReplayKey(message: Pick<Message, "content" | "toolCalls">): string;
|
|
4
4
|
/**
|
|
@@ -12,3 +12,11 @@ export declare function seedProviderReplayFromEvents(provider: LLMProvider, even
|
|
|
12
12
|
event: SessionEvent;
|
|
13
13
|
}>): void;
|
|
14
14
|
export declare function peekProviderReplay(provider: LLMProvider, content: string, toolCalls: ToolCall[]): ProviderReplay | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Pre-flight query for fallback routing: would `context` validate against
|
|
17
|
+
* `provider` (with `extensions`) before the request is sent? Seed any persisted
|
|
18
|
+
* replay (via `seedProviderReplayFromEvents`) first so the assessment reflects
|
|
19
|
+
* what the provider can actually replay. Providers that do not implement
|
|
20
|
+
* `assessReplayability` (no reasoning-replay requirement) are reported as ok.
|
|
21
|
+
*/
|
|
22
|
+
export declare function assessProviderReplayability(provider: LLMProvider, context: RenderedContext, extensions?: Record<string, unknown>): ReplayabilityAssessment;
|
|
@@ -83,3 +83,13 @@ export function seedProviderReplayFromEvents(provider, events) {
|
|
|
83
83
|
export function peekProviderReplay(provider, content, toolCalls) {
|
|
84
84
|
return provider.peekProviderReplay?.({ content, toolCalls });
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Pre-flight query for fallback routing: would `context` validate against
|
|
88
|
+
* `provider` (with `extensions`) before the request is sent? Seed any persisted
|
|
89
|
+
* replay (via `seedProviderReplayFromEvents`) first so the assessment reflects
|
|
90
|
+
* what the provider can actually replay. Providers that do not implement
|
|
91
|
+
* `assessReplayability` (no reasoning-replay requirement) are reported as ok.
|
|
92
|
+
*/
|
|
93
|
+
export function assessProviderReplayability(provider, context, extensions) {
|
|
94
|
+
return provider.assessReplayability?.(context, extensions) ?? { ok: true, offendingCallIds: [] };
|
|
95
|
+
}
|
package/dist/runtime/runner.js
CHANGED
|
@@ -276,15 +276,17 @@ export class RuntimeRunner {
|
|
|
276
276
|
// W0-ABI resume: skip nodes already completed before an interruption.
|
|
277
277
|
...(opts?.resumedCompleted?.length ? { resumed_completed: opts.resumedCompleted } : {}),
|
|
278
278
|
});
|
|
279
|
+
const collectNodes = (obs) => obs.find(o => o.kind === "workflow_batch_spawned")
|
|
280
|
+
?.nodes ?? [];
|
|
281
|
+
const findDone = (obs) => obs.find(o => o.kind === "workflow_completed");
|
|
282
|
+
let done = findDone(observations);
|
|
283
|
+
if (done)
|
|
284
|
+
return { completed: done.completed ?? [], failed: done.failed ?? [] };
|
|
285
|
+
let nodes = collectNodes(observations);
|
|
279
286
|
for (;;) {
|
|
280
|
-
const done = observations.find(o => o.kind === "workflow_completed");
|
|
281
|
-
if (done)
|
|
282
|
-
return { completed: done.completed ?? [], failed: done.failed ?? [] };
|
|
283
|
-
const batch = observations.find(o => o.kind === "workflow_batch_spawned");
|
|
284
|
-
const nodes = batch?.nodes ?? [];
|
|
285
287
|
if (nodes.length === 0)
|
|
286
288
|
return { completed: [], failed: [] }; // nothing to run (e.g. all gated)
|
|
287
|
-
// Run the
|
|
289
|
+
// Run the currently-runnable nodes in parallel — each is independent within a round.
|
|
288
290
|
const results = await Promise.all(nodes.map(node => orchestrator.run({
|
|
289
291
|
parentOpts: this.opts,
|
|
290
292
|
parentSessionId,
|
|
@@ -293,13 +295,23 @@ export class RuntimeRunner {
|
|
|
293
295
|
sessionLog: this.opts.sessionLog,
|
|
294
296
|
...(this.opts.subAgentHarness ? { harness: this.opts.subAgentHarness } : {}),
|
|
295
297
|
})));
|
|
296
|
-
// Feed completions back
|
|
297
|
-
|
|
298
|
+
// Feed completions back one at a time. The kernel's run-queue executor may spawn a node's
|
|
299
|
+
// dependents the moment *that* node completes (per-node unblock), so each feed can emit its
|
|
300
|
+
// own `workflow_batch_spawned`; ACCUMULATE them across the round rather than keeping only the
|
|
301
|
+
// last feed's (the old code overwrote `observations` per feed and dropped nodes unblocked by
|
|
302
|
+
// earlier completions — stalling uneven DAGs). Completion and new spawns are mutually
|
|
303
|
+
// exclusive per feed, so a `workflow_completed` only arrives once nothing remains to run.
|
|
304
|
+
const nextNodes = [];
|
|
305
|
+
done = undefined;
|
|
298
306
|
for (const result of results) {
|
|
299
|
-
|
|
307
|
+
const obs = kernelApply(runtime, this.pendingObservations, {
|
|
300
308
|
kind: "sub_agent_completed",
|
|
301
309
|
result: subAgentResultToKernel(result),
|
|
302
310
|
});
|
|
311
|
+
nextNodes.push(...collectNodes(obs));
|
|
312
|
+
const d = findDone(obs);
|
|
313
|
+
if (d)
|
|
314
|
+
done = d;
|
|
303
315
|
// Persist node completion for resume recovery.
|
|
304
316
|
await this.opts.sessionLog.append(parentSessionId, buildWorkflowNodeCompletedEvent({
|
|
305
317
|
turn: runtime.turn(),
|
|
@@ -307,6 +319,10 @@ export class RuntimeRunner {
|
|
|
307
319
|
termination: result.result.termination,
|
|
308
320
|
}));
|
|
309
321
|
}
|
|
322
|
+
if (done && nextNodes.length === 0) {
|
|
323
|
+
return { completed: done.completed ?? [], failed: done.failed ?? [] };
|
|
324
|
+
}
|
|
325
|
+
nodes = nextNodes;
|
|
310
326
|
}
|
|
311
327
|
}
|
|
312
328
|
/**
|
package/dist/types.d.ts
CHANGED
|
@@ -213,6 +213,13 @@ export interface ProviderReplay {
|
|
|
213
213
|
native_message?: unknown;
|
|
214
214
|
tool_calls?: unknown[];
|
|
215
215
|
}
|
|
216
|
+
/** Result of a pre-flight reasoning-replay assessment for a target provider. */
|
|
217
|
+
export interface ReplayabilityAssessment {
|
|
218
|
+
/** True when every reasoning-requiring tool-call turn has replay available. */
|
|
219
|
+
ok: boolean;
|
|
220
|
+
/** Tool-call ids whose turn lacks the required non-empty reasoning replay. */
|
|
221
|
+
offendingCallIds: string[];
|
|
222
|
+
}
|
|
216
223
|
/** Structured render output produced by the kernel for each LLM call. */
|
|
217
224
|
export interface RenderedContext {
|
|
218
225
|
/** Identity + Knowledge combined — for providers with a single system slot (OpenAI). */
|
|
@@ -247,6 +254,14 @@ export interface LLMProvider {
|
|
|
247
254
|
peekProviderReplay?(message: Pick<Message, "content" | "toolCalls">): ProviderReplay | undefined;
|
|
248
255
|
/** Restore provider-native replay fields when rebuilding history from SessionLog. */
|
|
249
256
|
seedProviderReplay?(message: Pick<Message, "content" | "toolCalls">, replay: ProviderReplay): void;
|
|
257
|
+
/**
|
|
258
|
+
* Pre-flight query: would this history validate against this provider with the
|
|
259
|
+
* given extensions, without sending the request? Returns the tool-call ids
|
|
260
|
+
* whose turn lacks the reasoning replay this provider requires, so an embedder
|
|
261
|
+
* can route around the failure (keep thinking on, disable it, or skip this
|
|
262
|
+
* candidate) before issuing the request. Seed any persisted replay first.
|
|
263
|
+
*/
|
|
264
|
+
assessReplayability?(context: RenderedContext, extensions?: Record<string, unknown>): ReplayabilityAssessment;
|
|
250
265
|
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
251
266
|
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
|
|
252
267
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstrike/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "DeepStrike Node.js SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@anthropic-ai/sdk": "^0.99.0",
|
|
23
|
-
"@deepstrike/core": "0.2.
|
|
23
|
+
"@deepstrike/core": "0.2.9",
|
|
24
24
|
"@google/generative-ai": "^0.24.1",
|
|
25
25
|
"openai": "^5.23.2"
|
|
26
26
|
},
|