@symerian/symi 3.0.18 → 3.0.19

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 (116) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/canvas-host/a2ui/.bundle.hash +1 -1
  3. package/package.json +1 -1
  4. package/extensions/copilot-proxy/README.md +0 -24
  5. package/extensions/copilot-proxy/index.ts +0 -154
  6. package/extensions/copilot-proxy/node_modules/.bin/symi +0 -21
  7. package/extensions/copilot-proxy/package.json +0 -15
  8. package/extensions/copilot-proxy/symi.plugin.json +0 -9
  9. package/extensions/device-pair/index.ts +0 -642
  10. package/extensions/device-pair/symi.plugin.json +0 -20
  11. package/extensions/diagnostics-otel/index.ts +0 -15
  12. package/extensions/diagnostics-otel/node_modules/.bin/acorn +0 -21
  13. package/extensions/diagnostics-otel/node_modules/.bin/symi +0 -21
  14. package/extensions/diagnostics-otel/package.json +0 -27
  15. package/extensions/diagnostics-otel/src/service.test.ts +0 -290
  16. package/extensions/diagnostics-otel/src/service.ts +0 -666
  17. package/extensions/diagnostics-otel/symi.plugin.json +0 -8
  18. package/extensions/google-antigravity-auth/README.md +0 -24
  19. package/extensions/google-antigravity-auth/index.ts +0 -424
  20. package/extensions/google-antigravity-auth/node_modules/.bin/symi +0 -21
  21. package/extensions/google-antigravity-auth/package.json +0 -15
  22. package/extensions/google-antigravity-auth/symi.plugin.json +0 -9
  23. package/extensions/google-gemini-cli-auth/README.md +0 -35
  24. package/extensions/google-gemini-cli-auth/index.ts +0 -75
  25. package/extensions/google-gemini-cli-auth/node_modules/.bin/symi +0 -21
  26. package/extensions/google-gemini-cli-auth/oauth.test.ts +0 -162
  27. package/extensions/google-gemini-cli-auth/oauth.ts +0 -636
  28. package/extensions/google-gemini-cli-auth/package.json +0 -15
  29. package/extensions/google-gemini-cli-auth/symi.plugin.json +0 -9
  30. package/extensions/learning-loop/index.ts +0 -159
  31. package/extensions/learning-loop/node_modules/.bin/symi +0 -21
  32. package/extensions/learning-loop/package.json +0 -18
  33. package/extensions/learning-loop/src/analytics/gateway-methods.ts +0 -230
  34. package/extensions/learning-loop/src/analytics/metrics-aggregator.ts +0 -153
  35. package/extensions/learning-loop/src/capture/run-tracker.ts +0 -181
  36. package/extensions/learning-loop/src/capture/serializer.ts +0 -74
  37. package/extensions/learning-loop/src/db.ts +0 -583
  38. package/extensions/learning-loop/src/feedback/explicit-feedback.ts +0 -58
  39. package/extensions/learning-loop/src/feedback/implicit-signals.ts +0 -89
  40. package/extensions/learning-loop/src/graph/edge-inference.ts +0 -189
  41. package/extensions/learning-loop/src/graph/graph-retrieval.ts +0 -144
  42. package/extensions/learning-loop/src/graph/graph-store.ts +0 -183
  43. package/extensions/learning-loop/src/hooks.ts +0 -244
  44. package/extensions/learning-loop/src/injection/cache.ts +0 -73
  45. package/extensions/learning-loop/src/injection/context-injector.ts +0 -104
  46. package/extensions/learning-loop/src/injection/prompt-builder.ts +0 -43
  47. package/extensions/learning-loop/src/learning/embedding-bridge.ts +0 -54
  48. package/extensions/learning-loop/src/learning/learning-extractor.ts +0 -217
  49. package/extensions/learning-loop/src/learning/learning-store.ts +0 -158
  50. package/extensions/learning-loop/src/learning/retrieval.ts +0 -87
  51. package/extensions/learning-loop/src/math/confidence-intervals.ts +0 -62
  52. package/extensions/learning-loop/src/math/ewma.ts +0 -51
  53. package/extensions/learning-loop/src/math/weighted-scorer.ts +0 -42
  54. package/extensions/learning-loop/src/schema.ts +0 -176
  55. package/extensions/learning-loop/src/scoring/normalization.ts +0 -32
  56. package/extensions/learning-loop/src/scoring/quality-engine.ts +0 -78
  57. package/extensions/learning-loop/src/scoring/signal-extractors.ts +0 -155
  58. package/extensions/learning-loop/src/test/context-injector.test.ts +0 -142
  59. package/extensions/learning-loop/src/test/fixes.test.ts +0 -1286
  60. package/extensions/learning-loop/src/test/graph.test.ts +0 -711
  61. package/extensions/learning-loop/src/test/integration.test.ts +0 -312
  62. package/extensions/learning-loop/src/test/learning-store.test.ts +0 -191
  63. package/extensions/learning-loop/src/test/math.test.ts +0 -148
  64. package/extensions/learning-loop/src/test/quality-engine.test.ts +0 -231
  65. package/extensions/learning-loop/src/test/run-tracker.test.ts +0 -143
  66. package/extensions/learning-loop/src/types.ts +0 -281
  67. package/extensions/learning-loop/symi.plugin.json +0 -46
  68. package/extensions/llm-task/README.md +0 -97
  69. package/extensions/llm-task/index.ts +0 -6
  70. package/extensions/llm-task/package.json +0 -12
  71. package/extensions/llm-task/src/llm-task-tool.test.ts +0 -138
  72. package/extensions/llm-task/src/llm-task-tool.ts +0 -249
  73. package/extensions/llm-task/symi.plugin.json +0 -21
  74. package/extensions/memory-lancedb/config.ts +0 -161
  75. package/extensions/memory-lancedb/index.test.ts +0 -330
  76. package/extensions/memory-lancedb/index.ts +0 -670
  77. package/extensions/memory-lancedb/node_modules/.bin/arrow2csv +0 -21
  78. package/extensions/memory-lancedb/node_modules/.bin/openai +0 -21
  79. package/extensions/memory-lancedb/node_modules/.bin/symi +0 -21
  80. package/extensions/memory-lancedb/package.json +0 -20
  81. package/extensions/memory-lancedb/symi.plugin.json +0 -71
  82. package/extensions/minimax-portal-auth/README.md +0 -33
  83. package/extensions/minimax-portal-auth/index.ts +0 -161
  84. package/extensions/minimax-portal-auth/node_modules/.bin/symi +0 -21
  85. package/extensions/minimax-portal-auth/oauth.ts +0 -247
  86. package/extensions/minimax-portal-auth/package.json +0 -15
  87. package/extensions/minimax-portal-auth/symi.plugin.json +0 -9
  88. package/extensions/model-equalizer/index.ts +0 -80
  89. package/extensions/model-equalizer/skills/model-equalizer/SKILL.md +0 -58
  90. package/extensions/model-equalizer/src/detection.ts +0 -62
  91. package/extensions/model-equalizer/src/enhancer.ts +0 -63
  92. package/extensions/model-equalizer/src/test/detection.test.ts +0 -218
  93. package/extensions/model-equalizer/src/test/enhancer.test.ts +0 -137
  94. package/extensions/model-equalizer/src/test/integration.test.ts +0 -185
  95. package/extensions/model-equalizer/src/types.ts +0 -24
  96. package/extensions/model-equalizer/symi.plugin.json +0 -12
  97. package/extensions/phone-control/index.ts +0 -421
  98. package/extensions/phone-control/symi.plugin.json +0 -10
  99. package/extensions/pipeline/README.md +0 -75
  100. package/extensions/pipeline/SKILL.md +0 -97
  101. package/extensions/pipeline/index.ts +0 -18
  102. package/extensions/pipeline/package.json +0 -11
  103. package/extensions/pipeline/src/pipeline-tool.test.ts +0 -345
  104. package/extensions/pipeline/src/pipeline-tool.ts +0 -266
  105. package/extensions/pipeline/src/windows-spawn.test.ts +0 -148
  106. package/extensions/pipeline/src/windows-spawn.ts +0 -193
  107. package/extensions/pipeline/symi.plugin.json +0 -10
  108. package/extensions/qwen-portal-auth/README.md +0 -24
  109. package/extensions/qwen-portal-auth/index.ts +0 -134
  110. package/extensions/qwen-portal-auth/oauth.ts +0 -190
  111. package/extensions/qwen-portal-auth/symi.plugin.json +0 -9
  112. package/extensions/talk-voice/index.ts +0 -150
  113. package/extensions/talk-voice/symi.plugin.json +0 -10
  114. package/extensions/thread-ownership/index.test.ts +0 -180
  115. package/extensions/thread-ownership/index.ts +0 -133
  116. package/extensions/thread-ownership/symi.plugin.json +0 -28
