@whitesands/agent-player 0.2.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -3
- package/README.md +234 -41
- package/dist/index.cjs +116 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +116 -24
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs +2 -1
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.d.cts +4 -2
- package/dist/protocol.d.ts +4 -2
- package/dist/protocol.js +2 -1
- package/dist/protocol.js.map +1 -1
- package/dist/testing.cjs +19 -20
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +3 -2
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +19 -20
- package/dist/testing.js.map +1 -1
- package/dist/{types-DCIs4-gP.d.cts → types-D0p6GrVs.d.cts} +9 -2
- package/dist/{types-DIF8LgGI.d.ts → types-DXM_HZHu.d.ts} +9 -2
- package/package.json +27 -4
package/dist/protocol.d.cts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Wire protocol between AgentServer (SDK) and the platform. Version 1.
|
|
3
|
+
*/
|
|
2
4
|
declare const PROTOCOL_VERSION = 1;
|
|
3
|
-
declare const SDK_VERSION = "
|
|
5
|
+
declare const SDK_VERSION = "1.1.0";
|
|
4
6
|
/** How long a disconnected session can be resumed. Shared by both sides. */
|
|
5
7
|
declare const SESSION_RESUME_GRACE_MS = 30000;
|
|
6
8
|
type JsonSchema = {
|
package/dist/protocol.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Wire protocol between AgentServer (SDK) and the platform. Version 1.
|
|
3
|
+
*/
|
|
2
4
|
declare const PROTOCOL_VERSION = 1;
|
|
3
|
-
declare const SDK_VERSION = "
|
|
5
|
+
declare const SDK_VERSION = "1.1.0";
|
|
4
6
|
/** How long a disconnected session can be resumed. Shared by both sides. */
|
|
5
7
|
declare const SESSION_RESUME_GRACE_MS = 30000;
|
|
6
8
|
type JsonSchema = {
|
package/dist/protocol.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// src/protocol.ts
|
|
2
2
|
var PROTOCOL_VERSION = 1;
|
|
3
|
-
var SDK_VERSION = "
|
|
3
|
+
var SDK_VERSION = "1.1.0";
|
|
4
4
|
var SESSION_RESUME_GRACE_MS = 3e4;
|
|
5
5
|
var DEFAULT_AGENT_INSTRUCTIONS = [
|
|
6
6
|
"You are a remote game agent. Tools are provided by the game.",
|
|
7
7
|
"On every turn: call wait first. It blocks until the game grants your turn and then returns the current view from see.",
|
|
8
8
|
"After wait returns, call exactly one action tool (not see, not wait).",
|
|
9
|
+
"The view's done list is what this character has already called in this session, oldest first. Use it when choosing the next action.",
|
|
9
10
|
"If an action tool returns an error, retry once with a corrected action. A second failure ends the turn as failed."
|
|
10
11
|
].join("\n");
|
|
11
12
|
function assertNever(value) {
|
package/dist/protocol.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocol.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../src/protocol.ts"],"sourcesContent":["/**\n * Wire protocol between AgentServer (SDK) and the platform. Version 1.\n */\n\nexport const PROTOCOL_VERSION = 1;\nexport const SDK_VERSION = \"1.1.0\";\n\n/** How long a disconnected session can be resumed. Shared by both sides. */\nexport const SESSION_RESUME_GRACE_MS = 30_000;\n\nexport type JsonSchema = {\n type?: string | string[];\n properties?: Record<string, JsonSchema>;\n required?: string[];\n description?: string;\n additionalProperties?: boolean;\n [key: string]: unknown;\n};\n\nexport type ToolDescriptor = {\n name: string;\n description: string;\n inputSchema: JsonSchema;\n};\n\nexport type TokenUsage = {\n promptTokens: number;\n completionTokens: number;\n};\n\nexport type AttemptRecord = {\n action: string;\n args: Record<string, unknown>;\n error: string;\n};\n\nexport type TurnReport = {\n ok: boolean;\n error?: string;\n action?: string;\n args?: Record<string, unknown>;\n result?: unknown;\n attempts?: number;\n attemptDetails?: AttemptRecord[];\n usage?: TokenUsage;\n connectionLost?: boolean;\n};\n\nexport type SessionEventName = \"action\" | \"retry\" | \"turnFailed\";\n\nexport type Envelope =\n | {\n type: \"hello\";\n platformKey: string;\n clientId: string;\n protocolVersion: number;\n sdkVersion: string;\n }\n | {\n type: \"hello_ok\";\n accountName: string;\n protocolVersion: number;\n }\n | {\n type: \"hello_err\";\n message: string;\n code?: \"AUTH\" | \"PROTOCOL\";\n }\n | {\n type: \"start_session\";\n sessionId: string;\n sessionToken: string;\n tools: ToolDescriptor[];\n instructions: string;\n goal?: string;\n name?: string;\n }\n | {\n type: \"session_started\";\n sessionId: string;\n }\n | {\n type: \"stop_session\";\n sessionId: string;\n }\n | {\n type: \"session_stopped\";\n sessionId: string;\n }\n | {\n type: \"mcp\";\n sessionId: string;\n message: unknown;\n }\n | ({\n type: \"turn_result\";\n sessionId: string;\n } & TurnReport)\n | {\n type: \"session_event\";\n sessionId: string;\n event: SessionEventName;\n action?: string;\n args?: Record<string, unknown>;\n error?: string;\n }\n | {\n type: \"ping\";\n id: string;\n }\n | {\n type: \"pong\";\n id: string;\n }\n | {\n type: \"resume_session\";\n sessionId: string;\n sessionToken: string;\n }\n | {\n type: \"session_resumed\";\n sessionId: string;\n }\n | {\n type: \"resume_err\";\n sessionId: string;\n message: string;\n }\n | {\n type: \"error\";\n message: string;\n sessionId?: string;\n };\n\nexport const DEFAULT_AGENT_INSTRUCTIONS = [\n \"You are a remote game agent. Tools are provided by the game.\",\n \"On every turn: call wait first. It blocks until the game grants your turn and then returns the current view from see.\",\n \"After wait returns, call exactly one action tool (not see, not wait).\",\n \"The view's done list is what this character has already called in this session, oldest first. Use it when choosing the next action.\",\n \"If an action tool returns an error, retry once with a corrected action. A second failure ends the turn as failed.\",\n].join(\"\\n\");\n\nexport function assertNever(value: never): never {\n throw new Error(`Unhandled protocol message: ${JSON.stringify(value)}`);\n}\n"],"mappings":";AAIO,IAAM,mBAAmB;AACzB,IAAM,cAAc;AAGpB,IAAM,0BAA0B;AA8HhC,IAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,IAAI;AAEJ,SAAS,YAAY,OAAqB;AAC/C,QAAM,IAAI,MAAM,+BAA+B,KAAK,UAAU,KAAK,CAAC,EAAE;AACxE;","names":[]}
|
package/dist/testing.cjs
CHANGED
|
@@ -51,11 +51,12 @@ function log(logger, level, message, extra) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
// src/protocol.ts
|
|
54
|
-
var SDK_VERSION = "
|
|
54
|
+
var SDK_VERSION = "1.1.0";
|
|
55
55
|
var DEFAULT_AGENT_INSTRUCTIONS = [
|
|
56
56
|
"You are a remote game agent. Tools are provided by the game.",
|
|
57
57
|
"On every turn: call wait first. It blocks until the game grants your turn and then returns the current view from see.",
|
|
58
58
|
"After wait returns, call exactly one action tool (not see, not wait).",
|
|
59
|
+
"The view's done list is what this character has already called in this session, oldest first. Use it when choosing the next action.",
|
|
59
60
|
"If an action tool returns an error, retry once with a corrected action. A second failure ends the turn as failed."
|
|
60
61
|
].join("\n");
|
|
61
62
|
|
|
@@ -184,28 +185,26 @@ Call exactly one action tool.`
|
|
|
184
185
|
}
|
|
185
186
|
];
|
|
186
187
|
let action = await this.decideAction(actionTools, messages, usage);
|
|
188
|
+
let result;
|
|
189
|
+
let firstError;
|
|
187
190
|
if (action.name === "wait" || action.name === "see") {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
+
firstError = `${action.name} is not an action`;
|
|
192
|
+
} else {
|
|
193
|
+
this.emit({ event: "action", action: action.name, args: action.arguments });
|
|
194
|
+
result = await this.callTool(action.name, action.arguments);
|
|
195
|
+
if (!result.isError) {
|
|
196
|
+
this.options.onTurnResult({
|
|
197
|
+
ok: true,
|
|
198
|
+
action: action.name,
|
|
199
|
+
args: action.arguments,
|
|
200
|
+
result: result.json,
|
|
201
|
+
attempts: 1,
|
|
202
|
+
usage
|
|
203
|
+
});
|
|
191
204
|
continue;
|
|
192
205
|
}
|
|
193
|
-
|
|
206
|
+
firstError = toolErrorMessage(result);
|
|
194
207
|
}
|
|
195
|
-
this.emit({ event: "action", action: action.name, args: action.arguments });
|
|
196
|
-
let result = await this.callTool(action.name, action.arguments);
|
|
197
|
-
if (!result.isError) {
|
|
198
|
-
this.options.onTurnResult({
|
|
199
|
-
ok: true,
|
|
200
|
-
action: action.name,
|
|
201
|
-
args: action.arguments,
|
|
202
|
-
result: result.json,
|
|
203
|
-
attempts: 1,
|
|
204
|
-
usage
|
|
205
|
-
});
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
const firstError = toolErrorMessage(result);
|
|
209
208
|
attemptDetails.push({ action: action.name, args: action.arguments, error: firstError });
|
|
210
209
|
log(this.options.logger, "warn", `session ${this.options.sessionId} action ${action.name} failed: ${firstError}; retrying once`);
|
|
211
210
|
this.emit({ event: "retry", action: action.name, args: action.arguments, error: firstError });
|
|
@@ -216,7 +215,7 @@ Call exactly one action tool.`
|
|
|
216
215
|
messages.push({
|
|
217
216
|
role: "tool",
|
|
218
217
|
name: action.name,
|
|
219
|
-
content: result
|
|
218
|
+
content: result?.text ?? firstError,
|
|
220
219
|
isError: true
|
|
221
220
|
});
|
|
222
221
|
messages.push({
|
package/dist/testing.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing.ts","../src/loop/model.ts","../src/loop/session-loop.ts","../src/logger.ts","../src/protocol.ts","../src/loop/test-platform.ts"],"sourcesContent":["export { createTestPlatform } from \"./loop/test-platform.js\";\nexport { SessionLoop, loopEventToAgentEvent, type SessionLoopOptions, type LoopEvent } from \"./loop/session-loop.js\";\nexport {\n createScriptedModel,\n type ModelClient,\n type ModelDecision,\n type ModelMessage,\n type ToolCallRequest,\n} from \"./loop/model.js\";\n","export type ToolCallRequest = {\n name: string;\n arguments: Record<string, unknown>;\n};\n\nexport type ModelMessage =\n | { role: \"system\"; content: string }\n | { role: \"user\"; content: string }\n | { role: \"assistant\"; content: string }\n | { role: \"tool\"; name: string; content: string; isError?: boolean };\n\nexport type ModelDecision = {\n toolCall: ToolCallRequest;\n usage: { promptTokens: number; completionTokens: number };\n};\n\nexport type ModelClient = {\n /** Ask the model to choose exactly one tool call. */\n decide(args: {\n instructions: string;\n tools: Array<{ name: string; description: string; inputSchema: unknown }>;\n messages: ModelMessage[];\n }): Promise<ModelDecision>;\n};\n\n/**\n * Scripted model for tests: returns predetermined tool calls in order.\n * When the queue is exhausted, always returns wait.\n */\nexport function createScriptedModel(script: ToolCallRequest[]): ModelClient {\n let index = 0;\n return {\n async decide() {\n const call = script[index] ?? { name: \"wait\", arguments: {} };\n index += 1;\n return {\n toolCall: call,\n usage: { promptTokens: 1, completionTokens: 1 },\n };\n },\n };\n}\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport type { Logger } from \"../logger.js\";\nimport { log } from \"../logger.js\";\nimport type { AgentEvent } from \"../types.js\";\nimport type { AttemptRecord, SessionEventName, TokenUsage, TurnReport } from \"../protocol.js\";\nimport { SDK_VERSION } from \"../protocol.js\";\nimport type { ModelClient, ModelMessage, ToolCallRequest } from \"./model.js\";\n\nexport type LoopEvent = {\n event: SessionEventName;\n action?: string;\n args?: Record<string, unknown>;\n error?: string;\n};\n\nexport function loopEventToAgentEvent(sessionId: string, event: LoopEvent): AgentEvent {\n if (event.event === \"action\") {\n return {\n type: \"action\",\n sessionId,\n action: event.action ?? \"unknown\",\n args: event.args ?? {},\n };\n }\n if (event.event === \"retry\") {\n return {\n type: \"retry\",\n sessionId,\n action: event.action ?? \"unknown\",\n args: event.args ?? {},\n error: event.error ?? \"action failed\",\n };\n }\n return {\n type: \"turnFailed\",\n sessionId,\n error: event.error ?? \"turn failed\",\n ...(event.action ? { action: event.action } : {}),\n ...(event.args ? { args: event.args } : {}),\n };\n}\n\nexport type SessionLoopOptions = {\n sessionId: string;\n transport: Transport;\n instructions: string;\n goal?: string;\n model: ModelClient;\n logger?: Logger;\n onUsage: (tokens: number) => void | Promise<void>;\n onTurnResult: (result: TurnReport) => void;\n onEvent?: (event: LoopEvent) => void;\n};\n\ntype ToolPayload = { isError: boolean; text: string; json: unknown };\n\nfunction parseToolPayload(result: {\n isError?: boolean;\n content: Array<{ type: string; text?: string }>;\n}): ToolPayload {\n const text = result.content\n .filter((entry) => entry.type === \"text\" && typeof entry.text === \"string\")\n .map((entry) => entry.text!)\n .join(\"\\n\");\n let json: unknown = text;\n try {\n json = JSON.parse(text);\n } catch {\n // keep text\n }\n return { isError: Boolean(result.isError), text, json };\n}\n\nfunction toolErrorMessage(result: ToolPayload): string {\n if (typeof result.json === \"object\" && result.json && \"error\" in result.json) {\n return String((result.json as { error: unknown }).error);\n }\n return result.text;\n}\n\nfunction composeInstructions(instructions: string, goal?: string): string {\n if (!goal) return instructions;\n return `${instructions}\\n\\nYour goal: ${goal}`;\n}\n\n/**\n * Per-session MCP client loop:\n * wait → read view → ask model → one action → retry once on error → report turn.\n */\nexport class SessionLoop {\n private readonly options: SessionLoopOptions;\n private client: Client | null = null;\n private stopped = false;\n private runPromise: Promise<void> | null = null;\n\n constructor(options: SessionLoopOptions) {\n this.options = options;\n }\n\n async start(): Promise<void> {\n if (this.runPromise) return;\n const client = new Client({ name: \"agent-player\", version: SDK_VERSION });\n await client.connect(this.options.transport);\n this.client = client;\n this.runPromise = this.run().catch((error) => {\n if (this.stopped) return;\n const message = error instanceof Error ? error.message : String(error);\n log(this.options.logger, \"error\", `session ${this.options.sessionId} crashed: ${message}`);\n this.options.onTurnResult({ ok: false, error: message, attempts: 0 });\n });\n }\n\n async stop(): Promise<void> {\n this.stopped = true;\n try {\n await this.client?.close();\n } catch {\n // ignore\n }\n this.client = null;\n await this.runPromise?.catch(() => undefined);\n this.runPromise = null;\n }\n\n private async callTool(name: string, args: Record<string, unknown>): Promise<ToolPayload> {\n if (!this.client) throw new Error(\"Client not connected\");\n const result = await this.client.callTool({ name, arguments: args });\n return parseToolPayload(result as {\n isError?: boolean;\n content: Array<{ type: string; text?: string }>;\n });\n }\n\n private async decideAction(\n tools: Array<{ name: string; description: string; inputSchema: unknown }>,\n messages: ModelMessage[],\n usage: TokenUsage,\n ): Promise<ToolCallRequest> {\n const decision = await this.options.model.decide({\n instructions: composeInstructions(this.options.instructions, this.options.goal),\n tools,\n messages,\n });\n usage.promptTokens += decision.usage.promptTokens;\n usage.completionTokens += decision.usage.completionTokens;\n const total = decision.usage.promptTokens + decision.usage.completionTokens;\n await this.options.onUsage(total);\n return decision.toolCall;\n }\n\n private emit(event: LoopEvent): void {\n this.options.onEvent?.(event);\n }\n\n private async run(): Promise<void> {\n if (!this.client) return;\n\n const listed = await this.client.listTools();\n const tools = listed.tools.map((tool) => ({\n name: tool.name,\n description: tool.description ?? tool.name,\n inputSchema: tool.inputSchema,\n }));\n const actionTools = tools.filter((tool) => tool.name !== \"wait\" && tool.name !== \"see\");\n\n while (!this.stopped) {\n const waitResult = await this.callTool(\"wait\", {});\n if (this.stopped) break;\n if (waitResult.isError) {\n this.options.onTurnResult({ ok: false, error: waitResult.text, attempts: 0 });\n continue;\n }\n\n const usage: TokenUsage = { promptTokens: 0, completionTokens: 0 };\n const attemptDetails: AttemptRecord[] = [];\n const messages: ModelMessage[] = [\n {\n role: \"user\",\n content: `It is your turn. Current view:\\n${waitResult.text}\\nCall exactly one action tool.`,\n },\n ];\n\n let action = await this.decideAction(actionTools, messages, usage);\n if (action.name === \"wait\" || action.name === \"see\") {\n const fallback = actionTools[0];\n if (!fallback) {\n this.options.onTurnResult({ ok: false, error: \"No action tools available\", attempts: 0, usage });\n continue;\n }\n action = { name: fallback.name, arguments: {} };\n }\n\n this.emit({ event: \"action\", action: action.name, args: action.arguments });\n let result = await this.callTool(action.name, action.arguments);\n if (!result.isError) {\n this.options.onTurnResult({\n ok: true,\n action: action.name,\n args: action.arguments,\n result: result.json,\n attempts: 1,\n usage,\n });\n continue;\n }\n\n const firstError = toolErrorMessage(result);\n attemptDetails.push({ action: action.name, args: action.arguments, error: firstError });\n log(this.options.logger, \"warn\", `session ${this.options.sessionId} action ${action.name} failed: ${firstError}; retrying once`);\n this.emit({ event: \"retry\", action: action.name, args: action.arguments, error: firstError });\n\n messages.push({\n role: \"assistant\",\n content: `Called ${action.name}(${JSON.stringify(action.arguments)})`,\n });\n messages.push({\n role: \"tool\",\n name: action.name,\n content: result.text,\n isError: true,\n });\n messages.push({\n role: \"user\",\n content: [\n `Action ${action.name} failed: ${firstError}`,\n \"Retry once with a different legal action (or different arguments).\",\n \"Do not repeat the same failing call. Do not call wait or see.\",\n `Current view was:\\n${waitResult.text}`,\n ].join(\"\\n\"),\n });\n\n const retry = await this.decideAction(actionTools, messages, usage);\n if (retry.name === \"wait\" || retry.name === \"see\") {\n const error = `Retry returned ${retry.name} instead of an action (after: ${firstError})`;\n this.emit({ event: \"turnFailed\", error });\n this.options.onTurnResult({\n ok: false,\n error,\n attempts: 1,\n attemptDetails,\n usage,\n });\n continue;\n }\n\n this.emit({ event: \"action\", action: retry.name, args: retry.arguments });\n result = await this.callTool(retry.name, retry.arguments);\n if (!result.isError) {\n this.options.onTurnResult({\n ok: true,\n action: retry.name,\n args: retry.arguments,\n result: result.json,\n attempts: 2,\n usage,\n });\n continue;\n }\n\n const secondError = toolErrorMessage(result);\n attemptDetails.push({ action: retry.name, args: retry.arguments, error: secondError });\n log(this.options.logger, \"warn\", `session ${this.options.sessionId} retry ${retry.name} failed: ${secondError}`);\n this.emit({ event: \"turnFailed\", action: retry.name, args: retry.arguments, error: secondError });\n this.options.onTurnResult({\n ok: false,\n error: secondError,\n attempts: 2,\n attemptDetails,\n usage,\n });\n }\n }\n}\n","export type Logger = {\n debug?: (message: string, extra?: unknown) => void;\n info?: (message: string, extra?: unknown) => void;\n warn?: (message: string, extra?: unknown) => void;\n error?: (message: string, extra?: unknown) => void;\n};\n\nexport function log(logger: Logger | undefined, level: keyof Logger, message: string, extra?: unknown): void {\n logger?.[level]?.(message, extra);\n}\n","/** Wire protocol between AgentServer (SDK) and agent-platform. Version 1. */\n\nexport const PROTOCOL_VERSION = 1;\nexport const SDK_VERSION = \"0.1.0\";\n\n/** How long a disconnected session can be resumed. Shared by both sides. */\nexport const SESSION_RESUME_GRACE_MS = 30_000;\n\nexport type JsonSchema = {\n type?: string | string[];\n properties?: Record<string, JsonSchema>;\n required?: string[];\n description?: string;\n additionalProperties?: boolean;\n [key: string]: unknown;\n};\n\nexport type ToolDescriptor = {\n name: string;\n description: string;\n inputSchema: JsonSchema;\n};\n\nexport type TokenUsage = {\n promptTokens: number;\n completionTokens: number;\n};\n\nexport type AttemptRecord = {\n action: string;\n args: Record<string, unknown>;\n error: string;\n};\n\nexport type TurnReport = {\n ok: boolean;\n error?: string;\n action?: string;\n args?: Record<string, unknown>;\n result?: unknown;\n attempts?: number;\n attemptDetails?: AttemptRecord[];\n usage?: TokenUsage;\n connectionLost?: boolean;\n};\n\nexport type SessionEventName = \"action\" | \"retry\" | \"turnFailed\";\n\nexport type Envelope =\n | {\n type: \"hello\";\n platformKey: string;\n clientId: string;\n protocolVersion: number;\n sdkVersion: string;\n }\n | {\n type: \"hello_ok\";\n accountName: string;\n protocolVersion: number;\n }\n | {\n type: \"hello_err\";\n message: string;\n code?: \"AUTH\" | \"PROTOCOL\";\n }\n | {\n type: \"start_session\";\n sessionId: string;\n sessionToken: string;\n tools: ToolDescriptor[];\n instructions: string;\n goal?: string;\n name?: string;\n }\n | {\n type: \"session_started\";\n sessionId: string;\n }\n | {\n type: \"stop_session\";\n sessionId: string;\n }\n | {\n type: \"session_stopped\";\n sessionId: string;\n }\n | {\n type: \"mcp\";\n sessionId: string;\n message: unknown;\n }\n | ({\n type: \"turn_result\";\n sessionId: string;\n } & TurnReport)\n | {\n type: \"session_event\";\n sessionId: string;\n event: SessionEventName;\n action?: string;\n args?: Record<string, unknown>;\n error?: string;\n }\n | {\n type: \"ping\";\n id: string;\n }\n | {\n type: \"pong\";\n id: string;\n }\n | {\n type: \"resume_session\";\n sessionId: string;\n sessionToken: string;\n }\n | {\n type: \"session_resumed\";\n sessionId: string;\n }\n | {\n type: \"resume_err\";\n sessionId: string;\n message: string;\n }\n | {\n type: \"error\";\n message: string;\n sessionId?: string;\n };\n\nexport const DEFAULT_AGENT_INSTRUCTIONS = [\n \"You are a remote game agent. Tools are provided by the game.\",\n \"On every turn: call wait first. It blocks until the game grants your turn and then returns the current view from see.\",\n \"After wait returns, call exactly one action tool (not see, not wait).\",\n \"If an action tool returns an error, retry once with a corrected action. A second failure ends the turn as failed.\",\n].join(\"\\n\");\n\nexport function assertNever(value: never): never {\n throw new Error(`Unhandled protocol message: ${JSON.stringify(value)}`);\n}\n","import type { InlinePlatform, StartSessionRequest } from \"../types.js\";\nimport { createScriptedModel, type ToolCallRequest } from \"./model.js\";\nimport { loopEventToAgentEvent, SessionLoop } from \"./session-loop.js\";\n\n/**\n * In-process platform that replays a script of tool calls.\n * Same session loop as production; no network and no platform key.\n */\nexport function createTestPlatform(script: ToolCallRequest[]): InlinePlatform {\n const model = createScriptedModel(script);\n const loops = new Map<string, SessionLoop>();\n\n return {\n async startSession(request: StartSessionRequest) {\n const loop = new SessionLoop({\n sessionId: request.sessionId,\n transport: request.transport,\n instructions: request.instructions,\n ...(request.goal ? { goal: request.goal } : {}),\n model,\n ...(request.logger ? { logger: request.logger } : {}),\n onUsage: async (tokens) => {\n await request.onUsage?.(tokens);\n },\n onTurnResult: request.onTurnResult,\n onEvent: (event) => {\n request.onEvent?.(loopEventToAgentEvent(request.sessionId, event));\n },\n });\n loops.set(request.sessionId, loop);\n await loop.start();\n },\n\n async stopSession(sessionId: string) {\n const loop = loops.get(sessionId);\n if (!loop) return;\n loops.delete(sessionId);\n await loop.stop();\n },\n\n async close() {\n const ids = [...loops.keys()];\n for (const id of ids) {\n const loop = loops.get(id);\n loops.delete(id);\n await loop?.stop();\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts","../src/loop/model.ts","../src/loop/session-loop.ts","../src/logger.ts","../src/protocol.ts","../src/loop/test-platform.ts"],"sourcesContent":["export type { InlinePlatform, StartSessionRequest } from \"./types.js\";\nexport { createTestPlatform } from \"./loop/test-platform.js\";\nexport { SessionLoop, loopEventToAgentEvent, type SessionLoopOptions, type LoopEvent } from \"./loop/session-loop.js\";\nexport {\n createScriptedModel,\n type ModelClient,\n type ModelDecision,\n type ModelMessage,\n type ToolCallRequest,\n} from \"./loop/model.js\";\n","export type ToolCallRequest = {\n name: string;\n arguments: Record<string, unknown>;\n};\n\nexport type ModelMessage =\n | { role: \"system\"; content: string }\n | { role: \"user\"; content: string }\n | { role: \"assistant\"; content: string }\n | { role: \"tool\"; name: string; content: string; isError?: boolean };\n\nexport type ModelDecision = {\n toolCall: ToolCallRequest;\n usage: { promptTokens: number; completionTokens: number };\n};\n\nexport type ModelClient = {\n /** Ask the model to choose exactly one tool call. */\n decide(args: {\n instructions: string;\n tools: Array<{ name: string; description: string; inputSchema: unknown }>;\n messages: ModelMessage[];\n }): Promise<ModelDecision>;\n};\n\n/**\n * Scripted model for tests: returns predetermined tool calls in order.\n * When the queue is exhausted, always returns wait.\n */\nexport function createScriptedModel(script: ToolCallRequest[]): ModelClient {\n let index = 0;\n return {\n async decide() {\n const call = script[index] ?? { name: \"wait\", arguments: {} };\n index += 1;\n return {\n toolCall: call,\n usage: { promptTokens: 1, completionTokens: 1 },\n };\n },\n };\n}\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport type { Logger } from \"../logger.js\";\nimport { log } from \"../logger.js\";\nimport type { AgentEvent } from \"../types.js\";\nimport type { AttemptRecord, SessionEventName, TokenUsage, TurnReport } from \"../protocol.js\";\nimport { SDK_VERSION } from \"../protocol.js\";\nimport type { ModelClient, ModelMessage, ToolCallRequest } from \"./model.js\";\n\nexport type LoopEvent = {\n event: SessionEventName;\n action?: string;\n args?: Record<string, unknown>;\n error?: string;\n};\n\nexport function loopEventToAgentEvent(sessionId: string, event: LoopEvent): AgentEvent {\n if (event.event === \"action\") {\n return {\n type: \"action\",\n sessionId,\n action: event.action ?? \"unknown\",\n args: event.args ?? {},\n };\n }\n if (event.event === \"retry\") {\n return {\n type: \"retry\",\n sessionId,\n action: event.action ?? \"unknown\",\n args: event.args ?? {},\n error: event.error ?? \"action failed\",\n };\n }\n return {\n type: \"turnFailed\",\n sessionId,\n error: event.error ?? \"turn failed\",\n ...(event.action ? { action: event.action } : {}),\n ...(event.args ? { args: event.args } : {}),\n };\n}\n\nexport type SessionLoopOptions = {\n sessionId: string;\n transport: Transport;\n instructions: string;\n goal?: string;\n model: ModelClient;\n logger?: Logger;\n onUsage: (tokens: number) => void | Promise<void>;\n onTurnResult: (result: TurnReport) => void;\n onEvent?: (event: LoopEvent) => void;\n};\n\ntype ToolPayload = { isError: boolean; text: string; json: unknown };\n\nfunction parseToolPayload(result: {\n isError?: boolean;\n content: Array<{ type: string; text?: string }>;\n}): ToolPayload {\n const text = result.content\n .filter((entry) => entry.type === \"text\" && typeof entry.text === \"string\")\n .map((entry) => entry.text!)\n .join(\"\\n\");\n let json: unknown = text;\n try {\n json = JSON.parse(text);\n } catch {\n // keep text\n }\n return { isError: Boolean(result.isError), text, json };\n}\n\nfunction toolErrorMessage(result: ToolPayload): string {\n if (typeof result.json === \"object\" && result.json && \"error\" in result.json) {\n return String((result.json as { error: unknown }).error);\n }\n return result.text;\n}\n\nfunction composeInstructions(instructions: string, goal?: string): string {\n if (!goal) return instructions;\n return `${instructions}\\n\\nYour goal: ${goal}`;\n}\n\n/**\n * Per-session MCP client loop:\n * wait → read view → ask model → one action → retry once on error → report turn.\n */\nexport class SessionLoop {\n private readonly options: SessionLoopOptions;\n private client: Client | null = null;\n private stopped = false;\n private runPromise: Promise<void> | null = null;\n\n constructor(options: SessionLoopOptions) {\n this.options = options;\n }\n\n async start(): Promise<void> {\n if (this.runPromise) return;\n const client = new Client({ name: \"agent-player\", version: SDK_VERSION });\n await client.connect(this.options.transport);\n this.client = client;\n this.runPromise = this.run().catch((error) => {\n if (this.stopped) return;\n const message = error instanceof Error ? error.message : String(error);\n log(this.options.logger, \"error\", `session ${this.options.sessionId} crashed: ${message}`);\n this.options.onTurnResult({ ok: false, error: message, attempts: 0 });\n });\n }\n\n async stop(): Promise<void> {\n this.stopped = true;\n try {\n await this.client?.close();\n } catch {\n // ignore\n }\n this.client = null;\n await this.runPromise?.catch(() => undefined);\n this.runPromise = null;\n }\n\n private async callTool(name: string, args: Record<string, unknown>): Promise<ToolPayload> {\n if (!this.client) throw new Error(\"Client not connected\");\n const result = await this.client.callTool({ name, arguments: args });\n return parseToolPayload(result as {\n isError?: boolean;\n content: Array<{ type: string; text?: string }>;\n });\n }\n\n private async decideAction(\n tools: Array<{ name: string; description: string; inputSchema: unknown }>,\n messages: ModelMessage[],\n usage: TokenUsage,\n ): Promise<ToolCallRequest> {\n const decision = await this.options.model.decide({\n instructions: composeInstructions(this.options.instructions, this.options.goal),\n tools,\n messages,\n });\n usage.promptTokens += decision.usage.promptTokens;\n usage.completionTokens += decision.usage.completionTokens;\n const total = decision.usage.promptTokens + decision.usage.completionTokens;\n await this.options.onUsage(total);\n return decision.toolCall;\n }\n\n private emit(event: LoopEvent): void {\n this.options.onEvent?.(event);\n }\n\n private async run(): Promise<void> {\n if (!this.client) return;\n\n const listed = await this.client.listTools();\n const tools = listed.tools.map((tool) => ({\n name: tool.name,\n description: tool.description ?? tool.name,\n inputSchema: tool.inputSchema,\n }));\n const actionTools = tools.filter((tool) => tool.name !== \"wait\" && tool.name !== \"see\");\n\n while (!this.stopped) {\n const waitResult = await this.callTool(\"wait\", {});\n if (this.stopped) break;\n if (waitResult.isError) {\n this.options.onTurnResult({ ok: false, error: waitResult.text, attempts: 0 });\n continue;\n }\n\n const usage: TokenUsage = { promptTokens: 0, completionTokens: 0 };\n const attemptDetails: AttemptRecord[] = [];\n const messages: ModelMessage[] = [\n {\n role: \"user\",\n content: `It is your turn. Current view:\\n${waitResult.text}\\nCall exactly one action tool.`,\n },\n ];\n\n let action = await this.decideAction(actionTools, messages, usage);\n let result: ToolPayload | undefined;\n let firstError: string;\n if (action.name === \"wait\" || action.name === \"see\") {\n // wait and see are how the loop reads state. They are not a move.\n firstError = `${action.name} is not an action`;\n } else {\n this.emit({ event: \"action\", action: action.name, args: action.arguments });\n result = await this.callTool(action.name, action.arguments);\n if (!result.isError) {\n this.options.onTurnResult({\n ok: true,\n action: action.name,\n args: action.arguments,\n result: result.json,\n attempts: 1,\n usage,\n });\n continue;\n }\n firstError = toolErrorMessage(result);\n }\n attemptDetails.push({ action: action.name, args: action.arguments, error: firstError });\n log(this.options.logger, \"warn\", `session ${this.options.sessionId} action ${action.name} failed: ${firstError}; retrying once`);\n this.emit({ event: \"retry\", action: action.name, args: action.arguments, error: firstError });\n\n messages.push({\n role: \"assistant\",\n content: `Called ${action.name}(${JSON.stringify(action.arguments)})`,\n });\n messages.push({\n role: \"tool\",\n name: action.name,\n content: result?.text ?? firstError,\n isError: true,\n });\n messages.push({\n role: \"user\",\n content: [\n `Action ${action.name} failed: ${firstError}`,\n \"Retry once with a different legal action (or different arguments).\",\n \"Do not repeat the same failing call. Do not call wait or see.\",\n `Current view was:\\n${waitResult.text}`,\n ].join(\"\\n\"),\n });\n\n const retry = await this.decideAction(actionTools, messages, usage);\n if (retry.name === \"wait\" || retry.name === \"see\") {\n const error = `Retry returned ${retry.name} instead of an action (after: ${firstError})`;\n this.emit({ event: \"turnFailed\", error });\n this.options.onTurnResult({\n ok: false,\n error,\n attempts: 1,\n attemptDetails,\n usage,\n });\n continue;\n }\n\n this.emit({ event: \"action\", action: retry.name, args: retry.arguments });\n result = await this.callTool(retry.name, retry.arguments);\n if (!result.isError) {\n this.options.onTurnResult({\n ok: true,\n action: retry.name,\n args: retry.arguments,\n result: result.json,\n attempts: 2,\n usage,\n });\n continue;\n }\n\n const secondError = toolErrorMessage(result);\n attemptDetails.push({ action: retry.name, args: retry.arguments, error: secondError });\n log(this.options.logger, \"warn\", `session ${this.options.sessionId} retry ${retry.name} failed: ${secondError}`);\n this.emit({ event: \"turnFailed\", action: retry.name, args: retry.arguments, error: secondError });\n this.options.onTurnResult({\n ok: false,\n error: secondError,\n attempts: 2,\n attemptDetails,\n usage,\n });\n }\n }\n}\n","export type Logger = {\n debug?: (message: string, extra?: unknown) => void;\n info?: (message: string, extra?: unknown) => void;\n warn?: (message: string, extra?: unknown) => void;\n error?: (message: string, extra?: unknown) => void;\n};\n\nexport function log(logger: Logger | undefined, level: keyof Logger, message: string, extra?: unknown): void {\n logger?.[level]?.(message, extra);\n}\n","/**\n * Wire protocol between AgentServer (SDK) and the platform. Version 1.\n */\n\nexport const PROTOCOL_VERSION = 1;\nexport const SDK_VERSION = \"1.1.0\";\n\n/** How long a disconnected session can be resumed. Shared by both sides. */\nexport const SESSION_RESUME_GRACE_MS = 30_000;\n\nexport type JsonSchema = {\n type?: string | string[];\n properties?: Record<string, JsonSchema>;\n required?: string[];\n description?: string;\n additionalProperties?: boolean;\n [key: string]: unknown;\n};\n\nexport type ToolDescriptor = {\n name: string;\n description: string;\n inputSchema: JsonSchema;\n};\n\nexport type TokenUsage = {\n promptTokens: number;\n completionTokens: number;\n};\n\nexport type AttemptRecord = {\n action: string;\n args: Record<string, unknown>;\n error: string;\n};\n\nexport type TurnReport = {\n ok: boolean;\n error?: string;\n action?: string;\n args?: Record<string, unknown>;\n result?: unknown;\n attempts?: number;\n attemptDetails?: AttemptRecord[];\n usage?: TokenUsage;\n connectionLost?: boolean;\n};\n\nexport type SessionEventName = \"action\" | \"retry\" | \"turnFailed\";\n\nexport type Envelope =\n | {\n type: \"hello\";\n platformKey: string;\n clientId: string;\n protocolVersion: number;\n sdkVersion: string;\n }\n | {\n type: \"hello_ok\";\n accountName: string;\n protocolVersion: number;\n }\n | {\n type: \"hello_err\";\n message: string;\n code?: \"AUTH\" | \"PROTOCOL\";\n }\n | {\n type: \"start_session\";\n sessionId: string;\n sessionToken: string;\n tools: ToolDescriptor[];\n instructions: string;\n goal?: string;\n name?: string;\n }\n | {\n type: \"session_started\";\n sessionId: string;\n }\n | {\n type: \"stop_session\";\n sessionId: string;\n }\n | {\n type: \"session_stopped\";\n sessionId: string;\n }\n | {\n type: \"mcp\";\n sessionId: string;\n message: unknown;\n }\n | ({\n type: \"turn_result\";\n sessionId: string;\n } & TurnReport)\n | {\n type: \"session_event\";\n sessionId: string;\n event: SessionEventName;\n action?: string;\n args?: Record<string, unknown>;\n error?: string;\n }\n | {\n type: \"ping\";\n id: string;\n }\n | {\n type: \"pong\";\n id: string;\n }\n | {\n type: \"resume_session\";\n sessionId: string;\n sessionToken: string;\n }\n | {\n type: \"session_resumed\";\n sessionId: string;\n }\n | {\n type: \"resume_err\";\n sessionId: string;\n message: string;\n }\n | {\n type: \"error\";\n message: string;\n sessionId?: string;\n };\n\nexport const DEFAULT_AGENT_INSTRUCTIONS = [\n \"You are a remote game agent. Tools are provided by the game.\",\n \"On every turn: call wait first. It blocks until the game grants your turn and then returns the current view from see.\",\n \"After wait returns, call exactly one action tool (not see, not wait).\",\n \"The view's done list is what this character has already called in this session, oldest first. Use it when choosing the next action.\",\n \"If an action tool returns an error, retry once with a corrected action. A second failure ends the turn as failed.\",\n].join(\"\\n\");\n\nexport function assertNever(value: never): never {\n throw new Error(`Unhandled protocol message: ${JSON.stringify(value)}`);\n}\n","import type { InlinePlatform, StartSessionRequest } from \"../types.js\";\nimport { createScriptedModel, type ToolCallRequest } from \"./model.js\";\nimport { loopEventToAgentEvent, SessionLoop } from \"./session-loop.js\";\n\n/**\n * In-process platform that replays a script of tool calls.\n * Same session loop as production; no network and no platform key.\n */\nexport function createTestPlatform(script: ToolCallRequest[]): InlinePlatform {\n const model = createScriptedModel(script);\n const loops = new Map<string, SessionLoop>();\n\n return {\n async startSession(request: StartSessionRequest) {\n const loop = new SessionLoop({\n sessionId: request.sessionId,\n transport: request.transport,\n instructions: request.instructions,\n ...(request.goal ? { goal: request.goal } : {}),\n model,\n ...(request.logger ? { logger: request.logger } : {}),\n onUsage: async (tokens) => {\n await request.onUsage?.(tokens);\n },\n onTurnResult: request.onTurnResult,\n onEvent: (event) => {\n request.onEvent?.(loopEventToAgentEvent(request.sessionId, event));\n },\n });\n loops.set(request.sessionId, loop);\n await loop.start();\n },\n\n async stopSession(sessionId: string) {\n const loop = loops.get(sessionId);\n if (!loop) return;\n loops.delete(sessionId);\n await loop.stop();\n },\n\n async close() {\n const ids = [...loops.keys()];\n for (const id of ids) {\n const loop = loops.get(id);\n loops.delete(id);\n await loop?.stop();\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/testing.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { I as InlinePlatform, L as Logger, b as AgentEvent } from './types-
|
|
1
|
+
import { I as InlinePlatform, L as Logger, b as AgentEvent } from './types-D0p6GrVs.cjs';
|
|
2
|
+
export { d as StartSessionRequest } from './types-D0p6GrVs.cjs';
|
|
2
3
|
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
3
4
|
import { SessionEventName, TurnReport } from './protocol.cjs';
|
|
4
5
|
|
|
@@ -88,4 +89,4 @@ declare class SessionLoop {
|
|
|
88
89
|
private run;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
export { type LoopEvent, type ModelClient, type ModelDecision, type ModelMessage, SessionLoop, type SessionLoopOptions, type ToolCallRequest, createScriptedModel, createTestPlatform, loopEventToAgentEvent };
|
|
92
|
+
export { InlinePlatform, type LoopEvent, type ModelClient, type ModelDecision, type ModelMessage, SessionLoop, type SessionLoopOptions, type ToolCallRequest, createScriptedModel, createTestPlatform, loopEventToAgentEvent };
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { I as InlinePlatform, L as Logger, b as AgentEvent } from './types-
|
|
1
|
+
import { I as InlinePlatform, L as Logger, b as AgentEvent } from './types-DXM_HZHu.js';
|
|
2
|
+
export { d as StartSessionRequest } from './types-DXM_HZHu.js';
|
|
2
3
|
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
3
4
|
import { SessionEventName, TurnReport } from './protocol.js';
|
|
4
5
|
|
|
@@ -88,4 +89,4 @@ declare class SessionLoop {
|
|
|
88
89
|
private run;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
export { type LoopEvent, type ModelClient, type ModelDecision, type ModelMessage, SessionLoop, type SessionLoopOptions, type ToolCallRequest, createScriptedModel, createTestPlatform, loopEventToAgentEvent };
|
|
92
|
+
export { InlinePlatform, type LoopEvent, type ModelClient, type ModelDecision, type ModelMessage, SessionLoop, type SessionLoopOptions, type ToolCallRequest, createScriptedModel, createTestPlatform, loopEventToAgentEvent };
|
package/dist/testing.js
CHANGED
|
@@ -22,11 +22,12 @@ function log(logger, level, message, extra) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// src/protocol.ts
|
|
25
|
-
var SDK_VERSION = "
|
|
25
|
+
var SDK_VERSION = "1.1.0";
|
|
26
26
|
var DEFAULT_AGENT_INSTRUCTIONS = [
|
|
27
27
|
"You are a remote game agent. Tools are provided by the game.",
|
|
28
28
|
"On every turn: call wait first. It blocks until the game grants your turn and then returns the current view from see.",
|
|
29
29
|
"After wait returns, call exactly one action tool (not see, not wait).",
|
|
30
|
+
"The view's done list is what this character has already called in this session, oldest first. Use it when choosing the next action.",
|
|
30
31
|
"If an action tool returns an error, retry once with a corrected action. A second failure ends the turn as failed."
|
|
31
32
|
].join("\n");
|
|
32
33
|
|
|
@@ -155,28 +156,26 @@ Call exactly one action tool.`
|
|
|
155
156
|
}
|
|
156
157
|
];
|
|
157
158
|
let action = await this.decideAction(actionTools, messages, usage);
|
|
159
|
+
let result;
|
|
160
|
+
let firstError;
|
|
158
161
|
if (action.name === "wait" || action.name === "see") {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
firstError = `${action.name} is not an action`;
|
|
163
|
+
} else {
|
|
164
|
+
this.emit({ event: "action", action: action.name, args: action.arguments });
|
|
165
|
+
result = await this.callTool(action.name, action.arguments);
|
|
166
|
+
if (!result.isError) {
|
|
167
|
+
this.options.onTurnResult({
|
|
168
|
+
ok: true,
|
|
169
|
+
action: action.name,
|
|
170
|
+
args: action.arguments,
|
|
171
|
+
result: result.json,
|
|
172
|
+
attempts: 1,
|
|
173
|
+
usage
|
|
174
|
+
});
|
|
162
175
|
continue;
|
|
163
176
|
}
|
|
164
|
-
|
|
177
|
+
firstError = toolErrorMessage(result);
|
|
165
178
|
}
|
|
166
|
-
this.emit({ event: "action", action: action.name, args: action.arguments });
|
|
167
|
-
let result = await this.callTool(action.name, action.arguments);
|
|
168
|
-
if (!result.isError) {
|
|
169
|
-
this.options.onTurnResult({
|
|
170
|
-
ok: true,
|
|
171
|
-
action: action.name,
|
|
172
|
-
args: action.arguments,
|
|
173
|
-
result: result.json,
|
|
174
|
-
attempts: 1,
|
|
175
|
-
usage
|
|
176
|
-
});
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
const firstError = toolErrorMessage(result);
|
|
180
179
|
attemptDetails.push({ action: action.name, args: action.arguments, error: firstError });
|
|
181
180
|
log(this.options.logger, "warn", `session ${this.options.sessionId} action ${action.name} failed: ${firstError}; retrying once`);
|
|
182
181
|
this.emit({ event: "retry", action: action.name, args: action.arguments, error: firstError });
|
|
@@ -187,7 +186,7 @@ Call exactly one action tool.`
|
|
|
187
186
|
messages.push({
|
|
188
187
|
role: "tool",
|
|
189
188
|
name: action.name,
|
|
190
|
-
content: result
|
|
189
|
+
content: result?.text ?? firstError,
|
|
191
190
|
isError: true
|
|
192
191
|
});
|
|
193
192
|
messages.push({
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loop/model.ts","../src/loop/session-loop.ts","../src/logger.ts","../src/protocol.ts","../src/loop/test-platform.ts"],"sourcesContent":["export type ToolCallRequest = {\n name: string;\n arguments: Record<string, unknown>;\n};\n\nexport type ModelMessage =\n | { role: \"system\"; content: string }\n | { role: \"user\"; content: string }\n | { role: \"assistant\"; content: string }\n | { role: \"tool\"; name: string; content: string; isError?: boolean };\n\nexport type ModelDecision = {\n toolCall: ToolCallRequest;\n usage: { promptTokens: number; completionTokens: number };\n};\n\nexport type ModelClient = {\n /** Ask the model to choose exactly one tool call. */\n decide(args: {\n instructions: string;\n tools: Array<{ name: string; description: string; inputSchema: unknown }>;\n messages: ModelMessage[];\n }): Promise<ModelDecision>;\n};\n\n/**\n * Scripted model for tests: returns predetermined tool calls in order.\n * When the queue is exhausted, always returns wait.\n */\nexport function createScriptedModel(script: ToolCallRequest[]): ModelClient {\n let index = 0;\n return {\n async decide() {\n const call = script[index] ?? { name: \"wait\", arguments: {} };\n index += 1;\n return {\n toolCall: call,\n usage: { promptTokens: 1, completionTokens: 1 },\n };\n },\n };\n}\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport type { Logger } from \"../logger.js\";\nimport { log } from \"../logger.js\";\nimport type { AgentEvent } from \"../types.js\";\nimport type { AttemptRecord, SessionEventName, TokenUsage, TurnReport } from \"../protocol.js\";\nimport { SDK_VERSION } from \"../protocol.js\";\nimport type { ModelClient, ModelMessage, ToolCallRequest } from \"./model.js\";\n\nexport type LoopEvent = {\n event: SessionEventName;\n action?: string;\n args?: Record<string, unknown>;\n error?: string;\n};\n\nexport function loopEventToAgentEvent(sessionId: string, event: LoopEvent): AgentEvent {\n if (event.event === \"action\") {\n return {\n type: \"action\",\n sessionId,\n action: event.action ?? \"unknown\",\n args: event.args ?? {},\n };\n }\n if (event.event === \"retry\") {\n return {\n type: \"retry\",\n sessionId,\n action: event.action ?? \"unknown\",\n args: event.args ?? {},\n error: event.error ?? \"action failed\",\n };\n }\n return {\n type: \"turnFailed\",\n sessionId,\n error: event.error ?? \"turn failed\",\n ...(event.action ? { action: event.action } : {}),\n ...(event.args ? { args: event.args } : {}),\n };\n}\n\nexport type SessionLoopOptions = {\n sessionId: string;\n transport: Transport;\n instructions: string;\n goal?: string;\n model: ModelClient;\n logger?: Logger;\n onUsage: (tokens: number) => void | Promise<void>;\n onTurnResult: (result: TurnReport) => void;\n onEvent?: (event: LoopEvent) => void;\n};\n\ntype ToolPayload = { isError: boolean; text: string; json: unknown };\n\nfunction parseToolPayload(result: {\n isError?: boolean;\n content: Array<{ type: string; text?: string }>;\n}): ToolPayload {\n const text = result.content\n .filter((entry) => entry.type === \"text\" && typeof entry.text === \"string\")\n .map((entry) => entry.text!)\n .join(\"\\n\");\n let json: unknown = text;\n try {\n json = JSON.parse(text);\n } catch {\n // keep text\n }\n return { isError: Boolean(result.isError), text, json };\n}\n\nfunction toolErrorMessage(result: ToolPayload): string {\n if (typeof result.json === \"object\" && result.json && \"error\" in result.json) {\n return String((result.json as { error: unknown }).error);\n }\n return result.text;\n}\n\nfunction composeInstructions(instructions: string, goal?: string): string {\n if (!goal) return instructions;\n return `${instructions}\\n\\nYour goal: ${goal}`;\n}\n\n/**\n * Per-session MCP client loop:\n * wait → read view → ask model → one action → retry once on error → report turn.\n */\nexport class SessionLoop {\n private readonly options: SessionLoopOptions;\n private client: Client | null = null;\n private stopped = false;\n private runPromise: Promise<void> | null = null;\n\n constructor(options: SessionLoopOptions) {\n this.options = options;\n }\n\n async start(): Promise<void> {\n if (this.runPromise) return;\n const client = new Client({ name: \"agent-player\", version: SDK_VERSION });\n await client.connect(this.options.transport);\n this.client = client;\n this.runPromise = this.run().catch((error) => {\n if (this.stopped) return;\n const message = error instanceof Error ? error.message : String(error);\n log(this.options.logger, \"error\", `session ${this.options.sessionId} crashed: ${message}`);\n this.options.onTurnResult({ ok: false, error: message, attempts: 0 });\n });\n }\n\n async stop(): Promise<void> {\n this.stopped = true;\n try {\n await this.client?.close();\n } catch {\n // ignore\n }\n this.client = null;\n await this.runPromise?.catch(() => undefined);\n this.runPromise = null;\n }\n\n private async callTool(name: string, args: Record<string, unknown>): Promise<ToolPayload> {\n if (!this.client) throw new Error(\"Client not connected\");\n const result = await this.client.callTool({ name, arguments: args });\n return parseToolPayload(result as {\n isError?: boolean;\n content: Array<{ type: string; text?: string }>;\n });\n }\n\n private async decideAction(\n tools: Array<{ name: string; description: string; inputSchema: unknown }>,\n messages: ModelMessage[],\n usage: TokenUsage,\n ): Promise<ToolCallRequest> {\n const decision = await this.options.model.decide({\n instructions: composeInstructions(this.options.instructions, this.options.goal),\n tools,\n messages,\n });\n usage.promptTokens += decision.usage.promptTokens;\n usage.completionTokens += decision.usage.completionTokens;\n const total = decision.usage.promptTokens + decision.usage.completionTokens;\n await this.options.onUsage(total);\n return decision.toolCall;\n }\n\n private emit(event: LoopEvent): void {\n this.options.onEvent?.(event);\n }\n\n private async run(): Promise<void> {\n if (!this.client) return;\n\n const listed = await this.client.listTools();\n const tools = listed.tools.map((tool) => ({\n name: tool.name,\n description: tool.description ?? tool.name,\n inputSchema: tool.inputSchema,\n }));\n const actionTools = tools.filter((tool) => tool.name !== \"wait\" && tool.name !== \"see\");\n\n while (!this.stopped) {\n const waitResult = await this.callTool(\"wait\", {});\n if (this.stopped) break;\n if (waitResult.isError) {\n this.options.onTurnResult({ ok: false, error: waitResult.text, attempts: 0 });\n continue;\n }\n\n const usage: TokenUsage = { promptTokens: 0, completionTokens: 0 };\n const attemptDetails: AttemptRecord[] = [];\n const messages: ModelMessage[] = [\n {\n role: \"user\",\n content: `It is your turn. Current view:\\n${waitResult.text}\\nCall exactly one action tool.`,\n },\n ];\n\n let action = await this.decideAction(actionTools, messages, usage);\n if (action.name === \"wait\" || action.name === \"see\") {\n const fallback = actionTools[0];\n if (!fallback) {\n this.options.onTurnResult({ ok: false, error: \"No action tools available\", attempts: 0, usage });\n continue;\n }\n action = { name: fallback.name, arguments: {} };\n }\n\n this.emit({ event: \"action\", action: action.name, args: action.arguments });\n let result = await this.callTool(action.name, action.arguments);\n if (!result.isError) {\n this.options.onTurnResult({\n ok: true,\n action: action.name,\n args: action.arguments,\n result: result.json,\n attempts: 1,\n usage,\n });\n continue;\n }\n\n const firstError = toolErrorMessage(result);\n attemptDetails.push({ action: action.name, args: action.arguments, error: firstError });\n log(this.options.logger, \"warn\", `session ${this.options.sessionId} action ${action.name} failed: ${firstError}; retrying once`);\n this.emit({ event: \"retry\", action: action.name, args: action.arguments, error: firstError });\n\n messages.push({\n role: \"assistant\",\n content: `Called ${action.name}(${JSON.stringify(action.arguments)})`,\n });\n messages.push({\n role: \"tool\",\n name: action.name,\n content: result.text,\n isError: true,\n });\n messages.push({\n role: \"user\",\n content: [\n `Action ${action.name} failed: ${firstError}`,\n \"Retry once with a different legal action (or different arguments).\",\n \"Do not repeat the same failing call. Do not call wait or see.\",\n `Current view was:\\n${waitResult.text}`,\n ].join(\"\\n\"),\n });\n\n const retry = await this.decideAction(actionTools, messages, usage);\n if (retry.name === \"wait\" || retry.name === \"see\") {\n const error = `Retry returned ${retry.name} instead of an action (after: ${firstError})`;\n this.emit({ event: \"turnFailed\", error });\n this.options.onTurnResult({\n ok: false,\n error,\n attempts: 1,\n attemptDetails,\n usage,\n });\n continue;\n }\n\n this.emit({ event: \"action\", action: retry.name, args: retry.arguments });\n result = await this.callTool(retry.name, retry.arguments);\n if (!result.isError) {\n this.options.onTurnResult({\n ok: true,\n action: retry.name,\n args: retry.arguments,\n result: result.json,\n attempts: 2,\n usage,\n });\n continue;\n }\n\n const secondError = toolErrorMessage(result);\n attemptDetails.push({ action: retry.name, args: retry.arguments, error: secondError });\n log(this.options.logger, \"warn\", `session ${this.options.sessionId} retry ${retry.name} failed: ${secondError}`);\n this.emit({ event: \"turnFailed\", action: retry.name, args: retry.arguments, error: secondError });\n this.options.onTurnResult({\n ok: false,\n error: secondError,\n attempts: 2,\n attemptDetails,\n usage,\n });\n }\n }\n}\n","export type Logger = {\n debug?: (message: string, extra?: unknown) => void;\n info?: (message: string, extra?: unknown) => void;\n warn?: (message: string, extra?: unknown) => void;\n error?: (message: string, extra?: unknown) => void;\n};\n\nexport function log(logger: Logger | undefined, level: keyof Logger, message: string, extra?: unknown): void {\n logger?.[level]?.(message, extra);\n}\n","/** Wire protocol between AgentServer (SDK) and agent-platform. Version 1. */\n\nexport const PROTOCOL_VERSION = 1;\nexport const SDK_VERSION = \"0.1.0\";\n\n/** How long a disconnected session can be resumed. Shared by both sides. */\nexport const SESSION_RESUME_GRACE_MS = 30_000;\n\nexport type JsonSchema = {\n type?: string | string[];\n properties?: Record<string, JsonSchema>;\n required?: string[];\n description?: string;\n additionalProperties?: boolean;\n [key: string]: unknown;\n};\n\nexport type ToolDescriptor = {\n name: string;\n description: string;\n inputSchema: JsonSchema;\n};\n\nexport type TokenUsage = {\n promptTokens: number;\n completionTokens: number;\n};\n\nexport type AttemptRecord = {\n action: string;\n args: Record<string, unknown>;\n error: string;\n};\n\nexport type TurnReport = {\n ok: boolean;\n error?: string;\n action?: string;\n args?: Record<string, unknown>;\n result?: unknown;\n attempts?: number;\n attemptDetails?: AttemptRecord[];\n usage?: TokenUsage;\n connectionLost?: boolean;\n};\n\nexport type SessionEventName = \"action\" | \"retry\" | \"turnFailed\";\n\nexport type Envelope =\n | {\n type: \"hello\";\n platformKey: string;\n clientId: string;\n protocolVersion: number;\n sdkVersion: string;\n }\n | {\n type: \"hello_ok\";\n accountName: string;\n protocolVersion: number;\n }\n | {\n type: \"hello_err\";\n message: string;\n code?: \"AUTH\" | \"PROTOCOL\";\n }\n | {\n type: \"start_session\";\n sessionId: string;\n sessionToken: string;\n tools: ToolDescriptor[];\n instructions: string;\n goal?: string;\n name?: string;\n }\n | {\n type: \"session_started\";\n sessionId: string;\n }\n | {\n type: \"stop_session\";\n sessionId: string;\n }\n | {\n type: \"session_stopped\";\n sessionId: string;\n }\n | {\n type: \"mcp\";\n sessionId: string;\n message: unknown;\n }\n | ({\n type: \"turn_result\";\n sessionId: string;\n } & TurnReport)\n | {\n type: \"session_event\";\n sessionId: string;\n event: SessionEventName;\n action?: string;\n args?: Record<string, unknown>;\n error?: string;\n }\n | {\n type: \"ping\";\n id: string;\n }\n | {\n type: \"pong\";\n id: string;\n }\n | {\n type: \"resume_session\";\n sessionId: string;\n sessionToken: string;\n }\n | {\n type: \"session_resumed\";\n sessionId: string;\n }\n | {\n type: \"resume_err\";\n sessionId: string;\n message: string;\n }\n | {\n type: \"error\";\n message: string;\n sessionId?: string;\n };\n\nexport const DEFAULT_AGENT_INSTRUCTIONS = [\n \"You are a remote game agent. Tools are provided by the game.\",\n \"On every turn: call wait first. It blocks until the game grants your turn and then returns the current view from see.\",\n \"After wait returns, call exactly one action tool (not see, not wait).\",\n \"If an action tool returns an error, retry once with a corrected action. A second failure ends the turn as failed.\",\n].join(\"\\n\");\n\nexport function assertNever(value: never): never {\n throw new Error(`Unhandled protocol message: ${JSON.stringify(value)}`);\n}\n","import type { InlinePlatform, StartSessionRequest } from \"../types.js\";\nimport { createScriptedModel, type ToolCallRequest } from \"./model.js\";\nimport { loopEventToAgentEvent, SessionLoop } from \"./session-loop.js\";\n\n/**\n * In-process platform that replays a script of tool calls.\n * Same session loop as production; no network and no platform key.\n */\nexport function createTestPlatform(script: ToolCallRequest[]): InlinePlatform {\n const model = createScriptedModel(script);\n const loops = new Map<string, SessionLoop>();\n\n return {\n async startSession(request: StartSessionRequest) {\n const loop = new SessionLoop({\n sessionId: request.sessionId,\n transport: request.transport,\n instructions: request.instructions,\n ...(request.goal ? { goal: request.goal } : {}),\n model,\n ...(request.logger ? { logger: request.logger } : {}),\n onUsage: async (tokens) => {\n await request.onUsage?.(tokens);\n },\n onTurnResult: request.onTurnResult,\n onEvent: (event) => {\n request.onEvent?.(loopEventToAgentEvent(request.sessionId, event));\n },\n });\n loops.set(request.sessionId, loop);\n await loop.start();\n },\n\n async stopSession(sessionId: string) {\n const loop = loops.get(sessionId);\n if (!loop) return;\n loops.delete(sessionId);\n await loop.stop();\n },\n\n async close() {\n const ids = [...loops.keys()];\n for (const id of ids) {\n const loop = loops.get(id);\n loops.delete(id);\n await loop?.stop();\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/loop/model.ts","../src/loop/session-loop.ts","../src/logger.ts","../src/protocol.ts","../src/loop/test-platform.ts"],"sourcesContent":["export type ToolCallRequest = {\n name: string;\n arguments: Record<string, unknown>;\n};\n\nexport type ModelMessage =\n | { role: \"system\"; content: string }\n | { role: \"user\"; content: string }\n | { role: \"assistant\"; content: string }\n | { role: \"tool\"; name: string; content: string; isError?: boolean };\n\nexport type ModelDecision = {\n toolCall: ToolCallRequest;\n usage: { promptTokens: number; completionTokens: number };\n};\n\nexport type ModelClient = {\n /** Ask the model to choose exactly one tool call. */\n decide(args: {\n instructions: string;\n tools: Array<{ name: string; description: string; inputSchema: unknown }>;\n messages: ModelMessage[];\n }): Promise<ModelDecision>;\n};\n\n/**\n * Scripted model for tests: returns predetermined tool calls in order.\n * When the queue is exhausted, always returns wait.\n */\nexport function createScriptedModel(script: ToolCallRequest[]): ModelClient {\n let index = 0;\n return {\n async decide() {\n const call = script[index] ?? { name: \"wait\", arguments: {} };\n index += 1;\n return {\n toolCall: call,\n usage: { promptTokens: 1, completionTokens: 1 },\n };\n },\n };\n}\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport type { Logger } from \"../logger.js\";\nimport { log } from \"../logger.js\";\nimport type { AgentEvent } from \"../types.js\";\nimport type { AttemptRecord, SessionEventName, TokenUsage, TurnReport } from \"../protocol.js\";\nimport { SDK_VERSION } from \"../protocol.js\";\nimport type { ModelClient, ModelMessage, ToolCallRequest } from \"./model.js\";\n\nexport type LoopEvent = {\n event: SessionEventName;\n action?: string;\n args?: Record<string, unknown>;\n error?: string;\n};\n\nexport function loopEventToAgentEvent(sessionId: string, event: LoopEvent): AgentEvent {\n if (event.event === \"action\") {\n return {\n type: \"action\",\n sessionId,\n action: event.action ?? \"unknown\",\n args: event.args ?? {},\n };\n }\n if (event.event === \"retry\") {\n return {\n type: \"retry\",\n sessionId,\n action: event.action ?? \"unknown\",\n args: event.args ?? {},\n error: event.error ?? \"action failed\",\n };\n }\n return {\n type: \"turnFailed\",\n sessionId,\n error: event.error ?? \"turn failed\",\n ...(event.action ? { action: event.action } : {}),\n ...(event.args ? { args: event.args } : {}),\n };\n}\n\nexport type SessionLoopOptions = {\n sessionId: string;\n transport: Transport;\n instructions: string;\n goal?: string;\n model: ModelClient;\n logger?: Logger;\n onUsage: (tokens: number) => void | Promise<void>;\n onTurnResult: (result: TurnReport) => void;\n onEvent?: (event: LoopEvent) => void;\n};\n\ntype ToolPayload = { isError: boolean; text: string; json: unknown };\n\nfunction parseToolPayload(result: {\n isError?: boolean;\n content: Array<{ type: string; text?: string }>;\n}): ToolPayload {\n const text = result.content\n .filter((entry) => entry.type === \"text\" && typeof entry.text === \"string\")\n .map((entry) => entry.text!)\n .join(\"\\n\");\n let json: unknown = text;\n try {\n json = JSON.parse(text);\n } catch {\n // keep text\n }\n return { isError: Boolean(result.isError), text, json };\n}\n\nfunction toolErrorMessage(result: ToolPayload): string {\n if (typeof result.json === \"object\" && result.json && \"error\" in result.json) {\n return String((result.json as { error: unknown }).error);\n }\n return result.text;\n}\n\nfunction composeInstructions(instructions: string, goal?: string): string {\n if (!goal) return instructions;\n return `${instructions}\\n\\nYour goal: ${goal}`;\n}\n\n/**\n * Per-session MCP client loop:\n * wait → read view → ask model → one action → retry once on error → report turn.\n */\nexport class SessionLoop {\n private readonly options: SessionLoopOptions;\n private client: Client | null = null;\n private stopped = false;\n private runPromise: Promise<void> | null = null;\n\n constructor(options: SessionLoopOptions) {\n this.options = options;\n }\n\n async start(): Promise<void> {\n if (this.runPromise) return;\n const client = new Client({ name: \"agent-player\", version: SDK_VERSION });\n await client.connect(this.options.transport);\n this.client = client;\n this.runPromise = this.run().catch((error) => {\n if (this.stopped) return;\n const message = error instanceof Error ? error.message : String(error);\n log(this.options.logger, \"error\", `session ${this.options.sessionId} crashed: ${message}`);\n this.options.onTurnResult({ ok: false, error: message, attempts: 0 });\n });\n }\n\n async stop(): Promise<void> {\n this.stopped = true;\n try {\n await this.client?.close();\n } catch {\n // ignore\n }\n this.client = null;\n await this.runPromise?.catch(() => undefined);\n this.runPromise = null;\n }\n\n private async callTool(name: string, args: Record<string, unknown>): Promise<ToolPayload> {\n if (!this.client) throw new Error(\"Client not connected\");\n const result = await this.client.callTool({ name, arguments: args });\n return parseToolPayload(result as {\n isError?: boolean;\n content: Array<{ type: string; text?: string }>;\n });\n }\n\n private async decideAction(\n tools: Array<{ name: string; description: string; inputSchema: unknown }>,\n messages: ModelMessage[],\n usage: TokenUsage,\n ): Promise<ToolCallRequest> {\n const decision = await this.options.model.decide({\n instructions: composeInstructions(this.options.instructions, this.options.goal),\n tools,\n messages,\n });\n usage.promptTokens += decision.usage.promptTokens;\n usage.completionTokens += decision.usage.completionTokens;\n const total = decision.usage.promptTokens + decision.usage.completionTokens;\n await this.options.onUsage(total);\n return decision.toolCall;\n }\n\n private emit(event: LoopEvent): void {\n this.options.onEvent?.(event);\n }\n\n private async run(): Promise<void> {\n if (!this.client) return;\n\n const listed = await this.client.listTools();\n const tools = listed.tools.map((tool) => ({\n name: tool.name,\n description: tool.description ?? tool.name,\n inputSchema: tool.inputSchema,\n }));\n const actionTools = tools.filter((tool) => tool.name !== \"wait\" && tool.name !== \"see\");\n\n while (!this.stopped) {\n const waitResult = await this.callTool(\"wait\", {});\n if (this.stopped) break;\n if (waitResult.isError) {\n this.options.onTurnResult({ ok: false, error: waitResult.text, attempts: 0 });\n continue;\n }\n\n const usage: TokenUsage = { promptTokens: 0, completionTokens: 0 };\n const attemptDetails: AttemptRecord[] = [];\n const messages: ModelMessage[] = [\n {\n role: \"user\",\n content: `It is your turn. Current view:\\n${waitResult.text}\\nCall exactly one action tool.`,\n },\n ];\n\n let action = await this.decideAction(actionTools, messages, usage);\n let result: ToolPayload | undefined;\n let firstError: string;\n if (action.name === \"wait\" || action.name === \"see\") {\n // wait and see are how the loop reads state. They are not a move.\n firstError = `${action.name} is not an action`;\n } else {\n this.emit({ event: \"action\", action: action.name, args: action.arguments });\n result = await this.callTool(action.name, action.arguments);\n if (!result.isError) {\n this.options.onTurnResult({\n ok: true,\n action: action.name,\n args: action.arguments,\n result: result.json,\n attempts: 1,\n usage,\n });\n continue;\n }\n firstError = toolErrorMessage(result);\n }\n attemptDetails.push({ action: action.name, args: action.arguments, error: firstError });\n log(this.options.logger, \"warn\", `session ${this.options.sessionId} action ${action.name} failed: ${firstError}; retrying once`);\n this.emit({ event: \"retry\", action: action.name, args: action.arguments, error: firstError });\n\n messages.push({\n role: \"assistant\",\n content: `Called ${action.name}(${JSON.stringify(action.arguments)})`,\n });\n messages.push({\n role: \"tool\",\n name: action.name,\n content: result?.text ?? firstError,\n isError: true,\n });\n messages.push({\n role: \"user\",\n content: [\n `Action ${action.name} failed: ${firstError}`,\n \"Retry once with a different legal action (or different arguments).\",\n \"Do not repeat the same failing call. Do not call wait or see.\",\n `Current view was:\\n${waitResult.text}`,\n ].join(\"\\n\"),\n });\n\n const retry = await this.decideAction(actionTools, messages, usage);\n if (retry.name === \"wait\" || retry.name === \"see\") {\n const error = `Retry returned ${retry.name} instead of an action (after: ${firstError})`;\n this.emit({ event: \"turnFailed\", error });\n this.options.onTurnResult({\n ok: false,\n error,\n attempts: 1,\n attemptDetails,\n usage,\n });\n continue;\n }\n\n this.emit({ event: \"action\", action: retry.name, args: retry.arguments });\n result = await this.callTool(retry.name, retry.arguments);\n if (!result.isError) {\n this.options.onTurnResult({\n ok: true,\n action: retry.name,\n args: retry.arguments,\n result: result.json,\n attempts: 2,\n usage,\n });\n continue;\n }\n\n const secondError = toolErrorMessage(result);\n attemptDetails.push({ action: retry.name, args: retry.arguments, error: secondError });\n log(this.options.logger, \"warn\", `session ${this.options.sessionId} retry ${retry.name} failed: ${secondError}`);\n this.emit({ event: \"turnFailed\", action: retry.name, args: retry.arguments, error: secondError });\n this.options.onTurnResult({\n ok: false,\n error: secondError,\n attempts: 2,\n attemptDetails,\n usage,\n });\n }\n }\n}\n","export type Logger = {\n debug?: (message: string, extra?: unknown) => void;\n info?: (message: string, extra?: unknown) => void;\n warn?: (message: string, extra?: unknown) => void;\n error?: (message: string, extra?: unknown) => void;\n};\n\nexport function log(logger: Logger | undefined, level: keyof Logger, message: string, extra?: unknown): void {\n logger?.[level]?.(message, extra);\n}\n","/**\n * Wire protocol between AgentServer (SDK) and the platform. Version 1.\n */\n\nexport const PROTOCOL_VERSION = 1;\nexport const SDK_VERSION = \"1.1.0\";\n\n/** How long a disconnected session can be resumed. Shared by both sides. */\nexport const SESSION_RESUME_GRACE_MS = 30_000;\n\nexport type JsonSchema = {\n type?: string | string[];\n properties?: Record<string, JsonSchema>;\n required?: string[];\n description?: string;\n additionalProperties?: boolean;\n [key: string]: unknown;\n};\n\nexport type ToolDescriptor = {\n name: string;\n description: string;\n inputSchema: JsonSchema;\n};\n\nexport type TokenUsage = {\n promptTokens: number;\n completionTokens: number;\n};\n\nexport type AttemptRecord = {\n action: string;\n args: Record<string, unknown>;\n error: string;\n};\n\nexport type TurnReport = {\n ok: boolean;\n error?: string;\n action?: string;\n args?: Record<string, unknown>;\n result?: unknown;\n attempts?: number;\n attemptDetails?: AttemptRecord[];\n usage?: TokenUsage;\n connectionLost?: boolean;\n};\n\nexport type SessionEventName = \"action\" | \"retry\" | \"turnFailed\";\n\nexport type Envelope =\n | {\n type: \"hello\";\n platformKey: string;\n clientId: string;\n protocolVersion: number;\n sdkVersion: string;\n }\n | {\n type: \"hello_ok\";\n accountName: string;\n protocolVersion: number;\n }\n | {\n type: \"hello_err\";\n message: string;\n code?: \"AUTH\" | \"PROTOCOL\";\n }\n | {\n type: \"start_session\";\n sessionId: string;\n sessionToken: string;\n tools: ToolDescriptor[];\n instructions: string;\n goal?: string;\n name?: string;\n }\n | {\n type: \"session_started\";\n sessionId: string;\n }\n | {\n type: \"stop_session\";\n sessionId: string;\n }\n | {\n type: \"session_stopped\";\n sessionId: string;\n }\n | {\n type: \"mcp\";\n sessionId: string;\n message: unknown;\n }\n | ({\n type: \"turn_result\";\n sessionId: string;\n } & TurnReport)\n | {\n type: \"session_event\";\n sessionId: string;\n event: SessionEventName;\n action?: string;\n args?: Record<string, unknown>;\n error?: string;\n }\n | {\n type: \"ping\";\n id: string;\n }\n | {\n type: \"pong\";\n id: string;\n }\n | {\n type: \"resume_session\";\n sessionId: string;\n sessionToken: string;\n }\n | {\n type: \"session_resumed\";\n sessionId: string;\n }\n | {\n type: \"resume_err\";\n sessionId: string;\n message: string;\n }\n | {\n type: \"error\";\n message: string;\n sessionId?: string;\n };\n\nexport const DEFAULT_AGENT_INSTRUCTIONS = [\n \"You are a remote game agent. Tools are provided by the game.\",\n \"On every turn: call wait first. It blocks until the game grants your turn and then returns the current view from see.\",\n \"After wait returns, call exactly one action tool (not see, not wait).\",\n \"The view's done list is what this character has already called in this session, oldest first. Use it when choosing the next action.\",\n \"If an action tool returns an error, retry once with a corrected action. A second failure ends the turn as failed.\",\n].join(\"\\n\");\n\nexport function assertNever(value: never): never {\n throw new Error(`Unhandled protocol message: ${JSON.stringify(value)}`);\n}\n","import type { InlinePlatform, StartSessionRequest } from \"../types.js\";\nimport { createScriptedModel, type ToolCallRequest } from \"./model.js\";\nimport { loopEventToAgentEvent, SessionLoop } from \"./session-loop.js\";\n\n/**\n * In-process platform that replays a script of tool calls.\n * Same session loop as production; no network and no platform key.\n */\nexport function createTestPlatform(script: ToolCallRequest[]): InlinePlatform {\n const model = createScriptedModel(script);\n const loops = new Map<string, SessionLoop>();\n\n return {\n async startSession(request: StartSessionRequest) {\n const loop = new SessionLoop({\n sessionId: request.sessionId,\n transport: request.transport,\n instructions: request.instructions,\n ...(request.goal ? { goal: request.goal } : {}),\n model,\n ...(request.logger ? { logger: request.logger } : {}),\n onUsage: async (tokens) => {\n await request.onUsage?.(tokens);\n },\n onTurnResult: request.onTurnResult,\n onEvent: (event) => {\n request.onEvent?.(loopEventToAgentEvent(request.sessionId, event));\n },\n });\n loops.set(request.sessionId, loop);\n await loop.start();\n },\n\n async stopSession(sessionId: string) {\n const loop = loops.get(sessionId);\n if (!loop) return;\n loops.delete(sessionId);\n await loop.stop();\n },\n\n async close() {\n const ids = [...loops.keys()];\n for (const id of ids) {\n const loop = loops.get(id);\n loops.delete(id);\n await loop?.stop();\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -96,7 +96,7 @@ type AgentServerOptions = {
|
|
|
96
96
|
platformKey?: string;
|
|
97
97
|
/**
|
|
98
98
|
* WebSocket URL of the platform.
|
|
99
|
-
* Defaults to `
|
|
99
|
+
* Defaults to `wss://api.whitesandsint.com`.
|
|
100
100
|
*/
|
|
101
101
|
platformUrl?: string;
|
|
102
102
|
/**
|
|
@@ -112,6 +112,13 @@ type AgentServerOptions = {
|
|
|
112
112
|
onEvent?: EventListener;
|
|
113
113
|
/** Where the SDK writes warnings. Nothing is written when omitted. */
|
|
114
114
|
logger?: Logger;
|
|
115
|
+
/**
|
|
116
|
+
* SQLite file of actions each character has already called.
|
|
117
|
+
* Later views include that session's `done` list.
|
|
118
|
+
* Defaults to `.wsi/agent-memory.sqlite` in the current directory.
|
|
119
|
+
* Pass false to keep no action history.
|
|
120
|
+
*/
|
|
121
|
+
memoryPath?: string | false;
|
|
115
122
|
};
|
|
116
123
|
|
|
117
|
-
export type { AgentServerOptions as A, EventListener as E, InlinePlatform as I, Logger as L, StartOptions as S, TurnOptions as T, AgentHandle as a, AgentEvent as b,
|
|
124
|
+
export type { AgentServerOptions as A, EventListener as E, InlinePlatform as I, Logger as L, StartOptions as S, TurnOptions as T, AgentHandle as a, AgentEvent as b, TurnResult as c, StartSessionRequest as d };
|
|
@@ -96,7 +96,7 @@ type AgentServerOptions = {
|
|
|
96
96
|
platformKey?: string;
|
|
97
97
|
/**
|
|
98
98
|
* WebSocket URL of the platform.
|
|
99
|
-
* Defaults to `
|
|
99
|
+
* Defaults to `wss://api.whitesandsint.com`.
|
|
100
100
|
*/
|
|
101
101
|
platformUrl?: string;
|
|
102
102
|
/**
|
|
@@ -112,6 +112,13 @@ type AgentServerOptions = {
|
|
|
112
112
|
onEvent?: EventListener;
|
|
113
113
|
/** Where the SDK writes warnings. Nothing is written when omitted. */
|
|
114
114
|
logger?: Logger;
|
|
115
|
+
/**
|
|
116
|
+
* SQLite file of actions each character has already called.
|
|
117
|
+
* Later views include that session's `done` list.
|
|
118
|
+
* Defaults to `.wsi/agent-memory.sqlite` in the current directory.
|
|
119
|
+
* Pass false to keep no action history.
|
|
120
|
+
*/
|
|
121
|
+
memoryPath?: string | false;
|
|
115
122
|
};
|
|
116
123
|
|
|
117
|
-
export type { AgentServerOptions as A, EventListener as E, InlinePlatform as I, Logger as L, StartOptions as S, TurnOptions as T, AgentHandle as a, AgentEvent as b,
|
|
124
|
+
export type { AgentServerOptions as A, EventListener as E, InlinePlatform as I, Logger as L, StartOptions as S, TurnOptions as T, AgentHandle as a, AgentEvent as b, TurnResult as c, StartSessionRequest as d };
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whitesands/agent-player",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Start a remote game agent from a player class. The SDK owns the MCP server; the model stays on the platform.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/White-Sands-Interactive/WSI-Node.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/White-Sands-Interactive/WSI-Node#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/White-Sands-Interactive/WSI-Node/issues"
|
|
12
|
+
},
|
|
5
13
|
"type": "module",
|
|
6
14
|
"license": "MIT",
|
|
7
15
|
"main": "./dist/index.cjs",
|
|
@@ -34,23 +42,38 @@
|
|
|
34
42
|
"publishConfig": {
|
|
35
43
|
"access": "public"
|
|
36
44
|
},
|
|
45
|
+
"workspaces": [
|
|
46
|
+
"examples/*"
|
|
47
|
+
],
|
|
37
48
|
"scripts": {
|
|
38
|
-
"build": "tsup",
|
|
49
|
+
"build": "tsup && npm run build -w @wsi/forest-example && npm run build -w @wsi/courier-example && npm run build -w @wsi/ferry-example",
|
|
39
50
|
"docs": "typedoc",
|
|
40
|
-
"clean": "rm -rf dist docs"
|
|
51
|
+
"clean": "rm -rf dist docs",
|
|
52
|
+
"release": "tsup && node scripts/publish.mjs",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest",
|
|
55
|
+
"forest": "npm run start -w @wsi/forest-example --",
|
|
56
|
+
"courier": "npm run start -w @wsi/courier-example --",
|
|
57
|
+
"ferry": "npm run start -w @wsi/ferry-example --"
|
|
41
58
|
},
|
|
42
59
|
"engines": {
|
|
43
60
|
"node": ">=20"
|
|
44
61
|
},
|
|
45
62
|
"dependencies": {
|
|
46
63
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
64
|
+
"better-sqlite3": "^12.11.1",
|
|
47
65
|
"ws": "^8.18.1",
|
|
48
66
|
"zod": "^3.24.2",
|
|
49
67
|
"zod-to-json-schema": "^3.24.5"
|
|
50
68
|
},
|
|
51
69
|
"devDependencies": {
|
|
70
|
+
"@changesets/cli": "^2.29.2",
|
|
71
|
+
"@types/better-sqlite3": "^9.6.0",
|
|
72
|
+
"@types/node": "^22.13.10",
|
|
52
73
|
"@types/ws": "^8.18.0",
|
|
53
74
|
"tsup": "^8.4.0",
|
|
54
|
-
"typedoc": "^0.28.1"
|
|
75
|
+
"typedoc": "^0.28.1",
|
|
76
|
+
"typescript": "^5.8.2",
|
|
77
|
+
"vitest": "^3.0.9"
|
|
55
78
|
}
|
|
56
79
|
}
|