@snack-kit/porygon 0.2.0 → 0.4.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/dist/index.d.cts CHANGED
@@ -128,7 +128,7 @@ interface AgentErrorMessage extends BaseAgentMessage {
128
128
  * 适配器能力声明
129
129
  */
130
130
  interface AdapterCapabilities {
131
- features: Set<"streaming" | "session-resume" | "system-prompt" | "tool-restriction" | "mcp" | "subagents" | "worktree" | "budget-limit" | "serve-mode">;
131
+ features: Set<"streaming" | "session-resume" | "system-prompt" | "tool-restriction" | "mcp" | "subagents" | "worktree" | "budget-limit" | "serve-mode" | "interactive-session">;
132
132
  /**
133
133
  * 流式输出模式:
134
134
  * - 'delta': 后端原生产生增量 stream_chunk 事件(如 OpenCode)
@@ -209,6 +209,38 @@ interface InterceptorContext {
209
209
  */
210
210
  type InterceptorFn = (text: string, context: InterceptorContext) => string | boolean | undefined | Promise<string | boolean | undefined>;
211
211
 
212
+ /**
213
+ * 便捷的多轮对话封装。
214
+ *
215
+ * 底层仍是 per-turn spawn + --resume,但对调用方透明:
216
+ * ```ts
217
+ * const session = porygon.session({ systemPrompt: '...' });
218
+ * for await (const msg of session.send('Hello')) { ... }
219
+ * for await (const msg of session.send('继续')) { ... } // 自动 resume
220
+ * session.close();
221
+ * ```
222
+ */
223
+ declare class InteractiveSession {
224
+ readonly initialSessionId: string;
225
+ private resolvedSessionId?;
226
+ private adapter;
227
+ private baseRequest;
228
+ private firstSent;
229
+ private closed;
230
+ constructor(initialSessionId: string, adapter: IAgentAdapter, baseRequest: Omit<PromptRequest, "prompt">);
231
+ /** 当前生效的 sessionId(首次 send 后反映 CLI 返回的真实 ID) */
232
+ get sessionId(): string;
233
+ /** 会话是否仍然活跃 */
234
+ get isActive(): boolean;
235
+ /**
236
+ * 发送一条消息,返回流式响应。
237
+ * 首次调用使用 initialSessionId,后续自动附加 resume。
238
+ */
239
+ send(prompt: string): AsyncGenerator<AgentMessage>;
240
+ /** 关闭会话(仅清理内部状态,无进程需要释放) */
241
+ close(): void;
242
+ }
243
+
212
244
  /**
213
245
  * 健康检查结果(扁平化结构)
214
246
  */
@@ -270,6 +302,11 @@ declare class Porygon extends EventEmitter {
270
302
  * @returns 最终结果文本
271
303
  */
272
304
  run(request: PromptRequest): Promise<string>;
305
+ /**
306
+ * 创建交互式多轮对话会话。
307
+ * 自动管理 sessionId 和 resume,对调用方透明。
308
+ */
309
+ session(options?: Omit<PromptRequest, "prompt">): InteractiveSession;
273
310
  /**
274
311
  * 注册拦截器
275
312
  * @param direction 拦截方向
@@ -458,6 +495,8 @@ interface SpawnOptions {
458
495
  cwd?: string;
459
496
  env?: Record<string, string>;
460
497
  timeoutMs?: number;
498
+ /** 写入 stdin 后自动关闭的数据 */
499
+ stdinData?: string;
461
500
  }
462
501
  /**
463
502
  * 进程执行结果
@@ -681,6 +720,11 @@ declare class ClaudeAdapter extends AbstractAgentAdapter {
681
720
  * @param noProxy 不走代理的地址列表
682
721
  */
683
722
  private applyProxyEnv;
723
+ /**
724
+ * 构建通过 stdin 传递给 Claude CLI 的数据。
725
+ * 使用 stdin 而非 CLI 参数传递 prompt,避免超长参数导致的 403 错误。
726
+ */
727
+ private buildStdinData;
684
728
  private buildArgs;
685
729
  }
686
730
 
@@ -765,4 +809,4 @@ declare class OpenCodeAdapter extends AbstractAgentAdapter {
765
809
  private getConfigPath;
766
810
  }
767
811
 
768
- export { AbstractAgentAdapter, type AdapterCapabilities, AdapterIncompatibleError, AdapterNotAvailableError, AdapterNotFoundError, type AgentAssistantMessage, type AgentErrorMessage, AgentExecutionError, type AgentMessage, type AgentResultMessage, type AgentStreamChunkMessage, type AgentSystemMessage, AgentTimeoutError, type AgentToolUseMessage, type BackendConfig, ClaudeAdapter, type CompatibilityResult, ConfigValidationError, type GuardAction, type GuardOptions, type HealthCheckResult, type IAgentAdapter, type InterceptorContext, type InterceptorDirection, type InterceptorFn, InterceptorRejectedError, type McpServerConfig, type ModelInfo, OpenCodeAdapter, Porygon, type PorygonConfig, PorygonError, type PorygonEvents, type PromptRequest, type ProxyConfig, type SessionInfo, type SessionListOptions, SessionNotFoundError, createInputGuard, createOutputGuard, createPorygon };
812
+ export { AbstractAgentAdapter, type AdapterCapabilities, AdapterIncompatibleError, AdapterNotAvailableError, AdapterNotFoundError, type AgentAssistantMessage, type AgentErrorMessage, AgentExecutionError, type AgentMessage, type AgentResultMessage, type AgentStreamChunkMessage, type AgentSystemMessage, AgentTimeoutError, type AgentToolUseMessage, type BackendConfig, ClaudeAdapter, type CompatibilityResult, ConfigValidationError, type GuardAction, type GuardOptions, type HealthCheckResult, type IAgentAdapter, InteractiveSession, type InterceptorContext, type InterceptorDirection, type InterceptorFn, InterceptorRejectedError, type McpServerConfig, type ModelInfo, OpenCodeAdapter, Porygon, type PorygonConfig, PorygonError, type PorygonEvents, type PromptRequest, type ProxyConfig, type SessionInfo, type SessionListOptions, SessionNotFoundError, createInputGuard, createOutputGuard, createPorygon };
package/dist/index.d.ts CHANGED
@@ -128,7 +128,7 @@ interface AgentErrorMessage extends BaseAgentMessage {
128
128
  * 适配器能力声明
129
129
  */
130
130
  interface AdapterCapabilities {
131
- features: Set<"streaming" | "session-resume" | "system-prompt" | "tool-restriction" | "mcp" | "subagents" | "worktree" | "budget-limit" | "serve-mode">;
131
+ features: Set<"streaming" | "session-resume" | "system-prompt" | "tool-restriction" | "mcp" | "subagents" | "worktree" | "budget-limit" | "serve-mode" | "interactive-session">;
132
132
  /**
133
133
  * 流式输出模式:
134
134
  * - 'delta': 后端原生产生增量 stream_chunk 事件(如 OpenCode)
@@ -209,6 +209,38 @@ interface InterceptorContext {
209
209
  */
210
210
  type InterceptorFn = (text: string, context: InterceptorContext) => string | boolean | undefined | Promise<string | boolean | undefined>;
211
211
 
212
+ /**
213
+ * 便捷的多轮对话封装。
214
+ *
215
+ * 底层仍是 per-turn spawn + --resume,但对调用方透明:
216
+ * ```ts
217
+ * const session = porygon.session({ systemPrompt: '...' });
218
+ * for await (const msg of session.send('Hello')) { ... }
219
+ * for await (const msg of session.send('继续')) { ... } // 自动 resume
220
+ * session.close();
221
+ * ```
222
+ */
223
+ declare class InteractiveSession {
224
+ readonly initialSessionId: string;
225
+ private resolvedSessionId?;
226
+ private adapter;
227
+ private baseRequest;
228
+ private firstSent;
229
+ private closed;
230
+ constructor(initialSessionId: string, adapter: IAgentAdapter, baseRequest: Omit<PromptRequest, "prompt">);
231
+ /** 当前生效的 sessionId(首次 send 后反映 CLI 返回的真实 ID) */
232
+ get sessionId(): string;
233
+ /** 会话是否仍然活跃 */
234
+ get isActive(): boolean;
235
+ /**
236
+ * 发送一条消息,返回流式响应。
237
+ * 首次调用使用 initialSessionId,后续自动附加 resume。
238
+ */
239
+ send(prompt: string): AsyncGenerator<AgentMessage>;
240
+ /** 关闭会话(仅清理内部状态,无进程需要释放) */
241
+ close(): void;
242
+ }
243
+
212
244
  /**
213
245
  * 健康检查结果(扁平化结构)
214
246
  */
@@ -270,6 +302,11 @@ declare class Porygon extends EventEmitter {
270
302
  * @returns 最终结果文本
271
303
  */
272
304
  run(request: PromptRequest): Promise<string>;
305
+ /**
306
+ * 创建交互式多轮对话会话。
307
+ * 自动管理 sessionId 和 resume,对调用方透明。
308
+ */
309
+ session(options?: Omit<PromptRequest, "prompt">): InteractiveSession;
273
310
  /**
274
311
  * 注册拦截器
275
312
  * @param direction 拦截方向
@@ -458,6 +495,8 @@ interface SpawnOptions {
458
495
  cwd?: string;
459
496
  env?: Record<string, string>;
460
497
  timeoutMs?: number;
498
+ /** 写入 stdin 后自动关闭的数据 */
499
+ stdinData?: string;
461
500
  }
462
501
  /**
463
502
  * 进程执行结果
@@ -681,6 +720,11 @@ declare class ClaudeAdapter extends AbstractAgentAdapter {
681
720
  * @param noProxy 不走代理的地址列表
682
721
  */
683
722
  private applyProxyEnv;
723
+ /**
724
+ * 构建通过 stdin 传递给 Claude CLI 的数据。
725
+ * 使用 stdin 而非 CLI 参数传递 prompt,避免超长参数导致的 403 错误。
726
+ */
727
+ private buildStdinData;
684
728
  private buildArgs;
685
729
  }
686
730
 
@@ -765,4 +809,4 @@ declare class OpenCodeAdapter extends AbstractAgentAdapter {
765
809
  private getConfigPath;
766
810
  }
767
811
 
768
- export { AbstractAgentAdapter, type AdapterCapabilities, AdapterIncompatibleError, AdapterNotAvailableError, AdapterNotFoundError, type AgentAssistantMessage, type AgentErrorMessage, AgentExecutionError, type AgentMessage, type AgentResultMessage, type AgentStreamChunkMessage, type AgentSystemMessage, AgentTimeoutError, type AgentToolUseMessage, type BackendConfig, ClaudeAdapter, type CompatibilityResult, ConfigValidationError, type GuardAction, type GuardOptions, type HealthCheckResult, type IAgentAdapter, type InterceptorContext, type InterceptorDirection, type InterceptorFn, InterceptorRejectedError, type McpServerConfig, type ModelInfo, OpenCodeAdapter, Porygon, type PorygonConfig, PorygonError, type PorygonEvents, type PromptRequest, type ProxyConfig, type SessionInfo, type SessionListOptions, SessionNotFoundError, createInputGuard, createOutputGuard, createPorygon };
812
+ export { AbstractAgentAdapter, type AdapterCapabilities, AdapterIncompatibleError, AdapterNotAvailableError, AdapterNotFoundError, type AgentAssistantMessage, type AgentErrorMessage, AgentExecutionError, type AgentMessage, type AgentResultMessage, type AgentStreamChunkMessage, type AgentSystemMessage, AgentTimeoutError, type AgentToolUseMessage, type BackendConfig, ClaudeAdapter, type CompatibilityResult, ConfigValidationError, type GuardAction, type GuardOptions, type HealthCheckResult, type IAgentAdapter, InteractiveSession, type InterceptorContext, type InterceptorDirection, type InterceptorFn, InterceptorRejectedError, type McpServerConfig, type ModelInfo, OpenCodeAdapter, Porygon, type PorygonConfig, PorygonError, type PorygonEvents, type PromptRequest, type ProxyConfig, type SessionInfo, type SessionListOptions, SessionNotFoundError, createInputGuard, createOutputGuard, createPorygon };
package/dist/index.js CHANGED
@@ -309,12 +309,18 @@ var EphemeralProcess = class {
309
309
  throw new Error("Process aborted before start");
310
310
  }
311
311
  this.aborted = false;
312
+ const useStdin = options.stdinData !== void 0;
312
313
  const child = spawn(options.command, options.args, {
313
314
  cwd: options.cwd,
314
315
  env: options.env ?? void 0,
315
- stdio: ["ignore", "pipe", "pipe"]
316
+ stdio: [useStdin ? "pipe" : "ignore", "pipe", "pipe"]
316
317
  });
317
318
  this.childProcess = child;
319
+ if (useStdin && child.stdin) {
320
+ child.stdin.write(options.stdinData, () => {
321
+ child.stdin.end();
322
+ });
323
+ }
318
324
  let timeoutTimer;
319
325
  if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
320
326
  timeoutTimer = setTimeout(() => {
@@ -811,14 +817,15 @@ function mapClaudeEvent(event, sessionId) {
811
817
  return [];
812
818
  }
813
819
  if (isResultEvent(event)) {
820
+ const usage = event.usage;
814
821
  return [{
815
822
  ...baseFields,
816
823
  type: "result",
817
824
  text: event.result,
818
- costUsd: event.cost_usd,
825
+ costUsd: event.total_cost_usd ?? event.cost_usd,
819
826
  durationMs: event.duration_ms,
820
- inputTokens: event.input_tokens,
821
- outputTokens: event.output_tokens
827
+ inputTokens: usage?.input_tokens ?? event.input_tokens,
828
+ outputTokens: usage?.output_tokens ?? event.output_tokens
822
829
  }];
823
830
  }
824
831
  return [];
@@ -926,7 +933,8 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
926
933
  "tool-restriction",
927
934
  "mcp",
928
935
  "subagents",
929
- "worktree"
936
+ "worktree",
937
+ "interactive-session"
930
938
  ]),
931
939
  streamingMode: "chunked",
932
940
  outputFormats: ["text", "json", "stream-json"],
@@ -959,12 +967,14 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
959
967
  if (v) cleanEnv[k] = v;
960
968
  }
961
969
  }
