@northflare/runner 0.0.8 → 0.0.10
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/components/claude-sdk-manager.d.ts +1 -1
- package/dist/components/claude-sdk-manager.d.ts.map +1 -1
- package/dist/components/claude-sdk-manager.js +30 -16
- package/dist/components/claude-sdk-manager.js.map +1 -1
- package/dist/components/codex-sdk-manager.d.ts +60 -0
- package/dist/components/codex-sdk-manager.d.ts.map +1 -0
- package/dist/components/codex-sdk-manager.js +988 -0
- package/dist/components/codex-sdk-manager.js.map +1 -0
- package/dist/components/message-handler-sse.d.ts +3 -0
- package/dist/components/message-handler-sse.d.ts.map +1 -1
- package/dist/components/message-handler-sse.js +66 -21
- package/dist/components/message-handler-sse.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/runner-sse.d.ts +4 -0
- package/dist/runner-sse.d.ts.map +1 -1
- package/dist/runner-sse.js +63 -20
- package/dist/runner-sse.js.map +1 -1
- package/dist/runner.js +3 -3
- package/dist/types/claude.d.ts +11 -1
- package/dist/types/claude.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/runner-interface.d.ts +2 -0
- package/dist/types/runner-interface.d.ts.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +1 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/console.d.ts.map +1 -1
- package/dist/utils/console.js +2 -1
- package/dist/utils/console.js.map +1 -1
- package/dist/utils/debug.d.ts +2 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/debug.js +19 -0
- package/dist/utils/debug.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +6 -4
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/model.d.ts +6 -0
- package/dist/utils/model.d.ts.map +1 -0
- package/dist/utils/model.js +23 -0
- package/dist/utils/model.js.map +1 -0
- package/dist/utils/status-line.d.ts +0 -8
- package/dist/utils/status-line.d.ts.map +1 -1
- package/dist/utils/status-line.js +9 -8
- package/dist/utils/status-line.js.map +1 -1
- package/dist/utils/tool-response-sanitizer.d.ts +9 -0
- package/dist/utils/tool-response-sanitizer.d.ts.map +1 -0
- package/dist/utils/tool-response-sanitizer.js +122 -0
- package/dist/utils/tool-response-sanitizer.js.map +1 -0
- package/exceptions.log +2 -0
- package/lib/codex-sdk/.prettierignore +3 -0
- package/lib/codex-sdk/.prettierrc +5 -0
- package/lib/codex-sdk/README.md +133 -0
- package/lib/codex-sdk/dist/index.d.ts +260 -0
- package/lib/codex-sdk/dist/index.js +426 -0
- package/lib/codex-sdk/eslint.config.js +21 -0
- package/lib/codex-sdk/jest.config.cjs +31 -0
- package/lib/codex-sdk/package.json +65 -0
- package/lib/codex-sdk/samples/basic_streaming.ts +90 -0
- package/lib/codex-sdk/samples/helpers.ts +8 -0
- package/lib/codex-sdk/samples/structured_output.ts +22 -0
- package/lib/codex-sdk/samples/structured_output_zod.ts +19 -0
- package/lib/codex-sdk/src/codex.ts +38 -0
- package/lib/codex-sdk/src/codexOptions.ts +10 -0
- package/lib/codex-sdk/src/events.ts +80 -0
- package/lib/codex-sdk/src/exec.ts +336 -0
- package/lib/codex-sdk/src/index.ts +39 -0
- package/lib/codex-sdk/src/items.ts +127 -0
- package/lib/codex-sdk/src/outputSchemaFile.ts +40 -0
- package/lib/codex-sdk/src/thread.ts +155 -0
- package/lib/codex-sdk/src/threadOptions.ts +18 -0
- package/lib/codex-sdk/src/turnOptions.ts +6 -0
- package/lib/codex-sdk/tests/abort.test.ts +165 -0
- package/lib/codex-sdk/tests/codexExecSpy.ts +37 -0
- package/lib/codex-sdk/tests/responsesProxy.ts +225 -0
- package/lib/codex-sdk/tests/run.test.ts +687 -0
- package/lib/codex-sdk/tests/runStreamed.test.ts +211 -0
- package/lib/codex-sdk/tsconfig.json +24 -0
- package/lib/codex-sdk/tsup.config.ts +12 -0
- package/package.json +3 -1
- package/rejections.log +5 -0
- package/src/components/claude-sdk-manager.ts +38 -17
- package/src/components/codex-sdk-manager.ts +1349 -0
- package/src/components/message-handler-sse.ts +94 -22
- package/src/index.ts +4 -2
- package/src/runner-sse.ts +75 -20
- package/src/types/claude.ts +12 -1
- package/src/types/index.ts +1 -0
- package/src/types/runner-interface.ts +3 -1
- package/src/utils/config.ts +1 -0
- package/src/utils/console.ts +4 -2
- package/src/utils/debug.ts +18 -0
- package/src/utils/logger.ts +8 -5
- package/src/utils/model.ts +29 -0
- package/src/utils/status-line.ts +9 -8
- package/src/utils/tool-response-sanitizer.ts +160 -0
- package/tests/tool-response-sanitizer.test.ts +63 -0
- package/src/utils/codex-sdk.js +0 -448
- package/src/utils/sdk-demo.js +0 -34
package/src/utils/logger.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import winston from "winston";
|
|
6
6
|
import path from "path";
|
|
7
|
+
import { isRunnerDebugEnabled } from "./debug";
|
|
7
8
|
|
|
8
9
|
// Custom log levels
|
|
9
10
|
const levels = {
|
|
@@ -29,6 +30,8 @@ const colors = {
|
|
|
29
30
|
|
|
30
31
|
winston.addColors(colors);
|
|
31
32
|
|
|
33
|
+
const debugEnabled = isRunnerDebugEnabled();
|
|
34
|
+
|
|
32
35
|
// Format for console output
|
|
33
36
|
const consoleFormat = winston.format.combine(
|
|
34
37
|
winston.format.colorize({ all: true }),
|
|
@@ -36,7 +39,7 @@ const consoleFormat = winston.format.combine(
|
|
|
36
39
|
winston.format.printf(({ timestamp, level, message, stack, ...metadata }) => {
|
|
37
40
|
let msg = `${timestamp} [${level}]: ${message}`;
|
|
38
41
|
// Only show stack traces and metadata in debug mode
|
|
39
|
-
if (
|
|
42
|
+
if (debugEnabled) {
|
|
40
43
|
if (stack) {
|
|
41
44
|
msg += `\n${stack}`;
|
|
42
45
|
}
|
|
@@ -59,13 +62,13 @@ const fileFormat = winston.format.combine(
|
|
|
59
62
|
|
|
60
63
|
// Create logger instance
|
|
61
64
|
export const logger = winston.createLogger({
|
|
62
|
-
level:
|
|
65
|
+
level: debugEnabled ? "debug" : "info",
|
|
63
66
|
levels,
|
|
64
67
|
transports: [
|
|
65
68
|
// Always include console transport for errors
|
|
66
69
|
new winston.transports.Console({
|
|
67
70
|
format: consoleFormat,
|
|
68
|
-
level:
|
|
71
|
+
level: debugEnabled ? "debug" : "error",
|
|
69
72
|
}),
|
|
70
73
|
],
|
|
71
74
|
});
|
|
@@ -94,7 +97,7 @@ export function configureFileLogging(logDir: string): void {
|
|
|
94
97
|
);
|
|
95
98
|
|
|
96
99
|
// Debug log file (only in debug mode)
|
|
97
|
-
if (
|
|
100
|
+
if (debugEnabled) {
|
|
98
101
|
logger.add(
|
|
99
102
|
new winston.transports.File({
|
|
100
103
|
filename: path.join(logDir, "debug.log"),
|
|
@@ -128,4 +131,4 @@ logger.rejections.handle(
|
|
|
128
131
|
);
|
|
129
132
|
|
|
130
133
|
// Export default logger
|
|
131
|
-
export default logger;
|
|
134
|
+
export default logger;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type ModelReasoningEffort = "low" | "medium" | "high";
|
|
2
|
+
|
|
3
|
+
const REASONING_VALUES: readonly ModelReasoningEffort[] = [
|
|
4
|
+
"low",
|
|
5
|
+
"medium",
|
|
6
|
+
"high",
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
export function parseModelValue(value?: string | null): {
|
|
10
|
+
baseModel?: string;
|
|
11
|
+
reasoningEffort?: ModelReasoningEffort;
|
|
12
|
+
} {
|
|
13
|
+
if (!value) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const [model, possibleEffort] = value.split(":");
|
|
18
|
+
if (
|
|
19
|
+
possibleEffort &&
|
|
20
|
+
REASONING_VALUES.includes(possibleEffort as ModelReasoningEffort)
|
|
21
|
+
) {
|
|
22
|
+
return {
|
|
23
|
+
baseModel: model,
|
|
24
|
+
reasoningEffort: possibleEffort as ModelReasoningEffort,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { baseModel: value };
|
|
29
|
+
}
|
package/src/utils/status-line.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* StatusLineManager - Manages persistent status line output for active
|
|
3
|
-
*
|
|
2
|
+
* StatusLineManager - Manages persistent status line output for active agent processes
|
|
3
|
+
*
|
|
4
4
|
* This component maintains a single-line status output that updates every minute to show
|
|
5
|
-
* the count of active
|
|
5
|
+
* the count of active agent processes. It prevents the machine from entering idle
|
|
6
6
|
* state while processes are running and provides clear visual feedback without cluttering
|
|
7
7
|
* the terminal with multiple log lines.
|
|
8
8
|
*/
|
|
9
|
+
import { isRunnerDebugEnabled } from "./debug";
|
|
9
10
|
|
|
10
11
|
export class StatusLineManager {
|
|
11
12
|
private intervalId?: NodeJS.Timeout;
|
|
@@ -15,7 +16,7 @@ export class StatusLineManager {
|
|
|
15
16
|
|
|
16
17
|
constructor() {
|
|
17
18
|
// Only enable status line when not in debug mode
|
|
18
|
-
this.isEnabled =
|
|
19
|
+
this.isEnabled = !isRunnerDebugEnabled();
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -84,11 +85,11 @@ export class StatusLineManager {
|
|
|
84
85
|
|
|
85
86
|
let newLine: string;
|
|
86
87
|
if (this.activeCount === 0) {
|
|
87
|
-
newLine = "No active
|
|
88
|
+
newLine = "No active agent processes";
|
|
88
89
|
} else if (this.activeCount === 1) {
|
|
89
|
-
newLine = `${timeStr} 1 active
|
|
90
|
+
newLine = `${timeStr} 1 active agent process`;
|
|
90
91
|
} else {
|
|
91
|
-
newLine = `${timeStr} ${this.activeCount} active
|
|
92
|
+
newLine = `${timeStr} ${this.activeCount} active agent processes`;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
// If this is the first status line, position it
|
|
@@ -118,4 +119,4 @@ export class StatusLineManager {
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
// Singleton instance
|
|
121
|
-
export const statusLineManager = new StatusLineManager();
|
|
122
|
+
export const statusLineManager = new StatusLineManager();
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
export const TOOL_RESPONSE_BYTE_LIMIT = 500 * 1024; // 500 KiB cap per tool response payload
|
|
2
|
+
export const TOOL_RESPONSE_SUFFIX = "... [truncated]";
|
|
3
|
+
|
|
4
|
+
export interface ToolResponseSanitizationResult {
|
|
5
|
+
params: any;
|
|
6
|
+
truncated: boolean;
|
|
7
|
+
toolUseId?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface TruncationContext {
|
|
11
|
+
bytesRemaining: number;
|
|
12
|
+
truncated: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function sanitizeToolResponsePayload(
|
|
16
|
+
method: string | undefined,
|
|
17
|
+
params: any
|
|
18
|
+
): ToolResponseSanitizationResult {
|
|
19
|
+
if (method !== "message.agent" || !params) {
|
|
20
|
+
return { params, truncated: false };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!isToolResultPayload(params)) {
|
|
24
|
+
return { params, truncated: false };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const context: TruncationContext = {
|
|
28
|
+
bytesRemaining: TOOL_RESPONSE_BYTE_LIMIT,
|
|
29
|
+
truncated: false,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const [sanitized, changed] = truncateStringsRecursively(params, context);
|
|
33
|
+
|
|
34
|
+
if (!changed && !context.truncated) {
|
|
35
|
+
return { params, truncated: false };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
params: sanitized,
|
|
40
|
+
truncated: true,
|
|
41
|
+
toolUseId: extractToolUseId(params),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isToolResultPayload(payload: any): boolean {
|
|
46
|
+
if (!payload) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (payload.type === "tool_result" || payload.subtype === "tool_result") {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(payload.content)) {
|
|
53
|
+
return payload.content.some((entry: any) => isToolResultBlock(entry));
|
|
54
|
+
}
|
|
55
|
+
return isToolResultBlock(payload.content);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isToolResultBlock(block: any): boolean {
|
|
59
|
+
if (!block) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
return (
|
|
63
|
+
block.type === "tool_result" ||
|
|
64
|
+
block.subtype === "tool_result" ||
|
|
65
|
+
typeof block.tool_use_id === "string"
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function extractToolUseId(payload: any): string | undefined {
|
|
70
|
+
if (!payload) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
if (typeof payload.tool_use_id === "string") {
|
|
74
|
+
return payload.tool_use_id;
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(payload.content)) {
|
|
77
|
+
const match = payload.content.find(
|
|
78
|
+
(entry: any) => entry && typeof entry.tool_use_id === "string"
|
|
79
|
+
);
|
|
80
|
+
if (match) {
|
|
81
|
+
return match.tool_use_id;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (payload.content && typeof payload.content === "object") {
|
|
85
|
+
return extractToolUseId(payload.content);
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function truncateStringsRecursively(
|
|
91
|
+
value: any,
|
|
92
|
+
context: TruncationContext
|
|
93
|
+
): [any, boolean] {
|
|
94
|
+
if (typeof value === "string") {
|
|
95
|
+
return truncateStringValue(value, context);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (Array.isArray(value)) {
|
|
99
|
+
let mutated = false;
|
|
100
|
+
let result: any[] = value;
|
|
101
|
+
for (let i = 0; i < value.length; i++) {
|
|
102
|
+
const [newItem, changed] = truncateStringsRecursively(value[i], context);
|
|
103
|
+
if (changed) {
|
|
104
|
+
if (!mutated) {
|
|
105
|
+
result = value.slice();
|
|
106
|
+
mutated = true;
|
|
107
|
+
}
|
|
108
|
+
result[i] = newItem;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return [result, mutated];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (value && typeof value === "object") {
|
|
115
|
+
let mutated = false;
|
|
116
|
+
let result: Record<string, any> = value;
|
|
117
|
+
for (const key of Object.keys(value)) {
|
|
118
|
+
const [newVal, changed] = truncateStringsRecursively(
|
|
119
|
+
value[key],
|
|
120
|
+
context
|
|
121
|
+
);
|
|
122
|
+
if (changed) {
|
|
123
|
+
if (!mutated) {
|
|
124
|
+
result = { ...value };
|
|
125
|
+
mutated = true;
|
|
126
|
+
}
|
|
127
|
+
result[key] = newVal;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return [result, mutated];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return [value, false];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function truncateStringValue(
|
|
137
|
+
value: string,
|
|
138
|
+
context: TruncationContext
|
|
139
|
+
): [string, boolean] {
|
|
140
|
+
if (context.bytesRemaining <= 0) {
|
|
141
|
+
if (value.length === 0) {
|
|
142
|
+
return [value, false];
|
|
143
|
+
}
|
|
144
|
+
context.truncated = true;
|
|
145
|
+
return ["", true];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const byteLength = Buffer.byteLength(value, "utf8");
|
|
149
|
+
if (byteLength <= context.bytesRemaining) {
|
|
150
|
+
context.bytesRemaining -= byteLength;
|
|
151
|
+
return [value, false];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const suffixBytes = Buffer.byteLength(TOOL_RESPONSE_SUFFIX, "utf8");
|
|
155
|
+
const maxBytes = Math.max(0, context.bytesRemaining - suffixBytes);
|
|
156
|
+
const truncatedBuffer = Buffer.from(value, "utf8").subarray(0, maxBytes);
|
|
157
|
+
context.bytesRemaining = 0;
|
|
158
|
+
context.truncated = true;
|
|
159
|
+
return [truncatedBuffer.toString("utf8") + TOOL_RESPONSE_SUFFIX, true];
|
|
160
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
sanitizeToolResponsePayload,
|
|
4
|
+
TOOL_RESPONSE_BYTE_LIMIT,
|
|
5
|
+
TOOL_RESPONSE_SUFFIX,
|
|
6
|
+
} from "../src/utils/tool-response-sanitizer";
|
|
7
|
+
|
|
8
|
+
describe("sanitizeToolResponsePayload", () => {
|
|
9
|
+
it("returns original params for non message.agent notifications", () => {
|
|
10
|
+
const payload = { type: "tool_result", content: [] };
|
|
11
|
+
const result = sanitizeToolResponsePayload("runner.heartbeat", payload);
|
|
12
|
+
expect(result.truncated).toBe(false);
|
|
13
|
+
expect(result.params).toBe(payload);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("truncates oversized tool_result strings", () => {
|
|
17
|
+
const longText = "x".repeat(TOOL_RESPONSE_BYTE_LIMIT + 128);
|
|
18
|
+
const payload = {
|
|
19
|
+
type: "tool_result",
|
|
20
|
+
tool_use_id: "tool-123",
|
|
21
|
+
content: [{ type: "text", text: longText }],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const result = sanitizeToolResponsePayload("message.agent", payload);
|
|
25
|
+
expect(result.truncated).toBe(true);
|
|
26
|
+
expect(result.toolUseId).toBe("tool-123");
|
|
27
|
+
|
|
28
|
+
const sanitizedText = result.params.content[0].text as string;
|
|
29
|
+
expect(sanitizedText.endsWith(TOOL_RESPONSE_SUFFIX)).toBe(true);
|
|
30
|
+
expect(Buffer.byteLength(sanitizedText, "utf8")).toBeLessThanOrEqual(
|
|
31
|
+
TOOL_RESPONSE_BYTE_LIMIT + Buffer.byteLength(TOOL_RESPONSE_SUFFIX, "utf8")
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Ensure original payload was not mutated
|
|
35
|
+
expect(payload.content[0].text).toBe(longText);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("enforces the byte budget across multiple string blocks", () => {
|
|
39
|
+
const firstBlockBytes = 400 * 1024;
|
|
40
|
+
const secondBlockBytes = 200 * 1024;
|
|
41
|
+
const payload = {
|
|
42
|
+
type: "tool_result",
|
|
43
|
+
content: [
|
|
44
|
+
{ type: "text", text: "a".repeat(firstBlockBytes) },
|
|
45
|
+
{ type: "text", text: "b".repeat(secondBlockBytes) },
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const result = sanitizeToolResponsePayload("message.agent", payload);
|
|
50
|
+
expect(result.truncated).toBe(true);
|
|
51
|
+
|
|
52
|
+
const first = result.params.content[0].text as string;
|
|
53
|
+
const second = result.params.content[1].text as string;
|
|
54
|
+
|
|
55
|
+
expect(Buffer.byteLength(first, "utf8")).toBe(firstBlockBytes);
|
|
56
|
+
expect(second.endsWith(TOOL_RESPONSE_SUFFIX)).toBe(true);
|
|
57
|
+
const combinedBytes =
|
|
58
|
+
Buffer.byteLength(first, "utf8") + Buffer.byteLength(second, "utf8");
|
|
59
|
+
expect(combinedBytes).toBeLessThanOrEqual(
|
|
60
|
+
TOOL_RESPONSE_BYTE_LIMIT + Buffer.byteLength(TOOL_RESPONSE_SUFFIX, "utf8")
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
});
|