@kolisachint/hoocode-agent 0.4.74 → 0.4.76
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/CHANGELOG.md +57 -0
- package/dist/cli/args.d.ts +0 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +0 -11
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-frontmatter.d.ts.map +1 -1
- package/dist/core/agent-frontmatter.js +2 -2
- package/dist/core/agent-frontmatter.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +0 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +0 -1
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -17
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +10 -199
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +7 -4
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/compaction/utils.d.ts.map +1 -1
- package/dist/core/compaction/utils.js +8 -5
- package/dist/core/compaction/utils.js.map +1 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +13 -4
- package/dist/core/exec.js.map +1 -1
- package/dist/core/model-categories.d.ts +28 -0
- package/dist/core/model-categories.d.ts.map +1 -0
- package/dist/core/model-categories.js +51 -0
- package/dist/core/model-categories.js.map +1 -0
- package/dist/core/model-registry.d.ts +0 -6
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +0 -32
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/sdk.d.ts +0 -6
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +0 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +16 -3
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +14 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/subagent-pool-instance.d.ts.map +1 -1
- package/dist/core/subagent-pool-instance.js +9 -1
- package/dist/core/subagent-pool-instance.js.map +1 -1
- package/dist/core/subagent-pool.d.ts +5 -0
- package/dist/core/subagent-pool.d.ts.map +1 -1
- package/dist/core/subagent-pool.js +13 -2
- package/dist/core/subagent-pool.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +3 -0
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +132 -40
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +7 -1
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/fd-utils.d.ts +23 -0
- package/dist/core/tools/fd-utils.d.ts.map +1 -0
- package/dist/core/tools/fd-utils.js +47 -0
- package/dist/core/tools/fd-utils.js.map +1 -0
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +15 -2
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +3 -24
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/glob.d.ts +42 -0
- package/dist/core/tools/glob.d.ts.map +1 -0
- package/dist/core/tools/glob.js +432 -0
- package/dist/core/tools/glob.js.map +1 -0
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +34 -19
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +4 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +13 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +12 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +58 -33
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +4 -0
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +39 -20
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/output-compression.d.ts +53 -0
- package/dist/core/tools/output-compression.d.ts.map +1 -0
- package/dist/core/tools/output-compression.js +392 -0
- package/dist/core/tools/output-compression.js.map +1 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +9 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +9 -2
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +4 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +0 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +2 -2
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/task-panel.d.ts.map +1 -1
- package/dist/modes/interactive/components/task-panel.js +10 -26
- package/dist/modes/interactive/components/task-panel.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +16 -0
- package/dist/utils/tools-manager.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
- package/templates/agents/explore.md +1 -1
- package/templates/agents/general-purpose.md +1 -1
- package/templates/agents/plan.md +1 -1
- package/dist/core/routing/local-inference.d.ts +0 -128
- package/dist/core/routing/local-inference.d.ts.map +0 -1
- package/dist/core/routing/local-inference.js +0 -145
- package/dist/core/routing/local-inference.js.map +0 -1
- package/dist/core/routing/metrics.d.ts +0 -27
- package/dist/core/routing/metrics.d.ts.map +0 -1
- package/dist/core/routing/metrics.js +0 -35
- package/dist/core/routing/metrics.js.map +0 -1
- package/dist/core/routing/mlx-server.d.ts +0 -43
- package/dist/core/routing/mlx-server.d.ts.map +0 -1
- package/dist/core/routing/mlx-server.js +0 -115
- package/dist/core/routing/mlx-server.js.map +0 -1
- package/dist/core/routing/tool-result-prompts.d.ts +0 -26
- package/dist/core/routing/tool-result-prompts.d.ts.map +0 -1
- package/dist/core/routing/tool-result-prompts.js +0 -45
- package/dist/core/routing/tool-result-prompts.js.map +0 -1
package/templates/agents/plan.md
CHANGED
|
@@ -15,7 +15,7 @@ description: |
|
|
|
15
15
|
Cost: Low–medium (read-only)
|
|
16
16
|
Isolation: Can run in parallel with explore tasks
|
|
17
17
|
tools: read, grep, find, ls
|
|
18
|
-
model:
|
|
18
|
+
model: standard
|
|
19
19
|
background: false
|
|
20
20
|
---
|
|
21
21
|
You are a planning agent running inside hoocode. You research the codebase and
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local-inference routing.
|
|
3
|
-
*
|
|
4
|
-
* Optional, opt-in routing of certain non-critical work (conversation
|
|
5
|
-
* compaction, and large bash tool-result compression) to a local
|
|
6
|
-
* "executor" model running on an OpenAI-compatible endpoint (for example an
|
|
7
|
-
* MLX server), while the primary model handles all planning, reasoning, edits,
|
|
8
|
-
* and tool-call synthesis.
|
|
9
|
-
*
|
|
10
|
-
* Everything here is INERT unless explicitly enabled via the
|
|
11
|
-
* `--enable-local-inference` flag or the `HOOCODE_ROUTING_MODE` env var. On any
|
|
12
|
-
* executor resolution/availability problem the caller falls back to the primary
|
|
13
|
-
* model (compaction) or the raw tool result (tool-result compression). The
|
|
14
|
-
* router never throws into the agent loop.
|
|
15
|
-
*
|
|
16
|
-
* Design and validation: see docs/local-executor-routing.md.
|
|
17
|
-
*/
|
|
18
|
-
import type { Api, Model } from "@kolisachint/hoocode-ai";
|
|
19
|
-
import type { ModelRegistry } from "../model-registry.js";
|
|
20
|
-
/** Work that may be routed to the executor instead of the primary model. */
|
|
21
|
-
export type TurnKind = "primary" | "summarization" | "tool-result";
|
|
22
|
-
export type RoutingMode = "primary-only" | "executor-for-summarization" | "executor-for-tool-results";
|
|
23
|
-
export declare const ROUTING_MODES: readonly RoutingMode[];
|
|
24
|
-
/**
|
|
25
|
-
* Tool names whose output is worth compressing (validated). Others pass
|
|
26
|
-
* through. Only `bash` qualifies: its verbose output is mostly low-value noise
|
|
27
|
-
* around a few load-bearing facts. `read` was measured to compress ~0% on real
|
|
28
|
-
* source code (every line is a keep-line) and was removed. Fact-list tools
|
|
29
|
-
* (grep/find/ls) were never compressible (every line is a distinct fact).
|
|
30
|
-
*/
|
|
31
|
-
export declare const COMPRESSIBLE_TOOLS: Set<string>;
|
|
32
|
-
/** Optional local server the harness manages for the executor. */
|
|
33
|
-
export interface ExecutorServerConfig {
|
|
34
|
-
/** Command to launch (default: "mlx_lm.server"). */
|
|
35
|
-
command?: string;
|
|
36
|
-
/** Extra args appended to the launch command. */
|
|
37
|
-
args?: string[];
|
|
38
|
-
/** Host to health-check and bind (default: derived from executor baseUrl or 127.0.0.1). */
|
|
39
|
-
host?: string;
|
|
40
|
-
/** Port to health-check and bind (default: derived from executor baseUrl or 8080). */
|
|
41
|
-
port?: number;
|
|
42
|
-
/** Max milliseconds to wait for the server to become healthy (default: 30000). */
|
|
43
|
-
startupTimeoutMs?: number;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Executor model reference as configured in models.json.
|
|
47
|
-
*
|
|
48
|
-
* The size band (`minBytes`/`maxBytes`) is wired entirely from config — there
|
|
49
|
-
* are no built-in byte defaults. When a bound is omitted it is not applied:
|
|
50
|
-
* `minBytes` defaults to 0 (no lower gate) and `maxBytes` to unbounded. Set
|
|
51
|
-
* both in models.json to gate which inputs route to the executor; on local
|
|
52
|
-
* hardware a `maxBytes` guards against slow runs / GPU OOM, while a hosted or
|
|
53
|
-
* large-memory executor can leave it unset (or high) to offload large inputs.
|
|
54
|
-
*/
|
|
55
|
-
export interface ExecutorConfig {
|
|
56
|
-
provider: string;
|
|
57
|
-
model: string;
|
|
58
|
-
/** Minimum input size (bytes) before local inference is attempted. Omitted = 0 (no lower gate). */
|
|
59
|
-
minBytes?: number;
|
|
60
|
-
/** Maximum input size (bytes); larger inputs fall back to the primary model. Omitted = unbounded. */
|
|
61
|
-
maxBytes?: number;
|
|
62
|
-
/** When set, the harness spawns/health-checks/stops this local server. */
|
|
63
|
-
server?: ExecutorServerConfig;
|
|
64
|
-
}
|
|
65
|
-
/** `routing` block in models.json. */
|
|
66
|
-
export interface RoutingConfig {
|
|
67
|
-
mode?: RoutingMode;
|
|
68
|
-
executor?: ExecutorConfig;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Resolve the effective routing mode from CLI flag, env var, and config.
|
|
72
|
-
*
|
|
73
|
-
* Activation requires either the flag or the env var; config alone never
|
|
74
|
-
* activates routing (decision: explicit opt-in only). When activated without an
|
|
75
|
-
* explicit mode, defaults to `executor-for-summarization` (the lowest-risk
|
|
76
|
-
* mode). When not activated, always `primary-only`.
|
|
77
|
-
*/
|
|
78
|
-
export declare function resolveRoutingMode(opts: {
|
|
79
|
-
enableFlag?: boolean;
|
|
80
|
-
envMode?: string;
|
|
81
|
-
configMode?: RoutingMode;
|
|
82
|
-
}): RoutingMode;
|
|
83
|
-
/**
|
|
84
|
-
* Router that decides, per turn kind, whether to use the executor model and
|
|
85
|
-
* resolves it from the registry. Holds no mutable state beyond the resolved
|
|
86
|
-
* executor model.
|
|
87
|
-
*/
|
|
88
|
-
export declare class LocalInferenceRouter {
|
|
89
|
-
private readonly mode;
|
|
90
|
-
private readonly executorConfig;
|
|
91
|
-
private readonly executor;
|
|
92
|
-
private readonly minBytes;
|
|
93
|
-
private readonly maxBytes;
|
|
94
|
-
private constructor();
|
|
95
|
-
static create(opts: {
|
|
96
|
-
mode: RoutingMode;
|
|
97
|
-
config: RoutingConfig | undefined;
|
|
98
|
-
registry: ModelRegistry;
|
|
99
|
-
}): LocalInferenceRouter;
|
|
100
|
-
getMode(): RoutingMode;
|
|
101
|
-
/** True when routing is active and an executor model is resolved and usable. */
|
|
102
|
-
isExecutorAvailable(): boolean;
|
|
103
|
-
getExecutorConfig(): ExecutorConfig | undefined;
|
|
104
|
-
/**
|
|
105
|
-
* Pick the model to use for a turn. Returns the executor when the mode routes
|
|
106
|
-
* that turn kind and the executor is available; otherwise returns the primary
|
|
107
|
-
* model.
|
|
108
|
-
*/
|
|
109
|
-
selectModel(turnKind: TurnKind, primary: Model<Api>): Model<Api>;
|
|
110
|
-
/** True when an input size falls within the configured local-inference band. */
|
|
111
|
-
withinSizeBand(bytes: number): boolean;
|
|
112
|
-
/** The configured size band, for logging/diagnostics. */
|
|
113
|
-
getSizeBand(): {
|
|
114
|
-
minBytes: number;
|
|
115
|
-
maxBytes: number;
|
|
116
|
-
};
|
|
117
|
-
/** Whether a given tool's result should be compressed via the executor. */
|
|
118
|
-
shouldCompressToolResult(toolName: string, contentBytes: number): boolean;
|
|
119
|
-
/**
|
|
120
|
-
* Whether to route summarization to the executor for a conversation of the
|
|
121
|
-
* given serialized size. Requires summarization routing active, the executor
|
|
122
|
-
* available, and the size within the band (oversized conversations fall back
|
|
123
|
-
* to the primary to avoid slow local runs and GPU OOM).
|
|
124
|
-
*/
|
|
125
|
-
shouldRouteSummarization(bytes: number): boolean;
|
|
126
|
-
getExecutorModel(): Model<Api> | undefined;
|
|
127
|
-
}
|
|
128
|
-
//# sourceMappingURL=local-inference.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"local-inference.d.ts","sourceRoot":"","sources":["../../../src/core/routing/local-inference.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local-inference routing.\n *\n * Optional, opt-in routing of certain non-critical work (conversation\n * compaction, and large bash tool-result compression) to a local\n * \"executor\" model running on an OpenAI-compatible endpoint (for example an\n * MLX server), while the primary model handles all planning, reasoning, edits,\n * and tool-call synthesis.\n *\n * Everything here is INERT unless explicitly enabled via the\n * `--enable-local-inference` flag or the `HOOCODE_ROUTING_MODE` env var. On any\n * executor resolution/availability problem the caller falls back to the primary\n * model (compaction) or the raw tool result (tool-result compression). The\n * router never throws into the agent loop.\n *\n * Design and validation: see docs/local-executor-routing.md.\n */\n\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport type { ModelRegistry } from \"../model-registry.js\";\n\n/** Work that may be routed to the executor instead of the primary model. */\nexport type TurnKind = \"primary\" | \"summarization\" | \"tool-result\";\n\nexport type RoutingMode = \"primary-only\" | \"executor-for-summarization\" | \"executor-for-tool-results\";\n\nexport const ROUTING_MODES: readonly RoutingMode[] = [\n\t\"primary-only\",\n\t\"executor-for-summarization\",\n\t\"executor-for-tool-results\",\n] as const;\n\n/**\n * Tool names whose output is worth compressing (validated). Others pass\n * through. Only `bash` qualifies: its verbose output is mostly low-value noise\n * around a few load-bearing facts. `read` was measured to compress ~0% on real\n * source code (every line is a keep-line) and was removed. Fact-list tools\n * (grep/find/ls) were never compressible (every line is a distinct fact).\n */\nexport const COMPRESSIBLE_TOOLS = new Set([\"bash\"]);\n\n/** Optional local server the harness manages for the executor. */\nexport interface ExecutorServerConfig {\n\t/** Command to launch (default: \"mlx_lm.server\"). */\n\tcommand?: string;\n\t/** Extra args appended to the launch command. */\n\targs?: string[];\n\t/** Host to health-check and bind (default: derived from executor baseUrl or 127.0.0.1). */\n\thost?: string;\n\t/** Port to health-check and bind (default: derived from executor baseUrl or 8080). */\n\tport?: number;\n\t/** Max milliseconds to wait for the server to become healthy (default: 30000). */\n\tstartupTimeoutMs?: number;\n}\n\n/**\n * Executor model reference as configured in models.json.\n *\n * The size band (`minBytes`/`maxBytes`) is wired entirely from config — there\n * are no built-in byte defaults. When a bound is omitted it is not applied:\n * `minBytes` defaults to 0 (no lower gate) and `maxBytes` to unbounded. Set\n * both in models.json to gate which inputs route to the executor; on local\n * hardware a `maxBytes` guards against slow runs / GPU OOM, while a hosted or\n * large-memory executor can leave it unset (or high) to offload large inputs.\n */\nexport interface ExecutorConfig {\n\tprovider: string;\n\tmodel: string;\n\t/** Minimum input size (bytes) before local inference is attempted. Omitted = 0 (no lower gate). */\n\tminBytes?: number;\n\t/** Maximum input size (bytes); larger inputs fall back to the primary model. Omitted = unbounded. */\n\tmaxBytes?: number;\n\t/** When set, the harness spawns/health-checks/stops this local server. */\n\tserver?: ExecutorServerConfig;\n}\n\n/** `routing` block in models.json. */\nexport interface RoutingConfig {\n\tmode?: RoutingMode;\n\texecutor?: ExecutorConfig;\n}\n\nfunction isRoutingMode(value: unknown): value is RoutingMode {\n\treturn typeof value === \"string\" && (ROUTING_MODES as readonly string[]).includes(value);\n}\n\n/**\n * Resolve the effective routing mode from CLI flag, env var, and config.\n *\n * Activation requires either the flag or the env var; config alone never\n * activates routing (decision: explicit opt-in only). When activated without an\n * explicit mode, defaults to `executor-for-summarization` (the lowest-risk\n * mode). When not activated, always `primary-only`.\n */\nexport function resolveRoutingMode(opts: {\n\tenableFlag?: boolean;\n\tenvMode?: string;\n\tconfigMode?: RoutingMode;\n}): RoutingMode {\n\tconst envMode = opts.envMode?.trim();\n\tconst envActivates = envMode !== undefined && envMode !== \"\" && envMode !== \"primary-only\";\n\tconst activated = opts.enableFlag === true || envActivates;\n\tif (!activated) return \"primary-only\";\n\n\tif (envMode && isRoutingMode(envMode)) return envMode;\n\tif (opts.configMode && isRoutingMode(opts.configMode)) return opts.configMode;\n\treturn \"executor-for-summarization\";\n}\n\n/**\n * Router that decides, per turn kind, whether to use the executor model and\n * resolves it from the registry. Holds no mutable state beyond the resolved\n * executor model.\n */\nexport class LocalInferenceRouter {\n\tprivate readonly mode: RoutingMode;\n\tprivate readonly executorConfig: ExecutorConfig | undefined;\n\tprivate readonly executor: Model<Api> | undefined;\n\tprivate readonly minBytes: number;\n\tprivate readonly maxBytes: number;\n\n\tprivate constructor(\n\t\tmode: RoutingMode,\n\t\texecutorConfig: ExecutorConfig | undefined,\n\t\texecutor: Model<Api> | undefined,\n\t) {\n\t\tthis.mode = mode;\n\t\tthis.executorConfig = executorConfig;\n\t\tthis.executor = executor;\n\t\t// No built-in byte defaults: an omitted bound is simply not applied\n\t\t// (min 0 = no lower gate, max +Infinity = unbounded). The band is wired\n\t\t// entirely from the models.json executor config.\n\t\tthis.minBytes = executorConfig?.minBytes ?? 0;\n\t\tthis.maxBytes = executorConfig?.maxBytes ?? Number.POSITIVE_INFINITY;\n\t}\n\n\tstatic create(opts: {\n\t\tmode: RoutingMode;\n\t\tconfig: RoutingConfig | undefined;\n\t\tregistry: ModelRegistry;\n\t}): LocalInferenceRouter {\n\t\tconst executorConfig = opts.config?.executor;\n\t\tlet executor: Model<Api> | undefined;\n\t\tif (opts.mode !== \"primary-only\" && executorConfig) {\n\t\t\texecutor = opts.registry.find(executorConfig.provider, executorConfig.model);\n\t\t}\n\t\treturn new LocalInferenceRouter(opts.mode, executorConfig, executor);\n\t}\n\n\tgetMode(): RoutingMode {\n\t\treturn this.mode;\n\t}\n\n\t/** True when routing is active and an executor model is resolved and usable. */\n\tisExecutorAvailable(): boolean {\n\t\treturn this.mode !== \"primary-only\" && this.executor !== undefined;\n\t}\n\n\tgetExecutorConfig(): ExecutorConfig | undefined {\n\t\treturn this.executorConfig;\n\t}\n\n\t/**\n\t * Pick the model to use for a turn. Returns the executor when the mode routes\n\t * that turn kind and the executor is available; otherwise returns the primary\n\t * model.\n\t */\n\tselectModel(turnKind: TurnKind, primary: Model<Api>): Model<Api> {\n\t\tif (!this.isExecutorAvailable() || !this.executor) return primary;\n\t\tswitch (this.mode) {\n\t\t\tcase \"executor-for-summarization\":\n\t\t\t\treturn turnKind === \"summarization\" ? this.executor : primary;\n\t\t\tcase \"executor-for-tool-results\":\n\t\t\t\treturn turnKind === \"tool-result\" ? this.executor : primary;\n\t\t\tcase \"primary-only\":\n\t\t\t\treturn primary;\n\t\t}\n\t}\n\n\t/** True when an input size falls within the configured local-inference band. */\n\twithinSizeBand(bytes: number): boolean {\n\t\treturn bytes >= this.minBytes && bytes <= this.maxBytes;\n\t}\n\n\t/** The configured size band, for logging/diagnostics. */\n\tgetSizeBand(): { minBytes: number; maxBytes: number } {\n\t\treturn { minBytes: this.minBytes, maxBytes: this.maxBytes };\n\t}\n\n\t/** Whether a given tool's result should be compressed via the executor. */\n\tshouldCompressToolResult(toolName: string, contentBytes: number): boolean {\n\t\tif (this.mode !== \"executor-for-tool-results\") return false;\n\t\tif (!this.isExecutorAvailable()) return false;\n\t\tif (!COMPRESSIBLE_TOOLS.has(toolName)) return false;\n\t\treturn this.withinSizeBand(contentBytes);\n\t}\n\n\t/**\n\t * Whether to route summarization to the executor for a conversation of the\n\t * given serialized size. Requires summarization routing active, the executor\n\t * available, and the size within the band (oversized conversations fall back\n\t * to the primary to avoid slow local runs and GPU OOM).\n\t */\n\tshouldRouteSummarization(bytes: number): boolean {\n\t\tif (this.mode !== \"executor-for-summarization\") return false;\n\t\tif (!this.isExecutorAvailable()) return false;\n\t\treturn this.withinSizeBand(bytes);\n\t}\n\n\tgetExecutorModel(): Model<Api> | undefined {\n\t\treturn this.executor;\n\t}\n}\n"]}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local-inference routing.
|
|
3
|
-
*
|
|
4
|
-
* Optional, opt-in routing of certain non-critical work (conversation
|
|
5
|
-
* compaction, and large bash tool-result compression) to a local
|
|
6
|
-
* "executor" model running on an OpenAI-compatible endpoint (for example an
|
|
7
|
-
* MLX server), while the primary model handles all planning, reasoning, edits,
|
|
8
|
-
* and tool-call synthesis.
|
|
9
|
-
*
|
|
10
|
-
* Everything here is INERT unless explicitly enabled via the
|
|
11
|
-
* `--enable-local-inference` flag or the `HOOCODE_ROUTING_MODE` env var. On any
|
|
12
|
-
* executor resolution/availability problem the caller falls back to the primary
|
|
13
|
-
* model (compaction) or the raw tool result (tool-result compression). The
|
|
14
|
-
* router never throws into the agent loop.
|
|
15
|
-
*
|
|
16
|
-
* Design and validation: see docs/local-executor-routing.md.
|
|
17
|
-
*/
|
|
18
|
-
export const ROUTING_MODES = [
|
|
19
|
-
"primary-only",
|
|
20
|
-
"executor-for-summarization",
|
|
21
|
-
"executor-for-tool-results",
|
|
22
|
-
];
|
|
23
|
-
/**
|
|
24
|
-
* Tool names whose output is worth compressing (validated). Others pass
|
|
25
|
-
* through. Only `bash` qualifies: its verbose output is mostly low-value noise
|
|
26
|
-
* around a few load-bearing facts. `read` was measured to compress ~0% on real
|
|
27
|
-
* source code (every line is a keep-line) and was removed. Fact-list tools
|
|
28
|
-
* (grep/find/ls) were never compressible (every line is a distinct fact).
|
|
29
|
-
*/
|
|
30
|
-
export const COMPRESSIBLE_TOOLS = new Set(["bash"]);
|
|
31
|
-
function isRoutingMode(value) {
|
|
32
|
-
return typeof value === "string" && ROUTING_MODES.includes(value);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Resolve the effective routing mode from CLI flag, env var, and config.
|
|
36
|
-
*
|
|
37
|
-
* Activation requires either the flag or the env var; config alone never
|
|
38
|
-
* activates routing (decision: explicit opt-in only). When activated without an
|
|
39
|
-
* explicit mode, defaults to `executor-for-summarization` (the lowest-risk
|
|
40
|
-
* mode). When not activated, always `primary-only`.
|
|
41
|
-
*/
|
|
42
|
-
export function resolveRoutingMode(opts) {
|
|
43
|
-
const envMode = opts.envMode?.trim();
|
|
44
|
-
const envActivates = envMode !== undefined && envMode !== "" && envMode !== "primary-only";
|
|
45
|
-
const activated = opts.enableFlag === true || envActivates;
|
|
46
|
-
if (!activated)
|
|
47
|
-
return "primary-only";
|
|
48
|
-
if (envMode && isRoutingMode(envMode))
|
|
49
|
-
return envMode;
|
|
50
|
-
if (opts.configMode && isRoutingMode(opts.configMode))
|
|
51
|
-
return opts.configMode;
|
|
52
|
-
return "executor-for-summarization";
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Router that decides, per turn kind, whether to use the executor model and
|
|
56
|
-
* resolves it from the registry. Holds no mutable state beyond the resolved
|
|
57
|
-
* executor model.
|
|
58
|
-
*/
|
|
59
|
-
export class LocalInferenceRouter {
|
|
60
|
-
mode;
|
|
61
|
-
executorConfig;
|
|
62
|
-
executor;
|
|
63
|
-
minBytes;
|
|
64
|
-
maxBytes;
|
|
65
|
-
constructor(mode, executorConfig, executor) {
|
|
66
|
-
this.mode = mode;
|
|
67
|
-
this.executorConfig = executorConfig;
|
|
68
|
-
this.executor = executor;
|
|
69
|
-
// No built-in byte defaults: an omitted bound is simply not applied
|
|
70
|
-
// (min 0 = no lower gate, max +Infinity = unbounded). The band is wired
|
|
71
|
-
// entirely from the models.json executor config.
|
|
72
|
-
this.minBytes = executorConfig?.minBytes ?? 0;
|
|
73
|
-
this.maxBytes = executorConfig?.maxBytes ?? Number.POSITIVE_INFINITY;
|
|
74
|
-
}
|
|
75
|
-
static create(opts) {
|
|
76
|
-
const executorConfig = opts.config?.executor;
|
|
77
|
-
let executor;
|
|
78
|
-
if (opts.mode !== "primary-only" && executorConfig) {
|
|
79
|
-
executor = opts.registry.find(executorConfig.provider, executorConfig.model);
|
|
80
|
-
}
|
|
81
|
-
return new LocalInferenceRouter(opts.mode, executorConfig, executor);
|
|
82
|
-
}
|
|
83
|
-
getMode() {
|
|
84
|
-
return this.mode;
|
|
85
|
-
}
|
|
86
|
-
/** True when routing is active and an executor model is resolved and usable. */
|
|
87
|
-
isExecutorAvailable() {
|
|
88
|
-
return this.mode !== "primary-only" && this.executor !== undefined;
|
|
89
|
-
}
|
|
90
|
-
getExecutorConfig() {
|
|
91
|
-
return this.executorConfig;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Pick the model to use for a turn. Returns the executor when the mode routes
|
|
95
|
-
* that turn kind and the executor is available; otherwise returns the primary
|
|
96
|
-
* model.
|
|
97
|
-
*/
|
|
98
|
-
selectModel(turnKind, primary) {
|
|
99
|
-
if (!this.isExecutorAvailable() || !this.executor)
|
|
100
|
-
return primary;
|
|
101
|
-
switch (this.mode) {
|
|
102
|
-
case "executor-for-summarization":
|
|
103
|
-
return turnKind === "summarization" ? this.executor : primary;
|
|
104
|
-
case "executor-for-tool-results":
|
|
105
|
-
return turnKind === "tool-result" ? this.executor : primary;
|
|
106
|
-
case "primary-only":
|
|
107
|
-
return primary;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
/** True when an input size falls within the configured local-inference band. */
|
|
111
|
-
withinSizeBand(bytes) {
|
|
112
|
-
return bytes >= this.minBytes && bytes <= this.maxBytes;
|
|
113
|
-
}
|
|
114
|
-
/** The configured size band, for logging/diagnostics. */
|
|
115
|
-
getSizeBand() {
|
|
116
|
-
return { minBytes: this.minBytes, maxBytes: this.maxBytes };
|
|
117
|
-
}
|
|
118
|
-
/** Whether a given tool's result should be compressed via the executor. */
|
|
119
|
-
shouldCompressToolResult(toolName, contentBytes) {
|
|
120
|
-
if (this.mode !== "executor-for-tool-results")
|
|
121
|
-
return false;
|
|
122
|
-
if (!this.isExecutorAvailable())
|
|
123
|
-
return false;
|
|
124
|
-
if (!COMPRESSIBLE_TOOLS.has(toolName))
|
|
125
|
-
return false;
|
|
126
|
-
return this.withinSizeBand(contentBytes);
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Whether to route summarization to the executor for a conversation of the
|
|
130
|
-
* given serialized size. Requires summarization routing active, the executor
|
|
131
|
-
* available, and the size within the band (oversized conversations fall back
|
|
132
|
-
* to the primary to avoid slow local runs and GPU OOM).
|
|
133
|
-
*/
|
|
134
|
-
shouldRouteSummarization(bytes) {
|
|
135
|
-
if (this.mode !== "executor-for-summarization")
|
|
136
|
-
return false;
|
|
137
|
-
if (!this.isExecutorAvailable())
|
|
138
|
-
return false;
|
|
139
|
-
return this.withinSizeBand(bytes);
|
|
140
|
-
}
|
|
141
|
-
getExecutorModel() {
|
|
142
|
-
return this.executor;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
//# sourceMappingURL=local-inference.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"local-inference.js","sourceRoot":"","sources":["../../../src/core/routing/local-inference.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local-inference routing.\n *\n * Optional, opt-in routing of certain non-critical work (conversation\n * compaction, and large bash tool-result compression) to a local\n * \"executor\" model running on an OpenAI-compatible endpoint (for example an\n * MLX server), while the primary model handles all planning, reasoning, edits,\n * and tool-call synthesis.\n *\n * Everything here is INERT unless explicitly enabled via the\n * `--enable-local-inference` flag or the `HOOCODE_ROUTING_MODE` env var. On any\n * executor resolution/availability problem the caller falls back to the primary\n * model (compaction) or the raw tool result (tool-result compression). The\n * router never throws into the agent loop.\n *\n * Design and validation: see docs/local-executor-routing.md.\n */\n\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport type { ModelRegistry } from \"../model-registry.js\";\n\n/** Work that may be routed to the executor instead of the primary model. */\nexport type TurnKind = \"primary\" | \"summarization\" | \"tool-result\";\n\nexport type RoutingMode = \"primary-only\" | \"executor-for-summarization\" | \"executor-for-tool-results\";\n\nexport const ROUTING_MODES: readonly RoutingMode[] = [\n\t\"primary-only\",\n\t\"executor-for-summarization\",\n\t\"executor-for-tool-results\",\n] as const;\n\n/**\n * Tool names whose output is worth compressing (validated). Others pass\n * through. Only `bash` qualifies: its verbose output is mostly low-value noise\n * around a few load-bearing facts. `read` was measured to compress ~0% on real\n * source code (every line is a keep-line) and was removed. Fact-list tools\n * (grep/find/ls) were never compressible (every line is a distinct fact).\n */\nexport const COMPRESSIBLE_TOOLS = new Set([\"bash\"]);\n\n/** Optional local server the harness manages for the executor. */\nexport interface ExecutorServerConfig {\n\t/** Command to launch (default: \"mlx_lm.server\"). */\n\tcommand?: string;\n\t/** Extra args appended to the launch command. */\n\targs?: string[];\n\t/** Host to health-check and bind (default: derived from executor baseUrl or 127.0.0.1). */\n\thost?: string;\n\t/** Port to health-check and bind (default: derived from executor baseUrl or 8080). */\n\tport?: number;\n\t/** Max milliseconds to wait for the server to become healthy (default: 30000). */\n\tstartupTimeoutMs?: number;\n}\n\n/**\n * Executor model reference as configured in models.json.\n *\n * The size band (`minBytes`/`maxBytes`) is wired entirely from config — there\n * are no built-in byte defaults. When a bound is omitted it is not applied:\n * `minBytes` defaults to 0 (no lower gate) and `maxBytes` to unbounded. Set\n * both in models.json to gate which inputs route to the executor; on local\n * hardware a `maxBytes` guards against slow runs / GPU OOM, while a hosted or\n * large-memory executor can leave it unset (or high) to offload large inputs.\n */\nexport interface ExecutorConfig {\n\tprovider: string;\n\tmodel: string;\n\t/** Minimum input size (bytes) before local inference is attempted. Omitted = 0 (no lower gate). */\n\tminBytes?: number;\n\t/** Maximum input size (bytes); larger inputs fall back to the primary model. Omitted = unbounded. */\n\tmaxBytes?: number;\n\t/** When set, the harness spawns/health-checks/stops this local server. */\n\tserver?: ExecutorServerConfig;\n}\n\n/** `routing` block in models.json. */\nexport interface RoutingConfig {\n\tmode?: RoutingMode;\n\texecutor?: ExecutorConfig;\n}\n\nfunction isRoutingMode(value: unknown): value is RoutingMode {\n\treturn typeof value === \"string\" && (ROUTING_MODES as readonly string[]).includes(value);\n}\n\n/**\n * Resolve the effective routing mode from CLI flag, env var, and config.\n *\n * Activation requires either the flag or the env var; config alone never\n * activates routing (decision: explicit opt-in only). When activated without an\n * explicit mode, defaults to `executor-for-summarization` (the lowest-risk\n * mode). When not activated, always `primary-only`.\n */\nexport function resolveRoutingMode(opts: {\n\tenableFlag?: boolean;\n\tenvMode?: string;\n\tconfigMode?: RoutingMode;\n}): RoutingMode {\n\tconst envMode = opts.envMode?.trim();\n\tconst envActivates = envMode !== undefined && envMode !== \"\" && envMode !== \"primary-only\";\n\tconst activated = opts.enableFlag === true || envActivates;\n\tif (!activated) return \"primary-only\";\n\n\tif (envMode && isRoutingMode(envMode)) return envMode;\n\tif (opts.configMode && isRoutingMode(opts.configMode)) return opts.configMode;\n\treturn \"executor-for-summarization\";\n}\n\n/**\n * Router that decides, per turn kind, whether to use the executor model and\n * resolves it from the registry. Holds no mutable state beyond the resolved\n * executor model.\n */\nexport class LocalInferenceRouter {\n\tprivate readonly mode: RoutingMode;\n\tprivate readonly executorConfig: ExecutorConfig | undefined;\n\tprivate readonly executor: Model<Api> | undefined;\n\tprivate readonly minBytes: number;\n\tprivate readonly maxBytes: number;\n\n\tprivate constructor(\n\t\tmode: RoutingMode,\n\t\texecutorConfig: ExecutorConfig | undefined,\n\t\texecutor: Model<Api> | undefined,\n\t) {\n\t\tthis.mode = mode;\n\t\tthis.executorConfig = executorConfig;\n\t\tthis.executor = executor;\n\t\t// No built-in byte defaults: an omitted bound is simply not applied\n\t\t// (min 0 = no lower gate, max +Infinity = unbounded). The band is wired\n\t\t// entirely from the models.json executor config.\n\t\tthis.minBytes = executorConfig?.minBytes ?? 0;\n\t\tthis.maxBytes = executorConfig?.maxBytes ?? Number.POSITIVE_INFINITY;\n\t}\n\n\tstatic create(opts: {\n\t\tmode: RoutingMode;\n\t\tconfig: RoutingConfig | undefined;\n\t\tregistry: ModelRegistry;\n\t}): LocalInferenceRouter {\n\t\tconst executorConfig = opts.config?.executor;\n\t\tlet executor: Model<Api> | undefined;\n\t\tif (opts.mode !== \"primary-only\" && executorConfig) {\n\t\t\texecutor = opts.registry.find(executorConfig.provider, executorConfig.model);\n\t\t}\n\t\treturn new LocalInferenceRouter(opts.mode, executorConfig, executor);\n\t}\n\n\tgetMode(): RoutingMode {\n\t\treturn this.mode;\n\t}\n\n\t/** True when routing is active and an executor model is resolved and usable. */\n\tisExecutorAvailable(): boolean {\n\t\treturn this.mode !== \"primary-only\" && this.executor !== undefined;\n\t}\n\n\tgetExecutorConfig(): ExecutorConfig | undefined {\n\t\treturn this.executorConfig;\n\t}\n\n\t/**\n\t * Pick the model to use for a turn. Returns the executor when the mode routes\n\t * that turn kind and the executor is available; otherwise returns the primary\n\t * model.\n\t */\n\tselectModel(turnKind: TurnKind, primary: Model<Api>): Model<Api> {\n\t\tif (!this.isExecutorAvailable() || !this.executor) return primary;\n\t\tswitch (this.mode) {\n\t\t\tcase \"executor-for-summarization\":\n\t\t\t\treturn turnKind === \"summarization\" ? this.executor : primary;\n\t\t\tcase \"executor-for-tool-results\":\n\t\t\t\treturn turnKind === \"tool-result\" ? this.executor : primary;\n\t\t\tcase \"primary-only\":\n\t\t\t\treturn primary;\n\t\t}\n\t}\n\n\t/** True when an input size falls within the configured local-inference band. */\n\twithinSizeBand(bytes: number): boolean {\n\t\treturn bytes >= this.minBytes && bytes <= this.maxBytes;\n\t}\n\n\t/** The configured size band, for logging/diagnostics. */\n\tgetSizeBand(): { minBytes: number; maxBytes: number } {\n\t\treturn { minBytes: this.minBytes, maxBytes: this.maxBytes };\n\t}\n\n\t/** Whether a given tool's result should be compressed via the executor. */\n\tshouldCompressToolResult(toolName: string, contentBytes: number): boolean {\n\t\tif (this.mode !== \"executor-for-tool-results\") return false;\n\t\tif (!this.isExecutorAvailable()) return false;\n\t\tif (!COMPRESSIBLE_TOOLS.has(toolName)) return false;\n\t\treturn this.withinSizeBand(contentBytes);\n\t}\n\n\t/**\n\t * Whether to route summarization to the executor for a conversation of the\n\t * given serialized size. Requires summarization routing active, the executor\n\t * available, and the size within the band (oversized conversations fall back\n\t * to the primary to avoid slow local runs and GPU OOM).\n\t */\n\tshouldRouteSummarization(bytes: number): boolean {\n\t\tif (this.mode !== \"executor-for-summarization\") return false;\n\t\tif (!this.isExecutorAvailable()) return false;\n\t\treturn this.withinSizeBand(bytes);\n\t}\n\n\tgetExecutorModel(): Model<Api> | undefined {\n\t\treturn this.executor;\n\t}\n}\n"]}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local-inference routing metrics and diagnostic logging.
|
|
3
|
-
*
|
|
4
|
-
* Routing is an optimization, so failures must never surface as errors in the
|
|
5
|
-
* agent UI. This module provides a single, quiet sink for routing diagnostics
|
|
6
|
-
* and per-turn metrics. Output is gated behind HOOCODE_ROUTING_DEBUG so normal
|
|
7
|
-
* sessions stay silent (decision: fall back, log silently, never hard-fail).
|
|
8
|
-
*/
|
|
9
|
-
import type { TurnKind } from "./local-inference.js";
|
|
10
|
-
/** Record that an executor turn failed and the caller fell back to primary/raw. */
|
|
11
|
-
export declare function logLocalInferenceFallback(turnKind: TurnKind, error: unknown): void;
|
|
12
|
-
/** Per-turn routing metrics for cost/perf comparison. */
|
|
13
|
-
export interface RoutingMetrics {
|
|
14
|
-
turnKind: TurnKind;
|
|
15
|
-
provider: string;
|
|
16
|
-
model: string;
|
|
17
|
-
inputTokens?: number;
|
|
18
|
-
outputTokens?: number;
|
|
19
|
-
latencyMs: number;
|
|
20
|
-
/** Bytes before/after for tool-result compression. */
|
|
21
|
-
bytesBefore?: number;
|
|
22
|
-
bytesAfter?: number;
|
|
23
|
-
fallback: boolean;
|
|
24
|
-
}
|
|
25
|
-
/** Record per-turn routing metrics (quiet unless HOOCODE_ROUTING_DEBUG). */
|
|
26
|
-
export declare function logRoutingMetrics(m: RoutingMetrics): void;
|
|
27
|
-
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../../src/core/routing/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAOrD,mFAAmF;AACnF,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAIlF;AAED,yDAAyD;AACzD,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,cAAc,GAAG,IAAI,CAYzD","sourcesContent":["/**\n * Local-inference routing metrics and diagnostic logging.\n *\n * Routing is an optimization, so failures must never surface as errors in the\n * agent UI. This module provides a single, quiet sink for routing diagnostics\n * and per-turn metrics. Output is gated behind HOOCODE_ROUTING_DEBUG so normal\n * sessions stay silent (decision: fall back, log silently, never hard-fail).\n */\n\nimport type { TurnKind } from \"./local-inference.js\";\n\nfunction debugEnabled(): boolean {\n\tconst v = process.env.HOOCODE_ROUTING_DEBUG;\n\treturn v === \"1\" || v === \"true\" || v === \"yes\";\n}\n\n/** Record that an executor turn failed and the caller fell back to primary/raw. */\nexport function logLocalInferenceFallback(turnKind: TurnKind, error: unknown): void {\n\tif (!debugEnabled()) return;\n\tconst message = error instanceof Error ? error.message : String(error);\n\tconsole.warn(`[local-inference] ${turnKind} fell back to primary: ${message}`);\n}\n\n/** Per-turn routing metrics for cost/perf comparison. */\nexport interface RoutingMetrics {\n\tturnKind: TurnKind;\n\tprovider: string;\n\tmodel: string;\n\tinputTokens?: number;\n\toutputTokens?: number;\n\tlatencyMs: number;\n\t/** Bytes before/after for tool-result compression. */\n\tbytesBefore?: number;\n\tbytesAfter?: number;\n\tfallback: boolean;\n}\n\n/** Record per-turn routing metrics (quiet unless HOOCODE_ROUTING_DEBUG). */\nexport function logRoutingMetrics(m: RoutingMetrics): void {\n\tif (!debugEnabled()) return;\n\tconst parts = [\n\t\t`turn=${m.turnKind}`,\n\t\t`model=${m.provider}/${m.model}`,\n\t\t`latency=${m.latencyMs}ms`,\n\t\tm.inputTokens !== undefined ? `in=${m.inputTokens}` : undefined,\n\t\tm.outputTokens !== undefined ? `out=${m.outputTokens}` : undefined,\n\t\tm.bytesBefore !== undefined && m.bytesAfter !== undefined ? `bytes=${m.bytesBefore}->${m.bytesAfter}` : undefined,\n\t\tm.fallback ? \"fallback=true\" : undefined,\n\t].filter((p): p is string => p !== undefined);\n\tconsole.error(`[local-inference] ${parts.join(\" \")}`);\n}\n"]}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local-inference routing metrics and diagnostic logging.
|
|
3
|
-
*
|
|
4
|
-
* Routing is an optimization, so failures must never surface as errors in the
|
|
5
|
-
* agent UI. This module provides a single, quiet sink for routing diagnostics
|
|
6
|
-
* and per-turn metrics. Output is gated behind HOOCODE_ROUTING_DEBUG so normal
|
|
7
|
-
* sessions stay silent (decision: fall back, log silently, never hard-fail).
|
|
8
|
-
*/
|
|
9
|
-
function debugEnabled() {
|
|
10
|
-
const v = process.env.HOOCODE_ROUTING_DEBUG;
|
|
11
|
-
return v === "1" || v === "true" || v === "yes";
|
|
12
|
-
}
|
|
13
|
-
/** Record that an executor turn failed and the caller fell back to primary/raw. */
|
|
14
|
-
export function logLocalInferenceFallback(turnKind, error) {
|
|
15
|
-
if (!debugEnabled())
|
|
16
|
-
return;
|
|
17
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
18
|
-
console.warn(`[local-inference] ${turnKind} fell back to primary: ${message}`);
|
|
19
|
-
}
|
|
20
|
-
/** Record per-turn routing metrics (quiet unless HOOCODE_ROUTING_DEBUG). */
|
|
21
|
-
export function logRoutingMetrics(m) {
|
|
22
|
-
if (!debugEnabled())
|
|
23
|
-
return;
|
|
24
|
-
const parts = [
|
|
25
|
-
`turn=${m.turnKind}`,
|
|
26
|
-
`model=${m.provider}/${m.model}`,
|
|
27
|
-
`latency=${m.latencyMs}ms`,
|
|
28
|
-
m.inputTokens !== undefined ? `in=${m.inputTokens}` : undefined,
|
|
29
|
-
m.outputTokens !== undefined ? `out=${m.outputTokens}` : undefined,
|
|
30
|
-
m.bytesBefore !== undefined && m.bytesAfter !== undefined ? `bytes=${m.bytesBefore}->${m.bytesAfter}` : undefined,
|
|
31
|
-
m.fallback ? "fallback=true" : undefined,
|
|
32
|
-
].filter((p) => p !== undefined);
|
|
33
|
-
console.error(`[local-inference] ${parts.join(" ")}`);
|
|
34
|
-
}
|
|
35
|
-
//# sourceMappingURL=metrics.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../../src/core/routing/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,SAAS,YAAY,GAAY;IAChC,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAC5C,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,KAAK,CAAC;AAAA,CAChD;AAED,mFAAmF;AACnF,MAAM,UAAU,yBAAyB,CAAC,QAAkB,EAAE,KAAc,EAAQ;IACnF,IAAI,CAAC,YAAY,EAAE;QAAE,OAAO;IAC5B,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,IAAI,CAAC,qBAAqB,QAAQ,0BAA0B,OAAO,EAAE,CAAC,CAAC;AAAA,CAC/E;AAgBD,4EAA4E;AAC5E,MAAM,UAAU,iBAAiB,CAAC,CAAiB,EAAQ;IAC1D,IAAI,CAAC,YAAY,EAAE;QAAE,OAAO;IAC5B,MAAM,KAAK,GAAG;QACb,QAAQ,CAAC,CAAC,QAAQ,EAAE;QACpB,SAAS,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,KAAK,EAAE;QAChC,WAAW,CAAC,CAAC,SAAS,IAAI;QAC1B,CAAC,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS;QAC/D,CAAC,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,SAAS;QAClE,CAAC,CAAC,WAAW,KAAK,SAAS,IAAI,CAAC,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,SAAS;QACjH,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS;KACxC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;IAC9C,OAAO,CAAC,KAAK,CAAC,qBAAqB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAAA,CACtD","sourcesContent":["/**\n * Local-inference routing metrics and diagnostic logging.\n *\n * Routing is an optimization, so failures must never surface as errors in the\n * agent UI. This module provides a single, quiet sink for routing diagnostics\n * and per-turn metrics. Output is gated behind HOOCODE_ROUTING_DEBUG so normal\n * sessions stay silent (decision: fall back, log silently, never hard-fail).\n */\n\nimport type { TurnKind } from \"./local-inference.js\";\n\nfunction debugEnabled(): boolean {\n\tconst v = process.env.HOOCODE_ROUTING_DEBUG;\n\treturn v === \"1\" || v === \"true\" || v === \"yes\";\n}\n\n/** Record that an executor turn failed and the caller fell back to primary/raw. */\nexport function logLocalInferenceFallback(turnKind: TurnKind, error: unknown): void {\n\tif (!debugEnabled()) return;\n\tconst message = error instanceof Error ? error.message : String(error);\n\tconsole.warn(`[local-inference] ${turnKind} fell back to primary: ${message}`);\n}\n\n/** Per-turn routing metrics for cost/perf comparison. */\nexport interface RoutingMetrics {\n\tturnKind: TurnKind;\n\tprovider: string;\n\tmodel: string;\n\tinputTokens?: number;\n\toutputTokens?: number;\n\tlatencyMs: number;\n\t/** Bytes before/after for tool-result compression. */\n\tbytesBefore?: number;\n\tbytesAfter?: number;\n\tfallback: boolean;\n}\n\n/** Record per-turn routing metrics (quiet unless HOOCODE_ROUTING_DEBUG). */\nexport function logRoutingMetrics(m: RoutingMetrics): void {\n\tif (!debugEnabled()) return;\n\tconst parts = [\n\t\t`turn=${m.turnKind}`,\n\t\t`model=${m.provider}/${m.model}`,\n\t\t`latency=${m.latencyMs}ms`,\n\t\tm.inputTokens !== undefined ? `in=${m.inputTokens}` : undefined,\n\t\tm.outputTokens !== undefined ? `out=${m.outputTokens}` : undefined,\n\t\tm.bytesBefore !== undefined && m.bytesAfter !== undefined ? `bytes=${m.bytesBefore}->${m.bytesAfter}` : undefined,\n\t\tm.fallback ? \"fallback=true\" : undefined,\n\t].filter((p): p is string => p !== undefined);\n\tconsole.error(`[local-inference] ${parts.join(\" \")}`);\n}\n"]}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local executor server lifecycle manager.
|
|
3
|
-
*
|
|
4
|
-
* When `routing.executor.server` is configured AND local-inference routing is
|
|
5
|
-
* active, the harness can spawn a local OpenAI-compatible server (for example
|
|
6
|
-
* `mlx_lm.server`), wait for it to become healthy, and stop it on shutdown.
|
|
7
|
-
*
|
|
8
|
-
* This is best-effort: if the server fails to start or never becomes healthy,
|
|
9
|
-
* `ensureStarted()` resolves to false and the caller degrades to the primary
|
|
10
|
-
* model (routing falls back). It never throws into the agent loop.
|
|
11
|
-
*/
|
|
12
|
-
import type { ExecutorServerConfig } from "./local-inference.js";
|
|
13
|
-
export interface MlxServerOptions {
|
|
14
|
-
config: ExecutorServerConfig;
|
|
15
|
-
/** Executor model id, passed as --model. */
|
|
16
|
-
modelId: string;
|
|
17
|
-
/** Executor baseUrl, used to derive host/port and health endpoint. */
|
|
18
|
-
baseUrl?: string;
|
|
19
|
-
}
|
|
20
|
-
export declare class MlxServerManager {
|
|
21
|
-
private readonly command;
|
|
22
|
-
private readonly args;
|
|
23
|
-
private readonly host;
|
|
24
|
-
private readonly port;
|
|
25
|
-
private readonly startupTimeoutMs;
|
|
26
|
-
private readonly modelId;
|
|
27
|
-
private child;
|
|
28
|
-
private startPromise;
|
|
29
|
-
constructor(opts: MlxServerOptions);
|
|
30
|
-
private healthUrl;
|
|
31
|
-
private isHealthy;
|
|
32
|
-
/**
|
|
33
|
-
* Ensure the server is running and healthy. Idempotent: concurrent callers
|
|
34
|
-
* share one start attempt. Returns false on any failure (caller degrades to
|
|
35
|
-
* primary). If a server is already healthy (user-started), reuses it without
|
|
36
|
-
* spawning.
|
|
37
|
-
*/
|
|
38
|
-
ensureStarted(signal?: AbortSignal): Promise<boolean>;
|
|
39
|
-
private start;
|
|
40
|
-
/** Stop the spawned server (no-op if we reused an external one). */
|
|
41
|
-
stop(): void;
|
|
42
|
-
}
|
|
43
|
-
//# sourceMappingURL=mlx-server.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mlx-server.d.ts","sourceRoot":"","sources":["../../../src/core/routing/mlx-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAoBjE,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,gBAAgB;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAW;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,YAAY,CAA+B;IAEnD,YAAY,IAAI,EAAE,gBAAgB,EAQjC;IAED,OAAO,CAAC,SAAS;YAIH,SAAS;IASvB;;;;;OAKG;IACG,aAAa,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAI1D;YAEa,KAAK;IAmCnB,oEAAoE;IACpE,IAAI,IAAI,IAAI,CAMX;CACD","sourcesContent":["/**\n * Local executor server lifecycle manager.\n *\n * When `routing.executor.server` is configured AND local-inference routing is\n * active, the harness can spawn a local OpenAI-compatible server (for example\n * `mlx_lm.server`), wait for it to become healthy, and stop it on shutdown.\n *\n * This is best-effort: if the server fails to start or never becomes healthy,\n * `ensureStarted()` resolves to false and the caller degrades to the primary\n * model (routing falls back). It never throws into the agent loop.\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport type { ExecutorServerConfig } from \"./local-inference.js\";\nimport { logLocalInferenceFallback } from \"./metrics.js\";\n\nconst DEFAULT_COMMAND = \"mlx_lm.server\";\nconst DEFAULT_HOST = \"127.0.0.1\";\nconst DEFAULT_PORT = 8080;\nconst DEFAULT_STARTUP_TIMEOUT_MS = 30_000;\nconst HEALTH_POLL_INTERVAL_MS = 500;\n\n/** Derive host/port from a baseUrl like \"http://127.0.0.1:8080/v1\". */\nfunction parseBaseUrl(baseUrl: string | undefined): { host?: string; port?: number } {\n\tif (!baseUrl) return {};\n\ttry {\n\t\tconst u = new URL(baseUrl);\n\t\treturn { host: u.hostname, port: u.port ? Number(u.port) : undefined };\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nexport interface MlxServerOptions {\n\tconfig: ExecutorServerConfig;\n\t/** Executor model id, passed as --model. */\n\tmodelId: string;\n\t/** Executor baseUrl, used to derive host/port and health endpoint. */\n\tbaseUrl?: string;\n}\n\nexport class MlxServerManager {\n\tprivate readonly command: string;\n\tprivate readonly args: string[];\n\tprivate readonly host: string;\n\tprivate readonly port: number;\n\tprivate readonly startupTimeoutMs: number;\n\tprivate readonly modelId: string;\n\tprivate child: ChildProcess | undefined;\n\tprivate startPromise: Promise<boolean> | undefined;\n\n\tconstructor(opts: MlxServerOptions) {\n\t\tconst fromUrl = parseBaseUrl(opts.baseUrl);\n\t\tthis.command = opts.config.command ?? DEFAULT_COMMAND;\n\t\tthis.args = opts.config.args ?? [];\n\t\tthis.host = opts.config.host ?? fromUrl.host ?? DEFAULT_HOST;\n\t\tthis.port = opts.config.port ?? fromUrl.port ?? DEFAULT_PORT;\n\t\tthis.startupTimeoutMs = opts.config.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;\n\t\tthis.modelId = opts.modelId;\n\t}\n\n\tprivate healthUrl(): string {\n\t\treturn `http://${this.host}:${this.port}/v1/models`;\n\t}\n\n\tprivate async isHealthy(signal?: AbortSignal): Promise<boolean> {\n\t\ttry {\n\t\t\tconst res = await fetch(this.healthUrl(), { method: \"GET\", signal });\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Ensure the server is running and healthy. Idempotent: concurrent callers\n\t * share one start attempt. Returns false on any failure (caller degrades to\n\t * primary). If a server is already healthy (user-started), reuses it without\n\t * spawning.\n\t */\n\tasync ensureStarted(signal?: AbortSignal): Promise<boolean> {\n\t\tif (this.startPromise) return this.startPromise;\n\t\tthis.startPromise = this.start(signal);\n\t\treturn this.startPromise;\n\t}\n\n\tprivate async start(signal?: AbortSignal): Promise<boolean> {\n\t\t// Reuse an already-running server (e.g. user-started) without spawning.\n\t\tif (await this.isHealthy(signal)) return true;\n\n\t\ttry {\n\t\t\tthis.child = spawn(\n\t\t\t\tthis.command,\n\t\t\t\t[...this.args, \"--model\", this.modelId, \"--host\", this.host, \"--port\", String(this.port)],\n\t\t\t\t{\n\t\t\t\t\tstdio: \"ignore\",\n\t\t\t\t\tdetached: false,\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.child.on(\"error\", (error) => {\n\t\t\t\tlogLocalInferenceFallback(\"primary\", error);\n\t\t\t\tthis.child = undefined;\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlogLocalInferenceFallback(\"primary\", error);\n\t\t\tthis.child = undefined;\n\t\t\treturn false;\n\t\t}\n\n\t\tconst deadline = Date.now() + this.startupTimeoutMs;\n\t\twhile (Date.now() < deadline) {\n\t\t\tif (signal?.aborted) return false;\n\t\t\tif (!this.child) return false; // spawn errored\n\t\t\tif (await this.isHealthy(signal)) return true;\n\t\t\tawait new Promise((r) => setTimeout(r, HEALTH_POLL_INTERVAL_MS));\n\t\t}\n\t\t// Timed out: stop whatever we spawned and degrade.\n\t\tthis.stop();\n\t\treturn false;\n\t}\n\n\t/** Stop the spawned server (no-op if we reused an external one). */\n\tstop(): void {\n\t\tif (this.child && !this.child.killed) {\n\t\t\tthis.child.kill(\"SIGTERM\");\n\t\t}\n\t\tthis.child = undefined;\n\t\tthis.startPromise = undefined;\n\t}\n}\n"]}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local executor server lifecycle manager.
|
|
3
|
-
*
|
|
4
|
-
* When `routing.executor.server` is configured AND local-inference routing is
|
|
5
|
-
* active, the harness can spawn a local OpenAI-compatible server (for example
|
|
6
|
-
* `mlx_lm.server`), wait for it to become healthy, and stop it on shutdown.
|
|
7
|
-
*
|
|
8
|
-
* This is best-effort: if the server fails to start or never becomes healthy,
|
|
9
|
-
* `ensureStarted()` resolves to false and the caller degrades to the primary
|
|
10
|
-
* model (routing falls back). It never throws into the agent loop.
|
|
11
|
-
*/
|
|
12
|
-
import { spawn } from "node:child_process";
|
|
13
|
-
import { logLocalInferenceFallback } from "./metrics.js";
|
|
14
|
-
const DEFAULT_COMMAND = "mlx_lm.server";
|
|
15
|
-
const DEFAULT_HOST = "127.0.0.1";
|
|
16
|
-
const DEFAULT_PORT = 8080;
|
|
17
|
-
const DEFAULT_STARTUP_TIMEOUT_MS = 30_000;
|
|
18
|
-
const HEALTH_POLL_INTERVAL_MS = 500;
|
|
19
|
-
/** Derive host/port from a baseUrl like "http://127.0.0.1:8080/v1". */
|
|
20
|
-
function parseBaseUrl(baseUrl) {
|
|
21
|
-
if (!baseUrl)
|
|
22
|
-
return {};
|
|
23
|
-
try {
|
|
24
|
-
const u = new URL(baseUrl);
|
|
25
|
-
return { host: u.hostname, port: u.port ? Number(u.port) : undefined };
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return {};
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
export class MlxServerManager {
|
|
32
|
-
command;
|
|
33
|
-
args;
|
|
34
|
-
host;
|
|
35
|
-
port;
|
|
36
|
-
startupTimeoutMs;
|
|
37
|
-
modelId;
|
|
38
|
-
child;
|
|
39
|
-
startPromise;
|
|
40
|
-
constructor(opts) {
|
|
41
|
-
const fromUrl = parseBaseUrl(opts.baseUrl);
|
|
42
|
-
this.command = opts.config.command ?? DEFAULT_COMMAND;
|
|
43
|
-
this.args = opts.config.args ?? [];
|
|
44
|
-
this.host = opts.config.host ?? fromUrl.host ?? DEFAULT_HOST;
|
|
45
|
-
this.port = opts.config.port ?? fromUrl.port ?? DEFAULT_PORT;
|
|
46
|
-
this.startupTimeoutMs = opts.config.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
|
|
47
|
-
this.modelId = opts.modelId;
|
|
48
|
-
}
|
|
49
|
-
healthUrl() {
|
|
50
|
-
return `http://${this.host}:${this.port}/v1/models`;
|
|
51
|
-
}
|
|
52
|
-
async isHealthy(signal) {
|
|
53
|
-
try {
|
|
54
|
-
const res = await fetch(this.healthUrl(), { method: "GET", signal });
|
|
55
|
-
return res.ok;
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* Ensure the server is running and healthy. Idempotent: concurrent callers
|
|
63
|
-
* share one start attempt. Returns false on any failure (caller degrades to
|
|
64
|
-
* primary). If a server is already healthy (user-started), reuses it without
|
|
65
|
-
* spawning.
|
|
66
|
-
*/
|
|
67
|
-
async ensureStarted(signal) {
|
|
68
|
-
if (this.startPromise)
|
|
69
|
-
return this.startPromise;
|
|
70
|
-
this.startPromise = this.start(signal);
|
|
71
|
-
return this.startPromise;
|
|
72
|
-
}
|
|
73
|
-
async start(signal) {
|
|
74
|
-
// Reuse an already-running server (e.g. user-started) without spawning.
|
|
75
|
-
if (await this.isHealthy(signal))
|
|
76
|
-
return true;
|
|
77
|
-
try {
|
|
78
|
-
this.child = spawn(this.command, [...this.args, "--model", this.modelId, "--host", this.host, "--port", String(this.port)], {
|
|
79
|
-
stdio: "ignore",
|
|
80
|
-
detached: false,
|
|
81
|
-
});
|
|
82
|
-
this.child.on("error", (error) => {
|
|
83
|
-
logLocalInferenceFallback("primary", error);
|
|
84
|
-
this.child = undefined;
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
logLocalInferenceFallback("primary", error);
|
|
89
|
-
this.child = undefined;
|
|
90
|
-
return false;
|
|
91
|
-
}
|
|
92
|
-
const deadline = Date.now() + this.startupTimeoutMs;
|
|
93
|
-
while (Date.now() < deadline) {
|
|
94
|
-
if (signal?.aborted)
|
|
95
|
-
return false;
|
|
96
|
-
if (!this.child)
|
|
97
|
-
return false; // spawn errored
|
|
98
|
-
if (await this.isHealthy(signal))
|
|
99
|
-
return true;
|
|
100
|
-
await new Promise((r) => setTimeout(r, HEALTH_POLL_INTERVAL_MS));
|
|
101
|
-
}
|
|
102
|
-
// Timed out: stop whatever we spawned and degrade.
|
|
103
|
-
this.stop();
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
/** Stop the spawned server (no-op if we reused an external one). */
|
|
107
|
-
stop() {
|
|
108
|
-
if (this.child && !this.child.killed) {
|
|
109
|
-
this.child.kill("SIGTERM");
|
|
110
|
-
}
|
|
111
|
-
this.child = undefined;
|
|
112
|
-
this.startPromise = undefined;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
//# sourceMappingURL=mlx-server.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"mlx-server.js","sourceRoot":"","sources":["../../../src/core/routing/mlx-server.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Local executor server lifecycle manager.\n *\n * When `routing.executor.server` is configured AND local-inference routing is\n * active, the harness can spawn a local OpenAI-compatible server (for example\n * `mlx_lm.server`), wait for it to become healthy, and stop it on shutdown.\n *\n * This is best-effort: if the server fails to start or never becomes healthy,\n * `ensureStarted()` resolves to false and the caller degrades to the primary\n * model (routing falls back). It never throws into the agent loop.\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport type { ExecutorServerConfig } from \"./local-inference.js\";\nimport { logLocalInferenceFallback } from \"./metrics.js\";\n\nconst DEFAULT_COMMAND = \"mlx_lm.server\";\nconst DEFAULT_HOST = \"127.0.0.1\";\nconst DEFAULT_PORT = 8080;\nconst DEFAULT_STARTUP_TIMEOUT_MS = 30_000;\nconst HEALTH_POLL_INTERVAL_MS = 500;\n\n/** Derive host/port from a baseUrl like \"http://127.0.0.1:8080/v1\". */\nfunction parseBaseUrl(baseUrl: string | undefined): { host?: string; port?: number } {\n\tif (!baseUrl) return {};\n\ttry {\n\t\tconst u = new URL(baseUrl);\n\t\treturn { host: u.hostname, port: u.port ? Number(u.port) : undefined };\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nexport interface MlxServerOptions {\n\tconfig: ExecutorServerConfig;\n\t/** Executor model id, passed as --model. */\n\tmodelId: string;\n\t/** Executor baseUrl, used to derive host/port and health endpoint. */\n\tbaseUrl?: string;\n}\n\nexport class MlxServerManager {\n\tprivate readonly command: string;\n\tprivate readonly args: string[];\n\tprivate readonly host: string;\n\tprivate readonly port: number;\n\tprivate readonly startupTimeoutMs: number;\n\tprivate readonly modelId: string;\n\tprivate child: ChildProcess | undefined;\n\tprivate startPromise: Promise<boolean> | undefined;\n\n\tconstructor(opts: MlxServerOptions) {\n\t\tconst fromUrl = parseBaseUrl(opts.baseUrl);\n\t\tthis.command = opts.config.command ?? DEFAULT_COMMAND;\n\t\tthis.args = opts.config.args ?? [];\n\t\tthis.host = opts.config.host ?? fromUrl.host ?? DEFAULT_HOST;\n\t\tthis.port = opts.config.port ?? fromUrl.port ?? DEFAULT_PORT;\n\t\tthis.startupTimeoutMs = opts.config.startupTimeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;\n\t\tthis.modelId = opts.modelId;\n\t}\n\n\tprivate healthUrl(): string {\n\t\treturn `http://${this.host}:${this.port}/v1/models`;\n\t}\n\n\tprivate async isHealthy(signal?: AbortSignal): Promise<boolean> {\n\t\ttry {\n\t\t\tconst res = await fetch(this.healthUrl(), { method: \"GET\", signal });\n\t\t\treturn res.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * Ensure the server is running and healthy. Idempotent: concurrent callers\n\t * share one start attempt. Returns false on any failure (caller degrades to\n\t * primary). If a server is already healthy (user-started), reuses it without\n\t * spawning.\n\t */\n\tasync ensureStarted(signal?: AbortSignal): Promise<boolean> {\n\t\tif (this.startPromise) return this.startPromise;\n\t\tthis.startPromise = this.start(signal);\n\t\treturn this.startPromise;\n\t}\n\n\tprivate async start(signal?: AbortSignal): Promise<boolean> {\n\t\t// Reuse an already-running server (e.g. user-started) without spawning.\n\t\tif (await this.isHealthy(signal)) return true;\n\n\t\ttry {\n\t\t\tthis.child = spawn(\n\t\t\t\tthis.command,\n\t\t\t\t[...this.args, \"--model\", this.modelId, \"--host\", this.host, \"--port\", String(this.port)],\n\t\t\t\t{\n\t\t\t\t\tstdio: \"ignore\",\n\t\t\t\t\tdetached: false,\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis.child.on(\"error\", (error) => {\n\t\t\t\tlogLocalInferenceFallback(\"primary\", error);\n\t\t\t\tthis.child = undefined;\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlogLocalInferenceFallback(\"primary\", error);\n\t\t\tthis.child = undefined;\n\t\t\treturn false;\n\t\t}\n\n\t\tconst deadline = Date.now() + this.startupTimeoutMs;\n\t\twhile (Date.now() < deadline) {\n\t\t\tif (signal?.aborted) return false;\n\t\t\tif (!this.child) return false; // spawn errored\n\t\t\tif (await this.isHealthy(signal)) return true;\n\t\t\tawait new Promise((r) => setTimeout(r, HEALTH_POLL_INTERVAL_MS));\n\t\t}\n\t\t// Timed out: stop whatever we spawned and degrade.\n\t\tthis.stop();\n\t\treturn false;\n\t}\n\n\t/** Stop the spawned server (no-op if we reused an external one). */\n\tstop(): void {\n\t\tif (this.child && !this.child.killed) {\n\t\t\tthis.child.kill(\"SIGTERM\");\n\t\t}\n\t\tthis.child = undefined;\n\t\tthis.startPromise = undefined;\n\t}\n}\n"]}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validated per-tool extractive compression prompts.
|
|
3
|
-
*
|
|
4
|
-
* These prompts were validated against Qwen3-4B (see
|
|
5
|
-
* docs/local-executor-routing.md). Only `bash` output compresses safely: its
|
|
6
|
-
* verbose output is mostly low-value noise (progress/passing lines) around a
|
|
7
|
-
* few load-bearing facts (errors, counts, exit codes). `read` was removed after
|
|
8
|
-
* measurement showed ~0% reduction on real source code (every line is a
|
|
9
|
-
* keep-line). Fact-list outputs (grep/find/ls) are intentionally excluded:
|
|
10
|
-
* every line is a distinct fact, so compression drops matches.
|
|
11
|
-
*
|
|
12
|
-
* The prompt is extractive (keep identifiers, drop only redundant filler),
|
|
13
|
-
* not abstractive (rewrite in prose), which is what made retention reliable.
|
|
14
|
-
*/
|
|
15
|
-
export declare const TOOL_RESULT_SYSTEM_PROMPT: string;
|
|
16
|
-
/** Get the extractive compression prompt for a tool, or undefined if not compressible. */
|
|
17
|
-
export declare function getToolResultPrompt(toolName: string): string | undefined;
|
|
18
|
-
/** Build the full prompt text for compressing a tool result. */
|
|
19
|
-
export declare function buildToolResultPrompt(toolName: string, output: string): string | undefined;
|
|
20
|
-
/**
|
|
21
|
-
* Remove reasoning-model `<think>...</think>` blocks (including empty ones that
|
|
22
|
-
* Qwen3 emits even under `/no_think`) and trim the result. Reasoning models
|
|
23
|
-
* leave these tags in the text stream; they must not leak into context.
|
|
24
|
-
*/
|
|
25
|
-
export declare function stripThinkTags(text: string): string;
|
|
26
|
-
//# sourceMappingURL=tool-result-prompts.d.ts.map
|