@@ -1,97 +0,0 @@
1
- # LLM Task (plugin)
2
-
3
- Adds an **optional** agent tool `llm-task` for running **JSON-only** LLM tasks
4
- (drafting, summarizing, classifying) with optional JSON Schema validation.
5
-
6
- Designed to be called from workflow engines (for example, Pipeline via
7
- `symi.invoke --each`) without adding new Symi code per workflow.
8
-
9
- ## Enable
10
-
11
- 1. Enable the plugin:
12
-
13
- ```json
14
- {
15
- "plugins": {
16
- "entries": {
17
- "llm-task": { "enabled": true }
18
- }
19
- }
20
- }
21
- ```
22
-
23
- 2. Allowlist the tool (it is registered with `optional: true`):
24
-
25
- ```json
26
- {
27
- "agents": {
28
- "list": [
29
- {
30
- "id": "main",
31
- "tools": { "allow": ["llm-task"] }
32
- }
33
- ]
34
- }
35
- }
36
- ```
37
-
38
- ## Config (optional)
39
-
40
- ```json
41
- {
42
- "plugins": {
43
- "entries": {
44
- "llm-task": {
45
- "enabled": true,
46
- "config": {
47
- "defaultProvider": "openai-codex",
48
- "defaultModel": "gpt-5.2",
49
- "defaultAuthProfileId": "main",
50
- "allowedModels": ["openai-codex/gpt-5.2"],
51
- "maxTokens": 800,
52
- "timeoutMs": 30000
53
- }
54
- }
55
- }
56
- }
57
- }
58
- ```
59
-
60
- `allowedModels` is an allowlist of `provider/model` strings. If set, any request
61
- outside the list is rejected.
62
-
63
- ## Tool API
64
-
65
- ### Parameters
66
-
67
- - `prompt` (string, required)
68
- - `input` (any, optional)
69
- - `schema` (object, optional JSON Schema)
70
- - `provider` (string, optional)
71
- - `model` (string, optional)
72
- - `authProfileId` (string, optional)
73
- - `temperature` (number, optional)
74
- - `maxTokens` (number, optional)
75
- - `timeoutMs` (number, optional)
76
-
77
- ### Output
78
-
79
- Returns `details.json` containing the parsed JSON (and validates against
80
- `schema` when provided).
81
-
82
- ## Notes
83
-
84
- - The tool is **JSON-only** and instructs the model to output only JSON
85
- (no code fences, no commentary).
86
- - No tools are exposed to the model for this run.
87
- - Side effects should be handled outside this tool (for example, approvals in
88
- Pipeline) before calling tools that send messages/emails.
89
-
90
- ## Bundled extension note
91
-
92
- This extension depends on Symi internal modules (the embedded agent runner).
93
- It is intended to ship as a **bundled** Symi extension (like `pipeline`) and
94
- be enabled via `plugins.entries` + tool allowlists.
95
-
96
- It is **not** currently designed to be copied into
97
- `~/.symi/extensions` as a standalone plugin directory.
@@ -1,6 +0,0 @@
1
- import type { AnyAgentTool, SymiPluginApi } from "../../src/plugins/types.js";
2
- import { createLlmTaskTool } from "./src/llm-task-tool.js";
3
-
4
- export default function register(api: SymiPluginApi) {
5
- api.registerTool(createLlmTaskTool(api) as unknown as AnyAgentTool, { optional: true });
6
- }
@@ -1,12 +0,0 @@
1
- {
2
- "name": "@symi/llm-task",
3
- "version": "3.0.9",
4
- "private": true,
5
- "description": "Symi JSON-only LLM task plugin",
6
- "type": "module",
7
- "symi": {
8
- "extensions": [
9
- "./index.ts"
10
- ]
11
- }
12
- }
@@ -1,138 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
-
3
- vi.mock("../../../src/agents/pi-embedded-runner.js", () => {
4
- return {
5
- runEmbeddedPiAgent: vi.fn(async () => ({
6
- meta: { startedAt: Date.now() },
7
- payloads: [{ text: "{}" }],
8
- })),
9
- };
10
- });
11
-
12
- import { runEmbeddedPiAgent } from "../../../src/agents/pi-embedded-runner.js";
13
- import { createLlmTaskTool } from "./llm-task-tool.js";
14
-
15
- // oxlint-disable-next-line typescript/no-explicit-any
16
- function fakeApi(overrides: any = {}) {
17
- return {
18
- id: "llm-task",
19
- name: "llm-task",
20
- source: "test",
21
- config: {
22
- agents: { defaults: { workspace: "/tmp", model: { primary: "openai-codex/gpt-5.2" } } },
23
- },
24
- pluginConfig: {},
25
- runtime: { version: "test" },
26
- logger: { debug() {}, info() {}, warn() {}, error() {} },
27
- registerTool() {},
28
- ...overrides,
29
- };
30
- }
31
-
32
- describe("llm-task tool (json-only)", () => {
33
- beforeEach(() => vi.clearAllMocks());
34
-
35
- it("returns parsed json", async () => {
36
- // oxlint-disable-next-line typescript/no-explicit-any
37
- (runEmbeddedPiAgent as any).mockResolvedValueOnce({
38
- meta: {},
39
- payloads: [{ text: JSON.stringify({ foo: "bar" }) }],
40
- });
41
- const tool = createLlmTaskTool(fakeApi());
42
- const res = await tool.execute("id", { prompt: "return foo" });
43
- // oxlint-disable-next-line typescript/no-explicit-any
44
- expect((res as any).details.json).toEqual({ foo: "bar" });
45
- });
46
-
47
- it("strips fenced json", async () => {
48
- // oxlint-disable-next-line typescript/no-explicit-any
49
- (runEmbeddedPiAgent as any).mockResolvedValueOnce({
50
- meta: {},
51
- payloads: [{ text: '```json\n{"ok":true}\n```' }],
52
- });
53
- const tool = createLlmTaskTool(fakeApi());
54
- const res = await tool.execute("id", { prompt: "return ok" });
55
- // oxlint-disable-next-line typescript/no-explicit-any
56
- expect((res as any).details.json).toEqual({ ok: true });
57
- });
58
-
59
- it("validates schema", async () => {
60
- // oxlint-disable-next-line typescript/no-explicit-any
61
- (runEmbeddedPiAgent as any).mockResolvedValueOnce({
62
- meta: {},
63
- payloads: [{ text: JSON.stringify({ foo: "bar" }) }],
64
- });
65
- const tool = createLlmTaskTool(fakeApi());
66
- const schema = {
67
- type: "object",
68
- properties: { foo: { type: "string" } },
69
- required: ["foo"],
70
- additionalProperties: false,
71
- };
72
- const res = await tool.execute("id", { prompt: "return foo", schema });
73
- // oxlint-disable-next-line typescript/no-explicit-any
74
- expect((res as any).details.json).toEqual({ foo: "bar" });
75
- });
76
-
77
- it("throws on invalid json", async () => {
78
- // oxlint-disable-next-line typescript/no-explicit-any
79
- (runEmbeddedPiAgent as any).mockResolvedValueOnce({
80
- meta: {},
81
- payloads: [{ text: "not-json" }],
82
- });
83
- const tool = createLlmTaskTool(fakeApi());
84
- await expect(tool.execute("id", { prompt: "x" })).rejects.toThrow(/invalid json/i);
85
- });
86
-
87
- it("throws on schema mismatch", async () => {
88
- // oxlint-disable-next-line typescript/no-explicit-any
89
- (runEmbeddedPiAgent as any).mockResolvedValueOnce({
90
- meta: {},
91
- payloads: [{ text: JSON.stringify({ foo: 1 }) }],
92
- });
93
- const tool = createLlmTaskTool(fakeApi());
94
- const schema = { type: "object", properties: { foo: { type: "string" } }, required: ["foo"] };
95
- await expect(tool.execute("id", { prompt: "x", schema })).rejects.toThrow(/match schema/i);
96
- });
97
-
98
- it("passes provider/model overrides to embedded runner", async () => {
99
- // oxlint-disable-next-line typescript/no-explicit-any
100
- (runEmbeddedPiAgent as any).mockResolvedValueOnce({
101
- meta: {},
102
- payloads: [{ text: JSON.stringify({ ok: true }) }],
103
- });
104
- const tool = createLlmTaskTool(fakeApi());
105
- await tool.execute("id", { prompt: "x", provider: "anthropic", model: "claude-4-sonnet" });
106
- // oxlint-disable-next-line typescript/no-explicit-any
107
- const call = (runEmbeddedPiAgent as any).mock.calls[0]?.[0];
108
- expect(call.provider).toBe("anthropic");
109
- expect(call.model).toBe("claude-4-sonnet");
110
- });
111
-
112
- it("enforces allowedModels", async () => {
113
- // oxlint-disable-next-line typescript/no-explicit-any
114
- (runEmbeddedPiAgent as any).mockResolvedValueOnce({
115
- meta: {},
116
- payloads: [{ text: JSON.stringify({ ok: true }) }],
117
- });
118
- const tool = createLlmTaskTool(
119
- fakeApi({ pluginConfig: { allowedModels: ["openai-codex/gpt-5.2"] } }),
120
- );
121
- await expect(
122
- tool.execute("id", { prompt: "x", provider: "anthropic", model: "claude-4-sonnet" }),
123
- ).rejects.toThrow(/not allowed/i);
124
- });
125
-
126
- it("disables tools for embedded run", async () => {
127
- // oxlint-disable-next-line typescript/no-explicit-any
128
- (runEmbeddedPiAgent as any).mockResolvedValueOnce({
129
- meta: {},
130
- payloads: [{ text: JSON.stringify({ ok: true }) }],
131
- });
132
- const tool = createLlmTaskTool(fakeApi());
133
- await tool.execute("id", { prompt: "x" });
134
- // oxlint-disable-next-line typescript/no-explicit-any
135
- const call = (runEmbeddedPiAgent as any).mock.calls[0]?.[0];
136
- expect(call.disableTools).toBe(true);
137
- });
138
- });
@@ -1,249 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import { Type } from "@sinclair/typebox";
5
- import Ajv from "ajv";
6
- // NOTE: This extension is intended to be bundled with Symi.
7
- // When running from source (tests/dev), Symi internals live under src/.
8
- // When running from a built install, internals live under dist/ (no src/ tree).
9
- // So we resolve internal imports dynamically with src-first, dist-fallback.
10
- import type { SymiPluginApi } from "../../../src/plugins/types.js";
11
-
12
- type RunEmbeddedPiAgentFn = (params: Record<string, unknown>) => Promise<unknown>;
13
-
14
- async function loadRunEmbeddedPiAgent(): Promise<RunEmbeddedPiAgentFn> {
15
- // Source checkout (tests/dev)
16
- try {
17
- const mod = await import("../../../src/agents/pi-embedded-runner.js");
18
- // oxlint-disable-next-line typescript/no-explicit-any
19
- if (typeof (mod as any).runEmbeddedPiAgent === "function") {
20
- // oxlint-disable-next-line typescript/no-explicit-any
21
- return (mod as any).runEmbeddedPiAgent;
22
- }
23
- } catch {
24
- // ignore
25
- }
26
-
27
- // Bundled install (built)
28
- const mod = await import("../../../src/agents/pi-embedded-runner.js");
29
- if (typeof mod.runEmbeddedPiAgent !== "function") {
30
- throw new Error("Internal error: runEmbeddedPiAgent not available");
31
- }
32
- return mod.runEmbeddedPiAgent as RunEmbeddedPiAgentFn;
33
- }
34
-
35
- function stripCodeFences(s: string): string {
36
- const trimmed = s.trim();
37
- const m = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
38
- if (m) {
39
- return (m[1] ?? "").trim();
40
- }
41
- return trimmed;
42
- }
43
-
44
- function collectText(payloads: Array<{ text?: string; isError?: boolean }> | undefined): string {
45
- const texts = (payloads ?? [])
46
- .filter((p) => !p.isError && typeof p.text === "string")
47
- .map((p) => p.text ?? "");
48
- return texts.join("\n").trim();
49
- }
50
-
51
- function toModelKey(provider?: string, model?: string): string | undefined {
52
- const p = provider?.trim();
53
- const m = model?.trim();
54
- if (!p || !m) {
55
- return undefined;
56
- }
57
- return `${p}/${m}`;
58
- }
59
-
60
- type PluginCfg = {
61
- defaultProvider?: string;
62
- defaultModel?: string;
63
- defaultAuthProfileId?: string;
64
- allowedModels?: string[];
65
- maxTokens?: number;
66
- timeoutMs?: number;
67
- };
68
-
69
- export function createLlmTaskTool(api: SymiPluginApi) {
70
- return {
71
- name: "llm-task",
72
- label: "LLM Task",
73
- description:
74
- "Run a generic JSON-only LLM task and return schema-validated JSON. Designed for orchestration from Pipeline workflows via symi.invoke.",
75
- parameters: Type.Object({
76
- prompt: Type.String({ description: "Task instruction for the LLM." }),
77
- input: Type.Optional(Type.Unknown({ description: "Optional input payload for the task." })),
78
- schema: Type.Optional(
79
- Type.Unknown({ description: "Optional JSON Schema to validate the returned JSON." }),
80
- ),
81
- provider: Type.Optional(
82
- Type.String({ description: "Provider override (e.g. openai-codex, anthropic)." }),
83
- ),
84
- model: Type.Optional(Type.String({ description: "Model id override." })),
85
- authProfileId: Type.Optional(Type.String({ description: "Auth profile override." })),
86
- temperature: Type.Optional(Type.Number({ description: "Best-effort temperature override." })),
87
- maxTokens: Type.Optional(Type.Number({ description: "Best-effort maxTokens override." })),
88
- timeoutMs: Type.Optional(Type.Number({ description: "Timeout for the LLM run." })),
89
- }),
90
-
91
- async execute(_id: string, params: Record<string, unknown>) {
92
- const prompt = typeof params.prompt === "string" ? params.prompt : "";
93
- if (!prompt.trim()) {
94
- throw new Error("prompt required");
95
- }
96
-
97
- const pluginCfg = (api.pluginConfig ?? {}) as PluginCfg;
98
-
99
- const primary = api.config?.agents?.defaults?.model?.primary;
100
- const primaryProvider = typeof primary === "string" ? primary.split("/")[0] : undefined;
101
- const primaryModel =
102
- typeof primary === "string" ? primary.split("/").slice(1).join("/") : undefined;
103
-
104
- const provider =
105
- (typeof params.provider === "string" && params.provider.trim()) ||
106
- (typeof pluginCfg.defaultProvider === "string" && pluginCfg.defaultProvider.trim()) ||
107
- primaryProvider ||
108
- undefined;
109
-
110
- const model =
111
- (typeof params.model === "string" && params.model.trim()) ||
112
- (typeof pluginCfg.defaultModel === "string" && pluginCfg.defaultModel.trim()) ||
113
- primaryModel ||
114
- undefined;
115
-
116
- const authProfileId =
117
- // oxlint-disable-next-line typescript/no-explicit-any
118
- (typeof (params as any).authProfileId === "string" &&
119
- // oxlint-disable-next-line typescript/no-explicit-any
120
- (params as any).authProfileId.trim()) ||
121
- (typeof pluginCfg.defaultAuthProfileId === "string" &&
122
- pluginCfg.defaultAuthProfileId.trim()) ||
123
- undefined;
124
-
125
- const modelKey = toModelKey(provider, model);
126
- if (!provider || !model || !modelKey) {
127
- throw new Error(
128
- `provider/model could not be resolved (provider=${String(provider ?? "")}, model=${String(model ?? "")})`,
129
- );
130
- }
131
-
132
- const allowed = Array.isArray(pluginCfg.allowedModels) ? pluginCfg.allowedModels : undefined;
133
- if (allowed && allowed.length > 0 && !allowed.includes(modelKey)) {
134
- throw new Error(
135
- `Model not allowed by llm-task plugin config: ${modelKey}. Allowed models: ${allowed.join(", ")}`,
136
- );
137
- }
138
-
139
- const timeoutMs =
140
- (typeof params.timeoutMs === "number" && params.timeoutMs > 0
141
- ? params.timeoutMs
142
- : undefined) ||
143
- (typeof pluginCfg.timeoutMs === "number" && pluginCfg.timeoutMs > 0
144
- ? pluginCfg.timeoutMs
145
- : undefined) ||
146
- 30_000;
147
-
148
- const streamParams = {
149
- temperature: typeof params.temperature === "number" ? params.temperature : undefined,
150
- maxTokens:
151
- typeof params.maxTokens === "number"
152
- ? params.maxTokens
153
- : typeof pluginCfg.maxTokens === "number"
154
- ? pluginCfg.maxTokens
155
- : undefined,
156
- };
157
-
158
- // oxlint-disable-next-line typescript/no-explicit-any
159
- const input = (params as any).input as unknown;
160
- let inputJson: string;
161
- try {
162
- inputJson = JSON.stringify(input ?? null, null, 2);
163
- } catch {
164
- throw new Error("input must be JSON-serializable");
165
- }
166
-
167
- const system = [
168
- "You are a JSON-only function.",
169
- "Return ONLY a valid JSON value.",
170
- "Do not wrap in markdown fences.",
171
- "Do not include commentary.",
172
- "Do not call tools.",
173
- ].join(" ");
174
-
175
- const fullPrompt = `${system}\n\nTASK:\n${prompt}\n\nINPUT_JSON:\n${inputJson}\n`;
176
-
177
- let tmpDir: string | null = null;
178
- try {
179
- tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "symi-llm-task-"));
180
- const sessionId = `llm-task-${Date.now()}`;
181
- const sessionFile = path.join(tmpDir, "session.json");
182
-
183
- const runEmbeddedPiAgent = await loadRunEmbeddedPiAgent();
184
-
185
- const result = await runEmbeddedPiAgent({
186
- sessionId,
187
- sessionFile,
188
- workspaceDir: api.config?.agents?.defaults?.workspace ?? process.cwd(),
189
- config: api.config,
190
- prompt: fullPrompt,
191
- timeoutMs,
192
- runId: `llm-task-${Date.now()}`,
193
- provider,
194
- model,
195
- authProfileId,
196
- authProfileIdSource: authProfileId ? "user" : "auto",
197
- streamParams,
198
- disableTools: true,
199
- });
200
-
201
- // oxlint-disable-next-line typescript/no-explicit-any
202
- const text = collectText((result as any).payloads);
203
- if (!text) {
204
- throw new Error("LLM returned empty output");
205
- }
206
-
207
- const raw = stripCodeFences(text);
208
- let parsed: unknown;
209
- try {
210
- parsed = JSON.parse(raw);
211
- } catch {
212
- throw new Error("LLM returned invalid JSON");
213
- }
214
-
215
- // oxlint-disable-next-line typescript/no-explicit-any
216
- const schema = (params as any).schema as unknown;
217
- if (schema && typeof schema === "object" && !Array.isArray(schema)) {
218
- const ajv = new Ajv.default({ allErrors: true, strict: false });
219
- // oxlint-disable-next-line typescript/no-explicit-any
220
- const validate = ajv.compile(schema as any);
221
- const ok = validate(parsed);
222
- if (!ok) {
223
- const msg =
224
- validate.errors
225
- ?.map(
226
- (e: { instancePath?: string; message?: string }) =>
227
- `${e.instancePath || "<root>"} ${e.message || "invalid"}`,
228
- )
229
- .join("; ") ?? "invalid";
230
- throw new Error(`LLM JSON did not match schema: ${msg}`);
231
- }
232
- }
233
-
234
- return {
235
- content: [{ type: "text", text: JSON.stringify(parsed, null, 2) }],
236
- details: { json: parsed, provider, model },
237
- };
238
- } finally {
239
- if (tmpDir) {
240
- try {
241
- await fs.rm(tmpDir, { recursive: true, force: true });
242
- } catch {
243
- // ignore
244
- }
245
- }
246
- }
247
- },
248
- };
249
- }
@@ -1,21 +0,0 @@
1
- {
2
- "id": "llm-task",
3
- "name": "LLM Task",
4
- "description": "Generic JSON-only LLM tool for structured tasks callable from workflows.",
5
- "configSchema": {
6
- "type": "object",
7
- "additionalProperties": false,
8
- "properties": {
9
- "defaultProvider": { "type": "string" },
10
- "defaultModel": { "type": "string" },
11
- "defaultAuthProfileId": { "type": "string" },
12
- "allowedModels": {
13
- "type": "array",
14
- "items": { "type": "string" },
15
- "description": "Allowlist of provider/model keys like openai-codex/gpt-5.2."
16
- },
17
- "maxTokens": { "type": "number" },
18
- "timeoutMs": { "type": "number" }
19
- }
20
- }
21
- }
@@ -1,161 +0,0 @@
1
- import fs from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
-
5
- export type MemoryConfig = {
6
- embedding: {
7
- provider: "openai";
8
- model?: string;
9
- apiKey: string;
10
- };
11
- dbPath?: string;
12
- autoCapture?: boolean;
13
- autoRecall?: boolean;
14
- captureMaxChars?: number;
15
- };
16
-
17
- export const MEMORY_CATEGORIES = ["preference", "fact", "decision", "entity", "other"] as const;
18
- export type MemoryCategory = (typeof MEMORY_CATEGORIES)[number];
19
-
20
- const DEFAULT_MODEL = "text-embedding-3-small";
21
- export const DEFAULT_CAPTURE_MAX_CHARS = 500;
22
- const LEGACY_STATE_DIRS: string[] = [];
23
-
24
- function resolveDefaultDbPath(): string {
25
- const home = homedir();
26
- const preferred = join(home, ".symi", "memory", "lancedb");
27
- try {
28
- if (fs.existsSync(preferred)) {
29
- return preferred;
30
- }
31
- } catch {
32
- // best-effort
33
- }
34
-
35
- for (const legacy of LEGACY_STATE_DIRS) {
36
- const candidate = join(home, legacy, "memory", "lancedb");
37
- try {
38
- if (fs.existsSync(candidate)) {
39
- return candidate;
40
- }
41
- } catch {
42
- // best-effort
43
- }
44
- }
45
-
46
- return preferred;
47
- }
48
-
49
- const DEFAULT_DB_PATH = resolveDefaultDbPath();
50
-
51
- const EMBEDDING_DIMENSIONS: Record<string, number> = {
52
- "text-embedding-3-small": 1536,
53
- "text-embedding-3-large": 3072,
54
- };
55
-
56
- function assertAllowedKeys(value: Record<string, unknown>, allowed: string[], label: string) {
57
- const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
58
- if (unknown.length === 0) {
59
- return;
60
- }
61
- throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`);
62
- }
63
-
64
- export function vectorDimsForModel(model: string): number {
65
- const dims = EMBEDDING_DIMENSIONS[model];
66
- if (!dims) {
67
- throw new Error(`Unsupported embedding model: ${model}`);
68
- }
69
- return dims;
70
- }
71
-
72
- function resolveEnvVars(value: string): string {
73
- return value.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
74
- const envValue = process.env[envVar];
75
- if (!envValue) {
76
- throw new Error(`Environment variable ${envVar} is not set`);
77
- }
78
- return envValue;
79
- });
80
- }
81
-
82
- function resolveEmbeddingModel(embedding: Record<string, unknown>): string {
83
- const model = typeof embedding.model === "string" ? embedding.model : DEFAULT_MODEL;
84
- vectorDimsForModel(model);
85
- return model;
86
- }
87
-
88
- export const memoryConfigSchema = {
89
- parse(value: unknown): MemoryConfig {
90
- if (!value || typeof value !== "object" || Array.isArray(value)) {
91
- throw new Error("memory config required");
92
- }
93
- const cfg = value as Record<string, unknown>;
94
- assertAllowedKeys(
95
- cfg,
96
- ["embedding", "dbPath", "autoCapture", "autoRecall", "captureMaxChars"],
97
- "memory config",
98
- );
99
-
100
- const embedding = cfg.embedding as Record<string, unknown> | undefined;
101
- if (!embedding || typeof embedding.apiKey !== "string") {
102
- throw new Error("embedding.apiKey is required");
103
- }
104
- assertAllowedKeys(embedding, ["apiKey", "model"], "embedding config");
105
-
106
- const model = resolveEmbeddingModel(embedding);
107
-
108
- const captureMaxChars =
109
- typeof cfg.captureMaxChars === "number" ? Math.floor(cfg.captureMaxChars) : undefined;
110
- if (
111
- typeof captureMaxChars === "number" &&
112
- (captureMaxChars < 100 || captureMaxChars > 10_000)
113
- ) {
114
- throw new Error("captureMaxChars must be between 100 and 10000");
115
- }
116
-
117
- return {
118
- embedding: {
119
- provider: "openai",
120
- model,
121
- apiKey: resolveEnvVars(embedding.apiKey),
122
- },
123
- dbPath: typeof cfg.dbPath === "string" ? cfg.dbPath : DEFAULT_DB_PATH,
124
- autoCapture: cfg.autoCapture === true,
125
- autoRecall: cfg.autoRecall !== false,
126
- captureMaxChars: captureMaxChars ?? DEFAULT_CAPTURE_MAX_CHARS,
127
- };
128
- },
129
- uiHints: {
130
- "embedding.apiKey": {
131
- label: "OpenAI API Key",
132
- sensitive: true,
133
- placeholder: "sk-proj-...",
134
- help: "API key for OpenAI embeddings (or use ${OPENAI_API_KEY})",
135
- },
136
- "embedding.model": {
137
- label: "Embedding Model",
138
- placeholder: DEFAULT_MODEL,
139
- help: "OpenAI embedding model to use",
140
- },
141
- dbPath: {
142
- label: "Database Path",
143
- placeholder: "~/.symi/memory/lancedb",
144
- advanced: true,
145
- },
146
- autoCapture: {
147
- label: "Auto-Capture",
148
- help: "Automatically capture important information from conversations",
149
- },
150
- autoRecall: {
151
- label: "Auto-Recall",
152
- help: "Automatically inject relevant memories into context",
153
- },
154
- captureMaxChars: {
155
- label: "Capture Max Chars",
156
- help: "Maximum message length eligible for auto-capture",
157
- advanced: true,
158
- placeholder: String(DEFAULT_CAPTURE_MAX_CHARS),
159
- },
160
- },
161
- };