@uncaged/workflow-agent-builtin 0.5.0-alpha.5

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 (92) hide show
  1. package/README.md +141 -0
  2. package/dist/agent.d.ts +3 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +89 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +5 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/detail.d.ts +13 -0
  11. package/dist/detail.d.ts.map +1 -0
  12. package/dist/detail.js +32 -0
  13. package/dist/detail.js.map +1 -0
  14. package/dist/index.d.ts +11 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +8 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/llm/index.d.ts +3 -0
  19. package/dist/llm/index.d.ts.map +1 -0
  20. package/dist/llm/index.js +2 -0
  21. package/dist/llm/index.js.map +1 -0
  22. package/dist/llm/llm.d.ts +5 -0
  23. package/dist/llm/llm.d.ts.map +1 -0
  24. package/dist/llm/llm.js +119 -0
  25. package/dist/llm/llm.js.map +1 -0
  26. package/dist/llm/types.d.ts +33 -0
  27. package/dist/llm/types.d.ts.map +1 -0
  28. package/dist/llm/types.js +2 -0
  29. package/dist/llm/types.js.map +1 -0
  30. package/dist/loop.d.ts +38 -0
  31. package/dist/loop.d.ts.map +1 -0
  32. package/dist/loop.js +156 -0
  33. package/dist/loop.js.map +1 -0
  34. package/dist/prompt.d.ts +11 -0
  35. package/dist/prompt.d.ts.map +1 -0
  36. package/dist/prompt.js +93 -0
  37. package/dist/prompt.js.map +1 -0
  38. package/dist/schemas.d.ts +4 -0
  39. package/dist/schemas.d.ts.map +1 -0
  40. package/dist/schemas.js +42 -0
  41. package/dist/schemas.js.map +1 -0
  42. package/dist/session.d.ts +10 -0
  43. package/dist/session.d.ts.map +1 -0
  44. package/dist/session.js +45 -0
  45. package/dist/session.js.map +1 -0
  46. package/dist/tools/index.d.ts +8 -0
  47. package/dist/tools/index.d.ts.map +1 -0
  48. package/dist/tools/index.js +33 -0
  49. package/dist/tools/index.js.map +1 -0
  50. package/dist/tools/path.d.ts +3 -0
  51. package/dist/tools/path.d.ts.map +1 -0
  52. package/dist/tools/path.js +6 -0
  53. package/dist/tools/path.js.map +1 -0
  54. package/dist/tools/read-file.d.ts +3 -0
  55. package/dist/tools/read-file.d.ts.map +1 -0
  56. package/dist/tools/read-file.js +39 -0
  57. package/dist/tools/read-file.js.map +1 -0
  58. package/dist/tools/run-command.d.ts +3 -0
  59. package/dist/tools/run-command.d.ts.map +1 -0
  60. package/dist/tools/run-command.js +81 -0
  61. package/dist/tools/run-command.js.map +1 -0
  62. package/dist/tools/types.d.ts +12 -0
  63. package/dist/tools/types.d.ts.map +1 -0
  64. package/dist/tools/types.js +2 -0
  65. package/dist/tools/types.js.map +1 -0
  66. package/dist/tools/write-file.d.ts +3 -0
  67. package/dist/tools/write-file.d.ts.map +1 -0
  68. package/dist/tools/write-file.js +35 -0
  69. package/dist/tools/write-file.js.map +1 -0
  70. package/dist/types.d.ts +42 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +2 -0
  73. package/dist/types.js.map +1 -0
  74. package/package.json +45 -0
  75. package/src/agent.ts +158 -0
  76. package/src/cli.ts +6 -0
  77. package/src/detail.ts +49 -0
  78. package/src/index.ts +16 -0
  79. package/src/llm/index.ts +7 -0
  80. package/src/llm/llm.ts +139 -0
  81. package/src/llm/types.ts +29 -0
  82. package/src/loop.ts +303 -0
  83. package/src/prompt.ts +115 -0
  84. package/src/schemas.ts +45 -0
  85. package/src/session.ts +59 -0
  86. package/src/tools/index.ts +44 -0
  87. package/src/tools/path.ts +6 -0
  88. package/src/tools/read-file.ts +41 -0
  89. package/src/tools/run-command.ts +95 -0
  90. package/src/tools/types.ts +13 -0
  91. package/src/tools/write-file.ts +36 -0
  92. package/src/types.ts +49 -0
