@open-cr-agent/runtime-direct 0.3.0 → 0.4.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/README.md +1 -1
- package/dist/effort.d.ts +14 -0
- package/dist/effort.js +49 -0
- package/dist/loop.d.ts +4 -3
- package/dist/loop.js +7 -2
- package/dist/openai.d.ts +11 -2
- package/dist/openai.js +24 -3
- package/dist/runtime.d.ts +4 -1
- package/dist/runtime.js +18 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
# @open-cr-agent/runtime-direct
|
|
2
2
|
|
|
3
|
-
The agent runtime of [Open-CR-Agent](https://github.com/jma49/Open-CR-Agent), the multi-agent code reviewer, that calls the OpenAI-compatible model endpoints declared in its configuration itself, with nothing else on the network. Most users want [`@open-cr-agent/cli`](https://www.npmjs.com/package/@open-cr-agent/cli), which provides the `ocra` command and installs this package; see the [manual](https://
|
|
3
|
+
The agent runtime of [Open-CR-Agent](https://github.com/jma49/Open-CR-Agent), the multi-agent code reviewer, that calls the OpenAI-compatible model endpoints declared in its configuration itself, with nothing else on the network. Most users want [`@open-cr-agent/cli`](https://www.npmjs.com/package/@open-cr-agent/cli), which provides the `ocra` command and installs this package; see the [manual](https://ocracloud.com). All `@open-cr-agent` packages are released together at one version. Apache-2.0.
|
package/dist/effort.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AppliedSettings, Effort, Sampling } from "@open-cr-agent/core";
|
|
2
|
+
import type { CallParams } from "./openai.js";
|
|
3
|
+
export type EffortStyle = "openai" | "openrouter";
|
|
4
|
+
export declare class EffortLedger {
|
|
5
|
+
private readonly sampling;
|
|
6
|
+
private readonly refused;
|
|
7
|
+
private readonly applied;
|
|
8
|
+
constructor(sampling: Sampling);
|
|
9
|
+
params(agent: string, effort: Effort | undefined, model: string, style?: EffortStyle): CallParams;
|
|
10
|
+
refuse(agent: string, model: string): void;
|
|
11
|
+
appliedTo(agent: string): AppliedSettings | undefined;
|
|
12
|
+
private record;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=effort.d.ts.map
|
package/dist/effort.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// What each call sends besides the conversation, and what the run applied
|
|
2
|
+
// per agent (ADR-0025). Reasoning models refuse a temperature (OpenAI) or
|
|
3
|
+
// need their own (Anthropic), so a call that asks for an effort other than
|
|
4
|
+
// "none" sends no sampling settings, even after its endpoint refused the
|
|
5
|
+
// effort: the agent's calls stay alike within a run.
|
|
6
|
+
export class EffortLedger {
|
|
7
|
+
sampling;
|
|
8
|
+
// Models whose endpoint refused the effort parameter: later calls go
|
|
9
|
+
// without it instead of paying for another refusal.
|
|
10
|
+
refused = new Set();
|
|
11
|
+
applied = new Map();
|
|
12
|
+
constructor(sampling) {
|
|
13
|
+
this.sampling = sampling;
|
|
14
|
+
}
|
|
15
|
+
params(agent, effort, model, style = "openai") {
|
|
16
|
+
if (effort === undefined)
|
|
17
|
+
return { ...this.sampling };
|
|
18
|
+
const keepsSampling = effort === "none";
|
|
19
|
+
const sends = !this.refused.has(model);
|
|
20
|
+
this.record(agent, sends, keepsSampling ? [] : requested(this.sampling));
|
|
21
|
+
return {
|
|
22
|
+
...(keepsSampling ? this.sampling : {}),
|
|
23
|
+
...(sends ? effortParam(effort, style) : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
refuse(agent, model) {
|
|
27
|
+
this.refused.add(model);
|
|
28
|
+
this.record(agent, false, []);
|
|
29
|
+
}
|
|
30
|
+
appliedTo(agent) {
|
|
31
|
+
const applied = this.applied.get(agent);
|
|
32
|
+
return applied && { ...applied };
|
|
33
|
+
}
|
|
34
|
+
record(agent, sent, notApplied) {
|
|
35
|
+
const before = this.applied.get(agent);
|
|
36
|
+
const left = [...new Set([...(before?.notApplied ?? []), ...notApplied])];
|
|
37
|
+
this.applied.set(agent, {
|
|
38
|
+
effort: (before?.effort ?? true) && sent,
|
|
39
|
+
...(left.length > 0 ? { notApplied: left } : {}),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function effortParam(effort, style) {
|
|
44
|
+
return style === "openrouter" ? { reasoning: { effort } } : { reasoning_effort: effort };
|
|
45
|
+
}
|
|
46
|
+
function requested(sampling) {
|
|
47
|
+
return ["temperature", "seed"].filter((key) => sampling[key] !== undefined);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=effort.js.map
|
package/dist/loop.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ModelPrice, ReviewContext,
|
|
1
|
+
import type { ModelPrice, ReviewContext, ToolDefinition, Usage } from "@open-cr-agent/core";
|
|
2
2
|
import { type AttemptOutcome } from "@open-cr-agent/core/internal";
|
|
3
|
-
import { type Endpoint } from "./openai.js";
|
|
3
|
+
import { type CallParams, type Endpoint } from "./openai.js";
|
|
4
4
|
export interface LoopInput {
|
|
5
5
|
endpoint: Endpoint;
|
|
6
6
|
model: string;
|
|
@@ -12,7 +12,8 @@ export interface LoopInput {
|
|
|
12
12
|
maxSteps: number;
|
|
13
13
|
resume?: string;
|
|
14
14
|
timeoutMs: number;
|
|
15
|
-
|
|
15
|
+
params?: CallParams;
|
|
16
|
+
onEffortRefused?: () => void;
|
|
16
17
|
signal: AbortSignal;
|
|
17
18
|
onUsage?: (spent: Usage) => void;
|
|
18
19
|
}
|
package/dist/loop.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { addUsage, emptyUsage, errorMessage, REVIEW_TOOLS, } from "@open-cr-agent/core/internal";
|
|
2
|
-
import { chat, toolSpec } from "./openai.js";
|
|
2
|
+
import { chat, toolSpec, withoutEffort, } from "./openai.js";
|
|
3
3
|
// The agent loop: ask the model, run the tools it calls against the review
|
|
4
4
|
// context, hand the results back, until it calls task_done, answers without
|
|
5
5
|
// tools, or runs out of steps. The model reaches nothing but these tools and
|
|
@@ -23,14 +23,19 @@ export async function runLoop(input) {
|
|
|
23
23
|
};
|
|
24
24
|
const texts = [];
|
|
25
25
|
let done = false;
|
|
26
|
+
let params = input.params ?? {};
|
|
26
27
|
while (outcome.steps < input.maxSteps) {
|
|
27
28
|
const response = await chat(input.endpoint, {
|
|
28
29
|
model: input.model,
|
|
29
30
|
messages,
|
|
30
31
|
...(specs.length > 0 ? { tools: specs } : {}),
|
|
31
|
-
...
|
|
32
|
+
...params,
|
|
32
33
|
}, input.price, signal);
|
|
33
34
|
outcome.steps += 1;
|
|
35
|
+
if (response.effortDropped) {
|
|
36
|
+
params = withoutEffort(params);
|
|
37
|
+
input.onEffortRefused?.();
|
|
38
|
+
}
|
|
34
39
|
if (!response.ok) {
|
|
35
40
|
outcome.error = timeout.aborted
|
|
36
41
|
? { message: `timed out after ${Math.round(input.timeoutMs / 1000)}s`, retryable: true }
|
package/dist/openai.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ModelPrice, ToolDefinition, Usage } from "@open-cr-agent/core";
|
|
1
|
+
import type { Effort, ModelPrice, ToolDefinition, Usage } from "@open-cr-agent/core";
|
|
2
2
|
import { type QuotaError } from "@open-cr-agent/core/internal";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
declare const toolCallSchema: z.ZodObject<{
|
|
@@ -40,14 +40,20 @@ export interface ChatRequest {
|
|
|
40
40
|
tools?: readonly ToolSpec[];
|
|
41
41
|
temperature?: number;
|
|
42
42
|
seed?: number;
|
|
43
|
+
reasoning_effort?: Effort;
|
|
44
|
+
reasoning?: {
|
|
45
|
+
effort: Effort;
|
|
46
|
+
};
|
|
43
47
|
}
|
|
48
|
+
export type CallParams = Pick<ChatRequest, "temperature" | "seed" | "reasoning_effort" | "reasoning">;
|
|
44
49
|
export interface ChatError {
|
|
45
50
|
message: string;
|
|
46
51
|
retryable: boolean;
|
|
47
52
|
quota?: QuotaError;
|
|
48
53
|
transient?: boolean;
|
|
54
|
+
effortRefused?: boolean;
|
|
49
55
|
}
|
|
50
|
-
export type ChatResponse = {
|
|
56
|
+
export type ChatResponse = ({
|
|
51
57
|
ok: true;
|
|
52
58
|
content: string;
|
|
53
59
|
toolCalls: ToolCall[];
|
|
@@ -55,9 +61,12 @@ export type ChatResponse = {
|
|
|
55
61
|
} | {
|
|
56
62
|
ok: false;
|
|
57
63
|
error: ChatError;
|
|
64
|
+
}) & {
|
|
65
|
+
effortDropped?: true;
|
|
58
66
|
};
|
|
59
67
|
export declare const TRANSIENT_RETRY_MS = 1000;
|
|
60
68
|
export declare function toolSpec(tool: ToolDefinition): ToolSpec;
|
|
61
69
|
export declare function chat(endpoint: Endpoint, request: ChatRequest, price: ModelPrice, signal: AbortSignal): Promise<ChatResponse>;
|
|
70
|
+
export declare function withoutEffort<T extends CallParams>(params: T): T;
|
|
62
71
|
export {};
|
|
63
72
|
//# sourceMappingURL=openai.d.ts.map
|
package/dist/openai.js
CHANGED
|
@@ -30,6 +30,8 @@ const errorBodySchema = z.object({
|
|
|
30
30
|
error: z.object({ message: z.string(), code: z.number().optional() }),
|
|
31
31
|
});
|
|
32
32
|
const AUTH_STATUS = new Set([401, 403]);
|
|
33
|
+
// How an endpoint names the effort parameters in a 400 that refuses them.
|
|
34
|
+
const EFFORT_PARAMETER = /\breasoning(?:_effort)?\b/i;
|
|
33
35
|
// How much of an error body an error message keeps: enough to diagnose, not
|
|
34
36
|
// a page of HTML.
|
|
35
37
|
const BODY_EXCERPT = 300;
|
|
@@ -43,10 +45,28 @@ export function toolSpec(tool) {
|
|
|
43
45
|
function: { name: tool.name, description: tool.description, parameters },
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
|
-
// One request to the endpoint
|
|
47
|
-
//
|
|
48
|
-
// and
|
|
48
|
+
// One request to the endpoint. A refused effort parameter is not the model's
|
|
49
|
+
// failure: the request goes once more without it, so the review still runs
|
|
50
|
+
// and the failback chain does not move.
|
|
49
51
|
export async function chat(endpoint, request, price, signal) {
|
|
52
|
+
const first = await sendRetrying(endpoint, request, price, signal);
|
|
53
|
+
if (first.ok || !first.error.effortRefused || !hasEffort(request))
|
|
54
|
+
return first;
|
|
55
|
+
return {
|
|
56
|
+
...(await sendRetrying(endpoint, withoutEffort(request), price, signal)),
|
|
57
|
+
effortDropped: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function hasEffort(params) {
|
|
61
|
+
return params.reasoning_effort !== undefined || params.reasoning !== undefined;
|
|
62
|
+
}
|
|
63
|
+
export function withoutEffort(params) {
|
|
64
|
+
const { reasoning_effort: _effort, reasoning: _reasoning, ...rest } = params;
|
|
65
|
+
return rest;
|
|
66
|
+
}
|
|
67
|
+
// Sent a second time when the first failed in a way a moment may cure. Only
|
|
68
|
+
// the key named for this provider goes out, and only to its base URL.
|
|
69
|
+
async function sendRetrying(endpoint, request, price, signal) {
|
|
50
70
|
const first = await send(endpoint, request, price, signal);
|
|
51
71
|
if (first.ok || !first.error.transient)
|
|
52
72
|
return first;
|
|
@@ -121,6 +141,7 @@ function failure(response, body, secrets, reportedCode) {
|
|
|
121
141
|
return {
|
|
122
142
|
message,
|
|
123
143
|
retryable: !AUTH_STATUS.has(status),
|
|
144
|
+
...(status === 400 && EFFORT_PARAMETER.test(body) ? { effortRefused: true } : {}),
|
|
124
145
|
...(quota ? { quota } : {}),
|
|
125
146
|
...(!quota && (status >= 500 || status === 408) ? { transient: true } : {}),
|
|
126
147
|
};
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AgentEvent, type AgentRuntime, type AgentTaskSpec, type AppliedSampling, type CompletionRequest, type CompletionResult, type RuntimeOptions } from "@open-cr-agent/core";
|
|
1
|
+
import { type AgentEvent, type AgentRuntime, type AgentTaskSpec, type AppliedSampling, type AppliedSettings, type CompletionRequest, type CompletionResult, type RuntimeOptions } from "@open-cr-agent/core";
|
|
2
2
|
export interface DirectRuntimeOptions extends RuntimeOptions {
|
|
3
3
|
fetch?: typeof fetch;
|
|
4
4
|
}
|
|
@@ -9,10 +9,13 @@ export declare class DirectRuntime implements AgentRuntime {
|
|
|
9
9
|
private readonly health;
|
|
10
10
|
private readonly tools;
|
|
11
11
|
private readonly fetch;
|
|
12
|
+
private readonly efforts;
|
|
12
13
|
constructor(options: DirectRuntimeOptions);
|
|
14
|
+
appliedTo(agent: string): AppliedSettings | undefined;
|
|
13
15
|
runTask(spec: AgentTaskSpec, signal: AbortSignal): AsyncIterable<AgentEvent>;
|
|
14
16
|
complete(request: CompletionRequest, signal: AbortSignal): Promise<CompletionResult>;
|
|
15
17
|
private unreachable;
|
|
18
|
+
private call;
|
|
16
19
|
private target;
|
|
17
20
|
}
|
|
18
21
|
//# sourceMappingURL=runtime.d.ts.map
|
package/dist/runtime.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { OcraError, } from "@open-cr-agent/core";
|
|
2
2
|
import { completeWithFailback, MAX_AGENT_STEPS, ModelHealth, parseModel, proxiedFetch, RESUME_MESSAGE, reviewTools, withFailback, } from "@open-cr-agent/core/internal";
|
|
3
|
+
import { EffortLedger } from "./effort.js";
|
|
3
4
|
import { runLoop } from "./loop.js";
|
|
4
5
|
// Talks to the declared OpenAI-compatible endpoints itself: no OpenCode
|
|
5
6
|
// process, no catalog fetch, no package install, nothing on disk. A model of
|
|
@@ -13,6 +14,7 @@ export class DirectRuntime {
|
|
|
13
14
|
health = new ModelHealth();
|
|
14
15
|
tools;
|
|
15
16
|
fetch;
|
|
17
|
+
efforts;
|
|
16
18
|
constructor(options) {
|
|
17
19
|
this.options = options;
|
|
18
20
|
this.tools = [...reviewTools, ...options.tools];
|
|
@@ -22,6 +24,10 @@ export class DirectRuntime {
|
|
|
22
24
|
...(temperature === undefined ? {} : { temperature }),
|
|
23
25
|
...(seed === undefined ? {} : { seed }),
|
|
24
26
|
};
|
|
27
|
+
this.efforts = new EffortLedger(this.sampling);
|
|
28
|
+
}
|
|
29
|
+
appliedTo(agent) {
|
|
30
|
+
return this.efforts.appliedTo(agent);
|
|
25
31
|
}
|
|
26
32
|
async *runTask(spec, signal) {
|
|
27
33
|
const chain = this.options.models[spec.modelTier] ?? [];
|
|
@@ -38,6 +44,7 @@ export class DirectRuntime {
|
|
|
38
44
|
signal,
|
|
39
45
|
attempt: (model, onUsage) => runLoop({
|
|
40
46
|
...this.target(model),
|
|
47
|
+
...this.call({ agent: spec.reviewer, effort: spec.effort, model }),
|
|
41
48
|
system: spec.systemPrompt,
|
|
42
49
|
user: spec.userPrompt,
|
|
43
50
|
tools: this.tools,
|
|
@@ -45,7 +52,6 @@ export class DirectRuntime {
|
|
|
45
52
|
maxSteps: MAX_AGENT_STEPS,
|
|
46
53
|
resume: RESUME_MESSAGE,
|
|
47
54
|
timeoutMs: spec.timeoutMs,
|
|
48
|
-
sampling: this.sampling,
|
|
49
55
|
signal,
|
|
50
56
|
onUsage,
|
|
51
57
|
}),
|
|
@@ -63,13 +69,13 @@ export class DirectRuntime {
|
|
|
63
69
|
signal,
|
|
64
70
|
attempt: (model) => runLoop({
|
|
65
71
|
...this.target(model),
|
|
72
|
+
...this.call({ agent: request.agent ?? request.tier, effort: request.effort, model }),
|
|
66
73
|
system: request.system,
|
|
67
74
|
user: request.user,
|
|
68
75
|
tools: [],
|
|
69
76
|
context: NO_CONTEXT,
|
|
70
77
|
maxSteps: 1,
|
|
71
78
|
timeoutMs: request.timeoutMs,
|
|
72
|
-
sampling: this.sampling,
|
|
73
79
|
signal,
|
|
74
80
|
}),
|
|
75
81
|
});
|
|
@@ -93,6 +99,16 @@ export class DirectRuntime {
|
|
|
93
99
|
}
|
|
94
100
|
return undefined;
|
|
95
101
|
}
|
|
102
|
+
// What one attempt sends besides the conversation, and where a refused
|
|
103
|
+
// effort is recorded.
|
|
104
|
+
call({ agent, effort, model }) {
|
|
105
|
+
const { providerID } = parseModel(model);
|
|
106
|
+
const style = this.options.providers?.[providerID]?.effort;
|
|
107
|
+
return {
|
|
108
|
+
params: this.efforts.params(agent, effort, model, style),
|
|
109
|
+
onEffortRefused: () => this.efforts.refuse(agent, model),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
96
112
|
target(model) {
|
|
97
113
|
const { providerID, modelID } = parseModel(model);
|
|
98
114
|
const provider = this.options.providers?.[providerID];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-cr-agent/runtime-direct",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Agent runtime for Open-CR-Agent that calls OpenAI-compatible endpoints directly",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"code-review",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"agents"
|
|
9
9
|
],
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
|
-
"homepage": "https://
|
|
11
|
+
"homepage": "https://ocracloud.com",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
14
|
"url": "git+https://github.com/jma49/Open-CR-Agent.git",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"!dist/**/*.map"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@open-cr-agent/core": "0.
|
|
36
|
+
"@open-cr-agent/core": "0.4.0",
|
|
37
37
|
"zod": "^4.6.5"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|