@di-code/coding-agent 0.1.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.
Files changed (157) hide show
  1. package/dist/cli.d.ts +26 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +117 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/config/models-loader.d.ts +4 -0
  6. package/dist/config/models-loader.d.ts.map +1 -0
  7. package/dist/config/models-loader.js +134 -0
  8. package/dist/config/models-loader.js.map +1 -0
  9. package/dist/config/models-types.d.ts +23 -0
  10. package/dist/config/models-types.d.ts.map +1 -0
  11. package/dist/config/models-types.js +2 -0
  12. package/dist/config/models-types.js.map +1 -0
  13. package/dist/config/settings-loader.d.ts +3 -0
  14. package/dist/config/settings-loader.d.ts.map +1 -0
  15. package/dist/config/settings-loader.js +130 -0
  16. package/dist/config/settings-loader.js.map +1 -0
  17. package/dist/config/settings-types.d.ts +21 -0
  18. package/dist/config/settings-types.d.ts.map +1 -0
  19. package/dist/config/settings-types.js +2 -0
  20. package/dist/config/settings-types.js.map +1 -0
  21. package/dist/core/context-builder.d.ts +8 -0
  22. package/dist/core/context-builder.d.ts.map +1 -0
  23. package/dist/core/context-builder.js +46 -0
  24. package/dist/core/context-builder.js.map +1 -0
  25. package/dist/core/model-catalog-store.d.ts +8 -0
  26. package/dist/core/model-catalog-store.d.ts.map +1 -0
  27. package/dist/core/model-catalog-store.js +68 -0
  28. package/dist/core/model-catalog-store.js.map +1 -0
  29. package/dist/core/model-composer.d.ts +4 -0
  30. package/dist/core/model-composer.d.ts.map +1 -0
  31. package/dist/core/model-composer.js +37 -0
  32. package/dist/core/model-composer.js.map +1 -0
  33. package/dist/core/session/session-manager.d.ts +44 -0
  34. package/dist/core/session/session-manager.d.ts.map +1 -0
  35. package/dist/core/session/session-manager.js +138 -0
  36. package/dist/core/session/session-manager.js.map +1 -0
  37. package/dist/core/session/session-storage.d.ts +11 -0
  38. package/dist/core/session/session-storage.d.ts.map +1 -0
  39. package/dist/core/session/session-storage.js +382 -0
  40. package/dist/core/session/session-storage.js.map +1 -0
  41. package/dist/core/session/types.d.ts +65 -0
  42. package/dist/core/session/types.d.ts.map +1 -0
  43. package/dist/core/session/types.js +28 -0
  44. package/dist/core/session/types.js.map +1 -0
  45. package/dist/core/session.d.ts +80 -0
  46. package/dist/core/session.d.ts.map +1 -0
  47. package/dist/core/session.js +262 -0
  48. package/dist/core/session.js.map +1 -0
  49. package/dist/core/tools/bash.d.ts +39 -0
  50. package/dist/core/tools/bash.d.ts.map +1 -0
  51. package/dist/core/tools/bash.js +208 -0
  52. package/dist/core/tools/bash.js.map +1 -0
  53. package/dist/core/tools/edit.d.ts +18 -0
  54. package/dist/core/tools/edit.d.ts.map +1 -0
  55. package/dist/core/tools/edit.js +107 -0
  56. package/dist/core/tools/edit.js.map +1 -0
  57. package/dist/core/tools/file-mutation-queue.d.ts +2 -0
  58. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -0
  59. package/dist/core/tools/file-mutation-queue.js +24 -0
  60. package/dist/core/tools/file-mutation-queue.js.map +1 -0
  61. package/dist/core/tools/path-boundary.d.ts +3 -0
  62. package/dist/core/tools/path-boundary.d.ts.map +1 -0
  63. package/dist/core/tools/path-boundary.js +62 -0
  64. package/dist/core/tools/path-boundary.js.map +1 -0
  65. package/dist/core/tools/read.d.ts +17 -0
  66. package/dist/core/tools/read.d.ts.map +1 -0
  67. package/dist/core/tools/read.js +132 -0
  68. package/dist/core/tools/read.js.map +1 -0
  69. package/dist/core/tools/write.d.ts +17 -0
  70. package/dist/core/tools/write.d.ts.map +1 -0
  71. package/dist/core/tools/write.js +45 -0
  72. package/dist/core/tools/write.js.map +1 -0
  73. package/dist/entry.d.ts +3 -0
  74. package/dist/entry.d.ts.map +1 -0
  75. package/dist/entry.js +27 -0
  76. package/dist/entry.js.map +1 -0
  77. package/dist/extensions/index.d.ts +5 -0
  78. package/dist/extensions/index.d.ts.map +1 -0
  79. package/dist/extensions/index.js +3 -0
  80. package/dist/extensions/index.js.map +1 -0
  81. package/dist/extensions/runtime.d.ts +46 -0
  82. package/dist/extensions/runtime.d.ts.map +1 -0
  83. package/dist/extensions/runtime.js +179 -0
  84. package/dist/extensions/runtime.js.map +1 -0
  85. package/dist/extensions/trust.d.ts +8 -0
  86. package/dist/extensions/trust.d.ts.map +1 -0
  87. package/dist/extensions/trust.js +59 -0
  88. package/dist/extensions/trust.js.map +1 -0
  89. package/dist/extensions/types.d.ts +46 -0
  90. package/dist/extensions/types.d.ts.map +1 -0
  91. package/dist/extensions/types.js +2 -0
  92. package/dist/extensions/types.js.map +1 -0
  93. package/dist/index.d.ts +8 -0
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +8 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/main.d.ts +19 -0
  98. package/dist/main.d.ts.map +1 -0
  99. package/dist/main.js +198 -0
  100. package/dist/main.js.map +1 -0
  101. package/dist/modes/interactive-components.d.ts +41 -0
  102. package/dist/modes/interactive-components.d.ts.map +1 -0
  103. package/dist/modes/interactive-components.js +195 -0
  104. package/dist/modes/interactive-components.js.map +1 -0
  105. package/dist/modes/interactive-layout.d.ts +20 -0
  106. package/dist/modes/interactive-layout.d.ts.map +1 -0
  107. package/dist/modes/interactive-layout.js +34 -0
  108. package/dist/modes/interactive-layout.js.map +1 -0
  109. package/dist/modes/interactive-state.d.ts +61 -0
  110. package/dist/modes/interactive-state.d.ts.map +1 -0
  111. package/dist/modes/interactive-state.js +201 -0
  112. package/dist/modes/interactive-state.js.map +1 -0
  113. package/dist/modes/interactive.d.ts +61 -0
  114. package/dist/modes/interactive.d.ts.map +1 -0
  115. package/dist/modes/interactive.js +436 -0
  116. package/dist/modes/interactive.js.map +1 -0
  117. package/dist/modes/json.d.ts +14 -0
  118. package/dist/modes/json.d.ts.map +1 -0
  119. package/dist/modes/json.js +26 -0
  120. package/dist/modes/json.js.map +1 -0
  121. package/dist/modes/print.d.ts +10 -0
  122. package/dist/modes/print.d.ts.map +1 -0
  123. package/dist/modes/print.js +28 -0
  124. package/dist/modes/print.js.map +1 -0
  125. package/dist/provider-onboarding.d.ts +13 -0
  126. package/dist/provider-onboarding.d.ts.map +1 -0
  127. package/dist/provider-onboarding.js +162 -0
  128. package/dist/provider-onboarding.js.map +1 -0
  129. package/dist/rpc/client.d.ts +38 -0
  130. package/dist/rpc/client.d.ts.map +1 -0
  131. package/dist/rpc/client.js +153 -0
  132. package/dist/rpc/client.js.map +1 -0
  133. package/dist/rpc/index.d.ts +5 -0
  134. package/dist/rpc/index.d.ts.map +1 -0
  135. package/dist/rpc/index.js +5 -0
  136. package/dist/rpc/index.js.map +1 -0
  137. package/dist/rpc/jsonl.d.ts +17 -0
  138. package/dist/rpc/jsonl.d.ts.map +1 -0
  139. package/dist/rpc/jsonl.js +52 -0
  140. package/dist/rpc/jsonl.js.map +1 -0
  141. package/dist/rpc/protocol.d.ts +80 -0
  142. package/dist/rpc/protocol.d.ts.map +1 -0
  143. package/dist/rpc/protocol.js +231 -0
  144. package/dist/rpc/protocol.js.map +1 -0
  145. package/dist/rpc/server.d.ts +41 -0
  146. package/dist/rpc/server.d.ts.map +1 -0
  147. package/dist/rpc/server.js +186 -0
  148. package/dist/rpc/server.js.map +1 -0
  149. package/dist/rpc-entry.d.ts +3 -0
  150. package/dist/rpc-entry.d.ts.map +1 -0
  151. package/dist/rpc-entry.js +28 -0
  152. package/dist/rpc-entry.js.map +1 -0
  153. package/dist/startup.d.ts +24 -0
  154. package/dist/startup.d.ts.map +1 -0
  155. package/dist/startup.js +227 -0
  156. package/dist/startup.js.map +1 -0
  157. package/package.json +43 -0
