@codeany/open-agent-sdk 0.2.1 → 0.2.2

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 (232) hide show
  1. package/.env.example +8 -0
  2. package/examples/01-simple-query.ts +43 -0
  3. package/examples/02-multi-tool.ts +44 -0
  4. package/examples/03-multi-turn.ts +39 -0
  5. package/examples/04-prompt-api.ts +29 -0
  6. package/examples/05-custom-system-prompt.ts +26 -0
  7. package/examples/06-mcp-server.ts +49 -0
  8. package/examples/07-custom-tools.ts +87 -0
  9. package/examples/08-official-api-compat.ts +38 -0
  10. package/examples/09-subagents.ts +48 -0
  11. package/examples/10-permissions.ts +40 -0
  12. package/examples/11-custom-mcp-tools.ts +101 -0
  13. package/examples/12-skills.ts +88 -0
  14. package/examples/13-hooks.ts +88 -0
  15. package/examples/14-openai-compat.ts +71 -0
  16. package/examples/web/index.html +365 -0
  17. package/examples/web/server.ts +157 -0
  18. package/package.json +1 -7
  19. package/src/engine.ts +3 -0
  20. package/tsconfig.json +19 -0
  21. package/dist/agent.d.ts +0 -113
  22. package/dist/agent.d.ts.map +0 -1
  23. package/dist/agent.js +0 -446
  24. package/dist/agent.js.map +0 -1
  25. package/dist/engine.d.ts +0 -62
  26. package/dist/engine.d.ts.map +0 -1
  27. package/dist/engine.js +0 -494
  28. package/dist/engine.js.map +0 -1
  29. package/dist/hooks.d.ts +0 -111
  30. package/dist/hooks.d.ts.map +0 -1
  31. package/dist/hooks.js +0 -179
  32. package/dist/hooks.js.map +0 -1
  33. package/dist/index.d.ts +0 -66
  34. package/dist/index.d.ts.map +0 -1
  35. package/dist/index.js +0 -133
  36. package/dist/index.js.map +0 -1
  37. package/dist/mcp/client.d.ts +0 -19
  38. package/dist/mcp/client.d.ts.map +0 -1
  39. package/dist/mcp/client.js +0 -126
  40. package/dist/mcp/client.js.map +0 -1
  41. package/dist/providers/anthropic.d.ts +0 -17
  42. package/dist/providers/anthropic.d.ts.map +0 -1
  43. package/dist/providers/anthropic.js +0 -47
  44. package/dist/providers/anthropic.js.map +0 -1
  45. package/dist/providers/index.d.ts +0 -20
  46. package/dist/providers/index.d.ts.map +0 -1
  47. package/dist/providers/index.js +0 -26
  48. package/dist/providers/index.js.map +0 -1
  49. package/dist/providers/openai.d.ts +0 -26
  50. package/dist/providers/openai.d.ts.map +0 -1
  51. package/dist/providers/openai.js +0 -212
  52. package/dist/providers/openai.js.map +0 -1
  53. package/dist/providers/types.d.ts +0 -84
  54. package/dist/providers/types.d.ts.map +0 -1
  55. package/dist/providers/types.js +0 -11
  56. package/dist/providers/types.js.map +0 -1
  57. package/dist/sdk-mcp-server.d.ts +0 -52
  58. package/dist/sdk-mcp-server.d.ts.map +0 -1
  59. package/dist/sdk-mcp-server.js +0 -57
  60. package/dist/sdk-mcp-server.js.map +0 -1
  61. package/dist/session.d.ts +0 -73
  62. package/dist/session.d.ts.map +0 -1
  63. package/dist/session.js +0 -159
  64. package/dist/session.js.map +0 -1
  65. package/dist/skills/bundled/commit.d.ts +0 -7
  66. package/dist/skills/bundled/commit.d.ts.map +0 -1
  67. package/dist/skills/bundled/commit.js +0 -35
  68. package/dist/skills/bundled/commit.js.map +0 -1
  69. package/dist/skills/bundled/debug.d.ts +0 -7
  70. package/dist/skills/bundled/debug.d.ts.map +0 -1
  71. package/dist/skills/bundled/debug.js +0 -46
  72. package/dist/skills/bundled/debug.js.map +0 -1
  73. package/dist/skills/bundled/index.d.ts +0 -11
  74. package/dist/skills/bundled/index.d.ts.map +0 -1
  75. package/dist/skills/bundled/index.js +0 -26
  76. package/dist/skills/bundled/index.js.map +0 -1
  77. package/dist/skills/bundled/review.d.ts +0 -7
  78. package/dist/skills/bundled/review.d.ts.map +0 -1
  79. package/dist/skills/bundled/review.js +0 -38
  80. package/dist/skills/bundled/review.js.map +0 -1
  81. package/dist/skills/bundled/simplify.d.ts +0 -8
  82. package/dist/skills/bundled/simplify.d.ts.map +0 -1
  83. package/dist/skills/bundled/simplify.js +0 -48
  84. package/dist/skills/bundled/simplify.js.map +0 -1
  85. package/dist/skills/bundled/test.d.ts +0 -7
  86. package/dist/skills/bundled/test.d.ts.map +0 -1
  87. package/dist/skills/bundled/test.js +0 -40
  88. package/dist/skills/bundled/test.js.map +0 -1
  89. package/dist/skills/index.d.ts +0 -7
  90. package/dist/skills/index.d.ts.map +0 -1
  91. package/dist/skills/index.js +0 -8
  92. package/dist/skills/index.js.map +0 -1
  93. package/dist/skills/registry.d.ts +0 -43
  94. package/dist/skills/registry.d.ts.map +0 -1
  95. package/dist/skills/registry.js +0 -111
  96. package/dist/skills/registry.js.map +0 -1
  97. package/dist/skills/types.d.ts +0 -83
  98. package/dist/skills/types.d.ts.map +0 -1
  99. package/dist/skills/types.js +0 -8
  100. package/dist/skills/types.js.map +0 -1
  101. package/dist/tool-helper.d.ts +0 -73
  102. package/dist/tool-helper.d.ts.map +0 -1
  103. package/dist/tool-helper.js +0 -86
  104. package/dist/tool-helper.js.map +0 -1
  105. package/dist/tools/agent-tool.d.ts +0 -17
  106. package/dist/tools/agent-tool.d.ts.map +0 -1
  107. package/dist/tools/agent-tool.js +0 -146
  108. package/dist/tools/agent-tool.js.map +0 -1
  109. package/dist/tools/ask-user.d.ts +0 -18
  110. package/dist/tools/ask-user.d.ts.map +0 -1
  111. package/dist/tools/ask-user.js +0 -72
  112. package/dist/tools/ask-user.js.map +0 -1
  113. package/dist/tools/bash.d.ts +0 -5
  114. package/dist/tools/bash.d.ts.map +0 -1
  115. package/dist/tools/bash.js +0 -67
  116. package/dist/tools/bash.js.map +0 -1
  117. package/dist/tools/config-tool.d.ts +0 -20
  118. package/dist/tools/config-tool.d.ts.map +0 -1
  119. package/dist/tools/config-tool.js +0 -83
  120. package/dist/tools/config-tool.js.map +0 -1
  121. package/dist/tools/cron-tools.d.ts +0 -33
  122. package/dist/tools/cron-tools.d.ts.map +0 -1
  123. package/dist/tools/cron-tools.js +0 -128
  124. package/dist/tools/cron-tools.js.map +0 -1
  125. package/dist/tools/edit.d.ts +0 -5
  126. package/dist/tools/edit.d.ts.map +0 -1
  127. package/dist/tools/edit.js +0 -70
  128. package/dist/tools/edit.js.map +0 -1
  129. package/dist/tools/glob.d.ts +0 -5
  130. package/dist/tools/glob.d.ts.map +0 -1
  131. package/dist/tools/glob.js +0 -75
  132. package/dist/tools/glob.js.map +0 -1
  133. package/dist/tools/grep.d.ts +0 -5
  134. package/dist/tools/grep.d.ts.map +0 -1
  135. package/dist/tools/grep.js +0 -168
  136. package/dist/tools/grep.js.map +0 -1
  137. package/dist/tools/index.d.ts +0 -45
  138. package/dist/tools/index.d.ts.map +0 -1
  139. package/dist/tools/index.js +0 -162
  140. package/dist/tools/index.js.map +0 -1
  141. package/dist/tools/lsp-tool.d.ts +0 -9
  142. package/dist/tools/lsp-tool.d.ts.map +0 -1
  143. package/dist/tools/lsp-tool.js +0 -137
  144. package/dist/tools/lsp-tool.js.map +0 -1
  145. package/dist/tools/mcp-resource-tools.d.ts +0 -14
  146. package/dist/tools/mcp-resource-tools.d.ts.map +0 -1
  147. package/dist/tools/mcp-resource-tools.js +0 -117
  148. package/dist/tools/mcp-resource-tools.js.map +0 -1
  149. package/dist/tools/notebook-edit.d.ts +0 -5
  150. package/dist/tools/notebook-edit.d.ts.map +0 -1
  151. package/dist/tools/notebook-edit.js +0 -85
  152. package/dist/tools/notebook-edit.js.map +0 -1
  153. package/dist/tools/plan-tools.d.ts +0 -12
  154. package/dist/tools/plan-tools.d.ts.map +0 -1
  155. package/dist/tools/plan-tools.js +0 -77
  156. package/dist/tools/plan-tools.js.map +0 -1
  157. package/dist/tools/read.d.ts +0 -5
  158. package/dist/tools/read.d.ts.map +0 -1
  159. package/dist/tools/read.js +0 -66
  160. package/dist/tools/read.js.map +0 -1
  161. package/dist/tools/send-message.d.ts +0 -31
  162. package/dist/tools/send-message.d.ts.map +0 -1
  163. package/dist/tools/send-message.js +0 -77
  164. package/dist/tools/send-message.js.map +0 -1
  165. package/dist/tools/skill-tool.d.ts +0 -9
  166. package/dist/tools/skill-tool.d.ts.map +0 -1
  167. package/dist/tools/skill-tool.js +0 -115
  168. package/dist/tools/skill-tool.js.map +0 -1
  169. package/dist/tools/task-tools.d.ts +0 -48
  170. package/dist/tools/task-tools.d.ts.map +0 -1
  171. package/dist/tools/task-tools.js +0 -242
  172. package/dist/tools/task-tools.js.map +0 -1
  173. package/dist/tools/team-tools.d.ts +0 -34
  174. package/dist/tools/team-tools.d.ts.map +0 -1
  175. package/dist/tools/team-tools.js +0 -103
  176. package/dist/tools/team-tools.js.map +0 -1
  177. package/dist/tools/todo-tool.d.ts +0 -22
  178. package/dist/tools/todo-tool.d.ts.map +0 -1
  179. package/dist/tools/todo-tool.js +0 -93
  180. package/dist/tools/todo-tool.js.map +0 -1
  181. package/dist/tools/tool-search.d.ts +0 -13
  182. package/dist/tools/tool-search.d.ts.map +0 -1
  183. package/dist/tools/tool-search.js +0 -76
  184. package/dist/tools/tool-search.js.map +0 -1
  185. package/dist/tools/types.d.ts +0 -29
  186. package/dist/tools/types.d.ts.map +0 -1
  187. package/dist/tools/types.js +0 -52
  188. package/dist/tools/types.js.map +0 -1
  189. package/dist/tools/web-fetch.d.ts +0 -5
  190. package/dist/tools/web-fetch.d.ts.map +0 -1
  191. package/dist/tools/web-fetch.js +0 -60
  192. package/dist/tools/web-fetch.js.map +0 -1
  193. package/dist/tools/web-search.d.ts +0 -5
  194. package/dist/tools/web-search.d.ts.map +0 -1
  195. package/dist/tools/web-search.js +0 -77
  196. package/dist/tools/web-search.js.map +0 -1
  197. package/dist/tools/worktree-tools.d.ts +0 -10
  198. package/dist/tools/worktree-tools.d.ts.map +0 -1
  199. package/dist/tools/worktree-tools.js +0 -130
  200. package/dist/tools/worktree-tools.js.map +0 -1
  201. package/dist/tools/write.d.ts +0 -5
  202. package/dist/tools/write.d.ts.map +0 -1
  203. package/dist/tools/write.js +0 -40
  204. package/dist/tools/write.js.map +0 -1
  205. package/dist/types.d.ts +0 -423
  206. package/dist/types.d.ts.map +0 -1
  207. package/dist/types.js +0 -5
  208. package/dist/types.js.map +0 -1
  209. package/dist/utils/compact.d.ts +0 -44
  210. package/dist/utils/compact.d.ts.map +0 -1
  211. package/dist/utils/compact.js +0 -162
  212. package/dist/utils/compact.js.map +0 -1
  213. package/dist/utils/context.d.ts +0 -35
  214. package/dist/utils/context.d.ts.map +0 -1
  215. package/dist/utils/context.js +0 -174
  216. package/dist/utils/context.js.map +0 -1
  217. package/dist/utils/fileCache.d.ts +0 -64
  218. package/dist/utils/fileCache.d.ts.map +0 -1
  219. package/dist/utils/fileCache.js +0 -117
  220. package/dist/utils/fileCache.js.map +0 -1
  221. package/dist/utils/messages.d.ts +0 -57
  222. package/dist/utils/messages.d.ts.map +0 -1
  223. package/dist/utils/messages.js +0 -152
  224. package/dist/utils/messages.js.map +0 -1
  225. package/dist/utils/retry.d.ts +0 -48
  226. package/dist/utils/retry.d.ts.map +0 -1
  227. package/dist/utils/retry.js +0 -111
  228. package/dist/utils/retry.js.map +0 -1
  229. package/dist/utils/tokens.d.ts +0 -57
  230. package/dist/utils/tokens.d.ts.map +0 -1
  231. package/dist/utils/tokens.js +0 -134
  232. package/dist/utils/tokens.js.map +0 -1
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Example 13: Hooks
3
+ *
4
+ * Shows how to use lifecycle hooks to intercept agent behavior.
5
+ * Hooks fire at key points: session start/end, before/after tool use,
6
+ * compaction, etc.
7
+ *
8
+ * Run: npx tsx examples/13-hooks.ts
9
+ */
10
+ import { createAgent, createHookRegistry } from '../src/index.js'
11
+ import type { HookInput } from '../src/index.js'
12
+
13
+ async function main() {
14
+ console.log('--- Example 13: Hooks ---\n')
15
+
16
+ // Create a hook registry with custom handlers
17
+ const registry = createHookRegistry({
18
+ SessionStart: [{
19
+ handler: async (input: HookInput) => {
20
+ console.log(`[Hook] Session started: ${input.sessionId}`)
21
+ },
22
+ }],
23
+ PreToolUse: [{
24
+ handler: async (input: HookInput) => {
25
+ console.log(`[Hook] About to use tool: ${input.toolName}`)
26
+ // You can block a tool by returning { block: true }
27
+ // return { block: true, message: 'Tool blocked by hook' }
28
+ },
29
+ }],
30
+ PostToolUse: [{
31
+ handler: async (input: HookInput) => {
32
+ const output = typeof input.toolOutput === 'string'
33
+ ? input.toolOutput.slice(0, 100)
34
+ : JSON.stringify(input.toolOutput).slice(0, 100)
35
+ console.log(`[Hook] Tool ${input.toolName} completed: ${output}...`)
36
+ },
37
+ }],
38
+ PostToolUseFailure: [{
39
+ handler: async (input: HookInput) => {
40
+ console.log(`[Hook] Tool ${input.toolName} FAILED: ${input.error}`)
41
+ },
42
+ }],
43
+ Stop: [{
44
+ handler: async () => {
45
+ console.log('[Hook] Agent loop completed')
46
+ },
47
+ }],
48
+ SessionEnd: [{
49
+ handler: async () => {
50
+ console.log('[Hook] Session ended')
51
+ },
52
+ }],
53
+ })
54
+
55
+ // Create agent with hook registry
56
+ // Note: For direct HookRegistry usage, we pass hooks via the engine config.
57
+ // The AgentOptions.hooks format also works (see below).
58
+ const agent = createAgent({
59
+ model: process.env.CODEANY_MODEL || 'claude-sonnet-4-6',
60
+ maxTurns: 5,
61
+ // Alternative: use AgentOptions.hooks format
62
+ hooks: {
63
+ PreToolUse: [{
64
+ hooks: [async (input: any, toolUseId: string) => {
65
+ console.log(`[AgentHook] PreToolUse: ${input.toolName} (${toolUseId})`)
66
+ }],
67
+ }],
68
+ },
69
+ })
70
+
71
+ for await (const event of agent.query('What files are in the current directory? Be brief.')) {
72
+ const msg = event as any
73
+ if (msg.type === 'assistant') {
74
+ for (const block of msg.message?.content || []) {
75
+ if (block.type === 'text' && block.text.trim()) {
76
+ console.log(`\nAssistant: ${block.text.slice(0, 200)}`)
77
+ }
78
+ }
79
+ }
80
+ if (msg.type === 'result') {
81
+ console.log(`\n--- ${msg.subtype} ---`)
82
+ }
83
+ }
84
+
85
+ await agent.close()
86
+ }
87
+
88
+ main().catch(console.error)
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Example 14: OpenAI-Compatible Models
3
+ *
4
+ * Shows how to use the SDK with OpenAI's API or any OpenAI-compatible
5
+ * endpoint (e.g., DeepSeek, Qwen, vLLM, Ollama).
6
+ *
7
+ * Environment variables:
8
+ * CODEANY_API_KEY=sk-... # Your OpenAI API key
9
+ * CODEANY_BASE_URL=https://api.openai.com/v1 # Optional, defaults to OpenAI
10
+ * CODEANY_API_TYPE=openai-completions # Optional, auto-detected from model name
11
+ *
12
+ * Run: npx tsx examples/14-openai-compat.ts
13
+ */
14
+ import { createAgent } from '../src/index.js'
15
+
16
+ async function main() {
17
+ console.log('--- Example 14: OpenAI-Compatible Models ---\n')
18
+
19
+ // Option 1: Explicit apiType
20
+ const agent = createAgent({
21
+ apiType: 'openai-completions',
22
+ model: process.env.CODEANY_MODEL || 'gpt-4o',
23
+ apiKey: process.env.CODEANY_API_KEY,
24
+ baseURL: process.env.CODEANY_BASE_URL || 'https://api.openai.com/v1',
25
+ maxTurns: 5,
26
+ })
27
+
28
+ console.log(`API Type: ${agent.getApiType()}`)
29
+ console.log(`Model: ${process.env.CODEANY_MODEL || 'gpt-4o'}\n`)
30
+
31
+ // Option 2: Auto-detected from model name (uncomment to try)
32
+ // const agent = createAgent({
33
+ // model: 'gpt-4o', // Auto-detects 'openai-completions'
34
+ // apiKey: process.env.CODEANY_API_KEY,
35
+ // })
36
+
37
+ // Option 3: DeepSeek example (uncomment to try)
38
+ // const agent = createAgent({
39
+ // model: 'deepseek-chat',
40
+ // apiKey: process.env.CODEANY_API_KEY,
41
+ // baseURL: 'https://api.deepseek.com/v1',
42
+ // })
43
+
44
+ // Option 4: Via environment variables only
45
+ // CODEANY_API_TYPE=openai-completions
46
+ // CODEANY_MODEL=gpt-4o
47
+ // CODEANY_API_KEY=sk-...
48
+ // CODEANY_BASE_URL=https://api.openai.com/v1
49
+ // const agent = createAgent()
50
+
51
+ for await (const event of agent.query('What is 2+2? Reply in one sentence.')) {
52
+ const msg = event as any
53
+ if (msg.type === 'assistant') {
54
+ for (const block of msg.message?.content || []) {
55
+ if (block.type === 'text' && block.text.trim()) {
56
+ console.log(`Assistant: ${block.text}`)
57
+ }
58
+ if (block.type === 'tool_use') {
59
+ console.log(`[Tool: ${block.name}] ${JSON.stringify(block.input)}`)
60
+ }
61
+ }
62
+ }
63
+ if (msg.type === 'result') {
64
+ console.log(`\n--- ${msg.subtype} (${msg.usage?.input_tokens}+${msg.usage?.output_tokens} tokens) ---`)
65
+ }
66
+ }
67
+
68
+ await agent.close()
69
+ }
70
+
71
+ main().catch(console.error)
@@ -0,0 +1,365 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Open Agent SDK</title>
7
+ <style>
8
+ *{margin:0;padding:0;box-sizing:border-box}
9
+ :root{
10
+ --bg:#f5f6f8;--surface:#fff;--border:#e5e7eb;
11
+ --text:#1a1a1a;--text2:#6b7280;--accent:#111;
12
+ --user-bg:#111;--user-fg:#fff;
13
+ --tool-bg:#f3f4f6;--tool-border:#e0e1e4;
14
+ --think-bg:#fffbeb;--think-border:#fde68a;
15
+ --ok-bg:#ecfdf5;--ok-border:#86efac;--ok-text:#166534;
16
+ --err-bg:#fef2f2;--err-border:#fca5a5;--err-text:#991b1b;
17
+ --radius:16px;
18
+ }
19
+ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',sans-serif;
20
+ background:var(--bg);color:var(--text);height:100vh;display:flex;flex-direction:column}
21
+
22
+ /* Header */
23
+ header{display:flex;align-items:center;justify-content:space-between;
24
+ padding:14px 20px;background:var(--surface);border-bottom:1px solid var(--border)}
25
+ header h1{font-size:16px;font-weight:600}
26
+ header button{width:34px;height:34px;border-radius:50%;border:1px solid var(--border);
27
+ background:var(--surface);font-size:18px;cursor:pointer;display:flex;align-items:center;justify-content:center}
28
+ header button:hover{background:var(--bg)}
29
+
30
+ /* Chat */
31
+ #chat{flex:1;overflow-y:auto;padding:24px 16px}
32
+ #chat-inner{max-width:800px;margin:0 auto;display:flex;flex-direction:column;gap:20px}
33
+
34
+ /* Welcome */
35
+ #welcome{text-align:center;padding:60px 0 30px}
36
+ #welcome h2{font-size:22px;font-weight:600;margin-bottom:22px;color:var(--text)}
37
+ .suggestions{display:grid;grid-template-columns:1fr 1fr;gap:10px;max-width:500px;margin:0 auto}
38
+ .suggestions button{padding:12px 16px;border:1px solid var(--border);border-radius:12px;
39
+ background:var(--surface);cursor:pointer;text-align:left;font-size:13px;color:var(--text);transition:border-color .15s}
40
+ .suggestions button:hover{border-color:#999}
41
+ .suggestions span{margin-right:6px}
42
+
43
+ /* Message rows */
44
+ .msg-row{display:flex}
45
+ .msg-row.user{justify-content:flex-end}
46
+ .msg-row.assistant{justify-content:flex-start}
47
+
48
+ /* Bubbles */
49
+ .bubble-user{background:var(--user-bg);color:var(--user-fg);padding:10px 16px;
50
+ border-radius:18px 18px 4px 18px;max-width:75%;font-size:14px;line-height:1.55;white-space:pre-wrap}
51
+ .bubble-assistant{max-width:85%;font-size:14.5px;line-height:1.65}
52
+ .bubble-assistant p{margin-bottom:8px}
53
+ .bubble-assistant p:last-child{margin-bottom:0}
54
+ .bubble-assistant strong{font-weight:600}
55
+ .bubble-assistant code{background:#f0f1f3;padding:1px 5px;border-radius:4px;font-size:13px}
56
+ .bubble-assistant pre{background:#1e1e1e;color:#d4d4d4;padding:14px 16px;border-radius:10px;
57
+ overflow-x:auto;margin:8px 0;font-size:13px;line-height:1.5}
58
+ .bubble-assistant pre code{background:none;padding:0;color:inherit}
59
+ .bubble-assistant ul,.bubble-assistant ol{padding-left:22px;margin:6px 0}
60
+ .bubble-assistant li{margin:3px 0}
61
+
62
+ /* Tool call card */
63
+ .tool-card{background:var(--tool-bg);border:1px solid var(--tool-border);border-radius:10px;
64
+ padding:10px 14px;margin:8px 0;font-size:13px}
65
+ .tool-card .tool-hd{display:flex;align-items:center;gap:6px;font-weight:600;margin-bottom:4px}
66
+ .tool-card .tool-icon{font-size:15px}
67
+ .tool-card .tool-input{color:var(--text2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
68
+ .tool-result{margin:4px 0 8px 12px;padding:8px 12px;border-left:3px solid var(--tool-border);
69
+ font-size:12.5px;color:var(--text2);max-height:200px;overflow-y:auto;white-space:pre-wrap;word-break:break-all}
70
+
71
+ /* Thinking */
72
+ .think-box{background:var(--think-bg);border:1px solid var(--think-border);border-radius:10px;
73
+ padding:10px 14px;margin:8px 0;font-size:13px;font-style:italic;color:#92400e}
74
+
75
+ /* Result banner */
76
+ .result-banner{border-radius:10px;padding:10px 16px;font-size:13px;margin:8px 0;display:flex;align-items:center;gap:8px}
77
+ .result-banner.ok{background:var(--ok-bg);border:1px solid var(--ok-border);color:var(--ok-text)}
78
+ .result-banner.err{background:var(--err-bg);border:1px solid var(--err-border);color:var(--err-text)}
79
+
80
+ /* Typing indicator */
81
+ .typing{display:flex;align-items:center;gap:4px;padding:8px 0}
82
+ .typing span{width:7px;height:7px;background:#aaa;border-radius:50%;animation:bounce .6s infinite alternate}
83
+ .typing span:nth-child(2){animation-delay:.15s}
84
+ .typing span:nth-child(3){animation-delay:.3s}
85
+ @keyframes bounce{to{transform:translateY(-6px);opacity:.4}}
86
+
87
+ /* Input area */
88
+ #input-area{padding:12px 16px 16px;background:var(--surface);border-top:1px solid var(--border)}
89
+ #input-wrap{max-width:800px;margin:0 auto;display:flex;align-items:flex-end;gap:10px;
90
+ border:1px solid var(--border);border-radius:26px;padding:6px 8px 6px 18px;background:var(--surface);
91
+ transition:border-color .2s}
92
+ #input-wrap:focus-within{border-color:#999}
93
+ #prompt{flex:1;border:none;outline:none;font-size:14px;line-height:1.5;resize:none;
94
+ min-height:24px;max-height:180px;font-family:inherit;background:transparent}
95
+ #send-btn{width:38px;height:38px;border-radius:50%;border:none;background:var(--accent);
96
+ color:#fff;font-size:18px;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;
97
+ transition:opacity .15s}
98
+ #send-btn:disabled{opacity:.35;cursor:default}
99
+
100
+ @keyframes fadeIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
101
+ .msg-row,.tool-card,.tool-result,.think-box,.result-banner{animation:fadeIn .2s ease}
102
+ </style>
103
+ </head>
104
+ <body>
105
+
106
+ <header>
107
+ <h1>Open Agent SDK</h1>
108
+ <button onclick="newSession()" title="New session">+</button>
109
+ </header>
110
+
111
+ <div id="chat">
112
+ <div id="chat-inner">
113
+ <div id="welcome">
114
+ <h2>What can I do for you?</h2>
115
+ <div class="suggestions">
116
+ <button onclick="useSuggestion(this)"><span>&#128193;</span>List files in this project</button>
117
+ <button onclick="useSuggestion(this)"><span>&#128196;</span>Read package.json</button>
118
+ <button onclick="useSuggestion(this)"><span>&#128202;</span>Count lines of code</button>
119
+ <button onclick="useSuggestion(this)"><span>&#128187;</span>Show system info</button>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </div>
124
+
125
+ <div id="input-area">
126
+ <div id="input-wrap">
127
+ <textarea id="prompt" rows="1" placeholder="Assign a task or ask anything" onkeydown="handleKey(event)" oninput="autoResize()"></textarea>
128
+ <button id="send-btn" onclick="sendMessage()">&#8593;</button>
129
+ </div>
130
+ </div>
131
+
132
+ <script>
133
+ const chatInner = document.getElementById('chat-inner');
134
+ const chatEl = document.getElementById('chat');
135
+ const promptEl = document.getElementById('prompt');
136
+ const sendBtn = document.getElementById('send-btn');
137
+ const welcomeEl = document.getElementById('welcome');
138
+
139
+ let streaming = false;
140
+
141
+ const TOOL_ICONS = {
142
+ Bash:'&#128736;',Read:'&#128196;',Write:'&#128221;',Edit:'&#9998;',
143
+ Glob:'&#128269;',Grep:'&#128270;',WebFetch:'&#127760;',WebSearch:'&#128269;',
144
+ Agent:'&#129302;',NotebookEdit:'&#128211;',TaskCreate:'&#128203;',
145
+ };
146
+
147
+ /* ---- helpers ---- */
148
+ function esc(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}
149
+
150
+ function renderMarkdown(raw){
151
+ let h = esc(raw);
152
+ // code blocks
153
+ h = h.replace(/```(\w*)\n([\s\S]*?)```/g, (_,lang,code) =>
154
+ `<pre><code class="lang-${lang}">${code.trim()}</code></pre>`);
155
+ // inline code
156
+ h = h.replace(/`([^`\n]+)`/g, '<code>$1</code>');
157
+ // bold
158
+ h = h.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
159
+ // unordered lists
160
+ h = h.replace(/^[\-\*] (.+)$/gm, '<li>$1</li>');
161
+ h = h.replace(/(<li>.*<\/li>\n?)+/g, m => '<ul>'+m+'</ul>');
162
+ // ordered lists
163
+ h = h.replace(/^\d+\. (.+)$/gm, '<li>$1</li>');
164
+ // paragraphs
165
+ h = h.replace(/\n{2,}/g, '</p><p>');
166
+ h = h.replace(/\n/g, '<br>');
167
+ return '<p>'+h+'</p>';
168
+ }
169
+
170
+ function scrollToBottom(){
171
+ requestAnimationFrame(()=>{ chatEl.scrollTop = chatEl.scrollHeight; });
172
+ }
173
+
174
+ function autoResize(){
175
+ promptEl.style.height = 'auto';
176
+ promptEl.style.height = Math.min(promptEl.scrollHeight, 180)+'px';
177
+ }
178
+
179
+ /* ---- actions ---- */
180
+ function handleKey(e){
181
+ if(e.key==='Enter' && !e.shiftKey){ e.preventDefault(); sendMessage(); }
182
+ }
183
+
184
+ function useSuggestion(btn){
185
+ const text = btn.textContent.replace(/^.\s*/u,'');
186
+ promptEl.value = text;
187
+ sendMessage();
188
+ }
189
+
190
+ async function newSession(){
191
+ await fetch('/api/new',{method:'POST'});
192
+ chatInner.innerHTML = '';
193
+ chatInner.appendChild(welcomeEl);
194
+ welcomeEl.style.display = '';
195
+ }
196
+
197
+ async function sendMessage(){
198
+ const text = promptEl.value.trim();
199
+ if(!text || streaming) return;
200
+ streaming = true;
201
+ sendBtn.disabled = true;
202
+ promptEl.value = '';
203
+ autoResize();
204
+
205
+ // hide welcome
206
+ welcomeEl.style.display = 'none';
207
+
208
+ // user bubble
209
+ const uRow = document.createElement('div');
210
+ uRow.className = 'msg-row user';
211
+ uRow.innerHTML = `<div class="bubble-user">${esc(text)}</div>`;
212
+ chatInner.appendChild(uRow);
213
+ scrollToBottom();
214
+
215
+ // typing indicator
216
+ const typingEl = document.createElement('div');
217
+ typingEl.className = 'msg-row assistant';
218
+ typingEl.innerHTML = '<div class="typing"><span></span><span></span><span></span></div>';
219
+ chatInner.appendChild(typingEl);
220
+ scrollToBottom();
221
+
222
+ // assistant wrapper
223
+ const aRow = document.createElement('div');
224
+ aRow.className = 'msg-row assistant';
225
+ const aBubble = document.createElement('div');
226
+ aBubble.className = 'bubble-assistant';
227
+ aRow.appendChild(aBubble);
228
+
229
+ let textAcc = '';
230
+ let textEl = null;
231
+ let typingRemoved = false;
232
+
233
+ function ensureTextEl(){
234
+ if(!textEl){
235
+ textEl = document.createElement('div');
236
+ aBubble.appendChild(textEl);
237
+ }
238
+ }
239
+
240
+ try{
241
+ const resp = await fetch('/api/chat',{
242
+ method:'POST',
243
+ headers:{'Content-Type':'application/json'},
244
+ body: JSON.stringify({message:text}),
245
+ });
246
+
247
+ if(!resp.ok){
248
+ throw new Error(`HTTP ${resp.status}: ${await resp.text()}`);
249
+ }
250
+
251
+ // Two code paths: streaming (ReadableStream) or fallback (text)
252
+ if(resp.body && typeof resp.body.getReader === 'function'){
253
+ // --- Streaming path ---
254
+ const reader = resp.body.getReader();
255
+ const decoder = new TextDecoder();
256
+ let buf = '';
257
+
258
+ while(true){
259
+ const {done,value} = await reader.read();
260
+ if(done) break;
261
+ buf += decoder.decode(value, {stream:true});
262
+ processSSEBuffer();
263
+ }
264
+ // flush remaining
265
+ if(buf.trim()) processSSEBuffer();
266
+
267
+ function processSSEBuffer(){
268
+ const lines = buf.split('\n');
269
+ buf = lines.pop() || '';
270
+ for(const line of lines) handleSSELine(line);
271
+ }
272
+ } else {
273
+ // --- Fallback: read full text at once (proxy/polyfill environments) ---
274
+ const fullText = await resp.text();
275
+ for(const line of fullText.split('\n')) handleSSELine(line);
276
+ }
277
+
278
+ function handleSSELine(line){
279
+ const trimmed = line.trim();
280
+ if(!trimmed.startsWith('data: ')) return;
281
+ let parsed;
282
+ try{ parsed = JSON.parse(trimmed.slice(6)); } catch{ return; }
283
+ const {event, data} = parsed;
284
+ if(!event || !data) return;
285
+
286
+ // remove typing on first real event
287
+ if(!typingRemoved){ typingRemoved = true; typingEl.remove(); chatInner.appendChild(aRow); }
288
+
289
+ switch(event){
290
+ case 'text':
291
+ textAcc += data.text;
292
+ ensureTextEl();
293
+ textEl.innerHTML = renderMarkdown(textAcc);
294
+ scrollToBottom();
295
+ break;
296
+
297
+ case 'tool_use':{
298
+ textAcc = ''; textEl = null;
299
+ const icon = TOOL_ICONS[data.name] || '&#128295;';
300
+ const inputStr = typeof data.input === 'string' ? data.input : JSON.stringify(data.input);
301
+ const card = document.createElement('div');
302
+ card.className = 'tool-card';
303
+ card.id = 'tool-'+data.id;
304
+ card.innerHTML = `<div class="tool-hd"><span class="tool-icon">${icon}</span>${esc(data.name)}</div>`
305
+ + `<div class="tool-input">${esc(inputStr).slice(0,200)}</div>`;
306
+ aBubble.appendChild(card);
307
+ scrollToBottom();
308
+ break;
309
+ }
310
+
311
+ case 'tool_result':{
312
+ const card = document.getElementById('tool-'+data.tool_use_id);
313
+ const rd = document.createElement('div');
314
+ rd.className = 'tool-result';
315
+ rd.textContent = (data.content||'').slice(0,3000);
316
+ if(card) card.after(rd); else aBubble.appendChild(rd);
317
+ scrollToBottom();
318
+ break;
319
+ }
320
+
321
+ case 'thinking':{
322
+ textAcc = ''; textEl = null;
323
+ const tb = document.createElement('div');
324
+ tb.className = 'think-box';
325
+ tb.textContent = data.thinking.slice(0,1000);
326
+ aBubble.appendChild(tb);
327
+ scrollToBottom();
328
+ break;
329
+ }
330
+
331
+ case 'result':{
332
+ const b = document.createElement('div');
333
+ b.className = 'result-banner ok';
334
+ b.innerHTML = `&#9989; Done &middot; $${(data.cost||0).toFixed(2)} &middot; ${((data.duration_ms||0)/1000).toFixed(1)}s &middot; ${(data.input_tokens||0)+(data.output_tokens||0)} tokens`;
335
+ aBubble.appendChild(b);
336
+ scrollToBottom();
337
+ break;
338
+ }
339
+
340
+ case 'error':{
341
+ const b = document.createElement('div');
342
+ b.className = 'result-banner err';
343
+ b.innerHTML = `&#10060; Error: ${esc(data.message||'unknown')}`;
344
+ aBubble.appendChild(b);
345
+ scrollToBottom();
346
+ break;
347
+ }
348
+ }
349
+ }
350
+
351
+ }catch(err){
352
+ if(!typingRemoved){ typingEl.remove(); chatInner.appendChild(aRow); }
353
+ const b = document.createElement('div');
354
+ b.className = 'result-banner err';
355
+ b.textContent = 'Network error: '+err.message;
356
+ aBubble.appendChild(b);
357
+ }
358
+
359
+ streaming = false;
360
+ sendBtn.disabled = false;
361
+ promptEl.focus();
362
+ }
363
+ </script>
364
+ </body>
365
+ </html>
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Web Chat Server
3
+ *
4
+ * A lightweight HTTP server providing:
5
+ * GET / — serves the chat UI
6
+ * POST /api/chat — SSE stream of agent events
7
+ * POST /api/new — resets the session
8
+ *
9
+ * Run: npx tsx examples/web/server.ts
10
+ */
11
+
12
+ import { createServer, type IncomingMessage, type ServerResponse } from 'http'
13
+ import { readFile } from 'fs/promises'
14
+ import { join, dirname } from 'path'
15
+ import { fileURLToPath } from 'url'
16
+ import { createAgent, type Agent } from '../../src/index.js'
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url))
19
+ const PORT = parseInt(process.env.PORT || '8081')
20
+
21
+ let agent: Agent | null = null
22
+
23
+ function getOrCreateAgent(): Agent {
24
+ if (!agent) {
25
+ agent = createAgent({
26
+ model: process.env.CODEANY_MODEL || 'claude-sonnet-4-6',
27
+ maxTurns: 20,
28
+ })
29
+ }
30
+ return agent
31
+ }
32
+
33
+ function resetAgent(): void {
34
+ agent?.close().catch(() => {})
35
+ agent = null
36
+ }
37
+
38
+ /** Read the full request body as a string. */
39
+ function readBody(req: IncomingMessage): Promise<string> {
40
+ return new Promise((resolve, reject) => {
41
+ const chunks: Buffer[] = []
42
+ req.on('data', (c: Buffer) => chunks.push(c))
43
+ req.on('end', () => resolve(Buffer.concat(chunks).toString()))
44
+ req.on('error', reject)
45
+ })
46
+ }
47
+
48
+ /** Handle POST /api/chat — SSE stream */
49
+ async function handleChat(req: IncomingMessage, res: ServerResponse) {
50
+ const body = JSON.parse(await readBody(req))
51
+ const prompt = body.message?.trim()
52
+ if (!prompt) {
53
+ res.writeHead(400, { 'Content-Type': 'application/json' })
54
+ res.end(JSON.stringify({ error: 'empty message' }))
55
+ return
56
+ }
57
+
58
+ // SSE headers
59
+ res.writeHead(200, {
60
+ 'Content-Type': 'text/event-stream',
61
+ 'Cache-Control': 'no-cache',
62
+ 'Connection': 'keep-alive',
63
+ 'X-Accel-Buffering': 'no',
64
+ })
65
+
66
+ const send = (event: string, data: unknown) => {
67
+ res.write(`data: ${JSON.stringify({ event, data })}\n\n`)
68
+ }
69
+
70
+ const ag = getOrCreateAgent()
71
+ const startMs = Date.now()
72
+
73
+ try {
74
+ for await (const ev of ag.query(prompt)) {
75
+ switch (ev.type) {
76
+ case 'assistant': {
77
+ for (const block of ev.message.content) {
78
+ if (block.type === 'text') {
79
+ send('text', { text: block.text })
80
+ } else if (block.type === 'tool_use') {
81
+ send('tool_use', {
82
+ id: block.id,
83
+ name: block.name,
84
+ input: block.input,
85
+ })
86
+ } else if ('thinking' in block) {
87
+ send('thinking', { thinking: (block as any).thinking })
88
+ }
89
+ }
90
+ break
91
+ }
92
+ case 'tool_result':
93
+ send('tool_result', {
94
+ tool_use_id: ev.result.tool_use_id,
95
+ content: ev.result.output,
96
+ is_error: false,
97
+ })
98
+ break
99
+ case 'result':
100
+ send('result', {
101
+ num_turns: ev.num_turns ?? 0,
102
+ input_tokens: ev.usage?.input_tokens ?? 0,
103
+ output_tokens: ev.usage?.output_tokens ?? 0,
104
+ cost: ev.total_cost_usd ?? ev.cost ?? 0,
105
+ duration_ms: Date.now() - startMs,
106
+ })
107
+ break
108
+ }
109
+ }
110
+ } catch (err: any) {
111
+ send('error', { message: err.message })
112
+ }
113
+
114
+ send('done', null)
115
+ res.end()
116
+ }
117
+
118
+ /** Handle POST /api/new */
119
+ function handleNewSession(_req: IncomingMessage, res: ServerResponse) {
120
+ resetAgent()
121
+ res.writeHead(200, { 'Content-Type': 'application/json' })
122
+ res.end(JSON.stringify({ ok: true }))
123
+ }
124
+
125
+ /** Serve the static index.html */
126
+ async function serveIndex(_req: IncomingMessage, res: ServerResponse) {
127
+ const html = await readFile(join(__dirname, 'index.html'), 'utf-8')
128
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
129
+ res.end(html)
130
+ }
131
+
132
+ // --- HTTP Server ---
133
+
134
+ const server = createServer(async (req, res) => {
135
+ const url = req.url || '/'
136
+ const method = req.method || 'GET'
137
+
138
+ try {
139
+ if (url === '/' && method === 'GET') return await serveIndex(req, res)
140
+ if (url === '/api/chat' && method === 'POST') return await handleChat(req, res)
141
+ if (url === '/api/new' && method === 'POST') return handleNewSession(req, res)
142
+
143
+ res.writeHead(404)
144
+ res.end('Not Found')
145
+ } catch (err: any) {
146
+ console.error(err)
147
+ if (!res.headersSent) {
148
+ res.writeHead(500, { 'Content-Type': 'application/json' })
149
+ }
150
+ res.end(JSON.stringify({ error: err.message }))
151
+ }
152
+ })
153
+
154
+ server.listen(PORT, () => {
155
+ console.log(`\n Open Agent SDK — Web Chat`)
156
+ console.log(` http://localhost:${PORT}\n`)
157
+ })
package/package.json CHANGED
@@ -12,13 +12,7 @@
12
12
  "bugs": {
13
13
  "url": "https://github.com/codeany-ai/open-agent-sdk-typescript/issues"
14
14
  },
15
- "version": "0.2.1",
16
- "files": [
17
- "dist",
18
- "src",
19
- "README.md",
20
- "LICENSE"
21
- ],
15
+ "version": "0.2.2",
22
16
  "description": "Open-source Agent SDK. Runs the full agent loop in-process — no local CLI required. Deploy anywhere: cloud, serverless, Docker, CI/CD.",
23
17
  "type": "module",
24
18
  "main": "./dist/index.js",