@@ -0,0 +1,12 @@
1
+ import type { JSONSchema } from "@uncaged/json-cas";
2
+ export type ToolContext = {
3
+ cwd: string;
4
+ storageRoot: string;
5
+ };
6
+ export type BuiltinTool = {
7
+ name: string;
8
+ description: string;
9
+ parameters: JSONSchema;
10
+ execute: (args: unknown, ctx: ToolContext) => Promise<string>;
11
+ };
12
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/D,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import type { BuiltinTool } from "./types.js";
2
+ export declare const writeFileTool: BuiltinTool;
3
+ //# sourceMappingURL=write-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-file.d.ts","sourceRoot":"","sources":["../../src/tools/write-file.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM9C,eAAO,MAAM,aAAa,EAAE,WA0B3B,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ import { resolvePath } from "./path.js";
4
+ function isRecord(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ export const writeFileTool = {
8
+ name: "write_file",
9
+ description: "Write UTF-8 text to a file in the workspace (creates parent directories).",
10
+ parameters: {
11
+ type: "object",
12
+ required: ["path", "content"],
13
+ properties: {
14
+ path: { type: "string", description: "Relative or absolute path within the workspace." },
15
+ content: { type: "string", description: "File contents to write." },
16
+ },
17
+ additionalProperties: false,
18
+ },
19
+ execute: async (args, ctx) => {
20
+ if (!isRecord(args) || typeof args.path !== "string" || typeof args.content !== "string") {
21
+ return "Error: path and content must be strings";
22
+ }
23
+ const resolved = resolvePath(ctx.cwd, args.path);
24
+ try {
25
+ await mkdir(dirname(resolved), { recursive: true });
26
+ await writeFile(resolved, args.content, "utf8");
27
+ return `Wrote ${args.content.length} bytes to ${args.path}`;
28
+ }
29
+ catch (cause) {
30
+ const message = cause instanceof Error ? cause.message : String(cause);
31
+ return `Error: ${message}`;
32
+ }
33
+ },
34
+ };
35
+ //# sourceMappingURL=write-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-file.js","sourceRoot":"","sources":["../../src/tools/write-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGxC,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAgB;IACxC,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,2EAA2E;IACxF,UAAU,EAAE;QACV,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,CAAC,MAAM,EAAE,SAAS,CAAC;QAC7B,UAAU,EAAE;YACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,iDAAiD,EAAE;YACxF,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,yBAAyB,EAAE;SACpE;QACD,oBAAoB,EAAE,KAAK;KAC5B;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;QAC3B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACzF,OAAO,yCAAyC,CAAC;QACnD,CAAC;QACD,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACpD,MAAM,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAChD,OAAO,SAAS,IAAI,CAAC,OAAO,CAAC,MAAM,aAAa,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,OAAO,UAAU,OAAO,EAAE,CAAC;QAC7B,CAAC;IACH,CAAC;CACF,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { ChatMessage } from "./llm/index.js";
2
+ export type BuiltinToolCallRecord = {
3
+ id: string;
4
+ name: string;
5
+ args: string;
6
+ };
7
+ export type BuiltinToolResultRecord = {
8
+ toolCallId: string;
9
+ name: string;
10
+ content: string;
11
+ };
12
+ export type BuiltinLoopTurn = {
13
+ assistantContent: string | null;
14
+ toolCalls: BuiltinToolCallRecord[] | null;
15
+ toolResults: BuiltinToolResultRecord[] | null;
16
+ };
17
+ export type BuiltinSessionState = {
18
+ sessionId: string;
19
+ model: string;
20
+ startedAtMs: number;
21
+ messages: ChatMessage[];
22
+ turns: BuiltinLoopTurn[];
23
+ };
24
+ export type BuiltinTurnRole = "assistant" | "tool";
25
+ export type BuiltinToolCall = {
26
+ name: string;
27
+ args: string;
28
+ };
29
+ export type BuiltinTurnPayload = {
30
+ role: BuiltinTurnRole;
31
+ content: string;
32
+ toolCalls: BuiltinToolCall[] | null;
33
+ reasoning: string | null;
34
+ };
35
+ export type BuiltinDetailPayload = {
36
+ sessionId: string;
37
+ model: string;
38
+ duration: number;
39
+ turnCount: number;
40
+ turns: string[];
41
+ };
42
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,qBAAqB,EAAE,GAAG,IAAI,CAAC;IAC1C,WAAW,EAAE,uBAAuB,EAAE,GAAG,IAAI,CAAC;CAC/C,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,MAAM,CAAC;AAEnD,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,eAAe,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC;IACpC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@uncaged/workflow-agent-builtin",
3
+ "version": "0.5.0-alpha.5",
4
+ "files": [
5
+ "src",
6
+ "dist",
7
+ "package.json"
8
+ ],
9
+ "type": "module",
10
+ "bin": {
11
+ "uwf-builtin": "./src/cli.ts"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "bun": "./src/index.ts",
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ }
19
+ },
20
+ "scripts": {
21
+ "test": "bun test",
22
+ "test:ci": "bun test"
23
+ },
24
+ "dependencies": {
25
+ "@uncaged/json-cas": "^0.5.3",
26
+ "@uncaged/workflow-util-agent": "^0.5.0-alpha.5",
27
+ "@uncaged/workflow-util": "^0.5.0-alpha.5"
28
+ },
29
+ "devDependencies": {
30
+ "typescript": "^5.8.3"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/shazhou-ww/uncaged-workflow.git",
38
+ "directory": "packages/workflow-agent-builtin"
39
+ },
40
+ "homepage": "https://github.com/shazhou-ww/uncaged-workflow#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/shazhou-ww/uncaged-workflow/issues"
43
+ },
44
+ "license": "MIT"
45
+ }
package/src/agent.ts ADDED
@@ -0,0 +1,158 @@
1
+ import type { Store } from "@uncaged/json-cas";
2
+ import { createLogger, generateUlid } from "@uncaged/workflow-util";
3
+ import {
4
+ type AgentContext,
5
+ type AgentRunResult,
6
+ createAgent,
7
+ loadWorkflowConfig,
8
+ resolveModel,
9
+ resolveStorageRoot,
10
+ } from "@uncaged/workflow-util-agent";
11
+
12
+ import { storeBuiltinDetail } from "./detail.js";
13
+ import type { ChatMessage } from "./llm/index.js";
14
+ import { BUILTIN_CONTINUE_MAX_TURNS, BUILTIN_MAX_TURNS, runBuiltinLoop } from "./loop.js";
15
+ import { buildBuiltinMessages } from "./prompt.js";
16
+ import { initSessionDir } from "./session.js";
17
+
18
+ const log = createLogger({ sink: { kind: "stderr" } });
19
+
20
+ const FRONTMATTER_FENCE = "---";
21
+
22
+ /**
23
+ * Strip any text before the first `---` fence.
24
+ * LLMs sometimes emit preamble text before the frontmatter block.
25
+ */
26
+ function stripPreamble(text: string): string {
27
+ if (text.startsWith(FRONTMATTER_FENCE)) {
28
+ return text;
29
+ }
30
+ const idx = text.indexOf(`\n${FRONTMATTER_FENCE}\n`);
31
+ if (idx !== -1) {
32
+ log("6GWRP3QX", `stripped ${idx + 1} chars of preamble before frontmatter`);
33
+ return text.slice(idx + 1);
34
+ }
35
+ return text;
36
+ }
37
+
38
+ type SessionRecord = {
39
+ sessionId: string;
40
+ model: string;
41
+ startedAtMs: number;
42
+ messages: ChatMessage[];
43
+ };
44
+
45
+ const sessions = new Map<string, SessionRecord>();
46
+
47
+ function getSession(sessionId: string): SessionRecord {
48
+ const session = sessions.get(sessionId);
49
+ if (session === undefined) {
50
+ throw new Error(`builtin session not found: ${sessionId}`);
51
+ }
52
+ return session;
53
+ }
54
+
55
+ function buildToolContext(storageRoot: string): { cwd: string; storageRoot: string } {
56
+ return {
57
+ cwd: process.cwd(),
58
+ storageRoot,
59
+ };
60
+ }
61
+
62
+ async function runBuiltinWithMessages(
63
+ storageRoot: string,
64
+ provider: ReturnType<typeof resolveModel>,
65
+ messages: ChatMessage[],
66
+ session: SessionRecord,
67
+ store: Store,
68
+ maxTurns: number,
69
+ noTools: boolean,
70
+ ): Promise<AgentRunResult> {
71
+ const loopResult = await runBuiltinLoop({
72
+ provider,
73
+ messages,
74
+ toolCtx: buildToolContext(storageRoot),
75
+ maxTurns,
76
+ storageRoot,
77
+ sessionId: session.sessionId,
78
+ noTools,
79
+ });
80
+
81
+ session.messages = loopResult.messages;
82
+
83
+ if (loopResult.turnCount === 0) {
84
+ log("5RWTK9NB", "no turns produced, returning empty output");
85
+ return { output: "", detailHash: "", sessionId: session.sessionId };
86
+ }
87
+
88
+ // Read jsonl → persist turns to CAS → store detail
89
+ const { detailHash } = await storeBuiltinDetail(
90
+ store,
91
+ storageRoot,
92
+ session.sessionId,
93
+ session.model,
94
+ session.startedAtMs,
95
+ );
96
+
97
+ return { output: stripPreamble(loopResult.finalText), detailHash, sessionId: session.sessionId };
98
+ }
99
+
100
+ async function runBuiltin(ctx: AgentContext): Promise<AgentRunResult> {
101
+ const storageRoot = resolveStorageRoot();
102
+ const config = await loadWorkflowConfig(storageRoot);
103
+ const provider = resolveModel(config, config.defaultModel);
104
+
105
+ const sessionId = generateUlid(Date.now());
106
+ await initSessionDir(storageRoot);
107
+ const messages = buildBuiltinMessages(ctx);
108
+
109
+ const session: SessionRecord = {
110
+ sessionId,
111
+ model: provider.model,
112
+ startedAtMs: Date.now(),
113
+ messages,
114
+ };
115
+ sessions.set(sessionId, session);
116
+
117
+ return runBuiltinWithMessages(
118
+ storageRoot,
119
+ provider,
120
+ messages,
121
+ session,
122
+ ctx.store,
123
+ BUILTIN_MAX_TURNS,
124
+ false,
125
+ );
126
+ }
127
+
128
+ async function continueBuiltin(
129
+ sessionId: string,
130
+ message: string,
131
+ store: Store,
132
+ ): Promise<AgentRunResult> {
133
+ const session = getSession(sessionId);
134
+ const storageRoot = resolveStorageRoot();
135
+ const config = await loadWorkflowConfig(storageRoot);
136
+ const provider = resolveModel(config, config.defaultModel);
137
+
138
+ const messages: ChatMessage[] = [...session.messages, { role: "user", content: message }];
139
+
140
+ return runBuiltinWithMessages(
141
+ storageRoot,
142
+ provider,
143
+ messages,
144
+ session,
145
+ store,
146
+ BUILTIN_CONTINUE_MAX_TURNS,
147
+ true,
148
+ );
149
+ }
150
+
151
+ /** Agent CLI factory: built-in LLM loop with file/shell tools. */
152
+ export function createBuiltinAgent(): () => Promise<void> {
153
+ return createAgent({
154
+ name: "builtin",
155
+ run: runBuiltin,
156
+ continue: continueBuiltin,
157
+ });
158
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { createBuiltinAgent } from "./agent.js";
4
+
5
+ const main = createBuiltinAgent();
6
+ void main();
package/src/detail.ts ADDED
@@ -0,0 +1,49 @@
1
+ import { bootstrap, putSchema, type Store } from "@uncaged/json-cas";
2
+
3
+ import { BUILTIN_DETAIL_SCHEMA, BUILTIN_TURN_SCHEMA } from "./schemas.js";
4
+ import { readSessionTurns } from "./session.js";
5
+ import type { BuiltinDetailPayload } from "./types.js";
6
+
7
+ type BuiltinSchemaHashes = {
8
+ turn: string;
9
+ detail: string;
10
+ };
11
+
12
+ export async function registerBuiltinSchemas(store: Store): Promise<BuiltinSchemaHashes> {
13
+ await bootstrap(store);
14
+ const [turn, detail] = await Promise.all([
15
+ putSchema(store, BUILTIN_TURN_SCHEMA),
16
+ putSchema(store, BUILTIN_DETAIL_SCHEMA),
17
+ ]);
18
+ return { turn, detail };
19
+ }
20
+
21
+ /** Read session jsonl, persist each turn to CAS, return detail hash. */
22
+ export async function storeBuiltinDetail(
23
+ store: Store,
24
+ storageRoot: string,
25
+ sessionId: string,
26
+ model: string,
27
+ startedAtMs: number,
28
+ nowMs: number = Date.now(),
29
+ ): Promise<{ detailHash: string; turnCount: number }> {
30
+ const schemas = await registerBuiltinSchemas(store);
31
+ const turns = await readSessionTurns(storageRoot, sessionId);
32
+
33
+ const turnHashes: string[] = [];
34
+ for (const turn of turns) {
35
+ const hash = await store.put(schemas.turn, turn);
36
+ turnHashes.push(hash);
37
+ }
38
+
39
+ const duration = Math.max(0, nowMs - startedAtMs);
40
+ const detail: BuiltinDetailPayload = {
41
+ sessionId,
42
+ model,
43
+ duration,
44
+ turnCount: turnHashes.length,
45
+ turns: turnHashes,
46
+ };
47
+ const detailHash = await store.put(schemas.detail, detail);
48
+ return { detailHash, turnCount: turnHashes.length };
49
+ }
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ export { createBuiltinAgent } from "./agent.js";
2
+ export { registerBuiltinSchemas, storeBuiltinDetail } from "./detail.js";
3
+ export type { ChatMessage, LlmAssistantResponse, LlmToolCall } from "./llm/index.js";
4
+ export { chatCompletionWithTools } from "./llm/index.js";
5
+ export { BUILTIN_CONTINUE_MAX_TURNS, BUILTIN_MAX_TURNS, runBuiltinLoop } from "./loop.js";
6
+ export { buildBuiltinMessages } from "./prompt.js";
7
+ export { appendSessionTurn, initSessionDir, readSessionTurns, removeSession } from "./session.js";
8
+ export type { BuiltinTool, ToolContext } from "./tools/index.js";
9
+ export { executeBuiltinTool, getBuiltinTools } from "./tools/index.js";
10
+ export type {
11
+ BuiltinDetailPayload,
12
+ BuiltinLoopTurn,
13
+ BuiltinToolCallRecord,
14
+ BuiltinToolResultRecord,
15
+ BuiltinTurnPayload,
16
+ } from "./types.js";
@@ -0,0 +1,7 @@
1
+ export { chatCompletionWithTools } from "./llm.js";
2
+ export type {
3
+ ChatMessage,
4
+ LlmAssistantResponse,
5
+ LlmToolCall,
6
+ OpenAiToolDefinition,
7
+ } from "./types.js";
package/src/llm/llm.ts ADDED
@@ -0,0 +1,139 @@
1
+ import type { ResolvedLlmProvider } from "@uncaged/workflow-util-agent";
2
+
3
+ import type {
4
+ ChatMessage,
5
+ LlmAssistantResponse,
6
+ LlmToolCall,
7
+ OpenAiToolDefinition,
8
+ } from "./types.js";
9
+
10
+ function isRecord(value: unknown): value is Record<string, unknown> {
11
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12
+ }
13
+
14
+ function chatUrl(baseUrl: string): string {
15
+ const trimmed = baseUrl.replace(/\/+$/, "");
16
+ return `${trimmed}/chat/completions`;
17
+ }
18
+
19
+ function parseToolCalls(raw: unknown): LlmToolCall[] | null {
20
+ if (!Array.isArray(raw) || raw.length === 0) {
21
+ return null;
22
+ }
23
+ const calls: LlmToolCall[] = [];
24
+ for (const entry of raw) {
25
+ if (!isRecord(entry)) {
26
+ continue;
27
+ }
28
+ const id = entry.id;
29
+ const fn = entry.function;
30
+ if (typeof id !== "string" || !isRecord(fn)) {
31
+ continue;
32
+ }
33
+ const name = fn.name;
34
+ const args = fn.arguments;
35
+ if (typeof name !== "string" || typeof args !== "string") {
36
+ continue;
37
+ }
38
+ calls.push({ id, name, arguments: args });
39
+ }
40
+ return calls.length > 0 ? calls : null;
41
+ }
42
+
43
+ function parseAssistantMessage(parsed: unknown): LlmAssistantResponse {
44
+ if (!isRecord(parsed)) {
45
+ throw new Error("LLM response is not an object");
46
+ }
47
+ const choices = parsed.choices;
48
+ if (!Array.isArray(choices) || choices.length === 0) {
49
+ throw new Error("LLM response has no choices");
50
+ }
51
+ const c0 = choices[0];
52
+ if (!isRecord(c0)) {
53
+ throw new Error("LLM choice is not an object");
54
+ }
55
+ const messageObj = c0.message;
56
+ if (!isRecord(messageObj)) {
57
+ throw new Error("LLM message is not an object");
58
+ }
59
+ const contentRaw = messageObj.content;
60
+ const content =
61
+ typeof contentRaw === "string"
62
+ ? contentRaw
63
+ : contentRaw === null || contentRaw === undefined
64
+ ? null
65
+ : null;
66
+ const toolCalls = parseToolCalls(messageObj.tool_calls);
67
+ return { content, toolCalls };
68
+ }
69
+
70
+ function serializeMessage(message: ChatMessage): Record<string, unknown> {
71
+ if (message.role === "tool") {
72
+ return {
73
+ role: "tool",
74
+ tool_call_id: message.tool_call_id,
75
+ content: message.content,
76
+ };
77
+ }
78
+ if (message.role === "assistant") {
79
+ const base: Record<string, unknown> = {
80
+ role: "assistant",
81
+ content: message.content,
82
+ };
83
+ if (message.tool_calls !== null && message.tool_calls.length > 0) {
84
+ base.tool_calls = message.tool_calls.map((call) => ({
85
+ id: call.id,
86
+ type: "function",
87
+ function: { name: call.name, arguments: call.arguments },
88
+ }));
89
+ }
90
+ return base;
91
+ }
92
+ return { role: message.role, content: message.content };
93
+ }
94
+
95
+ /** OpenAI-compatible chat completion with tool calling (non-streaming). */
96
+ export async function chatCompletionWithTools(
97
+ provider: ResolvedLlmProvider,
98
+ messages: ChatMessage[],
99
+ tools: OpenAiToolDefinition[] | null,
100
+ ): Promise<LlmAssistantResponse> {
101
+ const body: Record<string, unknown> = {
102
+ model: provider.model,
103
+ messages: messages.map(serializeMessage),
104
+ };
105
+ if (tools !== null && tools.length > 0) {
106
+ body.tools = tools;
107
+ body.tool_choice = "auto";
108
+ }
109
+
110
+ let response: Response;
111
+ try {
112
+ response = await fetch(chatUrl(provider.baseUrl), {
113
+ method: "POST",
114
+ headers: {
115
+ Authorization: `Bearer ${provider.apiKey}`,
116
+ "Content-Type": "application/json",
117
+ },
118
+ body: JSON.stringify(body),
119
+ });
120
+ } catch (cause) {
121
+ const message = cause instanceof Error ? cause.message : String(cause);
122
+ throw new Error(`LLM network error: ${message}`);
123
+ }
124
+
125
+ const responseText = await response.text();
126
+ if (!response.ok) {
127
+ throw new Error(`LLM HTTP ${response.status}: ${responseText.slice(0, 2000)}`);
128
+ }
129
+
130
+ let parsed: unknown;
131
+ try {
132
+ parsed = JSON.parse(responseText) as unknown;
133
+ } catch (cause) {
134
+ const message = cause instanceof Error ? cause.message : String(cause);
135
+ throw new Error(`LLM invalid JSON response: ${message}`);
136
+ }
137
+
138
+ return parseAssistantMessage(parsed);
139
+ }
@@ -0,0 +1,29 @@
1
+ export type LlmToolCall = {
2
+ id: string;
3
+ name: string;
4
+ arguments: string;
5
+ };
6
+
7
+ export type LlmAssistantResponse = {
8
+ content: string | null;
9
+ toolCalls: LlmToolCall[] | null;
10
+ };
11
+
12
+ export type ChatMessage =
13
+ | { role: "system"; content: string }
14
+ | { role: "user"; content: string }
15
+ | {
16
+ role: "assistant";
17
+ content: string | null;
18
+ tool_calls: LlmToolCall[] | null;
19
+ }
20
+ | { role: "tool"; tool_call_id: string; content: string };
21
+
22
+ export type OpenAiToolDefinition = {
23
+ type: "function";
24
+ function: {
25
+ name: string;
26
+ description: string;
27
+ parameters: Record<string, unknown>;
28
+ };
29
+ };