c0de-agent 0.0.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/cli/commands/acp.d.ts +19 -0
- package/dist/cli/commands/acp.js +50 -0
- package/dist/cli/commands/chat.d.ts +13 -0
- package/dist/cli/commands/chat.js +26 -0
- package/dist/cli/commands/config.d.ts +17 -0
- package/dist/cli/commands/config.js +77 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.js +14 -0
- package/dist/cli/commands/plugin.d.ts +11 -0
- package/dist/cli/commands/plugin.js +31 -0
- package/dist/cli/commands/serve.d.ts +18 -0
- package/dist/cli/commands/serve.js +35 -0
- package/dist/cli/commands/update.d.ts +24 -0
- package/dist/cli/commands/update.js +40 -0
- package/dist/cli/deps.d.ts +19 -0
- package/dist/cli/deps.js +57 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +149 -0
- package/dist/cli/modes/acp.d.ts +18 -0
- package/dist/cli/modes/acp.js +57 -0
- package/dist/cli/modes/print.d.ts +16 -0
- package/dist/cli/modes/print.js +30 -0
- package/dist/cli/parser.d.ts +19 -0
- package/dist/cli/parser.js +28 -0
- package/dist/cli/utils/output.d.ts +11 -0
- package/dist/cli/utils/output.js +23 -0
- package/dist/cli/utils/prompt.d.ts +12 -0
- package/dist/cli/utils/prompt.js +18 -0
- package/dist/core/agent.d.ts +12 -0
- package/dist/core/agent.js +95 -0
- package/dist/core/agents/builtin.d.ts +6 -0
- package/dist/core/agents/builtin.js +76 -0
- package/dist/core/agents/discovery.d.ts +6 -0
- package/dist/core/agents/discovery.js +96 -0
- package/dist/core/agents/index.d.ts +6 -0
- package/dist/core/agents/index.js +4 -0
- package/dist/core/agents/parallel.d.ts +13 -0
- package/dist/core/agents/parallel.js +37 -0
- package/dist/core/agents/registry.d.ts +4 -0
- package/dist/core/agents/registry.js +23 -0
- package/dist/core/agents/types.d.ts +37 -0
- package/dist/core/agents/types.js +1 -0
- package/dist/core/compact.d.ts +11 -0
- package/dist/core/compact.js +31 -0
- package/dist/core/config.d.ts +7 -0
- package/dist/core/config.js +84 -0
- package/dist/core/context.d.ts +23 -0
- package/dist/core/context.js +93 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +11 -0
- package/dist/core/loop.d.ts +25 -0
- package/dist/core/loop.js +651 -0
- package/dist/core/metrics.d.ts +27 -0
- package/dist/core/metrics.js +94 -0
- package/dist/core/prompt-registry.d.ts +21 -0
- package/dist/core/prompt-registry.js +200 -0
- package/dist/core/prompt.d.ts +11 -0
- package/dist/core/prompt.js +13 -0
- package/dist/core/secret.d.ts +12 -0
- package/dist/core/secret.js +51 -0
- package/dist/core/slash.d.ts +15 -0
- package/dist/core/slash.js +114 -0
- package/dist/core/steering.d.ts +8 -0
- package/dist/core/steering.js +15 -0
- package/dist/core/title.d.ts +42 -0
- package/dist/core/title.js +115 -0
- package/dist/core/tool-exec.d.ts +20 -0
- package/dist/core/tool-exec.js +64 -0
- package/dist/core/types.d.ts +95 -0
- package/dist/core/types.js +1 -0
- package/dist/core/worktree.d.ts +24 -0
- package/dist/core/worktree.js +68 -0
- package/dist/dap/index.d.ts +6 -0
- package/dist/dap/index.js +4 -0
- package/dist/dap/protocol.d.ts +40 -0
- package/dist/dap/protocol.js +132 -0
- package/dist/dap/session.d.ts +23 -0
- package/dist/dap/session.js +130 -0
- package/dist/dap/transport.d.ts +8 -0
- package/dist/dap/transport.js +30 -0
- package/dist/dap/types.d.ts +50 -0
- package/dist/dap/types.js +2 -0
- package/dist/db/client.d.ts +22 -0
- package/dist/db/client.js +39 -0
- package/dist/db/index.d.ts +6 -0
- package/dist/db/index.js +4 -0
- package/dist/db/migrate.d.ts +9 -0
- package/dist/db/migrate.js +15 -0
- package/dist/db/schema.d.ts +965 -0
- package/dist/db/schema.js +112 -0
- package/dist/db/types.d.ts +11 -0
- package/dist/db/types.js +1 -0
- package/dist/llm/index.d.ts +13 -0
- package/dist/llm/index.js +8 -0
- package/dist/llm/protocols/openai-compat.d.ts +139 -0
- package/dist/llm/protocols/openai-compat.js +214 -0
- package/dist/llm/protocols/utils/index.d.ts +2 -0
- package/dist/llm/protocols/utils/index.js +2 -0
- package/dist/llm/protocols/utils/lifecycle.d.ts +23 -0
- package/dist/llm/protocols/utils/lifecycle.js +46 -0
- package/dist/llm/protocols/utils/tool-stream.d.ts +46 -0
- package/dist/llm/protocols/utils/tool-stream.js +94 -0
- package/dist/llm/provider-error.d.ts +5 -0
- package/dist/llm/provider-error.js +33 -0
- package/dist/llm/provider.d.ts +32 -0
- package/dist/llm/provider.js +191 -0
- package/dist/llm/registry.d.ts +44 -0
- package/dist/llm/registry.js +107 -0
- package/dist/llm/retry.d.ts +37 -0
- package/dist/llm/retry.js +82 -0
- package/dist/llm/routing.d.ts +36 -0
- package/dist/llm/routing.js +53 -0
- package/dist/llm/schema/errors.d.ts +88 -0
- package/dist/llm/schema/errors.js +33 -0
- package/dist/llm/schema/events.d.ts +135 -0
- package/dist/llm/schema/events.js +35 -0
- package/dist/llm/schema/ids.d.ts +22 -0
- package/dist/llm/schema/ids.js +3 -0
- package/dist/llm/schema/index.d.ts +5 -0
- package/dist/llm/schema/index.js +5 -0
- package/dist/llm/schema/messages.d.ts +97 -0
- package/dist/llm/schema/messages.js +18 -0
- package/dist/llm/schema/options.d.ts +54 -0
- package/dist/llm/schema/options.js +54 -0
- package/dist/llm/token.d.ts +2 -0
- package/dist/llm/token.js +13 -0
- package/dist/llm/transport.d.ts +31 -0
- package/dist/llm/transport.js +165 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/plugins/builtin.d.ts +6 -0
- package/dist/plugins/builtin.js +54 -0
- package/dist/plugins/hooks.d.ts +3 -0
- package/dist/plugins/hooks.js +79 -0
- package/dist/plugins/index.d.ts +9 -0
- package/dist/plugins/index.js +8 -0
- package/dist/plugins/init.d.ts +23 -0
- package/dist/plugins/init.js +30 -0
- package/dist/plugins/lifecycle.d.ts +6 -0
- package/dist/plugins/lifecycle.js +74 -0
- package/dist/plugins/loader.d.ts +16 -0
- package/dist/plugins/loader.js +68 -0
- package/dist/plugins/logger.d.ts +4 -0
- package/dist/plugins/logger.js +31 -0
- package/dist/plugins/registry.d.ts +7 -0
- package/dist/plugins/registry.js +24 -0
- package/dist/plugins/types.d.ts +113 -0
- package/dist/plugins/types.js +1 -0
- package/dist/project/detect.d.ts +4 -0
- package/dist/project/detect.js +131 -0
- package/dist/project/index.d.ts +4 -0
- package/dist/project/index.js +2 -0
- package/dist/project/project.d.ts +23 -0
- package/dist/project/project.js +87 -0
- package/dist/project/resolve.d.ts +8 -0
- package/dist/project/resolve.js +81 -0
- package/dist/server/agent-manager.d.ts +32 -0
- package/dist/server/agent-manager.js +54 -0
- package/dist/server/app.d.ts +5 -0
- package/dist/server/app.js +75 -0
- package/dist/server/context.d.ts +20 -0
- package/dist/server/context.js +47 -0
- package/dist/server/dev.d.ts +11 -0
- package/dist/server/dev.js +83 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.js +7 -0
- package/dist/server/middleware/auth.d.ts +15 -0
- package/dist/server/middleware/auth.js +29 -0
- package/dist/server/middleware/cors.d.ts +16 -0
- package/dist/server/middleware/cors.js +44 -0
- package/dist/server/middleware/error.d.ts +15 -0
- package/dist/server/middleware/error.js +11 -0
- package/dist/server/permission/interactive.d.ts +24 -0
- package/dist/server/permission/interactive.js +56 -0
- package/dist/server/permission/store.d.ts +30 -0
- package/dist/server/permission/store.js +47 -0
- package/dist/server/routes/agent.d.ts +4 -0
- package/dist/server/routes/agent.js +19 -0
- package/dist/server/routes/catalog.d.ts +42 -0
- package/dist/server/routes/catalog.js +194 -0
- package/dist/server/routes/chat.d.ts +8 -0
- package/dist/server/routes/chat.js +282 -0
- package/dist/server/routes/commands.d.ts +5 -0
- package/dist/server/routes/commands.js +17 -0
- package/dist/server/routes/config.d.ts +4 -0
- package/dist/server/routes/config.js +27 -0
- package/dist/server/routes/files.d.ts +4 -0
- package/dist/server/routes/files.js +183 -0
- package/dist/server/routes/filesystem.d.ts +29 -0
- package/dist/server/routes/filesystem.js +108 -0
- package/dist/server/routes/health.d.ts +3 -0
- package/dist/server/routes/health.js +13 -0
- package/dist/server/routes/permissions.d.ts +5 -0
- package/dist/server/routes/permissions.js +23 -0
- package/dist/server/routes/project.d.ts +4 -0
- package/dist/server/routes/project.js +51 -0
- package/dist/server/routes/provider.d.ts +16 -0
- package/dist/server/routes/provider.js +66 -0
- package/dist/server/routes/session.d.ts +4 -0
- package/dist/server/routes/session.js +130 -0
- package/dist/server/routes/tool.d.ts +4 -0
- package/dist/server/routes/tool.js +28 -0
- package/dist/server/routes/update.d.ts +14 -0
- package/dist/server/routes/update.js +49 -0
- package/dist/server/server.d.ts +43 -0
- package/dist/server/server.js +197 -0
- package/dist/server/types.d.ts +73 -0
- package/dist/server/types.js +2 -0
- package/dist/server/util/safe-path.d.ts +7 -0
- package/dist/server/util/safe-path.js +7 -0
- package/dist/session/archive.d.ts +15 -0
- package/dist/session/archive.js +129 -0
- package/dist/session/branch.d.ts +9 -0
- package/dist/session/branch.js +63 -0
- package/dist/session/compaction.d.ts +27 -0
- package/dist/session/compaction.js +192 -0
- package/dist/session/context.d.ts +16 -0
- package/dist/session/context.js +173 -0
- package/dist/session/index.d.ts +10 -0
- package/dist/session/index.js +10 -0
- package/dist/session/message.d.ts +22 -0
- package/dist/session/message.js +145 -0
- package/dist/session/session.d.ts +37 -0
- package/dist/session/session.js +144 -0
- package/dist/session/snapshot.d.ts +11 -0
- package/dist/session/snapshot.js +60 -0
- package/dist/session/squash.d.ts +9 -0
- package/dist/session/squash.js +88 -0
- package/dist/session/token.d.ts +10 -0
- package/dist/session/token.js +33 -0
- package/dist/session/types.d.ts +122 -0
- package/dist/session/types.js +1 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/types/agent.d.ts +202 -0
- package/dist/shared/types/agent.js +1 -0
- package/dist/shared/types/base.d.ts +32 -0
- package/dist/shared/types/base.js +1 -0
- package/dist/shared/types/config.d.ts +104 -0
- package/dist/shared/types/config.js +1 -0
- package/dist/shared/types/index.d.ts +6 -0
- package/dist/shared/types/index.js +1 -0
- package/dist/shared/types/llm.d.ts +122 -0
- package/dist/shared/types/llm.js +1 -0
- package/dist/shared/types/message.d.ts +71 -0
- package/dist/shared/types/message.js +1 -0
- package/dist/shared/types/tool.d.ts +134 -0
- package/dist/shared/types/tool.js +1 -0
- package/dist/shared/utils/path.d.ts +15 -0
- package/dist/shared/utils/path.js +23 -0
- package/dist/shared/utils.d.ts +5 -0
- package/dist/shared/utils.js +10 -0
- package/dist/tools/builtin/bash.d.ts +12 -0
- package/dist/tools/builtin/bash.js +121 -0
- package/dist/tools/builtin/dap.d.ts +23 -0
- package/dist/tools/builtin/dap.js +231 -0
- package/dist/tools/builtin/edit.d.ts +12 -0
- package/dist/tools/builtin/edit.js +151 -0
- package/dist/tools/builtin/glob.d.ts +11 -0
- package/dist/tools/builtin/glob.js +120 -0
- package/dist/tools/builtin/grep.d.ts +6 -0
- package/dist/tools/builtin/grep.js +141 -0
- package/dist/tools/builtin/read.d.ts +6 -0
- package/dist/tools/builtin/read.js +73 -0
- package/dist/tools/builtin/resolvers.d.ts +12 -0
- package/dist/tools/builtin/resolvers.js +79 -0
- package/dist/tools/builtin/task.d.ts +9 -0
- package/dist/tools/builtin/task.js +100 -0
- package/dist/tools/builtin/write.d.ts +6 -0
- package/dist/tools/builtin/write.js +36 -0
- package/dist/tools/builtin/yield.d.ts +9 -0
- package/dist/tools/builtin/yield.js +40 -0
- package/dist/tools/executor.d.ts +9 -0
- package/dist/tools/executor.js +54 -0
- package/dist/tools/hashline/index.d.ts +2 -0
- package/dist/tools/hashline/index.js +3 -0
- package/dist/tools/hashline/patch.d.ts +48 -0
- package/dist/tools/hashline/patch.js +201 -0
- package/dist/tools/index.d.ts +30 -0
- package/dist/tools/index.js +55 -0
- package/dist/tools/permission.d.ts +13 -0
- package/dist/tools/permission.js +39 -0
- package/dist/tools/registry.d.ts +15 -0
- package/dist/tools/registry.js +54 -0
- package/dist/tools/resolver.d.ts +15 -0
- package/dist/tools/resolver.js +47 -0
- package/dist/tools/truncate.d.ts +8 -0
- package/dist/tools/truncate.js +50 -0
- package/dist/tools/types.d.ts +101 -0
- package/dist/tools/types.js +1 -0
- package/dist/tools/validate.d.ts +8 -0
- package/dist/tools/validate.js +130 -0
- package/dist/tools/websearch/fetch.d.ts +11 -0
- package/dist/tools/websearch/fetch.js +21 -0
- package/dist/tools/websearch/index.d.ts +19 -0
- package/dist/tools/websearch/index.js +78 -0
- package/dist/tools/websearch/providers/brave.d.ts +5 -0
- package/dist/tools/websearch/providers/brave.js +53 -0
- package/dist/tools/websearch/providers/duckduckgo.d.ts +3 -0
- package/dist/tools/websearch/providers/duckduckgo.js +75 -0
- package/dist/tools/websearch/providers/tavily.d.ts +11 -0
- package/dist/tools/websearch/providers/tavily.js +89 -0
- package/dist/tools/websearch/types.d.ts +51 -0
- package/dist/tools/websearch/types.js +18 -0
- package/dist/tools/websearch/websearch.d.ts +10 -0
- package/dist/tools/websearch/websearch.js +48 -0
- package/dist/update/index.d.ts +10 -0
- package/dist/update/index.js +6 -0
- package/dist/update/ipc.d.ts +15 -0
- package/dist/update/ipc.js +41 -0
- package/dist/update/scheduler.d.ts +34 -0
- package/dist/update/scheduler.js +57 -0
- package/dist/update/snapshot.d.ts +39 -0
- package/dist/update/snapshot.js +99 -0
- package/dist/update/updater.d.ts +48 -0
- package/dist/update/updater.js +83 -0
- package/dist/update/version.d.ts +18 -0
- package/dist/update/version.js +49 -0
- package/drizzle/0000_magical_nightshade.sql +55 -0
- package/drizzle/0001_chemical_doctor_faustus.sql +13 -0
- package/drizzle/0002_amusing_micromacro.sql +12 -0
- package/drizzle/0003_regular_purifiers.sql +2 -0
- package/drizzle/meta/0000_snapshot.json +448 -0
- package/drizzle/meta/0001_snapshot.json +539 -0
- package/drizzle/meta/0002_snapshot.json +633 -0
- package/drizzle/meta/0003_snapshot.json +645 -0
- package/drizzle/meta/_journal.json +34 -0
- package/package.json +100 -8
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { isContextOverflow } from './provider-error.js';
|
|
2
|
+
import { llmError } from './schema/errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parse a Server-Sent Events byte stream into individual `data:` payloads.
|
|
5
|
+
* Emits the decoded data string for each event block. Skips empty data and
|
|
6
|
+
* the `[DONE]` sentinel. Works for both OpenAI (`data: {...}`) and Anthropic
|
|
7
|
+
* (`event: {type}\ndata: {...}`) framing — only the `data:` lines are emitted.
|
|
8
|
+
*/
|
|
9
|
+
const sseFraming = async function* (stream) {
|
|
10
|
+
const decoder = new TextDecoder();
|
|
11
|
+
const reader = stream.getReader();
|
|
12
|
+
let buffer = '';
|
|
13
|
+
try {
|
|
14
|
+
for (;;) {
|
|
15
|
+
const { done, value } = await reader.read();
|
|
16
|
+
if (done)
|
|
17
|
+
break;
|
|
18
|
+
buffer += decoder.decode(value, { stream: true });
|
|
19
|
+
let sepIndex = buffer.indexOf('\n\n');
|
|
20
|
+
while (sepIndex !== -1) {
|
|
21
|
+
const block = buffer.slice(0, sepIndex);
|
|
22
|
+
buffer = buffer.slice(sepIndex + 2);
|
|
23
|
+
const data = extractData(block);
|
|
24
|
+
if (data !== null && data.length > 0 && data !== '[DONE]') {
|
|
25
|
+
yield data;
|
|
26
|
+
}
|
|
27
|
+
sepIndex = buffer.indexOf('\n\n');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const tailData = extractData(buffer);
|
|
31
|
+
if (tailData !== null && tailData.length > 0 && tailData !== '[DONE]') {
|
|
32
|
+
yield tailData;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
reader.releaseLock();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
/** Join all `data:` lines of an SSE block into one string. */
|
|
40
|
+
const extractData = (block) => {
|
|
41
|
+
const lines = block.split('\n');
|
|
42
|
+
const dataLines = [];
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
if (line.startsWith('data:')) {
|
|
45
|
+
dataLines.push(line.slice(5).trimStart());
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (dataLines.length === 0)
|
|
49
|
+
return null;
|
|
50
|
+
return dataLines.join('\n');
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Eagerly POST a JSON body and return the response body stream for framing.
|
|
54
|
+
* Throws an LLMError (classified) on non-2xx responses or transport failure.
|
|
55
|
+
* Used when the connection must be retryable independently of frame consumption.
|
|
56
|
+
*/
|
|
57
|
+
const httpPost = async (options) => {
|
|
58
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
59
|
+
let response;
|
|
60
|
+
try {
|
|
61
|
+
response = await fetchImpl(options.url, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: { 'content-type': 'application/json', ...options.headers },
|
|
64
|
+
body: JSON.stringify(options.body),
|
|
65
|
+
signal: options.signal,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (cause) {
|
|
69
|
+
throw llmError('ProviderShared', 'request', {
|
|
70
|
+
_tag: 'Transport',
|
|
71
|
+
message: `Failed to send request to ${options.url}: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
72
|
+
kind: 'network',
|
|
73
|
+
url: options.url,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (!response.ok || response.body === null) {
|
|
77
|
+
const text = await response.text().catch(() => '');
|
|
78
|
+
throw classifyHttpError(response.status, text, options.url, response.headers);
|
|
79
|
+
}
|
|
80
|
+
return response.body;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* POST a JSON body and yield SSE data frames from the response.
|
|
84
|
+
* Throws an LLMError on non-2xx responses, classifying context-overflow.
|
|
85
|
+
*/
|
|
86
|
+
const streamHTTP = async function* (options) {
|
|
87
|
+
const body = await httpPost(options);
|
|
88
|
+
yield* sseFraming(body);
|
|
89
|
+
};
|
|
90
|
+
/** Map an HTTP failure status + body to an LLMError. */
|
|
91
|
+
const classifyHttpError = (status, body, url, headers) => {
|
|
92
|
+
const http = {
|
|
93
|
+
request: { method: 'POST', url, headers: {} },
|
|
94
|
+
response: { status, headers: Object.fromEntries(headers.entries()) },
|
|
95
|
+
body,
|
|
96
|
+
};
|
|
97
|
+
if (status === 401 || status === 403) {
|
|
98
|
+
return llmError('ProviderShared', 'request', {
|
|
99
|
+
_tag: 'Authentication',
|
|
100
|
+
message: body || `Authentication failed (${status})`,
|
|
101
|
+
kind: status === 401 ? 'invalid' : 'insufficient-permissions',
|
|
102
|
+
http,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (status === 429) {
|
|
106
|
+
const retryAfterMs = parseRetryAfterMs(headers.get('retry-after-ms'), headers.get('retry-after'));
|
|
107
|
+
return llmError('ProviderShared', 'request', {
|
|
108
|
+
_tag: 'RateLimit',
|
|
109
|
+
message: body || 'Rate limited',
|
|
110
|
+
retryAfterMs,
|
|
111
|
+
http,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (status === 402 || /quota|billing|insufficient.{0,4}quota|exceeded.{0,12}limit/i.test(body)) {
|
|
115
|
+
return llmError('ProviderShared', 'request', {
|
|
116
|
+
_tag: 'QuotaExceeded',
|
|
117
|
+
message: body || `Quota exceeded (${status})`,
|
|
118
|
+
http,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (status >= 500) {
|
|
122
|
+
return llmError('ProviderShared', 'request', {
|
|
123
|
+
_tag: 'ProviderInternal',
|
|
124
|
+
message: body || `Provider error (${status})`,
|
|
125
|
+
status,
|
|
126
|
+
http,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
// Bodyless 413/400 (no error text) is a context-window overflow signal.
|
|
130
|
+
if (status === 413 || (status === 400 && body.length === 0) || isContextOverflow(body)) {
|
|
131
|
+
return llmError('ProviderShared', 'request', {
|
|
132
|
+
_tag: 'InvalidRequest',
|
|
133
|
+
message: body || 'Context overflow',
|
|
134
|
+
classification: 'context-overflow',
|
|
135
|
+
http,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (/content.{0,4}(filter|policy)|safety/i.test(body)) {
|
|
139
|
+
return llmError('ProviderShared', 'request', {
|
|
140
|
+
_tag: 'ContentPolicy',
|
|
141
|
+
message: body || 'Content policy violation',
|
|
142
|
+
http,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return llmError('ProviderShared', 'request', {
|
|
146
|
+
_tag: 'UnknownProvider',
|
|
147
|
+
message: body || `Unknown provider error (${status})`,
|
|
148
|
+
status,
|
|
149
|
+
http,
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
const parseRetryAfterMs = (retryAfterMs, retryAfter) => {
|
|
153
|
+
if (retryAfterMs !== null && retryAfterMs !== undefined) {
|
|
154
|
+
const parsed = Number.parseFloat(retryAfterMs);
|
|
155
|
+
if (!Number.isNaN(parsed))
|
|
156
|
+
return Math.ceil(parsed);
|
|
157
|
+
}
|
|
158
|
+
if (retryAfter !== null && retryAfter !== undefined) {
|
|
159
|
+
const parsed = Number.parseFloat(retryAfter);
|
|
160
|
+
if (!Number.isNaN(parsed))
|
|
161
|
+
return Math.ceil(parsed * 1000);
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
};
|
|
165
|
+
export { classifyHttpError, httpPost, sseFraming, streamHTTP };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Plugin, PluginRegistry, PluginServices } from './types.js';
|
|
2
|
+
declare function createToolAuditLogger(): Plugin;
|
|
3
|
+
declare function createWriteGuard(): Plugin;
|
|
4
|
+
declare const BUILTIN_PLUGINS: Plugin[];
|
|
5
|
+
declare function registerBuiltinHooks(registry: PluginRegistry, services: PluginServices, names?: string[]): Promise<void>;
|
|
6
|
+
export { BUILTIN_PLUGINS, createToolAuditLogger, createWriteGuard, registerBuiltinHooks };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/plugins/builtin.ts
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
4
|
+
import { activatePlugin } from './lifecycle.js';
|
|
5
|
+
import { createLogger } from './logger.js';
|
|
6
|
+
import { registerPlugin } from './registry.js';
|
|
7
|
+
function createToolAuditLogger() {
|
|
8
|
+
const logger = createLogger('tool-audit');
|
|
9
|
+
return {
|
|
10
|
+
name: 'tool-audit-log',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
description: 'Logs all tool execution activity',
|
|
13
|
+
setup: (ctx) => {
|
|
14
|
+
ctx.on('tool:before', (data) => {
|
|
15
|
+
logger.info(`tool:before → ${data.tool}`, { input: data.input });
|
|
16
|
+
});
|
|
17
|
+
ctx.on('tool:after', (data) => {
|
|
18
|
+
const status = data.result._tag;
|
|
19
|
+
logger.info(`tool:after ← ${data.tool} [${status}]`);
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function createWriteGuard() {
|
|
25
|
+
const logger = createLogger('write-guard');
|
|
26
|
+
return {
|
|
27
|
+
name: 'write-guard',
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
description: 'Warns before overwriting existing files',
|
|
30
|
+
setup: (ctx) => {
|
|
31
|
+
ctx.on('tool:before', (data) => {
|
|
32
|
+
if (data.tool !== 'write' && data.tool !== 'edit')
|
|
33
|
+
return;
|
|
34
|
+
const input = data.input;
|
|
35
|
+
const rawPath = input?.path ?? input?.file;
|
|
36
|
+
if (typeof rawPath !== 'string')
|
|
37
|
+
return;
|
|
38
|
+
const fullPath = isAbsolute(rawPath) ? rawPath : resolve(data.ctx.cwd, rawPath);
|
|
39
|
+
if (existsSync(fullPath)) {
|
|
40
|
+
logger.warn(`Overwriting existing file: ${fullPath}`);
|
|
41
|
+
}
|
|
42
|
+
}, 50);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const BUILTIN_PLUGINS = [createToolAuditLogger(), createWriteGuard()];
|
|
47
|
+
async function registerBuiltinHooks(registry, services, names) {
|
|
48
|
+
const selected = names ? BUILTIN_PLUGINS.filter((p) => names.includes(p.name)) : BUILTIN_PLUGINS;
|
|
49
|
+
for (const plugin of selected) {
|
|
50
|
+
registerPlugin(registry, plugin);
|
|
51
|
+
await activatePlugin(registry, plugin, services);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export { BUILTIN_PLUGINS, createToolAuditLogger, createWriteGuard, registerBuiltinHooks };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/plugins/hooks.ts
|
|
2
|
+
import { createLogger } from './logger.js';
|
|
3
|
+
function withTimeout(promise, ms) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const timer = setTimeout(() => reject(new Error(`Hook handler timed out after ${ms}ms`)), ms);
|
|
6
|
+
promise.then((val) => {
|
|
7
|
+
clearTimeout(timer);
|
|
8
|
+
resolve(val);
|
|
9
|
+
}, (err) => {
|
|
10
|
+
clearTimeout(timer);
|
|
11
|
+
reject(err);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function sortByPriority(regs) {
|
|
16
|
+
return regs.slice().sort((a, b) => a.priority - b.priority);
|
|
17
|
+
}
|
|
18
|
+
function createHookRunner(opts) {
|
|
19
|
+
const timeout = opts?.timeout ?? 5000;
|
|
20
|
+
const logger = opts?.logger ?? createLogger('hooks');
|
|
21
|
+
const handlers = new Map();
|
|
22
|
+
const on = (event, handler, priority) => {
|
|
23
|
+
const key = event;
|
|
24
|
+
const regs = handlers.get(key) ?? [];
|
|
25
|
+
regs.push({ handler, priority });
|
|
26
|
+
handlers.set(key, regs);
|
|
27
|
+
};
|
|
28
|
+
const off = (event, handler) => {
|
|
29
|
+
const key = event;
|
|
30
|
+
const regs = handlers.get(key);
|
|
31
|
+
if (!regs)
|
|
32
|
+
return;
|
|
33
|
+
const filtered = regs.filter((r) => r.handler !== handler);
|
|
34
|
+
handlers.set(key, filtered);
|
|
35
|
+
};
|
|
36
|
+
const runHooks = async (event, data) => {
|
|
37
|
+
const regs = handlers.get(event);
|
|
38
|
+
if (!regs || regs.length === 0)
|
|
39
|
+
return data;
|
|
40
|
+
let current = data;
|
|
41
|
+
for (const reg of sortByPriority(regs)) {
|
|
42
|
+
try {
|
|
43
|
+
const result = await withTimeout(Promise.resolve(reg.handler(current)), timeout);
|
|
44
|
+
if (result === false)
|
|
45
|
+
return false;
|
|
46
|
+
if (result !== undefined)
|
|
47
|
+
current = result;
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
logger.warn(`Handler for "${String(event)}" failed:`, err);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return current;
|
|
54
|
+
};
|
|
55
|
+
const fireHooks = async (event, data) => {
|
|
56
|
+
const regs = handlers.get(event);
|
|
57
|
+
if (!regs || regs.length === 0)
|
|
58
|
+
return;
|
|
59
|
+
await Promise.allSettled(sortByPriority(regs).map(async (reg) => {
|
|
60
|
+
try {
|
|
61
|
+
await withTimeout(Promise.resolve(reg.handler(data)), timeout);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
logger.warn(`Handler for "${String(event)}" failed:`, err);
|
|
65
|
+
}
|
|
66
|
+
}));
|
|
67
|
+
};
|
|
68
|
+
const dispose = () => {
|
|
69
|
+
handlers.clear();
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
on: ((event, handler, priority = 100) => on(event, handler, priority)),
|
|
73
|
+
off: off,
|
|
74
|
+
runHooks: runHooks,
|
|
75
|
+
fireHooks: fireHooks,
|
|
76
|
+
dispose,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export { createHookRunner };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { BUILTIN_PLUGINS, createToolAuditLogger, createWriteGuard, registerBuiltinHooks, } from './builtin.js';
|
|
2
|
+
export { createHookRunner } from './hooks.js';
|
|
3
|
+
export type { InitPluginsOptions, InitPluginsResult } from './init.js';
|
|
4
|
+
export { initPlugins } from './init.js';
|
|
5
|
+
export { activatePlugin, createPluginContext, deactivateAll, deactivatePlugin, } from './lifecycle.js';
|
|
6
|
+
export { discoverPlugins, loadPlugin, validatePluginModule } from './loader.js';
|
|
7
|
+
export { createLogger } from './logger.js';
|
|
8
|
+
export { createPluginRegistry, getPlugin, listPlugins, registerPlugin, unregisterPlugin, } from './registry.js';
|
|
9
|
+
export type { HookHandler, HookMap, HookRunner, HookRunnerOptions, Logger, LogLevel, Plugin, PluginContext, PluginRecord, PluginRegistry, PluginServices, PluginStatus, } from './types.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// src/plugins/index.ts
|
|
2
|
+
export { BUILTIN_PLUGINS, createToolAuditLogger, createWriteGuard, registerBuiltinHooks, } from './builtin.js';
|
|
3
|
+
export { createHookRunner } from './hooks.js';
|
|
4
|
+
export { initPlugins } from './init.js';
|
|
5
|
+
export { activatePlugin, createPluginContext, deactivateAll, deactivatePlugin, } from './lifecycle.js';
|
|
6
|
+
export { discoverPlugins, loadPlugin, validatePluginModule } from './loader.js';
|
|
7
|
+
export { createLogger } from './logger.js';
|
|
8
|
+
export { createPluginRegistry, getPlugin, listPlugins, registerPlugin, unregisterPlugin, } from './registry.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Config } from '../shared/types/config.js';
|
|
2
|
+
import type { HookRunner, PluginRegistry } from './types.js';
|
|
3
|
+
type InitPluginsOptions = {
|
|
4
|
+
cwd: string;
|
|
5
|
+
config: Config;
|
|
6
|
+
/** 工具注册表;插件通过 registerTool 往这里注入(spec §7.2 PluginContext)。 */
|
|
7
|
+
toolRegistry: unknown;
|
|
8
|
+
/** LLM 注册表;插件通过 registerProvider 往这里注入。 */
|
|
9
|
+
llmRegistry: unknown;
|
|
10
|
+
};
|
|
11
|
+
type InitPluginsResult = {
|
|
12
|
+
pluginRegistry: PluginRegistry;
|
|
13
|
+
hookRunner: HookRunner;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* 初始化插件系统:注册表 + hook runner + 内置插件 + 外部插件发现。
|
|
17
|
+
*
|
|
18
|
+
* 失败的外部插件被静默跳过(loader 已处理),单个坏插件不影响其余加载。
|
|
19
|
+
* 内置插件(tool-audit-log / write-guard)始终激活。
|
|
20
|
+
*/
|
|
21
|
+
declare function initPlugins(opts: InitPluginsOptions): Promise<InitPluginsResult>;
|
|
22
|
+
export type { InitPluginsOptions, InitPluginsResult };
|
|
23
|
+
export { initPlugins };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { registerBuiltinHooks } from './builtin.js';
|
|
2
|
+
import { createHookRunner } from './hooks.js';
|
|
3
|
+
import { activatePlugin } from './lifecycle.js';
|
|
4
|
+
import { discoverPlugins } from './loader.js';
|
|
5
|
+
import { createPluginRegistry, registerPlugin } from './registry.js';
|
|
6
|
+
/**
|
|
7
|
+
* 初始化插件系统:注册表 + hook runner + 内置插件 + 外部插件发现。
|
|
8
|
+
*
|
|
9
|
+
* 失败的外部插件被静默跳过(loader 已处理),单个坏插件不影响其余加载。
|
|
10
|
+
* 内置插件(tool-audit-log / write-guard)始终激活。
|
|
11
|
+
*/
|
|
12
|
+
async function initPlugins(opts) {
|
|
13
|
+
const hookRunner = createHookRunner();
|
|
14
|
+
const pluginRegistry = createPluginRegistry(hookRunner);
|
|
15
|
+
const services = {
|
|
16
|
+
config: opts.config,
|
|
17
|
+
toolRegistry: opts.toolRegistry,
|
|
18
|
+
llmRegistry: opts.llmRegistry,
|
|
19
|
+
};
|
|
20
|
+
// 内置插件先激活:它们注册 tool:before/after hook,对后续外部插件同样生效。
|
|
21
|
+
await registerBuiltinHooks(pluginRegistry, services);
|
|
22
|
+
// 发现并加载外部插件(项目 .c0de/plugins 与全局 ~/.c0de/plugins)。
|
|
23
|
+
const discovered = await discoverPlugins(opts.cwd);
|
|
24
|
+
for (const { plugin } of discovered) {
|
|
25
|
+
registerPlugin(pluginRegistry, plugin);
|
|
26
|
+
await activatePlugin(pluginRegistry, plugin, services);
|
|
27
|
+
}
|
|
28
|
+
return { pluginRegistry, hookRunner };
|
|
29
|
+
}
|
|
30
|
+
export { initPlugins };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { HookRunner, Plugin, PluginContext, PluginRegistry, PluginServices } from './types.js';
|
|
2
|
+
declare function createPluginContext(services: PluginServices, hookRunner: HookRunner, disposeHandlers: (() => void | Promise<void>)[]): PluginContext;
|
|
3
|
+
declare function activatePlugin(registry: PluginRegistry, plugin: Plugin, services: PluginServices): Promise<void>;
|
|
4
|
+
declare function deactivatePlugin(registry: PluginRegistry, name: string): Promise<void>;
|
|
5
|
+
declare function deactivateAll(registry: PluginRegistry): Promise<void>;
|
|
6
|
+
export { activatePlugin, createPluginContext, deactivateAll, deactivatePlugin };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/plugins/lifecycle.ts
|
|
2
|
+
import { registerProvider as registerLLMProvider } from '../llm/registry.js';
|
|
3
|
+
import { registerTool } from '../tools/registry.js';
|
|
4
|
+
import { createLogger } from './logger.js';
|
|
5
|
+
function createPluginContext(services, hookRunner, disposeHandlers) {
|
|
6
|
+
const toolRegistry = services.toolRegistry;
|
|
7
|
+
const llmRegistry = services.llmRegistry;
|
|
8
|
+
return {
|
|
9
|
+
registerTool: (tool) => {
|
|
10
|
+
registerTool(toolRegistry, tool);
|
|
11
|
+
},
|
|
12
|
+
registerProvider: (provider) => {
|
|
13
|
+
if (provider.baseURL) {
|
|
14
|
+
registerLLMProvider(llmRegistry, {
|
|
15
|
+
name: provider.name,
|
|
16
|
+
baseURL: provider.baseURL,
|
|
17
|
+
apiKey: provider.apiKey,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
on: hookRunner.on,
|
|
22
|
+
off: hookRunner.off,
|
|
23
|
+
getConfig: () => services.config,
|
|
24
|
+
getLogger: (name) => createLogger(name),
|
|
25
|
+
onDispose: (handler) => {
|
|
26
|
+
disposeHandlers.push(handler);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
async function activatePlugin(registry, plugin, services) {
|
|
31
|
+
const record = registry.plugins.get(plugin.name);
|
|
32
|
+
if (!record)
|
|
33
|
+
return;
|
|
34
|
+
if (record.status === 'active')
|
|
35
|
+
return;
|
|
36
|
+
const ctx = createPluginContext(services, registry.hookRunner, record.disposeHandlers);
|
|
37
|
+
try {
|
|
38
|
+
await plugin.setup(ctx);
|
|
39
|
+
record.status = 'active';
|
|
40
|
+
record.error = undefined;
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
record.status = 'error';
|
|
44
|
+
record.error = err instanceof Error ? err.message : String(err);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function deactivatePlugin(registry, name) {
|
|
48
|
+
const record = registry.plugins.get(name);
|
|
49
|
+
if (!record)
|
|
50
|
+
return;
|
|
51
|
+
for (const handler of record.disposeHandlers) {
|
|
52
|
+
try {
|
|
53
|
+
await handler();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Dispose errors are non-fatal
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
record.disposeHandlers = [];
|
|
60
|
+
if (record.plugin.dispose) {
|
|
61
|
+
try {
|
|
62
|
+
await record.plugin.dispose();
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Non-fatal
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
record.status = 'inactive';
|
|
69
|
+
}
|
|
70
|
+
async function deactivateAll(registry) {
|
|
71
|
+
const names = Array.from(registry.plugins.keys());
|
|
72
|
+
await Promise.all(names.map((name) => deactivatePlugin(registry, name)));
|
|
73
|
+
}
|
|
74
|
+
export { activatePlugin, createPluginContext, deactivateAll, deactivatePlugin };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Plugin } from './types.js';
|
|
2
|
+
type ValidationResult = {
|
|
3
|
+
valid: true;
|
|
4
|
+
plugin: Plugin;
|
|
5
|
+
} | {
|
|
6
|
+
valid: false;
|
|
7
|
+
error: string;
|
|
8
|
+
};
|
|
9
|
+
declare function validatePluginModule(mod: unknown): ValidationResult;
|
|
10
|
+
declare function loadPlugin(path: string): Promise<Plugin>;
|
|
11
|
+
declare function discoverPlugins(projectDir: string): Promise<{
|
|
12
|
+
name: string;
|
|
13
|
+
path: string;
|
|
14
|
+
plugin: Plugin;
|
|
15
|
+
}[]>;
|
|
16
|
+
export { discoverPlugins, loadPlugin, validatePluginModule };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// src/plugins/loader.ts
|
|
2
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
function validatePluginModule(mod) {
|
|
6
|
+
const candidate = mod?.default ?? mod;
|
|
7
|
+
if (typeof candidate !== 'object' || candidate === null) {
|
|
8
|
+
return { valid: false, error: 'Plugin module must export an object' };
|
|
9
|
+
}
|
|
10
|
+
const obj = candidate;
|
|
11
|
+
if (typeof obj.name !== 'string' || obj.name.length === 0) {
|
|
12
|
+
return { valid: false, error: 'Plugin must have a non-empty "name" string' };
|
|
13
|
+
}
|
|
14
|
+
if (typeof obj.version !== 'string') {
|
|
15
|
+
return { valid: false, error: 'Plugin must have a "version" string' };
|
|
16
|
+
}
|
|
17
|
+
if (typeof obj.setup !== 'function') {
|
|
18
|
+
return { valid: false, error: 'Plugin must have a "setup" function' };
|
|
19
|
+
}
|
|
20
|
+
return { valid: true, plugin: obj };
|
|
21
|
+
}
|
|
22
|
+
async function loadPlugin(path) {
|
|
23
|
+
// 动态导入运行时扫描到的插件路径,Vite 无法静态分析,故显式忽略
|
|
24
|
+
const mod = await import(/* @vite-ignore */ path);
|
|
25
|
+
const result = validatePluginModule(mod);
|
|
26
|
+
if (!result.valid) {
|
|
27
|
+
throw new Error(`Invalid plugin at ${path}: ${result.error}`);
|
|
28
|
+
}
|
|
29
|
+
return result.plugin;
|
|
30
|
+
}
|
|
31
|
+
function scanPluginDir(dir) {
|
|
32
|
+
if (!existsSync(dir))
|
|
33
|
+
return [];
|
|
34
|
+
const entries = readdirSync(dir);
|
|
35
|
+
const plugins = [];
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
const fullPath = join(dir, entry);
|
|
38
|
+
try {
|
|
39
|
+
if (!statSync(fullPath).isDirectory())
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const indexPath = join(fullPath, 'index.js');
|
|
46
|
+
if (existsSync(indexPath)) {
|
|
47
|
+
plugins.push({ name: entry, path: fullPath });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return plugins;
|
|
51
|
+
}
|
|
52
|
+
async function discoverPlugins(projectDir) {
|
|
53
|
+
const projectPluginsDir = join(projectDir, '.c0de', 'plugins');
|
|
54
|
+
const globalPluginsDir = join(homedir(), '.c0de', 'plugins');
|
|
55
|
+
const discovered = [...scanPluginDir(projectPluginsDir), ...scanPluginDir(globalPluginsDir)];
|
|
56
|
+
const results = [];
|
|
57
|
+
for (const entry of discovered) {
|
|
58
|
+
try {
|
|
59
|
+
const plugin = await loadPlugin(join(entry.path, 'index.js'));
|
|
60
|
+
results.push({ name: entry.name, path: entry.path, plugin });
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Skip plugins that fail to load
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
export { discoverPlugins, loadPlugin, validatePluginModule };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const LEVEL_ORDER = {
|
|
2
|
+
debug: 0,
|
|
3
|
+
info: 1,
|
|
4
|
+
warn: 2,
|
|
5
|
+
error: 3,
|
|
6
|
+
silent: 4,
|
|
7
|
+
};
|
|
8
|
+
/** Create a named, level-filtered logger that writes to console. */
|
|
9
|
+
function createLogger(name, level = 'info') {
|
|
10
|
+
const prefix = `[${name}]`;
|
|
11
|
+
const shouldLog = (target) => LEVEL_ORDER[level] <= LEVEL_ORDER[target];
|
|
12
|
+
return {
|
|
13
|
+
debug: (msg, ...args) => {
|
|
14
|
+
if (shouldLog('debug'))
|
|
15
|
+
console.debug(prefix, msg, ...args);
|
|
16
|
+
},
|
|
17
|
+
info: (msg, ...args) => {
|
|
18
|
+
if (shouldLog('info'))
|
|
19
|
+
console.info(prefix, msg, ...args);
|
|
20
|
+
},
|
|
21
|
+
warn: (msg, ...args) => {
|
|
22
|
+
if (shouldLog('warn'))
|
|
23
|
+
console.warn(prefix, msg, ...args);
|
|
24
|
+
},
|
|
25
|
+
error: (msg, ...args) => {
|
|
26
|
+
if (shouldLog('error'))
|
|
27
|
+
console.error(prefix, msg, ...args);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export { createLogger };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HookRunner, Plugin, PluginRecord, PluginRegistry } from './types.js';
|
|
2
|
+
declare function createPluginRegistry(hookRunner: HookRunner): PluginRegistry;
|
|
3
|
+
declare function registerPlugin(registry: PluginRegistry, plugin: Plugin): void;
|
|
4
|
+
declare function getPlugin(registry: PluginRegistry, name: string): PluginRecord | undefined;
|
|
5
|
+
declare function listPlugins(registry: PluginRegistry): PluginRecord[];
|
|
6
|
+
declare function unregisterPlugin(registry: PluginRegistry, name: string): boolean;
|
|
7
|
+
export { createPluginRegistry, getPlugin, listPlugins, registerPlugin, unregisterPlugin };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function createPluginRegistry(hookRunner) {
|
|
2
|
+
return {
|
|
3
|
+
plugins: new Map(),
|
|
4
|
+
hookRunner,
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
function registerPlugin(registry, plugin) {
|
|
8
|
+
const record = {
|
|
9
|
+
plugin,
|
|
10
|
+
status: 'loaded',
|
|
11
|
+
disposeHandlers: [],
|
|
12
|
+
};
|
|
13
|
+
registry.plugins.set(plugin.name, record);
|
|
14
|
+
}
|
|
15
|
+
function getPlugin(registry, name) {
|
|
16
|
+
return registry.plugins.get(name);
|
|
17
|
+
}
|
|
18
|
+
function listPlugins(registry) {
|
|
19
|
+
return Array.from(registry.plugins.values());
|
|
20
|
+
}
|
|
21
|
+
function unregisterPlugin(registry, name) {
|
|
22
|
+
return registry.plugins.delete(name);
|
|
23
|
+
}
|
|
24
|
+
export { createPluginRegistry, getPlugin, listPlugins, registerPlugin, unregisterPlugin };
|