package/dist/cli.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ export type OutputMode = "print" | "json" | "interactive";
2
+ export type CliCommand = {
3
+ kind: "help";
4
+ } | {
5
+ kind: "version";
6
+ } | {
7
+ kind: "run";
8
+ mode: OutputMode;
9
+ prompt: string;
10
+ sessionPath?: string;
11
+ continueSession?: true;
12
+ };
13
+ export declare class CliUsageError extends Error {
14
+ constructor(message: string);
15
+ }
16
+ export declare function parseCliArgs(args: readonly string[]): CliCommand;
17
+ export interface CliDependencies {
18
+ stdout(text: string): void;
19
+ stderr(text: string): void;
20
+ run(command: Extract<CliCommand, {
21
+ kind: "run";
22
+ }>): Promise<number>;
23
+ readonly version: string;
24
+ }
25
+ export declare function runCli(args: readonly string[], dependencies: CliDependencies): Promise<number>;
26
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,GAAG,aAAa,CAAC;AAE1D,MAAM,MAAM,UAAU,GACnB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC;AAEnG,qBAAa,aAAc,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI3B;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CA+EhE;AAcD,MAAM,WAAW,eAAe;IAC/B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU,EAAE;QAAE,IAAI,EAAE,KAAK,CAAA;KAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,YAAY,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBpG"}
package/dist/cli.js ADDED
@@ -0,0 +1,117 @@
1
+ export class CliUsageError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "CliUsageError";
5
+ }
6
+ }
7
+ export function parseCliArgs(args) {
8
+ const staticOption = args.find((argument) => ["-h", "--help", "-v", "--version"].includes(argument));
9
+ if (staticOption !== undefined) {
10
+ const kind = staticOption === "-h" || staticOption === "--help" ? "help" : "version";
11
+ if (args.length !== 1) {
12
+ throw new CliUsageError(`--${kind} must be used on its own.`);
13
+ }
14
+ return { kind };
15
+ }
16
+ let mode = "print";
17
+ let printAlias = false;
18
+ let sessionPath;
19
+ let continueSession = false;
20
+ const promptParts = [];
21
+ for (let index = 0; index < args.length; index++) {
22
+ const argument = args[index];
23
+ if (argument === "-p" || argument === "--print") {
24
+ printAlias = true;
25
+ continue;
26
+ }
27
+ if (argument === "--interactive") {
28
+ mode = "interactive";
29
+ continue;
30
+ }
31
+ if (argument === "-c" || argument === "--continue") {
32
+ continueSession = true;
33
+ continue;
34
+ }
35
+ if (argument === "--mode") {
36
+ const value = args[index + 1];
37
+ if (value === undefined) {
38
+ throw new CliUsageError("Option --mode requires a value.");
39
+ }
40
+ if (value !== "print" && value !== "json" && value !== "interactive") {
41
+ throw new CliUsageError(`Unsupported mode "${value}". Expected print, json, or interactive.`);
42
+ }
43
+ mode = value;
44
+ index++;
45
+ continue;
46
+ }
47
+ if (argument === "--session") {
48
+ const value = args[index + 1];
49
+ if (value === undefined) {
50
+ throw new CliUsageError("Option --session requires a value.");
51
+ }
52
+ if (value.trim().length === 0) {
53
+ throw new CliUsageError("Option --session requires a non-empty value.");
54
+ }
55
+ sessionPath = value;
56
+ index++;
57
+ continue;
58
+ }
59
+ if (argument?.startsWith("-")) {
60
+ throw new CliUsageError(`Unknown option "${argument}".`);
61
+ }
62
+ if (argument !== undefined) {
63
+ promptParts.push(argument);
64
+ }
65
+ }
66
+ if (printAlias && mode !== "print") {
67
+ throw new CliUsageError(`Cannot combine --print with --mode ${mode}.`);
68
+ }
69
+ if (continueSession && sessionPath !== undefined) {
70
+ throw new CliUsageError("Cannot combine --continue with --session.");
71
+ }
72
+ if (promptParts.length === 0 && mode !== "interactive") {
73
+ throw new CliUsageError("A prompt is required.");
74
+ }
75
+ return {
76
+ kind: "run",
77
+ mode,
78
+ prompt: promptParts.join(" "),
79
+ ...(sessionPath ? { sessionPath } : {}),
80
+ ...(continueSession ? { continueSession: true } : {}),
81
+ };
82
+ }
83
+ const HELP_TEXT = `Usage: di-code [options] <prompt>
84
+
85
+ Options:
86
+ -p, --print Print only the final assistant text (default)
87
+ --mode <mode> Output mode: print, json, or interactive
88
+ --interactive Start interactive terminal mode
89
+ --continue, -c Continue the most recently modified session
90
+ --session <path> Create or resume a JSONL session (relative to the work root)
91
+ -h, --help Show help
92
+ -v, --version Show version
93
+ `;
94
+ export async function runCli(args, dependencies) {
95
+ let command;
96
+ try {
97
+ command = parseCliArgs(args);
98
+ }
99
+ catch (cause) {
100
+ if (cause instanceof CliUsageError) {
101
+ dependencies.stderr(`${cause.message}\n`);
102
+ return 1;
103
+ }
104
+ throw cause;
105
+ }
106
+ switch (command.kind) {
107
+ case "help":
108
+ dependencies.stdout(HELP_TEXT);
109
+ return 0;
110
+ case "version":
111
+ dependencies.stdout(`${dependencies.version}\n`);
112
+ return 0;
113
+ case "run":
114
+ return dependencies.run(command);
115
+ }
116
+ }
117
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["export type OutputMode = \"print\" | \"json\" | \"interactive\";\n\nexport type CliCommand =\n\t| { kind: \"help\" }\n\t| { kind: \"version\" }\n\t| { kind: \"run\"; mode: OutputMode; prompt: string; sessionPath?: string; continueSession?: true };\n\nexport class CliUsageError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"CliUsageError\";\n\t}\n}\n\nexport function parseCliArgs(args: readonly string[]): CliCommand {\n\tconst staticOption = args.find((argument) => [\"-h\", \"--help\", \"-v\", \"--version\"].includes(argument));\n\tif (staticOption !== undefined) {\n\t\tconst kind = staticOption === \"-h\" || staticOption === \"--help\" ? \"help\" : \"version\";\n\t\tif (args.length !== 1) {\n\t\t\tthrow new CliUsageError(`--${kind} must be used on its own.`);\n\t\t}\n\t\treturn { kind };\n\t}\n\n\tlet mode: OutputMode = \"print\";\n\tlet printAlias = false;\n\tlet sessionPath: string | undefined;\n\tlet continueSession = false;\n\tconst promptParts: string[] = [];\n\n\tfor (let index = 0; index < args.length; index++) {\n\t\tconst argument = args[index];\n\t\tif (argument === \"-p\" || argument === \"--print\") {\n\t\t\tprintAlias = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (argument === \"--interactive\") {\n\t\t\tmode = \"interactive\";\n\t\t\tcontinue;\n\t\t}\n\t\tif (argument === \"-c\" || argument === \"--continue\") {\n\t\t\tcontinueSession = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (argument === \"--mode\") {\n\t\t\tconst value = args[index + 1];\n\t\t\tif (value === undefined) {\n\t\t\t\tthrow new CliUsageError(\"Option --mode requires a value.\");\n\t\t\t}\n\t\t\tif (value !== \"print\" && value !== \"json\" && value !== \"interactive\") {\n\t\t\t\tthrow new CliUsageError(`Unsupported mode \"${value}\". Expected print, json, or interactive.`);\n\t\t\t}\n\t\t\tmode = value;\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (argument === \"--session\") {\n\t\t\tconst value = args[index + 1];\n\t\t\tif (value === undefined) {\n\t\t\t\tthrow new CliUsageError(\"Option --session requires a value.\");\n\t\t\t}\n\t\t\tif (value.trim().length === 0) {\n\t\t\t\tthrow new CliUsageError(\"Option --session requires a non-empty value.\");\n\t\t\t}\n\t\t\tsessionPath = value;\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (argument?.startsWith(\"-\")) {\n\t\t\tthrow new CliUsageError(`Unknown option \"${argument}\".`);\n\t\t}\n\t\tif (argument !== undefined) {\n\t\t\tpromptParts.push(argument);\n\t\t}\n\t}\n\n\tif (printAlias && mode !== \"print\") {\n\t\tthrow new CliUsageError(`Cannot combine --print with --mode ${mode}.`);\n\t}\n\tif (continueSession && sessionPath !== undefined) {\n\t\tthrow new CliUsageError(\"Cannot combine --continue with --session.\");\n\t}\n\tif (promptParts.length === 0 && mode !== \"interactive\") {\n\t\tthrow new CliUsageError(\"A prompt is required.\");\n\t}\n\n\treturn {\n\t\tkind: \"run\",\n\t\tmode,\n\t\tprompt: promptParts.join(\" \"),\n\t\t...(sessionPath ? { sessionPath } : {}),\n\t\t...(continueSession ? { continueSession: true as const } : {}),\n\t};\n}\n\nconst HELP_TEXT = `Usage: di-code [options] <prompt>\n\nOptions:\n -p, --print Print only the final assistant text (default)\n --mode <mode> Output mode: print, json, or interactive\n --interactive Start interactive terminal mode\n --continue, -c Continue the most recently modified session\n --session <path> Create or resume a JSONL session (relative to the work root)\n -h, --help Show help\n -v, --version Show version\n`;\n\nexport interface CliDependencies {\n\tstdout(text: string): void;\n\tstderr(text: string): void;\n\trun(command: Extract<CliCommand, { kind: \"run\" }>): Promise<number>;\n\treadonly version: string;\n}\n\nexport async function runCli(args: readonly string[], dependencies: CliDependencies): Promise<number> {\n\tlet command: CliCommand;\n\ttry {\n\t\tcommand = parseCliArgs(args);\n\t} catch (cause) {\n\t\tif (cause instanceof CliUsageError) {\n\t\t\tdependencies.stderr(`${cause.message}\\n`);\n\t\t\treturn 1;\n\t\t}\n\t\tthrow cause;\n\t}\n\n\tswitch (command.kind) {\n\t\tcase \"help\":\n\t\t\tdependencies.stdout(HELP_TEXT);\n\t\t\treturn 0;\n\t\tcase \"version\":\n\t\t\tdependencies.stdout(`${dependencies.version}\\n`);\n\t\t\treturn 0;\n\t\tcase \"run\":\n\t\t\treturn dependencies.run(command);\n\t}\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { ModelsDocument } from "./models-types.ts";
2
+ export declare function loadModelsDocument(path: string): Promise<ModelsDocument>;
3
+ export declare function loadModelsDocuments(paths: readonly string[]): Promise<ModelsDocument>;
4
+ //# sourceMappingURL=models-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models-loader.d.ts","sourceRoot":"","sources":["../../src/config/models-loader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAkE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AA8FxH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAsB9E;AAED,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAiB3F"}
@@ -0,0 +1,134 @@
1
+ import { readFile } from "node:fs/promises";
2
+ const APIS = new Set(["openai-responses", "anthropic-messages"]);
3
+ const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
4
+ function isRecord(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ function validateUrl(value, path) {
8
+ if (typeof value !== "string")
9
+ throw new Error(`${path} must be a URL`);
10
+ let parsed;
11
+ try {
12
+ parsed = new URL(value);
13
+ }
14
+ catch {
15
+ throw new Error(`${path} must be an absolute http or https URL`);
16
+ }
17
+ if (!["http:", "https:"].includes(parsed.protocol) ||
18
+ parsed.username ||
19
+ parsed.password ||
20
+ parsed.search ||
21
+ parsed.hash) {
22
+ throw new Error(`${path} must be a credential-free http or https URL`);
23
+ }
24
+ return value.replace(/\/+$/, "");
25
+ }
26
+ function validateApi(value, path) {
27
+ if (typeof value !== "string" || !APIS.has(value)) {
28
+ throw new Error(`${path} uses an unsupported API`);
29
+ }
30
+ return value;
31
+ }
32
+ function validateModel(providerId, value, index) {
33
+ if (!isRecord(value) || typeof value.id !== "string" || value.id.trim().length === 0) {
34
+ throw new Error(`providers.${providerId}.models[${index}].id is required`);
35
+ }
36
+ const model = {
37
+ id: value.id,
38
+ ...(typeof value.name === "string" ? { name: value.name } : {}),
39
+ ...(value.api === undefined ? {} : { api: validateApi(value.api, `providers.${providerId}.models[${index}].api`) }),
40
+ ...(typeof value.reasoning === "boolean" ? { reasoning: value.reasoning } : {}),
41
+ };
42
+ return model;
43
+ }
44
+ function validateOverride(providerId, modelId, value) {
45
+ if (!isRecord(value))
46
+ throw new Error(`providers.${providerId}.modelOverrides.${modelId} must be an object`);
47
+ return {
48
+ ...(typeof value.name === "string" ? { name: value.name } : {}),
49
+ ...(value.api === undefined
50
+ ? {}
51
+ : { api: validateApi(value.api, `providers.${providerId}.modelOverrides.${modelId}.api`) }),
52
+ ...(typeof value.reasoning === "boolean" ? { reasoning: value.reasoning } : {}),
53
+ ...(Array.isArray(value.input) && value.input.every((item) => item === "text" || item === "image")
54
+ ? { input: [...value.input] }
55
+ : {}),
56
+ };
57
+ }
58
+ function validateProvider(providerId, value) {
59
+ if (!isRecord(value))
60
+ throw new Error(`providers.${providerId} must be an object`);
61
+ if ("apiKey" in value)
62
+ throw new Error(`providers.${providerId}.apiKey is not allowed; use apiKeyEnv`);
63
+ const baseUrl = value.baseUrl === undefined ? undefined : validateUrl(value.baseUrl, `providers.${providerId}.baseUrl`);
64
+ const providerApi = value.api === undefined ? undefined : validateApi(value.api, `providers.${providerId}.api`);
65
+ if (value.apiKeyEnv !== undefined && (typeof value.apiKeyEnv !== "string" || !ENV_NAME.test(value.apiKeyEnv))) {
66
+ throw new Error(`providers.${providerId}.apiKeyEnv must be an environment variable name`);
67
+ }
68
+ if (value.models !== undefined && (!Array.isArray(value.models) || value.models.length === 0)) {
69
+ throw new Error(`providers.${providerId}.models must contain at least one model when present`);
70
+ }
71
+ const models = value.models?.map((entry, index) => validateModel(providerId, entry, index));
72
+ let modelOverrides;
73
+ if (value.modelOverrides !== undefined) {
74
+ if (!isRecord(value.modelOverrides))
75
+ throw new Error(`providers.${providerId}.modelOverrides must be an object`);
76
+ modelOverrides = {};
77
+ for (const [modelId, override] of Object.entries(value.modelOverrides)) {
78
+ modelOverrides[modelId] = validateOverride(providerId, modelId, override);
79
+ }
80
+ }
81
+ return {
82
+ ...(typeof value.name === "string" ? { name: value.name } : {}),
83
+ ...(baseUrl === undefined ? {} : { baseUrl }),
84
+ ...(providerApi === undefined ? {} : { api: providerApi }),
85
+ ...(value.apiKeyEnv === undefined ? {} : { apiKeyEnv: value.apiKeyEnv }),
86
+ ...(models === undefined ? {} : { models }),
87
+ ...(modelOverrides === undefined ? {} : { modelOverrides }),
88
+ };
89
+ }
90
+ export async function loadModelsDocument(path) {
91
+ let text;
92
+ try {
93
+ text = await readFile(path, "utf8");
94
+ }
95
+ catch (cause) {
96
+ if (cause.code === "ENOENT")
97
+ return { providers: {} };
98
+ throw new Error(`Unable to read models.json ${path}: ${cause instanceof Error ? cause.message : String(cause)}`);
99
+ }
100
+ let parsed;
101
+ try {
102
+ parsed = JSON.parse(text);
103
+ }
104
+ catch {
105
+ throw new Error(`Invalid models.json ${path}: malformed JSON`);
106
+ }
107
+ if (!isRecord(parsed) || !isRecord(parsed.providers)) {
108
+ throw new Error(`Invalid models.json ${path}: providers must be an object`);
109
+ }
110
+ const providers = {};
111
+ for (const [providerId, value] of Object.entries(parsed.providers)) {
112
+ providers[providerId] = validateProvider(providerId, value);
113
+ }
114
+ return { providers: structuredClone(providers) };
115
+ }
116
+ export async function loadModelsDocuments(paths) {
117
+ const merged = {};
118
+ for (const path of paths) {
119
+ const document = await loadModelsDocument(path);
120
+ for (const [providerId, definition] of Object.entries(document.providers)) {
121
+ const previous = merged[providerId];
122
+ merged[providerId] = previous
123
+ ? {
124
+ ...previous,
125
+ ...definition,
126
+ models: definition.models ?? previous.models,
127
+ modelOverrides: { ...previous.modelOverrides, ...definition.modelOverrides },
128
+ }
129
+ : definition;
130
+ }
131
+ }
132
+ return { providers: structuredClone(merged) };
133
+ }
134
+ //# sourceMappingURL=models-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models-loader.js","sourceRoot":"","sources":["../../src/config/models-loader.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport type { ConfiguredApi, CustomModelDefinition, CustomProviderDefinition, ModelsDocument } from \"./models-types.ts\";\n\nconst APIS = new Set<ConfiguredApi>([\"openai-responses\", \"anthropic-messages\"]);\nconst ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction validateUrl(value: unknown, path: string): string {\n\tif (typeof value !== \"string\") throw new Error(`${path} must be a URL`);\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(value);\n\t} catch {\n\t\tthrow new Error(`${path} must be an absolute http or https URL`);\n\t}\n\tif (\n\t\t![\"http:\", \"https:\"].includes(parsed.protocol) ||\n\t\tparsed.username ||\n\t\tparsed.password ||\n\t\tparsed.search ||\n\t\tparsed.hash\n\t) {\n\t\tthrow new Error(`${path} must be a credential-free http or https URL`);\n\t}\n\treturn value.replace(/\\/+$/, \"\");\n}\n\nfunction validateApi(value: unknown, path: string): ConfiguredApi {\n\tif (typeof value !== \"string\" || !APIS.has(value as ConfiguredApi)) {\n\t\tthrow new Error(`${path} uses an unsupported API`);\n\t}\n\treturn value as ConfiguredApi;\n}\n\nfunction validateModel(providerId: string, value: unknown, index: number): CustomModelDefinition {\n\tif (!isRecord(value) || typeof value.id !== \"string\" || value.id.trim().length === 0) {\n\t\tthrow new Error(`providers.${providerId}.models[${index}].id is required`);\n\t}\n\tconst model: CustomModelDefinition = {\n\t\tid: value.id,\n\t\t...(typeof value.name === \"string\" ? { name: value.name } : {}),\n\t\t...(value.api === undefined ? {} : { api: validateApi(value.api, `providers.${providerId}.models[${index}].api`) }),\n\t\t...(typeof value.reasoning === \"boolean\" ? { reasoning: value.reasoning } : {}),\n\t};\n\treturn model;\n}\n\nfunction validateOverride(providerId: string, modelId: string, value: unknown): Omit<CustomModelDefinition, \"id\"> {\n\tif (!isRecord(value)) throw new Error(`providers.${providerId}.modelOverrides.${modelId} must be an object`);\n\treturn {\n\t\t...(typeof value.name === \"string\" ? { name: value.name } : {}),\n\t\t...(value.api === undefined\n\t\t\t? {}\n\t\t\t: { api: validateApi(value.api, `providers.${providerId}.modelOverrides.${modelId}.api`) }),\n\t\t...(typeof value.reasoning === \"boolean\" ? { reasoning: value.reasoning } : {}),\n\t\t...(Array.isArray(value.input) && value.input.every((item) => item === \"text\" || item === \"image\")\n\t\t\t? { input: [...value.input] as (\"text\" | \"image\")[] }\n\t\t\t: {}),\n\t};\n}\n\nfunction validateProvider(providerId: string, value: unknown): CustomProviderDefinition {\n\tif (!isRecord(value)) throw new Error(`providers.${providerId} must be an object`);\n\tif (\"apiKey\" in value) throw new Error(`providers.${providerId}.apiKey is not allowed; use apiKeyEnv`);\n\tconst baseUrl =\n\t\tvalue.baseUrl === undefined ? undefined : validateUrl(value.baseUrl, `providers.${providerId}.baseUrl`);\n\tconst providerApi = value.api === undefined ? undefined : validateApi(value.api, `providers.${providerId}.api`);\n\tif (value.apiKeyEnv !== undefined && (typeof value.apiKeyEnv !== \"string\" || !ENV_NAME.test(value.apiKeyEnv))) {\n\t\tthrow new Error(`providers.${providerId}.apiKeyEnv must be an environment variable name`);\n\t}\n\tif (value.models !== undefined && (!Array.isArray(value.models) || value.models.length === 0)) {\n\t\tthrow new Error(`providers.${providerId}.models must contain at least one model when present`);\n\t}\n\tconst models = value.models?.map((entry, index) => validateModel(providerId, entry, index));\n\tlet modelOverrides: Record<string, Omit<CustomModelDefinition, \"id\">> | undefined;\n\tif (value.modelOverrides !== undefined) {\n\t\tif (!isRecord(value.modelOverrides)) throw new Error(`providers.${providerId}.modelOverrides must be an object`);\n\t\tmodelOverrides = {};\n\t\tfor (const [modelId, override] of Object.entries(value.modelOverrides)) {\n\t\t\tmodelOverrides[modelId] = validateOverride(providerId, modelId, override);\n\t\t}\n\t}\n\treturn {\n\t\t...(typeof value.name === \"string\" ? { name: value.name } : {}),\n\t\t...(baseUrl === undefined ? {} : { baseUrl }),\n\t\t...(providerApi === undefined ? {} : { api: providerApi }),\n\t\t...(value.apiKeyEnv === undefined ? {} : { apiKeyEnv: value.apiKeyEnv }),\n\t\t...(models === undefined ? {} : { models }),\n\t\t...(modelOverrides === undefined ? {} : { modelOverrides }),\n\t};\n}\n\nexport async function loadModelsDocument(path: string): Promise<ModelsDocument> {\n\tlet text: string;\n\ttry {\n\t\ttext = await readFile(path, \"utf8\");\n\t} catch (cause) {\n\t\tif ((cause as NodeJS.ErrnoException).code === \"ENOENT\") return { providers: {} };\n\t\tthrow new Error(`Unable to read models.json ${path}: ${cause instanceof Error ? cause.message : String(cause)}`);\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(text) as unknown;\n\t} catch {\n\t\tthrow new Error(`Invalid models.json ${path}: malformed JSON`);\n\t}\n\tif (!isRecord(parsed) || !isRecord(parsed.providers)) {\n\t\tthrow new Error(`Invalid models.json ${path}: providers must be an object`);\n\t}\n\tconst providers: Record<string, CustomProviderDefinition> = {};\n\tfor (const [providerId, value] of Object.entries(parsed.providers)) {\n\t\tproviders[providerId] = validateProvider(providerId, value);\n\t}\n\treturn { providers: structuredClone(providers) };\n}\n\nexport async function loadModelsDocuments(paths: readonly string[]): Promise<ModelsDocument> {\n\tconst merged: Record<string, CustomProviderDefinition> = {};\n\tfor (const path of paths) {\n\t\tconst document = await loadModelsDocument(path);\n\t\tfor (const [providerId, definition] of Object.entries(document.providers)) {\n\t\t\tconst previous = merged[providerId];\n\t\t\tmerged[providerId] = previous\n\t\t\t\t? {\n\t\t\t\t\t\t...previous,\n\t\t\t\t\t\t...definition,\n\t\t\t\t\t\tmodels: definition.models ?? previous.models,\n\t\t\t\t\t\tmodelOverrides: { ...previous.modelOverrides, ...definition.modelOverrides },\n\t\t\t\t\t}\n\t\t\t\t: definition;\n\t\t}\n\t}\n\treturn { providers: structuredClone(merged) };\n}\n"]}
@@ -0,0 +1,23 @@
1
+ export type ConfiguredApi = "openai-responses" | "anthropic-messages";
2
+ export interface CustomModelDefinition {
3
+ readonly id: string;
4
+ readonly name?: string;
5
+ readonly api?: ConfiguredApi;
6
+ readonly reasoning?: boolean;
7
+ readonly input?: readonly ("text" | "image")[];
8
+ readonly contextWindow?: number;
9
+ readonly maxOutputTokens?: number;
10
+ readonly cost?: Partial<Record<"input" | "output" | "cacheRead" | "cacheWrite", number>>;
11
+ }
12
+ export interface CustomProviderDefinition {
13
+ readonly name?: string;
14
+ readonly baseUrl?: string;
15
+ readonly api?: ConfiguredApi;
16
+ readonly apiKeyEnv?: string;
17
+ readonly models?: readonly CustomModelDefinition[];
18
+ readonly modelOverrides?: Readonly<Record<string, Omit<CustomModelDefinition, "id">>>;
19
+ }
20
+ export interface ModelsDocument {
21
+ readonly providers: Readonly<Record<string, CustomProviderDefinition>>;
22
+ }
23
+ //# sourceMappingURL=models-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models-types.d.ts","sourceRoot":"","sources":["../../src/config/models-types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,kBAAkB,GAAG,oBAAoB,CAAC;AAEtE,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;IAC/C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;CACzF;AAED,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;IACnD,QAAQ,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;CACtF;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC;CACvE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=models-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models-types.js","sourceRoot":"","sources":["../../src/config/models-types.ts"],"names":[],"mappings":"","sourcesContent":["export type ConfiguredApi = \"openai-responses\" | \"anthropic-messages\";\n\nexport interface CustomModelDefinition {\n\treadonly id: string;\n\treadonly name?: string;\n\treadonly api?: ConfiguredApi;\n\treadonly reasoning?: boolean;\n\treadonly input?: readonly (\"text\" | \"image\")[];\n\treadonly contextWindow?: number;\n\treadonly maxOutputTokens?: number;\n\treadonly cost?: Partial<Record<\"input\" | \"output\" | \"cacheRead\" | \"cacheWrite\", number>>;\n}\n\nexport interface CustomProviderDefinition {\n\treadonly name?: string;\n\treadonly baseUrl?: string;\n\treadonly api?: ConfiguredApi;\n\treadonly apiKeyEnv?: string;\n\treadonly models?: readonly CustomModelDefinition[];\n\treadonly modelOverrides?: Readonly<Record<string, Omit<CustomModelDefinition, \"id\">>>;\n}\n\nexport interface ModelsDocument {\n\treadonly providers: Readonly<Record<string, CustomProviderDefinition>>;\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { LoadedSettings, SettingsLoadOptions } from "./settings-types.ts";
2
+ export declare function loadSettings(options?: SettingsLoadOptions): Promise<LoadedSettings>;
3
+ //# sourceMappingURL=settings-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-loader.d.ts","sourceRoot":"","sources":["../../src/config/settings-loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,cAAc,EAId,mBAAmB,EACnB,MAAM,qBAAqB,CAAC;AA0H7B,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,cAAc,CAAC,CAY7F"}
@@ -0,0 +1,130 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ function validateProviderSettings(value, source, provider) {
9
+ if (!isRecord(value))
10
+ throw new Error(`Invalid settings file ${source}: providers.${provider} must be an object.`);
11
+ if ("apiKey" in value)
12
+ throw new Error(`Invalid settings file ${source}: raw apiKey is not allowed; use apiKeyEnv.`);
13
+ const result = {};
14
+ if (value.apiKeyEnv !== undefined) {
15
+ if (typeof value.apiKeyEnv !== "string" || !ENV_NAME.test(value.apiKeyEnv)) {
16
+ throw new Error(`Invalid settings file ${source}: providers.${provider}.apiKeyEnv must be an environment variable name.`);
17
+ }
18
+ result.apiKeyEnv = value.apiKeyEnv;
19
+ }
20
+ if (value.baseUrl !== undefined) {
21
+ if (typeof value.baseUrl !== "string") {
22
+ throw new Error(`Invalid settings file ${source}: providers.${provider}.baseUrl must be a URL.`);
23
+ }
24
+ let url;
25
+ try {
26
+ url = new URL(value.baseUrl);
27
+ }
28
+ catch {
29
+ throw new Error(`Invalid settings file ${source}: providers.${provider}.baseUrl must be an absolute http or https URL.`);
30
+ }
31
+ if ((url.protocol !== "http:" && url.protocol !== "https:") ||
32
+ url.username ||
33
+ url.password ||
34
+ url.search ||
35
+ url.hash) {
36
+ throw new Error(`Invalid settings file ${source}: providers.${provider}.baseUrl must be a credential-free http(s) URL.`);
37
+ }
38
+ result.baseUrl = value.baseUrl.replace(/\/+$/, "");
39
+ }
40
+ return result;
41
+ }
42
+ function validateDocument(value, source) {
43
+ if (!isRecord(value))
44
+ throw new Error(`Invalid settings file ${source}: root must be a JSON object.`);
45
+ if ("apiKey" in value)
46
+ throw new Error(`Invalid settings file ${source}: raw apiKey is not allowed; use apiKeyEnv.`);
47
+ const result = {};
48
+ if (value.provider !== undefined) {
49
+ if (typeof value.provider !== "string" || value.provider.trim().length === 0) {
50
+ throw new Error(`Invalid settings file ${source}: provider must be a non-empty string.`);
51
+ }
52
+ result.provider = value.provider;
53
+ }
54
+ if (value.model !== undefined) {
55
+ if (typeof value.model !== "string" || value.model.trim().length === 0) {
56
+ throw new Error(`Invalid settings file ${source}: model must be a non-empty string.`);
57
+ }
58
+ result.model = value.model;
59
+ }
60
+ if (value.providers !== undefined) {
61
+ if (!isRecord(value.providers))
62
+ throw new Error(`Invalid settings file ${source}: providers must be an object.`);
63
+ const providers = {};
64
+ for (const [name, settings] of Object.entries(value.providers)) {
65
+ if (name.trim().length === 0)
66
+ throw new Error(`Invalid settings file ${source}: provider name must not be empty.`);
67
+ providers[name] = validateProviderSettings(settings, source, name);
68
+ }
69
+ result.providers = providers;
70
+ }
71
+ return result;
72
+ }
73
+ function mergeSettings(base, override) {
74
+ const providers = {};
75
+ const providerNames = new Set([...Object.keys(base.providers ?? {}), ...Object.keys(override.providers ?? {})]);
76
+ for (const provider of providerNames) {
77
+ const merged = { ...base.providers?.[provider], ...override.providers?.[provider] };
78
+ if (Object.keys(merged).length > 0)
79
+ providers[provider] = merged;
80
+ }
81
+ return {
82
+ ...base,
83
+ ...override,
84
+ providers: Object.keys(providers).length > 0 ? providers : undefined,
85
+ };
86
+ }
87
+ function globalSettingsPath(options) {
88
+ const env = options.env ?? process.env;
89
+ if (process.platform === "win32") {
90
+ const appData = options.appData ?? env.APPDATA;
91
+ if (appData)
92
+ return join(appData, "di-code", "settings.json");
93
+ }
94
+ const configHome = env.XDG_CONFIG_HOME;
95
+ return join(configHome || join(options.homeDir ?? homedir(), ".config"), "di-code", "settings.json");
96
+ }
97
+ async function readOptional(path) {
98
+ let text;
99
+ try {
100
+ text = await readFile(path, "utf8");
101
+ }
102
+ catch (cause) {
103
+ if (cause.code === "ENOENT")
104
+ return undefined;
105
+ throw new Error(`Unable to read settings file ${path}: ${cause instanceof Error ? cause.message : String(cause)}`);
106
+ }
107
+ let parsed;
108
+ try {
109
+ parsed = JSON.parse(text);
110
+ }
111
+ catch {
112
+ throw new Error(`Invalid settings file ${path}: malformed JSON.`);
113
+ }
114
+ return validateDocument(parsed, path);
115
+ }
116
+ export async function loadSettings(options = {}) {
117
+ const cwd = options.cwd ?? process.cwd();
118
+ const paths = [globalSettingsPath(options), join(cwd, ".di-code", "settings.json")];
119
+ let settings = {};
120
+ const sources = [];
121
+ for (const path of paths) {
122
+ const document = await readOptional(path);
123
+ if (document === undefined)
124
+ continue;
125
+ settings = mergeSettings(settings, document);
126
+ sources.push(path);
127
+ }
128
+ return { settings, sources };
129
+ }
130
+ //# sourceMappingURL=settings-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-loader.js","sourceRoot":"","sources":["../../src/config/settings-loader.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type {\n\tLoadedSettings,\n\tProviderName,\n\tProviderSettings,\n\tSettingsDocument,\n\tSettingsLoadOptions,\n} from \"./settings-types.ts\";\n\nconst ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction validateProviderSettings(value: unknown, source: string, provider: ProviderName): ProviderSettings {\n\tif (!isRecord(value)) throw new Error(`Invalid settings file ${source}: providers.${provider} must be an object.`);\n\tif (\"apiKey\" in value) throw new Error(`Invalid settings file ${source}: raw apiKey is not allowed; use apiKeyEnv.`);\n\tconst result: { apiKeyEnv?: string; baseUrl?: string } = {};\n\tif (value.apiKeyEnv !== undefined) {\n\t\tif (typeof value.apiKeyEnv !== \"string\" || !ENV_NAME.test(value.apiKeyEnv)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid settings file ${source}: providers.${provider}.apiKeyEnv must be an environment variable name.`,\n\t\t\t);\n\t\t}\n\t\tresult.apiKeyEnv = value.apiKeyEnv;\n\t}\n\tif (value.baseUrl !== undefined) {\n\t\tif (typeof value.baseUrl !== \"string\") {\n\t\t\tthrow new Error(`Invalid settings file ${source}: providers.${provider}.baseUrl must be a URL.`);\n\t\t}\n\t\tlet url: URL;\n\t\ttry {\n\t\t\turl = new URL(value.baseUrl);\n\t\t} catch {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid settings file ${source}: providers.${provider}.baseUrl must be an absolute http or https URL.`,\n\t\t\t);\n\t\t}\n\t\tif (\n\t\t\t(url.protocol !== \"http:\" && url.protocol !== \"https:\") ||\n\t\t\turl.username ||\n\t\t\turl.password ||\n\t\t\turl.search ||\n\t\t\turl.hash\n\t\t) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid settings file ${source}: providers.${provider}.baseUrl must be a credential-free http(s) URL.`,\n\t\t\t);\n\t\t}\n\t\tresult.baseUrl = value.baseUrl.replace(/\\/+$/, \"\");\n\t}\n\treturn result;\n}\n\nfunction validateDocument(value: unknown, source: string): SettingsDocument {\n\tif (!isRecord(value)) throw new Error(`Invalid settings file ${source}: root must be a JSON object.`);\n\tif (\"apiKey\" in value) throw new Error(`Invalid settings file ${source}: raw apiKey is not allowed; use apiKeyEnv.`);\n\tconst result: {\n\t\tprovider?: ProviderName;\n\t\tmodel?: string;\n\t\tproviders?: Partial<Record<ProviderName, ProviderSettings>>;\n\t} = {};\n\tif (value.provider !== undefined) {\n\t\tif (typeof value.provider !== \"string\" || value.provider.trim().length === 0) {\n\t\t\tthrow new Error(`Invalid settings file ${source}: provider must be a non-empty string.`);\n\t\t}\n\t\tresult.provider = value.provider;\n\t}\n\tif (value.model !== undefined) {\n\t\tif (typeof value.model !== \"string\" || value.model.trim().length === 0) {\n\t\t\tthrow new Error(`Invalid settings file ${source}: model must be a non-empty string.`);\n\t\t}\n\t\tresult.model = value.model;\n\t}\n\tif (value.providers !== undefined) {\n\t\tif (!isRecord(value.providers)) throw new Error(`Invalid settings file ${source}: providers must be an object.`);\n\t\tconst providers: Partial<Record<ProviderName, ProviderSettings>> = {};\n\t\tfor (const [name, settings] of Object.entries(value.providers)) {\n\t\t\tif (name.trim().length === 0)\n\t\t\t\tthrow new Error(`Invalid settings file ${source}: provider name must not be empty.`);\n\t\t\tproviders[name] = validateProviderSettings(settings, source, name);\n\t\t}\n\t\tresult.providers = providers;\n\t}\n\treturn result;\n}\n\nfunction mergeSettings(base: SettingsDocument, override: SettingsDocument): SettingsDocument {\n\tconst providers: Partial<Record<ProviderName, ProviderSettings>> = {};\n\tconst providerNames = new Set([...Object.keys(base.providers ?? {}), ...Object.keys(override.providers ?? {})]);\n\tfor (const provider of providerNames) {\n\t\tconst merged = { ...base.providers?.[provider], ...override.providers?.[provider] };\n\t\tif (Object.keys(merged).length > 0) providers[provider] = merged;\n\t}\n\treturn {\n\t\t...base,\n\t\t...override,\n\t\tproviders: Object.keys(providers).length > 0 ? providers : undefined,\n\t};\n}\n\nfunction globalSettingsPath(options: SettingsLoadOptions): string {\n\tconst env = options.env ?? process.env;\n\tif (process.platform === \"win32\") {\n\t\tconst appData = options.appData ?? env.APPDATA;\n\t\tif (appData) return join(appData, \"di-code\", \"settings.json\");\n\t}\n\tconst configHome = env.XDG_CONFIG_HOME;\n\treturn join(configHome || join(options.homeDir ?? homedir(), \".config\"), \"di-code\", \"settings.json\");\n}\n\nasync function readOptional(path: string): Promise<SettingsDocument | undefined> {\n\tlet text: string;\n\ttry {\n\t\ttext = await readFile(path, \"utf8\");\n\t} catch (cause) {\n\t\tif ((cause as NodeJS.ErrnoException).code === \"ENOENT\") return undefined;\n\t\tthrow new Error(`Unable to read settings file ${path}: ${cause instanceof Error ? cause.message : String(cause)}`);\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(text) as unknown;\n\t} catch {\n\t\tthrow new Error(`Invalid settings file ${path}: malformed JSON.`);\n\t}\n\treturn validateDocument(parsed, path);\n}\n\nexport async function loadSettings(options: SettingsLoadOptions = {}): Promise<LoadedSettings> {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst paths = [globalSettingsPath(options), join(cwd, \".di-code\", \"settings.json\")];\n\tlet settings: SettingsDocument = {};\n\tconst sources: string[] = [];\n\tfor (const path of paths) {\n\t\tconst document = await readOptional(path);\n\t\tif (document === undefined) continue;\n\t\tsettings = mergeSettings(settings, document);\n\t\tsources.push(path);\n\t}\n\treturn { settings, sources };\n}\n"]}
@@ -0,0 +1,21 @@
1
+ export type ProviderName = string;
2
+ export interface ProviderSettings {
3
+ readonly apiKeyEnv?: string;
4
+ readonly baseUrl?: string;
5
+ }
6
+ export interface SettingsDocument {
7
+ readonly provider?: ProviderName;
8
+ readonly model?: string;
9
+ readonly providers?: Partial<Record<ProviderName, ProviderSettings>>;
10
+ }
11
+ export interface SettingsLoadOptions {
12
+ readonly cwd?: string;
13
+ readonly env?: Readonly<Record<string, string | undefined>>;
14
+ readonly homeDir?: string;
15
+ readonly appData?: string;
16
+ }
17
+ export interface LoadedSettings {
18
+ readonly settings: SettingsDocument;
19
+ readonly sources: readonly string[];
20
+ }
21
+ //# sourceMappingURL=settings-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-types.d.ts","sourceRoot":"","sources":["../../src/config/settings-types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC,CAAC;CACrE;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=settings-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-types.js","sourceRoot":"","sources":["../../src/config/settings-types.ts"],"names":[],"mappings":"","sourcesContent":["export type ProviderName = string;\n\nexport interface ProviderSettings {\n\treadonly apiKeyEnv?: string;\n\treadonly baseUrl?: string;\n}\n\nexport interface SettingsDocument {\n\treadonly provider?: ProviderName;\n\treadonly model?: string;\n\treadonly providers?: Partial<Record<ProviderName, ProviderSettings>>;\n}\n\nexport interface SettingsLoadOptions {\n\treadonly cwd?: string;\n\treadonly env?: Readonly<Record<string, string | undefined>>;\n\treadonly homeDir?: string;\n\treadonly appData?: string;\n}\n\nexport interface LoadedSettings {\n\treadonly settings: SettingsDocument;\n\treadonly sources: readonly string[];\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type { Message } from "@di-code/ai";
2
+ import type { SessionEntry } from "./session/types.ts";
3
+ export interface BuiltSessionContext {
4
+ readonly messages: readonly Message[];
5
+ readonly sourceEntryIds: readonly (string | null)[];
6
+ }
7
+ export declare function buildSessionContext(entries: readonly SessionEntry[]): BuiltSessionContext;
8
+ //# sourceMappingURL=context-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-builder.d.ts","sourceRoot":"","sources":["../../src/core/context-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAe,MAAM,aAAa,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAA4C,MAAM,oBAAoB,CAAC;AAEjG,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,cAAc,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;CACpD;AAmBD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,mBAAmB,CAkCzF"}
@@ -0,0 +1,46 @@
1
+ function createSummaryMessage(entry) {
2
+ return {
3
+ role: "user",
4
+ content: [
5
+ {
6
+ type: "text",
7
+ text: `<conversation-summary>\n${entry.summary}\n</conversation-summary>`,
8
+ },
9
+ ],
10
+ timestamp: Date.parse(entry.timestamp),
11
+ };
12
+ }
13
+ function messageEntries(entries) {
14
+ return entries.filter((entry) => entry.type === "message");
15
+ }
16
+ export function buildSessionContext(entries) {
17
+ const snapshot = structuredClone([...entries]);
18
+ let summaryIndex = -1;
19
+ for (let index = snapshot.length - 1; index >= 0; index--) {
20
+ if (snapshot[index]?.type === "summary") {
21
+ summaryIndex = index;
22
+ break;
23
+ }
24
+ }
25
+ if (summaryIndex < 0) {
26
+ const messages = messageEntries(snapshot);
27
+ return {
28
+ messages: messages.map((entry) => entry.message),
29
+ sourceEntryIds: messages.map((entry) => entry.id),
30
+ };
31
+ }
32
+ const summary = snapshot[summaryIndex];
33
+ if (summary?.type !== "summary") {
34
+ throw new Error("Latest summary entry could not be resolved.");
35
+ }
36
+ const firstKeptIndex = snapshot.findIndex((entry, index) => index < summaryIndex && entry.type === "message" && entry.id === summary.firstKeptEntryId);
37
+ if (firstKeptIndex < 0) {
38
+ throw new Error("Summary firstKeptEntryId must reference an earlier message entry.");
39
+ }
40
+ const keptEntries = messageEntries(snapshot.slice(firstKeptIndex));
41
+ return {
42
+ messages: [createSummaryMessage(summary), ...keptEntries.map((entry) => entry.message)],
43
+ sourceEntryIds: [null, ...keptEntries.map((entry) => entry.id)],
44
+ };
45
+ }
46
+ //# sourceMappingURL=context-builder.js.map