@martinloop/mcp 0.3.7 → 0.3.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/README.md +196 -137
- package/dist/resources.js +148 -148
- package/dist/server.js +0 -0
- package/dist/tools/tool-support.js +11 -1
- package/dist/vendor/adapters/claude-cli.d.ts +2 -2
- package/dist/vendor/adapters/claude-cli.js +127 -51
- package/dist/vendor/adapters/codex-launcher.js +10 -1
- package/dist/vendor/adapters/openai-compatible.js +6 -6
- package/package.json +86 -76
- package/server.json +21 -21
- package/dist/vendor/adapters/adapters/src/claude-cli.d.ts +0 -146
- package/dist/vendor/adapters/adapters/src/claude-cli.js +0 -1100
- package/dist/vendor/adapters/adapters/src/cli-bridge.d.ts +0 -103
- package/dist/vendor/adapters/adapters/src/cli-bridge.js +0 -569
- package/dist/vendor/adapters/adapters/src/codex-launcher.d.ts +0 -78
- package/dist/vendor/adapters/adapters/src/codex-launcher.js +0 -663
- package/dist/vendor/adapters/adapters/src/direct-provider.d.ts +0 -10
- package/dist/vendor/adapters/adapters/src/direct-provider.js +0 -40
- package/dist/vendor/adapters/adapters/src/index.d.ts +0 -8
- package/dist/vendor/adapters/adapters/src/index.js +0 -8
- package/dist/vendor/adapters/adapters/src/openai-compatible.d.ts +0 -62
- package/dist/vendor/adapters/adapters/src/openai-compatible.js +0 -306
- package/dist/vendor/adapters/adapters/src/runtime-support.d.ts +0 -17
- package/dist/vendor/adapters/adapters/src/runtime-support.js +0 -59
- package/dist/vendor/adapters/adapters/src/stub-agent-cli.d.ts +0 -8
- package/dist/vendor/adapters/adapters/src/stub-agent-cli.js +0 -40
- package/dist/vendor/adapters/adapters/src/stub-direct-provider.d.ts +0 -8
- package/dist/vendor/adapters/adapters/src/stub-direct-provider.js +0 -12
- package/dist/vendor/adapters/adapters/src/verifier-only.d.ts +0 -9
- package/dist/vendor/adapters/adapters/src/verifier-only.js +0 -63
- package/dist/vendor/adapters/contracts/src/context-graph.d.ts +0 -48
- package/dist/vendor/adapters/contracts/src/context-graph.js +0 -26
- package/dist/vendor/adapters/contracts/src/execution-policy.d.ts +0 -78
- package/dist/vendor/adapters/contracts/src/execution-policy.js +0 -43
- package/dist/vendor/adapters/contracts/src/governance.d.ts +0 -24
- package/dist/vendor/adapters/contracts/src/governance.js +0 -11
- package/dist/vendor/adapters/contracts/src/identity.d.ts +0 -29
- package/dist/vendor/adapters/contracts/src/identity.js +0 -30
- package/dist/vendor/adapters/contracts/src/index.d.ts +0 -412
- package/dist/vendor/adapters/contracts/src/index.js +0 -225
- package/dist/vendor/adapters/contracts/src/operator.d.ts +0 -19
- package/dist/vendor/adapters/contracts/src/operator.js +0 -11
- package/dist/vendor/adapters/contracts/src/trajectory.d.ts +0 -26
- package/dist/vendor/adapters/contracts/src/trajectory.js +0 -27
- package/dist/vendor/adapters/core/src/compiler.d.ts +0 -52
- package/dist/vendor/adapters/core/src/compiler.js +0 -52
- package/dist/vendor/adapters/core/src/context-graph.d.ts +0 -3
- package/dist/vendor/adapters/core/src/context-graph.js +0 -371
- package/dist/vendor/adapters/core/src/context-integrity.d.ts +0 -26
- package/dist/vendor/adapters/core/src/context-integrity.js +0 -80
- package/dist/vendor/adapters/core/src/grounding.d.ts +0 -38
- package/dist/vendor/adapters/core/src/grounding.js +0 -273
- package/dist/vendor/adapters/core/src/identity.d.ts +0 -20
- package/dist/vendor/adapters/core/src/identity.js +0 -86
- package/dist/vendor/adapters/core/src/index.d.ts +0 -186
- package/dist/vendor/adapters/core/src/index.js +0 -1495
- package/dist/vendor/adapters/core/src/leash.d.ts +0 -48
- package/dist/vendor/adapters/core/src/leash.js +0 -497
- package/dist/vendor/adapters/core/src/persistence/compiler.d.ts +0 -18
- package/dist/vendor/adapters/core/src/persistence/compiler.js +0 -34
- package/dist/vendor/adapters/core/src/persistence/index.d.ts +0 -10
- package/dist/vendor/adapters/core/src/persistence/index.js +0 -5
- package/dist/vendor/adapters/core/src/persistence/integrity.d.ts +0 -39
- package/dist/vendor/adapters/core/src/persistence/integrity.js +0 -252
- package/dist/vendor/adapters/core/src/persistence/ledger.d.ts +0 -23
- package/dist/vendor/adapters/core/src/persistence/ledger.js +0 -9
- package/dist/vendor/adapters/core/src/persistence/memory-store.d.ts +0 -63
- package/dist/vendor/adapters/core/src/persistence/memory-store.js +0 -115
- package/dist/vendor/adapters/core/src/persistence/runs-reader.d.ts +0 -66
- package/dist/vendor/adapters/core/src/persistence/runs-reader.js +0 -140
- package/dist/vendor/adapters/core/src/persistence/store.d.ts +0 -89
- package/dist/vendor/adapters/core/src/persistence/store.js +0 -125
- package/dist/vendor/adapters/core/src/persistence/trace-store.d.ts +0 -85
- package/dist/vendor/adapters/core/src/persistence/trace-store.js +0 -125
- package/dist/vendor/adapters/core/src/policy-compiler.d.ts +0 -2
- package/dist/vendor/adapters/core/src/policy-compiler.js +0 -195
- package/dist/vendor/adapters/core/src/policy.d.ts +0 -156
- package/dist/vendor/adapters/core/src/policy.js +0 -704
- package/dist/vendor/adapters/core/src/repo-analyzer.d.ts +0 -53
- package/dist/vendor/adapters/core/src/repo-analyzer.js +0 -263
- package/dist/vendor/adapters/core/src/rollback.d.ts +0 -15
- package/dist/vendor/adapters/core/src/rollback.js +0 -253
- package/dist/vendor/adapters/core/src/routing.d.ts +0 -82
- package/dist/vendor/adapters/core/src/routing.js +0 -336
- package/dist/vendor/adapters/core/src/test-integrity.d.ts +0 -64
- package/dist/vendor/adapters/core/src/test-integrity.js +0 -197
- package/dist/vendor/adapters/core/src/trajectory.d.ts +0 -18
- package/dist/vendor/adapters/core/src/trajectory.js +0 -295
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { createAdapterCapabilities } from "./runtime-support.js";
|
|
2
|
-
export function createDirectProviderAdapter(options) {
|
|
3
|
-
return {
|
|
4
|
-
adapterId: `direct:${options.providerId}:${options.model}`,
|
|
5
|
-
kind: "direct-provider",
|
|
6
|
-
label: options.label ?? `Direct provider (${options.providerId}/${options.model})`,
|
|
7
|
-
metadata: {
|
|
8
|
-
providerId: options.providerId,
|
|
9
|
-
model: options.model,
|
|
10
|
-
transport: options.transport ?? "http",
|
|
11
|
-
capabilities: createAdapterCapabilities({
|
|
12
|
-
usageSettlement: true,
|
|
13
|
-
structuredErrors: true,
|
|
14
|
-
...options.capabilities
|
|
15
|
-
})
|
|
16
|
-
},
|
|
17
|
-
async execute(request) {
|
|
18
|
-
if (options.responder) {
|
|
19
|
-
return await options.responder(request);
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
status: "failed",
|
|
23
|
-
summary: `Direct provider ${options.providerId}/${options.model} is not configured for live inference.`,
|
|
24
|
-
usage: {
|
|
25
|
-
actualUsd: 0,
|
|
26
|
-
tokensIn: 0,
|
|
27
|
-
tokensOut: 0,
|
|
28
|
-
provenance: "unavailable"
|
|
29
|
-
},
|
|
30
|
-
verification: {
|
|
31
|
-
passed: false,
|
|
32
|
-
summary: "No live provider request was attempted."
|
|
33
|
-
},
|
|
34
|
-
failure: {
|
|
35
|
-
message: `Direct provider ${options.providerId}/${options.model} is not configured for live inference. environment_mismatch`
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export { createDirectProviderAdapter, type DirectProviderAdapterOptions } from "./direct-provider.js";
|
|
2
|
-
export { createStubDirectProviderAdapter, type StubDirectProviderAdapterOptions } from "./stub-direct-provider.js";
|
|
3
|
-
export { createStubAgentCliAdapter, type StubAgentCliAdapterOptions } from "./stub-agent-cli.js";
|
|
4
|
-
export { createAgentCliAdapter, createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter, type AgentCliAdapterOptions, type ClaudeCliAdapterOptions, type CodexCliAdapterOptions, type GeminiCliAdapterOptions, type CliArgsBuilder } from "./claude-cli.js";
|
|
5
|
-
export { createVerifierOnlyAdapter, type VerifierOnlyAdapterOptions } from "./verifier-only.js";
|
|
6
|
-
export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig, type OpenAiCompatibleAdapterOptions } from "./openai-compatible.js";
|
|
7
|
-
export { detectCodexHostPlatform, diagnoseCodexHost, probeCodexLaunch, resolveCliCommandAvailability, type CliCommandAvailability, type CodexHostDiagnosis, type CodexHostPlatform, type CodexLaunchProbeResult } from "./codex-launcher.js";
|
|
8
|
-
export { createSpawnPlan, type SpawnLike, type SpawnPlan, type SubprocessResult, type VerificationOutcome } from "./cli-bridge.js";
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export { createDirectProviderAdapter } from "./direct-provider.js";
|
|
2
|
-
export { createStubDirectProviderAdapter } from "./stub-direct-provider.js";
|
|
3
|
-
export { createStubAgentCliAdapter } from "./stub-agent-cli.js";
|
|
4
|
-
export { createAgentCliAdapter, createClaudeCliAdapter, createCodexCliAdapter, createGeminiCliAdapter } from "./claude-cli.js";
|
|
5
|
-
export { createVerifierOnlyAdapter } from "./verifier-only.js";
|
|
6
|
-
export { createOpenAiCompatibleAdapter, resolveOpenAiCompatibleRuntimeConfig } from "./openai-compatible.js";
|
|
7
|
-
export { detectCodexHostPlatform, diagnoseCodexHost, probeCodexLaunch, resolveCliCommandAvailability } from "./codex-launcher.js";
|
|
8
|
-
export { createSpawnPlan } from "./cli-bridge.js";
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenAI-compatible adapter for MartinLoop.
|
|
3
|
-
*
|
|
4
|
-
* Routes agent execution to any endpoint that implements the OpenAI
|
|
5
|
-
* Chat Completions API (`POST /v1/chat/completions`). This covers:
|
|
6
|
-
*
|
|
7
|
-
* Hosted via OpenRouter / Together.ai / Fireworks.ai:
|
|
8
|
-
* DeepSeek-V3, DeepSeek-R1, Qwen3-235B, Mistral Large, Codestral,
|
|
9
|
-
* Kimi k2, Nemotron-70B, and hundreds more.
|
|
10
|
-
*
|
|
11
|
-
* Local via Ollama / LM Studio / llama.cpp:
|
|
12
|
-
* Llama 3.x, Mistral 7B, Phi-4, Gemma 3, any GGUF model.
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* # Defaults to OpenAI's hosted endpoint when MARTIN_OPENAI_BASE_URL is unset.
|
|
16
|
-
* MARTIN_OPENAI_API_KEY=sk-...
|
|
17
|
-
* MARTIN_OPENAI_MODEL=gpt-4.1-mini
|
|
18
|
-
* martin-loop run "fix the bug" --engine openai
|
|
19
|
-
*
|
|
20
|
-
* # Or route to a third-party / self-hosted OpenAI-compatible endpoint:
|
|
21
|
-
* MARTIN_OPENAI_BASE_URL=https://openrouter.ai/api
|
|
22
|
-
* MARTIN_OPENAI_API_KEY=sk-or-...
|
|
23
|
-
* MARTIN_OPENAI_MODEL=deepseek/deepseek-chat
|
|
24
|
-
* martin-loop run "fix the bug" --engine openai
|
|
25
|
-
*
|
|
26
|
-
* Or for Ollama:
|
|
27
|
-
* MARTIN_OPENAI_BASE_URL=http://localhost:11434
|
|
28
|
-
* MARTIN_OPENAI_MODEL=llama3.3
|
|
29
|
-
* martin-loop run "fix the bug" --engine openai
|
|
30
|
-
*/
|
|
31
|
-
import type { MartinAdapter } from "../../../core/index.js";
|
|
32
|
-
export interface OpenAiCompatibleAdapterOptions {
|
|
33
|
-
/** Base URL of the OpenAI-compatible API. No trailing slash. */
|
|
34
|
-
baseUrl?: string;
|
|
35
|
-
/** API key. Empty string for local (Ollama/LM Studio) endpoints. */
|
|
36
|
-
apiKey?: string;
|
|
37
|
-
/** Model identifier passed as-is to the API (e.g. "deepseek/deepseek-chat"). */
|
|
38
|
-
model?: string;
|
|
39
|
-
/**
|
|
40
|
-
* System prompt prepended before the MartinLoop task prompt.
|
|
41
|
-
* Default instructs the model to act as a focused coding assistant.
|
|
42
|
-
*/
|
|
43
|
-
systemPrompt?: string;
|
|
44
|
-
/** Request timeout in milliseconds. Default: 300_000 (5 min). */
|
|
45
|
-
timeoutMs?: number;
|
|
46
|
-
/** Verifier timeout in milliseconds. Default: 120_000. */
|
|
47
|
-
verifyTimeoutMs?: number;
|
|
48
|
-
/** Working directory for git artifact collection and verification. */
|
|
49
|
-
workingDirectory?: string;
|
|
50
|
-
/** Optional fetch override for testing. */
|
|
51
|
-
fetchImpl?: typeof fetch;
|
|
52
|
-
}
|
|
53
|
-
export declare const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
|
|
54
|
-
export declare const DEFAULT_OPENAI_MODEL = "gpt-4.1-mini";
|
|
55
|
-
export declare function resolveOpenAiCompatibleRuntimeConfig(env?: NodeJS.ProcessEnv): {
|
|
56
|
-
baseUrl: string;
|
|
57
|
-
model: string;
|
|
58
|
-
apiKey: string;
|
|
59
|
-
apiKeyConfigured: boolean;
|
|
60
|
-
authPosture: "api_key" | "anonymous_or_local";
|
|
61
|
-
};
|
|
62
|
-
export declare function createOpenAiCompatibleAdapter(options: OpenAiCompatibleAdapterOptions): MartinAdapter;
|
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenAI-compatible adapter for MartinLoop.
|
|
3
|
-
*
|
|
4
|
-
* Routes agent execution to any endpoint that implements the OpenAI
|
|
5
|
-
* Chat Completions API (`POST /v1/chat/completions`). This covers:
|
|
6
|
-
*
|
|
7
|
-
* Hosted via OpenRouter / Together.ai / Fireworks.ai:
|
|
8
|
-
* DeepSeek-V3, DeepSeek-R1, Qwen3-235B, Mistral Large, Codestral,
|
|
9
|
-
* Kimi k2, Nemotron-70B, and hundreds more.
|
|
10
|
-
*
|
|
11
|
-
* Local via Ollama / LM Studio / llama.cpp:
|
|
12
|
-
* Llama 3.x, Mistral 7B, Phi-4, Gemma 3, any GGUF model.
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* # Defaults to OpenAI's hosted endpoint when MARTIN_OPENAI_BASE_URL is unset.
|
|
16
|
-
* MARTIN_OPENAI_API_KEY=sk-...
|
|
17
|
-
* MARTIN_OPENAI_MODEL=gpt-4.1-mini
|
|
18
|
-
* martin-loop run "fix the bug" --engine openai
|
|
19
|
-
*
|
|
20
|
-
* # Or route to a third-party / self-hosted OpenAI-compatible endpoint:
|
|
21
|
-
* MARTIN_OPENAI_BASE_URL=https://openrouter.ai/api
|
|
22
|
-
* MARTIN_OPENAI_API_KEY=sk-or-...
|
|
23
|
-
* MARTIN_OPENAI_MODEL=deepseek/deepseek-chat
|
|
24
|
-
* martin-loop run "fix the bug" --engine openai
|
|
25
|
-
*
|
|
26
|
-
* Or for Ollama:
|
|
27
|
-
* MARTIN_OPENAI_BASE_URL=http://localhost:11434
|
|
28
|
-
* MARTIN_OPENAI_MODEL=llama3.3
|
|
29
|
-
* martin-loop run "fix the bug" --engine openai
|
|
30
|
-
*/
|
|
31
|
-
import { readGitChangedFiles, runVerification } from "./cli-bridge.js";
|
|
32
|
-
import { createAdapterCapabilities, normalizeUsage } from "./runtime-support.js";
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
// OpenRouter/OpenAI-compatible model pricing ($/1K tokens)
|
|
35
|
-
// Automatically used when baseUrl contains openrouter.ai or known providers.
|
|
36
|
-
// Defaults to a conservative blended estimate for unknown models.
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
const KNOWN_MODEL_PRICING = {
|
|
39
|
-
// DeepSeek
|
|
40
|
-
"deepseek/deepseek-chat": { inputPer1K: 0.00027, outputPer1K: 0.0011 },
|
|
41
|
-
"deepseek/deepseek-r1": { inputPer1K: 0.0008, outputPer1K: 0.0032 },
|
|
42
|
-
"deepseek/deepseek-coder": { inputPer1K: 0.00014, outputPer1K: 0.00028 },
|
|
43
|
-
// Qwen
|
|
44
|
-
"qwen/qwen3-235b-a22b": { inputPer1K: 0.00022, outputPer1K: 0.00088 },
|
|
45
|
-
"qwen/qwen3-32b": { inputPer1K: 0.00009, outputPer1K: 0.00009 },
|
|
46
|
-
"qwen/qwen-2.5-coder-32b-instruct": { inputPer1K: 0.00007, outputPer1K: 0.00007 },
|
|
47
|
-
// Mistral
|
|
48
|
-
"mistralai/codestral-latest": { inputPer1K: 0.0003, outputPer1K: 0.0009 },
|
|
49
|
-
"mistralai/mistral-large": { inputPer1K: 0.003, outputPer1K: 0.009 },
|
|
50
|
-
"mistralai/mistral-small": { inputPer1K: 0.0001, outputPer1K: 0.0003 },
|
|
51
|
-
// Kimi
|
|
52
|
-
"moonshotai/kimi-k2": { inputPer1K: 0.00065, outputPer1K: 0.0026 },
|
|
53
|
-
// Nemotron
|
|
54
|
-
"nvidia/llama-3.1-nemotron-70b-instruct": { inputPer1K: 0.00012, outputPer1K: 0.0003 },
|
|
55
|
-
// Llama (via OpenRouter)
|
|
56
|
-
"meta-llama/llama-3.3-70b-instruct": { inputPer1K: 0.00012, outputPer1K: 0.0003 },
|
|
57
|
-
"meta-llama/llama-3.1-405b-instruct": { inputPer1K: 0.0008, outputPer1K: 0.0008 },
|
|
58
|
-
};
|
|
59
|
-
const FALLBACK_INPUT_PER_1K = 0.0003;
|
|
60
|
-
const FALLBACK_OUTPUT_PER_1K = 0.0012;
|
|
61
|
-
const CHARS_PER_TOKEN = 4;
|
|
62
|
-
function estimateCost(model, inputChars, outputChars) {
|
|
63
|
-
const pricing = KNOWN_MODEL_PRICING[model] ?? {
|
|
64
|
-
inputPer1K: FALLBACK_INPUT_PER_1K,
|
|
65
|
-
outputPer1K: FALLBACK_OUTPUT_PER_1K
|
|
66
|
-
};
|
|
67
|
-
const tokensIn = Math.ceil(inputChars / CHARS_PER_TOKEN);
|
|
68
|
-
const tokensOut = Math.ceil(outputChars / CHARS_PER_TOKEN);
|
|
69
|
-
const actualUsd = (tokensIn / 1000) * pricing.inputPer1K + (tokensOut / 1000) * pricing.outputPer1K;
|
|
70
|
-
return { tokensIn, tokensOut, actualUsd };
|
|
71
|
-
}
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
// Prompt builder
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
const DEFAULT_SYSTEM_PROMPT = `You are an expert software engineer executing a governed coding task.
|
|
76
|
-
Follow these rules exactly:
|
|
77
|
-
- Read the task description carefully and implement only what is asked.
|
|
78
|
-
- Do not add features, refactors, or improvements beyond the stated task.
|
|
79
|
-
- If the task asks you to write or modify code, output the complete file content with changes applied.
|
|
80
|
-
- Be precise, minimal, and test-backed in all changes.
|
|
81
|
-
- State what you changed and why at the end of your response.`;
|
|
82
|
-
export const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
|
|
83
|
-
export const DEFAULT_OPENAI_MODEL = "gpt-4.1-mini";
|
|
84
|
-
export function resolveOpenAiCompatibleRuntimeConfig(env = process.env) {
|
|
85
|
-
const apiKey = env["MARTIN_OPENAI_API_KEY"] ?? "";
|
|
86
|
-
return {
|
|
87
|
-
baseUrl: env["MARTIN_OPENAI_BASE_URL"] ?? DEFAULT_OPENAI_BASE_URL,
|
|
88
|
-
model: env["MARTIN_OPENAI_MODEL"] ?? DEFAULT_OPENAI_MODEL,
|
|
89
|
-
apiKey,
|
|
90
|
-
apiKeyConfigured: apiKey.length > 0,
|
|
91
|
-
authPosture: apiKey.length > 0 ? "api_key" : "anonymous_or_local"
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
function buildPrompt(request) {
|
|
95
|
-
const lines = [
|
|
96
|
-
`TASK: ${request.context.taskTitle}`,
|
|
97
|
-
``,
|
|
98
|
-
`OBJECTIVE:`,
|
|
99
|
-
request.context.objective,
|
|
100
|
-
``
|
|
101
|
-
];
|
|
102
|
-
if (request.context.focus) {
|
|
103
|
-
lines.push(`FOCUS: ${request.context.focus}`, ``);
|
|
104
|
-
}
|
|
105
|
-
if (request.context.verificationPlan.length > 0) {
|
|
106
|
-
lines.push(`VERIFICATION COMMANDS (must pass after your changes):`, ...request.context.verificationPlan.map((cmd) => ` ${cmd}`), ``);
|
|
107
|
-
}
|
|
108
|
-
if (request.previousAttempts.length > 0) {
|
|
109
|
-
const last = request.previousAttempts.at(-1);
|
|
110
|
-
if (last) {
|
|
111
|
-
lines.push(`PREVIOUS ATTEMPT SUMMARY:`, last.summary ?? "", ``);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
lines.push(`BUDGET REMAINING: $${request.context.remainingBudgetUsd.toFixed(4)} | Iterations left: ${request.context.remainingIterations}`);
|
|
115
|
-
return lines.join("\n");
|
|
116
|
-
}
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
// Factory
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
export function createOpenAiCompatibleAdapter(options) {
|
|
121
|
-
const workingDirectory = options.workingDirectory ?? process.cwd();
|
|
122
|
-
const timeoutMs = options.timeoutMs ?? 300_000;
|
|
123
|
-
const verifyTimeoutMs = options.verifyTimeoutMs ?? 120_000;
|
|
124
|
-
const systemPrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
125
|
-
const fetchFn = options.fetchImpl ?? globalThis.fetch;
|
|
126
|
-
const runtimeConfig = resolveOpenAiCompatibleRuntimeConfig();
|
|
127
|
-
const baseUrl = (options.baseUrl ?? runtimeConfig.baseUrl).replace(/\/$/, "");
|
|
128
|
-
const model = options.model ?? runtimeConfig.model;
|
|
129
|
-
const apiKey = options.apiKey ?? runtimeConfig.apiKey;
|
|
130
|
-
return {
|
|
131
|
-
adapterId: `openai-compatible:${model}`,
|
|
132
|
-
kind: "direct-provider",
|
|
133
|
-
label: `OpenAI-compatible: ${model}`,
|
|
134
|
-
metadata: {
|
|
135
|
-
providerId: "openai-compatible",
|
|
136
|
-
model,
|
|
137
|
-
transport: "http",
|
|
138
|
-
capabilities: createAdapterCapabilities({
|
|
139
|
-
preflight: true,
|
|
140
|
-
usageSettlement: true,
|
|
141
|
-
diffArtifacts: true
|
|
142
|
-
})
|
|
143
|
-
},
|
|
144
|
-
async execute(request) {
|
|
145
|
-
const prompt = buildPrompt(request);
|
|
146
|
-
const estimated = estimateCost(model, prompt.length, 2000);
|
|
147
|
-
const hasVerificationSteps = request.context.verificationPlan.length > 0 ||
|
|
148
|
-
(request.context.verificationStack?.length ?? 0) > 0;
|
|
149
|
-
const baselineChangedFiles = hasVerificationSteps
|
|
150
|
-
? new Set(await readGitChangedFiles(workingDirectory, 5_000))
|
|
151
|
-
: new Set();
|
|
152
|
-
// Preflight: bail if projected cost exceeds remaining budget
|
|
153
|
-
if (request.context.remainingBudgetUsd > 0 &&
|
|
154
|
-
estimated.actualUsd > request.context.remainingBudgetUsd * 0.95) {
|
|
155
|
-
return {
|
|
156
|
-
status: "failed",
|
|
157
|
-
summary: `Preflight: projected cost $${estimated.actualUsd.toFixed(4)} exceeds remaining budget $${request.context.remainingBudgetUsd.toFixed(4)}.`,
|
|
158
|
-
usage: normalizeUsage({
|
|
159
|
-
actualUsd: estimated.actualUsd,
|
|
160
|
-
estimatedUsd: estimated.actualUsd,
|
|
161
|
-
tokensIn: estimated.tokensIn,
|
|
162
|
-
tokensOut: estimated.tokensOut,
|
|
163
|
-
provenance: "estimated"
|
|
164
|
-
}),
|
|
165
|
-
verification: { passed: false, summary: "Stopped before execution: budget preflight failed." },
|
|
166
|
-
failure: { message: "budget_preflight_exceeded", classHint: "budget_pressure" }
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
// Call the OpenAI-compatible endpoint with exponential-backoff retry on
|
|
170
|
-
// transient failures (429 rate-limit, 503/5xx server errors, network errors).
|
|
171
|
-
// Auth errors (401/403) and bad-request errors (400) are not retried — they
|
|
172
|
-
// indicate a permanent configuration problem.
|
|
173
|
-
const MAX_RETRIES = 3;
|
|
174
|
-
const RETRYABLE_STATUS = new Set([429, 500, 502, 503, 504]);
|
|
175
|
-
const endpoint = `${baseUrl}/v1/chat/completions`;
|
|
176
|
-
let responseText = "";
|
|
177
|
-
let tokensIn = estimated.tokensIn;
|
|
178
|
-
let tokensOut = 0;
|
|
179
|
-
const headers = { "Content-Type": "application/json" };
|
|
180
|
-
if (apiKey)
|
|
181
|
-
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
182
|
-
if (baseUrl.includes("openrouter")) {
|
|
183
|
-
headers["HTTP-Referer"] = "https://martinloop.com";
|
|
184
|
-
headers["X-Title"] = "MartinLoop";
|
|
185
|
-
}
|
|
186
|
-
const requestBody = JSON.stringify({
|
|
187
|
-
model,
|
|
188
|
-
messages: [
|
|
189
|
-
{ role: "system", content: systemPrompt },
|
|
190
|
-
{ role: "user", content: prompt }
|
|
191
|
-
],
|
|
192
|
-
temperature: 0.2,
|
|
193
|
-
max_tokens: 8192
|
|
194
|
-
});
|
|
195
|
-
let lastError = "";
|
|
196
|
-
let succeeded = false;
|
|
197
|
-
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
198
|
-
const controller = new AbortController();
|
|
199
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
200
|
-
try {
|
|
201
|
-
const res = await fetchFn(endpoint, {
|
|
202
|
-
method: "POST",
|
|
203
|
-
headers,
|
|
204
|
-
body: requestBody,
|
|
205
|
-
signal: controller.signal
|
|
206
|
-
});
|
|
207
|
-
const body = (await res.json());
|
|
208
|
-
if (!res.ok || body.error) {
|
|
209
|
-
const errMsg = body.error?.message ?? `HTTP ${res.status}`;
|
|
210
|
-
if (RETRYABLE_STATUS.has(res.status) && attempt < MAX_RETRIES - 1) {
|
|
211
|
-
lastError = errMsg;
|
|
212
|
-
// Exponential backoff: 1s, 2s, 4s
|
|
213
|
-
await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
return {
|
|
217
|
-
status: "failed",
|
|
218
|
-
summary: `${model} API error: ${errMsg}`,
|
|
219
|
-
usage: normalizeUsage({ actualUsd: 0, tokensIn: 0, tokensOut: 0, provenance: "unavailable" }),
|
|
220
|
-
verification: { passed: false, summary: "API call failed before verifier." },
|
|
221
|
-
failure: { message: errMsg, classHint: "infrastructure_error" }
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
responseText = body.choices?.[0]?.message?.content ?? "";
|
|
225
|
-
if (body.usage) {
|
|
226
|
-
tokensIn = body.usage.prompt_tokens ?? tokensIn;
|
|
227
|
-
tokensOut = body.usage.completion_tokens ?? 0;
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
tokensOut = Math.ceil(responseText.length / CHARS_PER_TOKEN);
|
|
231
|
-
}
|
|
232
|
-
succeeded = true;
|
|
233
|
-
break;
|
|
234
|
-
}
|
|
235
|
-
catch (error) {
|
|
236
|
-
const isAbort = error instanceof Error && error.name === "AbortError";
|
|
237
|
-
if (isAbort || attempt === MAX_RETRIES - 1) {
|
|
238
|
-
const message = isAbort
|
|
239
|
-
? `${model} request timed out after ${timeoutMs}ms`
|
|
240
|
-
: String(error);
|
|
241
|
-
return {
|
|
242
|
-
status: "failed",
|
|
243
|
-
summary: message,
|
|
244
|
-
usage: normalizeUsage({ actualUsd: 0, tokensIn: 0, tokensOut: 0, provenance: "unavailable" }),
|
|
245
|
-
verification: { passed: false, summary: isAbort ? "Request timed out." : "Network error." },
|
|
246
|
-
failure: { message, classHint: "infrastructure_error" }
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
// Transient network error — retry with backoff
|
|
250
|
-
lastError = String(error);
|
|
251
|
-
await new Promise((resolve) => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
|
|
252
|
-
}
|
|
253
|
-
finally {
|
|
254
|
-
clearTimeout(timer);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (!succeeded) {
|
|
258
|
-
return {
|
|
259
|
-
status: "failed",
|
|
260
|
-
summary: `${model} API error after ${MAX_RETRIES} attempts: ${lastError}`,
|
|
261
|
-
usage: normalizeUsage({ actualUsd: 0, tokensIn: 0, tokensOut: 0, provenance: "unavailable" }),
|
|
262
|
-
verification: { passed: false, summary: "API call failed after retries." },
|
|
263
|
-
failure: { message: lastError, classHint: "infrastructure_error" }
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
if (!responseText.trim()) {
|
|
267
|
-
return {
|
|
268
|
-
status: "failed",
|
|
269
|
-
summary: `${model} returned an empty response.`,
|
|
270
|
-
usage: normalizeUsage({ actualUsd: 0, tokensIn, tokensOut: 0, provenance: "actual" }),
|
|
271
|
-
verification: { passed: false, summary: "Empty response — nothing to verify." },
|
|
272
|
-
failure: { message: "empty_response" }
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
// Run verification
|
|
276
|
-
const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack);
|
|
277
|
-
const execution = {
|
|
278
|
-
changedFiles: hasVerificationSteps
|
|
279
|
-
? (await readGitChangedFiles(workingDirectory, 5_000)).filter((file) => !baselineChangedFiles.has(file))
|
|
280
|
-
: []
|
|
281
|
-
};
|
|
282
|
-
const pricing = KNOWN_MODEL_PRICING[model] ?? {
|
|
283
|
-
inputPer1K: FALLBACK_INPUT_PER_1K,
|
|
284
|
-
outputPer1K: FALLBACK_OUTPUT_PER_1K
|
|
285
|
-
};
|
|
286
|
-
const actualUsd = (tokensIn / 1000) * pricing.inputPer1K + (tokensOut / 1000) * pricing.outputPer1K;
|
|
287
|
-
return {
|
|
288
|
-
status: verification.passed ? "completed" : "failed",
|
|
289
|
-
summary: verification.passed
|
|
290
|
-
? `${model} completed the task. Verifier passed.`
|
|
291
|
-
: `${model} completed but verifier failed: ${verification.summary}`,
|
|
292
|
-
usage: normalizeUsage({
|
|
293
|
-
actualUsd,
|
|
294
|
-
tokensIn,
|
|
295
|
-
tokensOut,
|
|
296
|
-
provenance: "actual"
|
|
297
|
-
}),
|
|
298
|
-
verification,
|
|
299
|
-
execution,
|
|
300
|
-
...(verification.passed ? {} : {
|
|
301
|
-
failure: { message: verification.summary }
|
|
302
|
-
})
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
};
|
|
306
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { MartinAdapter, MartinAdapterResult } from "../../../core/index.js";
|
|
2
|
-
type StructuredError = NonNullable<NonNullable<MartinAdapterResult["execution"]>["structuredErrors"]>[number];
|
|
3
|
-
type DiffStats = NonNullable<NonNullable<MartinAdapterResult["execution"]>["diffStats"]>;
|
|
4
|
-
export declare function createAdapterCapabilities(overrides?: Partial<NonNullable<MartinAdapter["metadata"]["capabilities"]>>): NonNullable<MartinAdapter["metadata"]["capabilities"]>;
|
|
5
|
-
export declare function normalizeUsage(input: {
|
|
6
|
-
actualUsd?: number;
|
|
7
|
-
estimatedUsd?: number;
|
|
8
|
-
tokensIn?: number;
|
|
9
|
-
tokensOut?: number;
|
|
10
|
-
cachedInputTokens?: number;
|
|
11
|
-
reasoningTokensOut?: number;
|
|
12
|
-
provenance?: MartinAdapterResult["usage"]["provenance"];
|
|
13
|
-
providerSettlement?: MartinAdapterResult["usage"]["providerSettlement"];
|
|
14
|
-
}): MartinAdapterResult["usage"];
|
|
15
|
-
export declare function diffStatsFromNumstat(stdout: string): DiffStats | undefined;
|
|
16
|
-
export declare function normalizeStructuredErrors(errors: StructuredError[] | undefined): StructuredError[];
|
|
17
|
-
export {};
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
export function createAdapterCapabilities(overrides = {}) {
|
|
2
|
-
return {
|
|
3
|
-
preflight: true,
|
|
4
|
-
usageSettlement: false,
|
|
5
|
-
diffArtifacts: false,
|
|
6
|
-
structuredErrors: false,
|
|
7
|
-
cachingSignals: false,
|
|
8
|
-
workspaceMutations: true,
|
|
9
|
-
...overrides
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
export function normalizeUsage(input) {
|
|
13
|
-
const provenance = input.provenance ??
|
|
14
|
-
(input.estimatedUsd !== undefined ? "estimated" : "actual");
|
|
15
|
-
const actualUsd = provenance === "estimated"
|
|
16
|
-
? input.estimatedUsd ?? input.actualUsd ?? 0
|
|
17
|
-
: input.actualUsd ?? 0;
|
|
18
|
-
return {
|
|
19
|
-
actualUsd: roundUsd(actualUsd),
|
|
20
|
-
...(input.estimatedUsd !== undefined
|
|
21
|
-
? { estimatedUsd: roundUsd(input.estimatedUsd) }
|
|
22
|
-
: {}),
|
|
23
|
-
tokensIn: input.tokensIn ?? 0,
|
|
24
|
-
tokensOut: input.tokensOut ?? 0,
|
|
25
|
-
...(input.cachedInputTokens !== undefined
|
|
26
|
-
? { cachedInputTokens: input.cachedInputTokens }
|
|
27
|
-
: {}),
|
|
28
|
-
...(input.reasoningTokensOut !== undefined
|
|
29
|
-
? { reasoningTokensOut: input.reasoningTokensOut }
|
|
30
|
-
: {}),
|
|
31
|
-
provenance,
|
|
32
|
-
...(input.providerSettlement ? { providerSettlement: input.providerSettlement } : {})
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
export function diffStatsFromNumstat(stdout) {
|
|
36
|
-
const lines = stdout
|
|
37
|
-
.split(/\r?\n/u)
|
|
38
|
-
.map((line) => line.trim())
|
|
39
|
-
.filter(Boolean);
|
|
40
|
-
if (lines.length === 0) {
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
let filesChanged = 0;
|
|
44
|
-
let addedLines = 0;
|
|
45
|
-
let deletedLines = 0;
|
|
46
|
-
for (const line of lines) {
|
|
47
|
-
const [added, deleted] = line.split(/\s+/u);
|
|
48
|
-
filesChanged += 1;
|
|
49
|
-
addedLines += Number(added) || 0;
|
|
50
|
-
deletedLines += Number(deleted) || 0;
|
|
51
|
-
}
|
|
52
|
-
return { filesChanged, addedLines, deletedLines };
|
|
53
|
-
}
|
|
54
|
-
export function normalizeStructuredErrors(errors) {
|
|
55
|
-
return errors?.slice(0, 10) ?? [];
|
|
56
|
-
}
|
|
57
|
-
function roundUsd(value) {
|
|
58
|
-
return Math.round(value * 1_000_000) / 1_000_000;
|
|
59
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { MartinAdapter, MartinAdapterRequest, MartinAdapterResult } from "../../../core/index.js";
|
|
2
|
-
export interface StubAgentCliAdapterOptions {
|
|
3
|
-
command: string[];
|
|
4
|
-
profile?: string;
|
|
5
|
-
label?: string;
|
|
6
|
-
responder?: (request: MartinAdapterRequest) => Promise<MartinAdapterResult> | MartinAdapterResult;
|
|
7
|
-
}
|
|
8
|
-
export declare function createStubAgentCliAdapter(options: StubAgentCliAdapterOptions): MartinAdapter;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { createAdapterCapabilities } from "./runtime-support.js";
|
|
2
|
-
export function createStubAgentCliAdapter(options) {
|
|
3
|
-
const metadata = {
|
|
4
|
-
command: options.command.join(" "),
|
|
5
|
-
providerId: "agent-cli",
|
|
6
|
-
model: options.profile ?? options.command[0] ?? "default",
|
|
7
|
-
transport: "cli",
|
|
8
|
-
capabilities: createAdapterCapabilities(),
|
|
9
|
-
...(options.profile ? { profile: options.profile } : {})
|
|
10
|
-
};
|
|
11
|
-
return {
|
|
12
|
-
adapterId: `agent-cli:${options.command.join(":")}`,
|
|
13
|
-
kind: "agent-cli",
|
|
14
|
-
label: options.label ?? `Stub agent CLI (${options.command.join(" ")})`,
|
|
15
|
-
metadata,
|
|
16
|
-
async execute(request) {
|
|
17
|
-
if (options.responder) {
|
|
18
|
-
return await options.responder(request);
|
|
19
|
-
}
|
|
20
|
-
return {
|
|
21
|
-
status: "failed",
|
|
22
|
-
summary: `Stub agent CLI ${options.command.join(" ")} did not execute a live session.`,
|
|
23
|
-
usage: {
|
|
24
|
-
actualUsd: 0,
|
|
25
|
-
tokensIn: 0,
|
|
26
|
-
tokensOut: 0,
|
|
27
|
-
provenance: "unavailable"
|
|
28
|
-
},
|
|
29
|
-
verification: {
|
|
30
|
-
passed: false,
|
|
31
|
-
summary: "No CLI execution was attempted."
|
|
32
|
-
},
|
|
33
|
-
failure: {
|
|
34
|
-
message: `Agent CLI ${options.command.join(" ")} is not configured for live execution.`,
|
|
35
|
-
classHint: "environment_mismatch"
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { MartinAdapter, MartinAdapterRequest, MartinAdapterResult } from "../../../core/index.js";
|
|
2
|
-
export interface StubDirectProviderAdapterOptions {
|
|
3
|
-
providerId: string;
|
|
4
|
-
model: string;
|
|
5
|
-
label?: string;
|
|
6
|
-
responder?: (request: MartinAdapterRequest) => Promise<MartinAdapterResult> | MartinAdapterResult;
|
|
7
|
-
}
|
|
8
|
-
export declare function createStubDirectProviderAdapter(options: StubDirectProviderAdapterOptions): MartinAdapter;
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { createDirectProviderAdapter } from "./direct-provider.js";
|
|
2
|
-
export function createStubDirectProviderAdapter(options) {
|
|
3
|
-
return createDirectProviderAdapter({
|
|
4
|
-
providerId: options.providerId,
|
|
5
|
-
model: options.model,
|
|
6
|
-
label: options.label ?? `Stub direct provider (${options.providerId}/${options.model})`,
|
|
7
|
-
capabilities: {
|
|
8
|
-
workspaceMutations: false
|
|
9
|
-
},
|
|
10
|
-
responder: options.responder
|
|
11
|
-
});
|
|
12
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { MartinAdapter } from "../../../core/index.js";
|
|
2
|
-
import { type SpawnLike } from "./cli-bridge.js";
|
|
3
|
-
export interface VerifierOnlyAdapterOptions {
|
|
4
|
-
workingDirectory?: string;
|
|
5
|
-
verifyTimeoutMs?: number;
|
|
6
|
-
label?: string;
|
|
7
|
-
spawnImpl?: SpawnLike;
|
|
8
|
-
}
|
|
9
|
-
export declare function createVerifierOnlyAdapter(options?: VerifierOnlyAdapterOptions): MartinAdapter;
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { readGitChangedFiles, runVerification } from "./cli-bridge.js";
|
|
2
|
-
import { createAdapterCapabilities, normalizeUsage } from "./runtime-support.js";
|
|
3
|
-
export function createVerifierOnlyAdapter(options = {}) {
|
|
4
|
-
const workingDirectory = options.workingDirectory ?? process.cwd();
|
|
5
|
-
const verifyTimeoutMs = options.verifyTimeoutMs ?? 120_000;
|
|
6
|
-
return {
|
|
7
|
-
adapterId: "direct:verifier:verify-only",
|
|
8
|
-
kind: "direct-provider",
|
|
9
|
-
label: options.label ?? "Verifier-only adapter",
|
|
10
|
-
metadata: {
|
|
11
|
-
providerId: "verifier",
|
|
12
|
-
model: "verify-only",
|
|
13
|
-
transport: "cli",
|
|
14
|
-
capabilities: createAdapterCapabilities({
|
|
15
|
-
usageSettlement: true,
|
|
16
|
-
diffArtifacts: true
|
|
17
|
-
})
|
|
18
|
-
},
|
|
19
|
-
async execute(request) {
|
|
20
|
-
const shouldTrackVerifierWrites = request.context.verificationPlan.length > 0 ||
|
|
21
|
-
(request.context.verificationStack?.length ?? 0) > 0;
|
|
22
|
-
const baselineChangedFiles = shouldTrackVerifierWrites
|
|
23
|
-
? new Set(await readGitChangedFiles(workingDirectory, 5_000))
|
|
24
|
-
: new Set();
|
|
25
|
-
const verification = await runVerification(request.context.verificationPlan, workingDirectory, verifyTimeoutMs, request.context.verificationStack, options.spawnImpl);
|
|
26
|
-
const changedFiles = shouldTrackVerifierWrites
|
|
27
|
-
? (await readGitChangedFiles(workingDirectory, 5_000)).filter((file) => !baselineChangedFiles.has(file))
|
|
28
|
-
: [];
|
|
29
|
-
const execution = { changedFiles };
|
|
30
|
-
if (verification.passed) {
|
|
31
|
-
return {
|
|
32
|
-
status: "completed",
|
|
33
|
-
summary: changedFiles.length > 0
|
|
34
|
-
? `Verifier-only run completed but modified files: ${changedFiles.join(", ")}`
|
|
35
|
-
: "Verifier-only run completed without file edits.",
|
|
36
|
-
usage: normalizeUsage({
|
|
37
|
-
actualUsd: 0,
|
|
38
|
-
tokensIn: 0,
|
|
39
|
-
tokensOut: 0,
|
|
40
|
-
provenance: "actual"
|
|
41
|
-
}),
|
|
42
|
-
verification,
|
|
43
|
-
execution
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
status: "failed",
|
|
48
|
-
summary: "Verifier-only run failed.",
|
|
49
|
-
usage: normalizeUsage({
|
|
50
|
-
actualUsd: 0,
|
|
51
|
-
tokensIn: 0,
|
|
52
|
-
tokensOut: 0,
|
|
53
|
-
provenance: "actual"
|
|
54
|
-
}),
|
|
55
|
-
verification,
|
|
56
|
-
execution,
|
|
57
|
-
failure: {
|
|
58
|
-
message: verification.summary
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
}
|