@townco/agent 0.1.55 → 0.1.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp-server/adapter.d.ts +1 -0
- package/dist/acp-server/adapter.js +8 -0
- package/dist/acp-server/http.js +63 -0
- package/dist/acp-server/session-storage.d.ts +14 -0
- package/dist/acp-server/session-storage.js +47 -0
- package/dist/definition/index.d.ts +9 -0
- package/dist/definition/index.js +9 -0
- package/dist/runner/agent-runner.d.ts +5 -1
- package/dist/runner/agent-runner.js +2 -1
- package/dist/runner/langchain/index.js +18 -6
- package/dist/runner/langchain/model-factory.d.ts +2 -0
- package/dist/runner/langchain/model-factory.js +19 -0
- package/dist/runner/langchain/tools/browser.d.ts +100 -0
- package/dist/runner/langchain/tools/browser.js +412 -0
- package/dist/runner/tools.d.ts +2 -2
- package/dist/runner/tools.js +1 -0
- package/dist/scaffold/index.js +7 -1
- package/dist/scaffold/templates/dot-claude/CLAUDE-append.md +2 -0
- package/dist/telemetry/index.d.ts +5 -0
- package/dist/telemetry/index.js +10 -0
- package/dist/templates/index.d.ts +2 -0
- package/dist/templates/index.js +29 -7
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -6
- package/templates/index.ts +39 -7
|
@@ -24,6 +24,7 @@ export declare class AgentAcpAdapter implements acp.Agent {
|
|
|
24
24
|
private agentVersion;
|
|
25
25
|
private agentDescription;
|
|
26
26
|
private agentSuggestedPrompts;
|
|
27
|
+
private agentInitialMessage;
|
|
27
28
|
private currentToolOverheadTokens;
|
|
28
29
|
private currentMcpOverheadTokens;
|
|
29
30
|
constructor(agent: AgentRunner, connection: acp.AgentSideConnection, agentDir?: string, agentName?: string);
|
|
@@ -105,6 +105,7 @@ export class AgentAcpAdapter {
|
|
|
105
105
|
agentVersion;
|
|
106
106
|
agentDescription;
|
|
107
107
|
agentSuggestedPrompts;
|
|
108
|
+
agentInitialMessage;
|
|
108
109
|
currentToolOverheadTokens = 0; // Track tool overhead for current turn
|
|
109
110
|
currentMcpOverheadTokens = 0; // Track MCP overhead for current turn
|
|
110
111
|
constructor(agent, connection, agentDir, agentName) {
|
|
@@ -117,6 +118,7 @@ export class AgentAcpAdapter {
|
|
|
117
118
|
this.agentVersion = agent.definition.version;
|
|
118
119
|
this.agentDescription = agent.definition.description;
|
|
119
120
|
this.agentSuggestedPrompts = agent.definition.suggestedPrompts;
|
|
121
|
+
this.agentInitialMessage = agent.definition.initialMessage;
|
|
120
122
|
this.noSession = process.env.TOWN_NO_SESSION === "true";
|
|
121
123
|
this.storage =
|
|
122
124
|
agentDir && agentName && !this.noSession
|
|
@@ -129,6 +131,7 @@ export class AgentAcpAdapter {
|
|
|
129
131
|
agentVersion: this.agentVersion,
|
|
130
132
|
agentDescription: this.agentDescription,
|
|
131
133
|
suggestedPrompts: this.agentSuggestedPrompts,
|
|
134
|
+
initialMessage: this.agentInitialMessage,
|
|
132
135
|
noSession: this.noSession,
|
|
133
136
|
hasStorage: this.storage !== null,
|
|
134
137
|
sessionStoragePath: this.storage ? `${agentDir}/.sessions` : null,
|
|
@@ -259,6 +262,9 @@ export class AgentAcpAdapter {
|
|
|
259
262
|
...(this.agentSuggestedPrompts
|
|
260
263
|
? { suggestedPrompts: this.agentSuggestedPrompts }
|
|
261
264
|
: {}),
|
|
265
|
+
...(this.agentInitialMessage
|
|
266
|
+
? { initialMessage: this.agentInitialMessage }
|
|
267
|
+
: {}),
|
|
262
268
|
...(toolsMetadata.length > 0 ? { tools: toolsMetadata } : {}),
|
|
263
269
|
...(mcpsMetadata.length > 0 ? { mcps: mcpsMetadata } : {}),
|
|
264
270
|
...(subagentsMetadata.length > 0 ? { subagents: subagentsMetadata } : {}),
|
|
@@ -273,6 +279,8 @@ export class AgentAcpAdapter {
|
|
|
273
279
|
context: [],
|
|
274
280
|
requestParams: params,
|
|
275
281
|
});
|
|
282
|
+
// Note: Initial message is sent by the HTTP transport when SSE connection is established
|
|
283
|
+
// This ensures the message is delivered after the client is ready to receive it
|
|
276
284
|
return {
|
|
277
285
|
sessionId,
|
|
278
286
|
};
|
package/dist/acp-server/http.js
CHANGED
|
@@ -10,6 +10,7 @@ import { streamSSE } from "hono/streaming";
|
|
|
10
10
|
import { createLogger, isSubagent } from "../logger.js";
|
|
11
11
|
import { makeRunnerFromDefinition } from "../runner";
|
|
12
12
|
import { AgentAcpAdapter } from "./adapter";
|
|
13
|
+
import { SessionStorage } from "./session-storage";
|
|
13
14
|
const logger = createLogger("http");
|
|
14
15
|
/**
|
|
15
16
|
* Compress a payload using gzip if it's too large for PostgreSQL NOTIFY
|
|
@@ -69,6 +70,10 @@ export function makeHttpTransport(agent, agentDir, agentName) {
|
|
|
69
70
|
const app = new Hono();
|
|
70
71
|
// Track active SSE streams by sessionId for direct output delivery
|
|
71
72
|
const sseStreams = new Map();
|
|
73
|
+
// Track sessions that have already received initial message
|
|
74
|
+
const initialMessageSentSessions = new Set();
|
|
75
|
+
// Get initial message config from agent definition
|
|
76
|
+
const initialMessageConfig = agentRunner.definition.initialMessage;
|
|
72
77
|
const decoder = new TextDecoder();
|
|
73
78
|
const encoder = new TextEncoder();
|
|
74
79
|
(async () => {
|
|
@@ -270,6 +275,28 @@ export function makeHttpTransport(agent, agentDir, agentName) {
|
|
|
270
275
|
allowMethods: ["GET", "POST", "OPTIONS"],
|
|
271
276
|
}));
|
|
272
277
|
app.get("/health", (c) => c.json({ ok: true }));
|
|
278
|
+
// List available sessions
|
|
279
|
+
app.get("/sessions", async (c) => {
|
|
280
|
+
if (!agentDir || !agentName) {
|
|
281
|
+
return c.json({ sessions: [], error: "Session storage not configured" });
|
|
282
|
+
}
|
|
283
|
+
const noSession = process.env.TOWN_NO_SESSION === "true";
|
|
284
|
+
if (noSession) {
|
|
285
|
+
return c.json({ sessions: [], error: "Sessions disabled" });
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
const storage = new SessionStorage(agentDir, agentName);
|
|
289
|
+
const sessions = await storage.listSessionsWithMetadata();
|
|
290
|
+
return c.json({ sessions });
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
logger.error("Failed to list sessions", { error });
|
|
294
|
+
return c.json({
|
|
295
|
+
sessions: [],
|
|
296
|
+
error: error instanceof Error ? error.message : String(error),
|
|
297
|
+
}, 500);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
273
300
|
// Serve static files from agent directory (for generated images, etc.)
|
|
274
301
|
if (agentDir) {
|
|
275
302
|
app.get("/static/*", async (c) => {
|
|
@@ -319,6 +346,42 @@ export function makeHttpTransport(agent, agentDir, agentName) {
|
|
|
319
346
|
// Register this stream for direct tool output delivery
|
|
320
347
|
sseStreams.set(sessionId, stream);
|
|
321
348
|
await stream.writeSSE({ event: "ping", data: "{}" });
|
|
349
|
+
// Send initial message if configured and not already sent for this session
|
|
350
|
+
if (initialMessageConfig?.enabled &&
|
|
351
|
+
initialMessageConfig.content &&
|
|
352
|
+
!initialMessageSentSessions.has(sessionId)) {
|
|
353
|
+
initialMessageSentSessions.add(sessionId);
|
|
354
|
+
// Process template variables in the content
|
|
355
|
+
let content = initialMessageConfig.content;
|
|
356
|
+
content = content.replace(/\{\{\.AgentName\}\}/g, agentName ?? "Agent");
|
|
357
|
+
const displayName = agentRunner.definition.displayName;
|
|
358
|
+
content = content.replace(/\{\{\.DisplayName\}\}/g, displayName ?? agentName ?? "Agent");
|
|
359
|
+
const initialMessage = {
|
|
360
|
+
jsonrpc: "2.0",
|
|
361
|
+
method: "session/update",
|
|
362
|
+
params: {
|
|
363
|
+
sessionId,
|
|
364
|
+
update: {
|
|
365
|
+
sessionUpdate: "agent_message_chunk",
|
|
366
|
+
content: {
|
|
367
|
+
type: "text",
|
|
368
|
+
text: content,
|
|
369
|
+
},
|
|
370
|
+
_meta: {
|
|
371
|
+
isInitialMessage: true,
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
await stream.writeSSE({
|
|
377
|
+
event: "message",
|
|
378
|
+
data: JSON.stringify(initialMessage),
|
|
379
|
+
});
|
|
380
|
+
logger.info("Sent initial message via SSE", {
|
|
381
|
+
sessionId,
|
|
382
|
+
contentPreview: content.slice(0, 100),
|
|
383
|
+
});
|
|
384
|
+
}
|
|
322
385
|
const hb = setInterval(() => {
|
|
323
386
|
// Heartbeat to keep proxies from terminating idle connections
|
|
324
387
|
void stream.writeSSE({ event: "ping", data: "{}" });
|
|
@@ -150,4 +150,18 @@ export declare class SessionStorage {
|
|
|
150
150
|
* List all session IDs
|
|
151
151
|
*/
|
|
152
152
|
listSessions(): Promise<string[]>;
|
|
153
|
+
/**
|
|
154
|
+
* Session summary for listing
|
|
155
|
+
*/
|
|
156
|
+
/**
|
|
157
|
+
* List all sessions with metadata
|
|
158
|
+
* Returns sessions sorted by updatedAt (most recent first)
|
|
159
|
+
*/
|
|
160
|
+
listSessionsWithMetadata(): Promise<Array<{
|
|
161
|
+
sessionId: string;
|
|
162
|
+
createdAt: string;
|
|
163
|
+
updatedAt: string;
|
|
164
|
+
messageCount: number;
|
|
165
|
+
firstUserMessage?: string;
|
|
166
|
+
}>>;
|
|
153
167
|
}
|
|
@@ -236,4 +236,51 @@ export class SessionStorage {
|
|
|
236
236
|
throw new Error(`Failed to list sessions: ${error instanceof Error ? error.message : String(error)}`);
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Session summary for listing
|
|
241
|
+
*/
|
|
242
|
+
/**
|
|
243
|
+
* List all sessions with metadata
|
|
244
|
+
* Returns sessions sorted by updatedAt (most recent first)
|
|
245
|
+
*/
|
|
246
|
+
async listSessionsWithMetadata() {
|
|
247
|
+
if (!existsSync(this.sessionsDir)) {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
const files = readdirSync(this.sessionsDir);
|
|
252
|
+
const sessionFiles = files.filter((file) => file.endsWith(".json") && !file.endsWith(".tmp"));
|
|
253
|
+
const sessions = [];
|
|
254
|
+
for (const file of sessionFiles) {
|
|
255
|
+
const sessionId = file.replace(".json", "");
|
|
256
|
+
try {
|
|
257
|
+
const session = this.loadSessionSync(sessionId);
|
|
258
|
+
if (session) {
|
|
259
|
+
// Find the first user message for preview
|
|
260
|
+
const firstUserMsg = session.messages.find((m) => m.role === "user");
|
|
261
|
+
const firstUserText = firstUserMsg?.content.find((c) => c.type === "text");
|
|
262
|
+
const entry = {
|
|
263
|
+
sessionId: session.sessionId,
|
|
264
|
+
createdAt: session.metadata.createdAt,
|
|
265
|
+
updatedAt: session.metadata.updatedAt,
|
|
266
|
+
messageCount: session.messages.length,
|
|
267
|
+
};
|
|
268
|
+
if (firstUserText && "text" in firstUserText) {
|
|
269
|
+
entry.firstUserMessage = firstUserText.text.slice(0, 100);
|
|
270
|
+
}
|
|
271
|
+
sessions.push(entry);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
// Skip invalid sessions
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Sort by updatedAt, most recent first
|
|
279
|
+
sessions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
280
|
+
return sessions;
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
throw new Error(`Failed to list sessions: ${error instanceof Error ? error.message : String(error)}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
239
286
|
}
|
|
@@ -26,6 +26,11 @@ export declare const HookConfigSchema: z.ZodObject<{
|
|
|
26
26
|
}, z.core.$strip>]>>;
|
|
27
27
|
callback: z.ZodString;
|
|
28
28
|
}, z.core.$strip>;
|
|
29
|
+
/** Initial message configuration schema. */
|
|
30
|
+
export declare const InitialMessageSchema: z.ZodObject<{
|
|
31
|
+
enabled: z.ZodBoolean;
|
|
32
|
+
content: z.ZodString;
|
|
33
|
+
}, z.core.$strip>;
|
|
29
34
|
/** Agent definition schema. */
|
|
30
35
|
export declare const AgentDefinitionSchema: z.ZodObject<{
|
|
31
36
|
displayName: z.ZodOptional<z.ZodString>;
|
|
@@ -74,4 +79,8 @@ export declare const AgentDefinitionSchema: z.ZodObject<{
|
|
|
74
79
|
}, z.core.$strip>]>>;
|
|
75
80
|
callback: z.ZodString;
|
|
76
81
|
}, z.core.$strip>>>;
|
|
82
|
+
initialMessage: z.ZodOptional<z.ZodObject<{
|
|
83
|
+
enabled: z.ZodBoolean;
|
|
84
|
+
content: z.ZodString;
|
|
85
|
+
}, z.core.$strip>>;
|
|
77
86
|
}, z.core.$strip>;
|
package/dist/definition/index.js
CHANGED
|
@@ -71,6 +71,13 @@ export const HookConfigSchema = z.object({
|
|
|
71
71
|
.optional(),
|
|
72
72
|
callback: z.string(),
|
|
73
73
|
});
|
|
74
|
+
/** Initial message configuration schema. */
|
|
75
|
+
export const InitialMessageSchema = z.object({
|
|
76
|
+
/** Whether the agent should send an initial message when a session starts. */
|
|
77
|
+
enabled: z.boolean(),
|
|
78
|
+
/** The content of the initial message to send. Supports template variables like {{.AgentName}}. */
|
|
79
|
+
content: z.string(),
|
|
80
|
+
});
|
|
74
81
|
/** Agent definition schema. */
|
|
75
82
|
export const AgentDefinitionSchema = z.object({
|
|
76
83
|
/** Human-readable display name for the agent (shown in UI). */
|
|
@@ -84,4 +91,6 @@ export const AgentDefinitionSchema = z.object({
|
|
|
84
91
|
mcps: z.array(McpConfigSchema).optional(),
|
|
85
92
|
harnessImplementation: z.literal("langchain").optional(),
|
|
86
93
|
hooks: z.array(HookConfigSchema).optional(),
|
|
94
|
+
/** Configuration for an initial message the agent sends when a session starts. */
|
|
95
|
+
initialMessage: InitialMessageSchema.optional(),
|
|
87
96
|
});
|
|
@@ -8,7 +8,7 @@ export declare const zAgentRunnerParams: z.ZodObject<{
|
|
|
8
8
|
suggestedPrompts: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
9
9
|
systemPrompt: z.ZodNullable<z.ZodString>;
|
|
10
10
|
model: z.ZodString;
|
|
11
|
-
tools: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">, z.ZodLiteral<"generate_image">]>, z.ZodObject<{
|
|
11
|
+
tools: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodUnion<readonly [z.ZodLiteral<"todo_write">, z.ZodLiteral<"get_weather">, z.ZodLiteral<"web_search">, z.ZodLiteral<"filesystem">, z.ZodLiteral<"generate_image">, z.ZodLiteral<"browser">]>, z.ZodObject<{
|
|
12
12
|
type: z.ZodLiteral<"custom">;
|
|
13
13
|
modulePath: z.ZodString;
|
|
14
14
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -52,6 +52,10 @@ export declare const zAgentRunnerParams: z.ZodObject<{
|
|
|
52
52
|
}, z.core.$strip>]>>;
|
|
53
53
|
callback: z.ZodString;
|
|
54
54
|
}, z.core.$strip>>>;
|
|
55
|
+
initialMessage: z.ZodOptional<z.ZodObject<{
|
|
56
|
+
enabled: z.ZodBoolean;
|
|
57
|
+
content: z.ZodString;
|
|
58
|
+
}, z.core.$strip>>;
|
|
55
59
|
}, z.core.$strip>;
|
|
56
60
|
export type CreateAgentRunnerParams = z.infer<typeof zAgentRunnerParams>;
|
|
57
61
|
export interface SessionMessage {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { HookConfigSchema, McpConfigSchema } from "../definition";
|
|
2
|
+
import { HookConfigSchema, InitialMessageSchema, McpConfigSchema, } from "../definition";
|
|
3
3
|
import { zToolType } from "./tools";
|
|
4
4
|
export const zAgentRunnerParams = z.object({
|
|
5
5
|
displayName: z.string().optional(),
|
|
@@ -11,4 +11,5 @@ export const zAgentRunnerParams = z.object({
|
|
|
11
11
|
tools: z.array(zToolType).optional(),
|
|
12
12
|
mcps: z.array(McpConfigSchema).optional(),
|
|
13
13
|
hooks: z.array(HookConfigSchema).optional(),
|
|
14
|
+
initialMessage: InitialMessageSchema.optional(),
|
|
14
15
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
|
|
2
2
|
import { context, propagation, trace } from "@opentelemetry/api";
|
|
3
|
+
import { loadAuthCredentials } from "@townco/core/auth";
|
|
3
4
|
import { AIMessageChunk, createAgent, ToolMessage, tool, } from "langchain";
|
|
4
5
|
import { z } from "zod";
|
|
5
6
|
import { SUBAGENT_MODE_KEY } from "../../acp-server/adapter";
|
|
@@ -8,6 +9,7 @@ import { telemetry } from "../../telemetry/index.js";
|
|
|
8
9
|
import { loadCustomToolModule, } from "../tool-loader.js";
|
|
9
10
|
import { createModelFromString, detectProvider } from "./model-factory.js";
|
|
10
11
|
import { makeOtelCallbacks } from "./otel-callbacks.js";
|
|
12
|
+
import { makeBrowserTools } from "./tools/browser";
|
|
11
13
|
import { makeFilesystemTools } from "./tools/filesystem";
|
|
12
14
|
import { makeGenerateImageTool } from "./tools/generate_image";
|
|
13
15
|
import { SUBAGENT_TOOL_NAME } from "./tools/subagent";
|
|
@@ -29,6 +31,7 @@ export const TOOL_REGISTRY = {
|
|
|
29
31
|
web_search: () => makeWebSearchTools(),
|
|
30
32
|
filesystem: () => makeFilesystemTools(process.cwd()),
|
|
31
33
|
generate_image: () => makeGenerateImageTool(),
|
|
34
|
+
browser: () => makeBrowserTools(),
|
|
32
35
|
};
|
|
33
36
|
// ============================================================================
|
|
34
37
|
// Custom tool loading
|
|
@@ -78,11 +81,14 @@ export class LangchainAgent {
|
|
|
78
81
|
const countedMessageIds = new Set();
|
|
79
82
|
// Track tool calls for which we've emitted preliminary notifications (from early tool_use blocks)
|
|
80
83
|
const preliminaryToolCallIds = new Set();
|
|
84
|
+
// Set session_id as a base attribute so all spans in this invocation include it
|
|
85
|
+
telemetry.setBaseAttributes({
|
|
86
|
+
"agent.session_id": req.sessionId,
|
|
87
|
+
});
|
|
81
88
|
// Start telemetry span for entire invocation
|
|
82
89
|
const invocationSpan = telemetry.startSpan("agent.invoke", {
|
|
83
90
|
"agent.model": this.definition.model,
|
|
84
91
|
"agent.subagent": meta?.[SUBAGENT_MODE_KEY] === true,
|
|
85
|
-
"agent.session_id": req.sessionId,
|
|
86
92
|
"agent.message_id": req.messageId,
|
|
87
93
|
}, parentContext);
|
|
88
94
|
// Create a context with the invocation span as active
|
|
@@ -294,7 +300,7 @@ export class LangchainAgent {
|
|
|
294
300
|
: wrappedTools;
|
|
295
301
|
// Wrap tools with tracing so each tool executes within its own span context.
|
|
296
302
|
// This ensures subagent spans are children of the Task tool span.
|
|
297
|
-
const finalTools = filteredTools.map((t) => wrapToolWithTracing(t
|
|
303
|
+
const finalTools = filteredTools.map((t) => wrapToolWithTracing(t));
|
|
298
304
|
// Create the model instance using the factory
|
|
299
305
|
// This detects the provider from the model string:
|
|
300
306
|
// - "gemini-2.0-flash" → Google Generative AI
|
|
@@ -776,12 +782,19 @@ const modelRequestSchema = z.object({
|
|
|
776
782
|
const makeMcpToolsClient = (mcpConfigs) => {
|
|
777
783
|
const mcpServers = mcpConfigs?.map((config) => {
|
|
778
784
|
if (typeof config === "string") {
|
|
779
|
-
//
|
|
780
|
-
const
|
|
785
|
+
// String configs use the centralized MCP proxy with auth
|
|
786
|
+
const credentials = loadAuthCredentials();
|
|
787
|
+
if (!credentials) {
|
|
788
|
+
throw new Error("Not logged in. Run 'town login' first to use cloud MCP servers.");
|
|
789
|
+
}
|
|
790
|
+
const proxyUrl = process.env.MCP_PROXY_URL ?? `${credentials.shed_url}/mcp_proxy`;
|
|
781
791
|
return [
|
|
782
792
|
config,
|
|
783
793
|
{
|
|
784
794
|
url: `${proxyUrl}?server=${config}`,
|
|
795
|
+
headers: {
|
|
796
|
+
Authorization: `Bearer ${credentials.access_token}`,
|
|
797
|
+
},
|
|
785
798
|
},
|
|
786
799
|
];
|
|
787
800
|
}
|
|
@@ -874,13 +887,12 @@ export { makeSubagentsTool } from "./tools/subagent.js";
|
|
|
874
887
|
* so any child operations (like subagent spawning) become children
|
|
875
888
|
* of the tool span rather than the parent invocation span.
|
|
876
889
|
*/
|
|
877
|
-
function wrapToolWithTracing(originalTool
|
|
890
|
+
function wrapToolWithTracing(originalTool) {
|
|
878
891
|
const wrappedFunc = async (input) => {
|
|
879
892
|
const toolInputJson = JSON.stringify(input);
|
|
880
893
|
const toolSpan = telemetry.startSpan("agent.tool_call", {
|
|
881
894
|
"tool.name": originalTool.name,
|
|
882
895
|
"tool.input": toolInputJson,
|
|
883
|
-
"agent.session_id": sessionId,
|
|
884
896
|
});
|
|
885
897
|
// Create a context with the tool span as active
|
|
886
898
|
const spanContext = toolSpan
|
|
@@ -4,6 +4,7 @@ import type { BaseChatModel } from "@langchain/core/language_models/chat_models"
|
|
|
4
4
|
* LangChain chat model instance.
|
|
5
5
|
*
|
|
6
6
|
* Detection logic:
|
|
7
|
+
* - If model starts with "town-" → Proxied via shed (strips prefix, uses TOWN_SHED_URL)
|
|
7
8
|
* - If model starts with "vertex-" → Google Vertex AI (strips prefix)
|
|
8
9
|
* - If model contains "gemini" (unprefixed) → Google Generative AI
|
|
9
10
|
* - If model contains "gpt" → OpenAI (future support)
|
|
@@ -12,6 +13,7 @@ import type { BaseChatModel } from "@langchain/core/language_models/chat_models"
|
|
|
12
13
|
* Supported formats:
|
|
13
14
|
* - Direct model name: "gemini-2.0-flash", "vertex-gemini-2.0-flash", "claude-sonnet-4-5-20250929"
|
|
14
15
|
* - Provider prefix: "google_vertexai:gemini-2.0-flash", "google_genai:gemini-2.0-flash", "anthropic:claude-3-5-sonnet"
|
|
16
|
+
* - Proxied: "town-claude-sonnet-4-5-20250929" (uses TOWN_SHED_URL or defaults to localhost:3000)
|
|
15
17
|
*/
|
|
16
18
|
export declare function createModelFromString(modelString: string): BaseChatModel;
|
|
17
19
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ChatAnthropic } from "@langchain/anthropic";
|
|
2
2
|
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
|
|
3
3
|
import { ChatVertexAI } from "@langchain/google-vertexai";
|
|
4
|
+
import { loadAuthCredentials } from "@townco/core/auth";
|
|
4
5
|
import { createLogger } from "../../logger.js";
|
|
5
6
|
const logger = createLogger("model-factory");
|
|
6
7
|
/**
|
|
@@ -8,6 +9,7 @@ const logger = createLogger("model-factory");
|
|
|
8
9
|
* LangChain chat model instance.
|
|
9
10
|
*
|
|
10
11
|
* Detection logic:
|
|
12
|
+
* - If model starts with "town-" → Proxied via shed (strips prefix, uses TOWN_SHED_URL)
|
|
11
13
|
* - If model starts with "vertex-" → Google Vertex AI (strips prefix)
|
|
12
14
|
* - If model contains "gemini" (unprefixed) → Google Generative AI
|
|
13
15
|
* - If model contains "gpt" → OpenAI (future support)
|
|
@@ -16,8 +18,25 @@ const logger = createLogger("model-factory");
|
|
|
16
18
|
* Supported formats:
|
|
17
19
|
* - Direct model name: "gemini-2.0-flash", "vertex-gemini-2.0-flash", "claude-sonnet-4-5-20250929"
|
|
18
20
|
* - Provider prefix: "google_vertexai:gemini-2.0-flash", "google_genai:gemini-2.0-flash", "anthropic:claude-3-5-sonnet"
|
|
21
|
+
* - Proxied: "town-claude-sonnet-4-5-20250929" (uses TOWN_SHED_URL or defaults to localhost:3000)
|
|
19
22
|
*/
|
|
20
23
|
export function createModelFromString(modelString) {
|
|
24
|
+
// Check for town- prefix for proxied models via shed
|
|
25
|
+
if (modelString.startsWith("town-")) {
|
|
26
|
+
const actualModel = modelString.slice(5); // strip "town-"
|
|
27
|
+
const credentials = loadAuthCredentials();
|
|
28
|
+
if (!credentials) {
|
|
29
|
+
throw new Error("Not logged in. Run 'town login' first.");
|
|
30
|
+
}
|
|
31
|
+
const shedUrl = credentials.shed_url ??
|
|
32
|
+
process.env.TOWN_SHED_URL ??
|
|
33
|
+
"http://localhost:3000";
|
|
34
|
+
return new ChatAnthropic({
|
|
35
|
+
model: actualModel,
|
|
36
|
+
anthropicApiUrl: `${shedUrl}/api/anthropic`,
|
|
37
|
+
apiKey: credentials.access_token,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
21
40
|
// Check if the model string uses provider prefix format
|
|
22
41
|
const parts = modelString.split(":", 2);
|
|
23
42
|
const maybeProvider = parts[0];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
interface BrowserNavigateResult {
|
|
3
|
+
success: boolean;
|
|
4
|
+
url?: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
liveViewUrl?: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
interface BrowserScreenshotResult {
|
|
10
|
+
success: boolean;
|
|
11
|
+
screenshotPath?: string;
|
|
12
|
+
liveViewUrl?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
interface BrowserExtractResult {
|
|
16
|
+
success: boolean;
|
|
17
|
+
content?: string;
|
|
18
|
+
url?: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
liveViewUrl?: string;
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
interface BrowserClickResult {
|
|
24
|
+
success: boolean;
|
|
25
|
+
message?: string;
|
|
26
|
+
liveViewUrl?: string;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
interface BrowserTypeResult {
|
|
30
|
+
success: boolean;
|
|
31
|
+
message?: string;
|
|
32
|
+
liveViewUrl?: string;
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
interface BrowserCloseResult {
|
|
36
|
+
success: boolean;
|
|
37
|
+
message?: string;
|
|
38
|
+
error?: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function makeBrowserTools(): readonly [import("langchain").DynamicStructuredTool<z.ZodObject<{
|
|
41
|
+
url: z.ZodString;
|
|
42
|
+
waitUntil: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
43
|
+
load: "load";
|
|
44
|
+
domcontentloaded: "domcontentloaded";
|
|
45
|
+
networkidle: "networkidle";
|
|
46
|
+
}>>>;
|
|
47
|
+
}, z.core.$strip>, {
|
|
48
|
+
url: string;
|
|
49
|
+
waitUntil: "load" | "domcontentloaded" | "networkidle";
|
|
50
|
+
}, {
|
|
51
|
+
url: string;
|
|
52
|
+
waitUntil?: "load" | "domcontentloaded" | "networkidle" | undefined;
|
|
53
|
+
}, BrowserNavigateResult>, import("langchain").DynamicStructuredTool<z.ZodObject<{
|
|
54
|
+
fullPage: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
55
|
+
selector: z.ZodOptional<z.ZodString>;
|
|
56
|
+
}, z.core.$strip>, {
|
|
57
|
+
fullPage: boolean;
|
|
58
|
+
selector?: string | undefined;
|
|
59
|
+
}, {
|
|
60
|
+
fullPage?: boolean | undefined;
|
|
61
|
+
selector?: string | undefined;
|
|
62
|
+
}, BrowserScreenshotResult>, import("langchain").DynamicStructuredTool<z.ZodObject<{
|
|
63
|
+
selector: z.ZodOptional<z.ZodString>;
|
|
64
|
+
extractType: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
65
|
+
text: "text";
|
|
66
|
+
html: "html";
|
|
67
|
+
}>>>;
|
|
68
|
+
}, z.core.$strip>, {
|
|
69
|
+
extractType: "text" | "html";
|
|
70
|
+
selector?: string | undefined;
|
|
71
|
+
}, {
|
|
72
|
+
selector?: string | undefined;
|
|
73
|
+
extractType?: "text" | "html" | undefined;
|
|
74
|
+
}, BrowserExtractResult>, import("langchain").DynamicStructuredTool<z.ZodObject<{
|
|
75
|
+
selector: z.ZodString;
|
|
76
|
+
button: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
77
|
+
left: "left";
|
|
78
|
+
right: "right";
|
|
79
|
+
middle: "middle";
|
|
80
|
+
}>>>;
|
|
81
|
+
}, z.core.$strip>, {
|
|
82
|
+
selector: string;
|
|
83
|
+
button: "left" | "right" | "middle";
|
|
84
|
+
}, {
|
|
85
|
+
selector: string;
|
|
86
|
+
button?: "left" | "right" | "middle" | undefined;
|
|
87
|
+
}, BrowserClickResult>, import("langchain").DynamicStructuredTool<z.ZodObject<{
|
|
88
|
+
selector: z.ZodString;
|
|
89
|
+
text: z.ZodString;
|
|
90
|
+
pressEnter: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
91
|
+
}, z.core.$strip>, {
|
|
92
|
+
selector: string;
|
|
93
|
+
text: string;
|
|
94
|
+
pressEnter: boolean;
|
|
95
|
+
}, {
|
|
96
|
+
selector: string;
|
|
97
|
+
text: string;
|
|
98
|
+
pressEnter?: boolean | undefined;
|
|
99
|
+
}, BrowserTypeResult>, import("langchain").DynamicStructuredTool<z.ZodObject<{}, z.core.$strip>, Record<string, never>, Record<string, never>, BrowserCloseResult>];
|
|
100
|
+
export {};
|