970
+ const stdinData = this.buildStdinData(request);
962
971
  const streamOptions = {
963
972
  command: this.cliCommand,
964
973
  args,
965
974
  ...cwd ? { cwd } : {},
966
975
  env: cleanEnv,
967
- timeoutMs: request.timeoutMs
976
+ timeoutMs: request.timeoutMs,
977
+ stdinData
968
978
  };
969
979
  const cmdStr = [this.cliCommand, ...args.map((a) => /[\s"']/.test(a) ? JSON.stringify(a) : a)].join(" ");
970
980
  const debugCmd = cwd ? `cd ${JSON.stringify(cwd)} && ${cmdStr}` : cmdStr;
@@ -1097,10 +1107,16 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
1097
1107
  env["NO_PROXY"] = noProxy;
1098
1108
  }
1099
1109
  }
1110
+ /**
1111
+ * 构建通过 stdin 传递给 Claude CLI 的数据。
1112
+ * 使用 stdin 而非 CLI 参数传递 prompt,避免超长参数导致的 403 错误。
1113
+ */
1114
+ buildStdinData(request) {
1115
+ return request.prompt;
1116
+ }
1100
1117
  buildArgs(request) {
1101
1118
  const args = [
1102
- "-p",
1103
- request.prompt,
1119
+ "--print",
1104
1120
  "--output-format",
1105
1121
  "stream-json",
1106
1122
  "--verbose"
@@ -1712,6 +1728,54 @@ var OpenCodeAdapter = class extends AbstractAgentAdapter {
1712
1728
  }
1713
1729
  };
1714
1730
 
1731
+ // src/session/interactive-session.ts
1732
+ var InteractiveSession = class {
1733
+ initialSessionId;
1734
+ resolvedSessionId;
1735
+ adapter;
1736
+ baseRequest;
1737
+ firstSent = false;
1738
+ closed = false;
1739
+ constructor(initialSessionId, adapter, baseRequest) {
1740
+ this.initialSessionId = initialSessionId;
1741
+ this.adapter = adapter;
1742
+ this.baseRequest = baseRequest;
1743
+ }
1744
+ /** 当前生效的 sessionId(首次 send 后反映 CLI 返回的真实 ID) */
1745
+ get sessionId() {
1746
+ return this.resolvedSessionId ?? this.initialSessionId;
1747
+ }
1748
+ /** 会话是否仍然活跃 */
1749
+ get isActive() {
1750
+ return !this.closed;
1751
+ }
1752
+ /**
1753
+ * 发送一条消息,返回流式响应。
1754
+ * 首次调用使用 initialSessionId,后续自动附加 resume。
1755
+ */
1756
+ async *send(prompt) {
1757
+ if (this.closed) {
1758
+ throw new SessionNotFoundError(this.sessionId);
1759
+ }
1760
+ const request = {
1761
+ ...this.baseRequest,
1762
+ prompt,
1763
+ ...this.firstSent ? { resume: this.sessionId } : {}
1764
+ };
1765
+ for await (const msg of this.adapter.query(request)) {
1766
+ if (!this.firstSent && msg.sessionId) {
1767
+ this.resolvedSessionId = msg.sessionId;
1768
+ }
1769
+ yield msg;
1770
+ }
1771
+ this.firstSent = true;
1772
+ }
1773
+ /** 关闭会话(仅清理内部状态,无进程需要释放) */
1774
+ close() {
1775
+ this.closed = true;
1776
+ }
1777
+ };
1778
+
1715
1779
  // src/porygon.ts
1716
1780
  var Porygon = class extends EventEmitter2 {
1717
1781
  config;
@@ -1811,6 +1875,21 @@ var Porygon = class extends EventEmitter2 {
1811
1875
  }
1812
1876
  return resultText;
1813
1877
  }
1878
+ /**
1879
+ * 创建交互式多轮对话会话。
1880
+ * 自动管理 sessionId 和 resume,对调用方透明。
1881
+ */
1882
+ session(options) {
1883
+ const backend = options?.backend ?? this.config.defaultBackend ?? "claude";
1884
+ const adapter = this.getAdapter(backend);
1885
+ const merged = this.mergeRequest({ ...options, prompt: "" }, backend);
1886
+ const { prompt: _, ...baseRequest } = merged;
1887
+ return new InteractiveSession(
1888
+ crypto.randomUUID(),
1889
+ adapter,
1890
+ baseRequest
1891
+ );
1892
+ }
1814
1893
  /**
1815
1894
  * 注册拦截器
1816
1895
  * @param direction 拦截方向
@@ -2058,6 +2137,7 @@ export {
2058
2137
  AgentTimeoutError,
2059
2138
  ClaudeAdapter,
2060
2139
  ConfigValidationError,
2140
+ InteractiveSession,
2061
2141
  InterceptorRejectedError,
2062
2142
  OpenCodeAdapter,
2063
2143
  Porygon,