@inixiative/agent-session 0.6.5 → 0.6.7

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.
@@ -23,9 +23,11 @@ import type {
23
23
  SessionArtifact,
24
24
  SessionEvent,
25
25
  SessionEventHandler,
26
+ SessionImage,
26
27
  SessionResult,
27
28
  ToolGate,
28
29
  } from './harness-session';
30
+ import { sendableImages } from './harness-session';
29
31
 
30
32
  // Re-export types so importers can stay on one path (mirrors claude-code-session).
31
33
  export type {
@@ -153,6 +155,7 @@ interface QueuedTurn {
153
155
  onAdmission?: import('./harness-session').SessionSendOptions['onAdmission'];
154
156
  evidence: TurnState;
155
157
  message: string;
158
+ images: readonly SessionImage[];
156
159
  timeout: number;
157
160
  resolve: (result: SessionResult) => void;
158
161
  reject: (error: Error) => void;
@@ -455,6 +458,7 @@ export class CodexAppServerSession implements HarnessSession {
455
458
  if (!this._alive) throw new Error('Session ended');
456
459
 
457
460
  const timeout = opts?.timeout ?? this._defaultTimeout;
461
+ const images = sendableImages(this.transport, opts?.images);
458
462
 
459
463
  let transformed = message;
460
464
  for (const hook of this._beforeSendHooks) {
@@ -473,6 +477,7 @@ export class CodexAppServerSession implements HarnessSession {
473
477
  const turn: QueuedTurn = {
474
478
  evidence,
475
479
  message: transformed,
480
+ images,
476
481
  timeout,
477
482
  resolve,
478
483
  reject,
@@ -539,7 +544,7 @@ export class CodexAppServerSession implements HarnessSession {
539
544
  this._resultText = '';
540
545
 
541
546
  // Resolves once the native terminal and the turn/start RPC have both settled.
542
- this._sendTurn(turn.message)
547
+ this._sendTurn(turn.message, turn.images)
543
548
  .then((tokens) => {
544
549
  if (this._inflight !== turn) return;
545
550
  turn.evidence.rpcSettled = true;
@@ -1209,7 +1214,10 @@ export class CodexAppServerSession implements HarnessSession {
1209
1214
  this._rpcNotify('initialized');
1210
1215
  }
1211
1216
 
1212
- private async _sendTurn(message: string): Promise<{ input: number; output: number } | undefined> {
1217
+ private async _sendTurn(
1218
+ message: string,
1219
+ images: readonly SessionImage[],
1220
+ ): Promise<{ input: number; output: number } | undefined> {
1213
1221
  const a = this._inflight!.evidence;
1214
1222
  if (!this._threadReady) {
1215
1223
  const binding = this._externalSessionId;
@@ -1344,7 +1352,13 @@ export class CodexAppServerSession implements HarnessSession {
1344
1352
  });
1345
1353
  await this._rpcRequest('turn/start', {
1346
1354
  threadId: this._externalSessionId,
1347
- input: [{ type: 'text', text: message }],
1355
+ input: [
1356
+ { type: 'text', text: message },
1357
+ ...images.map((image) => ({
1358
+ type: 'image',
1359
+ url: `data:${image.mediaType};base64,${image.data}`,
1360
+ })),
1361
+ ],
1348
1362
  ...(this._effort ? { effort: this._effort } : {}),
1349
1363
  });
1350
1364
  await completed; // Native terminal and RPC acknowledgment are both required.
@@ -266,10 +266,47 @@ export type SessionEventHandler = (event: SessionEvent) => void;
266
266
  */
267
267
  export type BeforeSendHook = (message: string) => string | Promise<string>;
268
268
 
269
+ export const SESSION_IMAGE_MEDIA_TYPES = [
270
+ 'image/png',
271
+ 'image/jpeg',
272
+ 'image/gif',
273
+ 'image/webp',
274
+ ] as const;
275
+ export type SessionImageMediaType = (typeof SESSION_IMAGE_MEDIA_TYPES)[number];
276
+
277
+ /** An image sent with a turn's text, where the transport declares `capabilities.images`. */
278
+ export interface SessionImage {
279
+ readonly mediaType: SessionImageMediaType;
280
+ /** The image bytes, base64 (no `data:` prefix). */
281
+ readonly data: string;
282
+ }
283
+
269
284
  export interface SessionSendOptions {
270
285
  timeout?: number;
271
286
  /** Required ownership registration, not an observer. Rejection prevents the native write. */
272
287
  onAdmission?: (attempt: SessionAttempt) => void | Promise<void>;
288
+ /**
289
+ * Images sent with the message, in order, as part of the same user turn. Refused (before
290
+ * anything is written) by a transport without `capabilities.images` and for any other media type.
291
+ */
292
+ images?: readonly SessionImage[];
293
+ }
294
+
295
+ /** Images a send may write: a copy of each, or a refusal naming the first one that is not. */
296
+ export function sendableImages(
297
+ transport: TransportDescriptor | undefined,
298
+ images: readonly SessionImage[] | undefined,
299
+ ): SessionImage[] {
300
+ if (!images?.length) return [];
301
+ if (!transport?.capabilities.images)
302
+ throw new Error(`Transport "${transport?.kind ?? 'unknown'}" does not accept image input`);
303
+ return images.map((image, index) => {
304
+ if (!SESSION_IMAGE_MEDIA_TYPES.includes(image?.mediaType))
305
+ throw new Error(`Image ${index + 1}: unsupported media type ${String(image?.mediaType)}`);
306
+ if (typeof image.data !== 'string' || !image.data || /[^A-Za-z0-9+/=]/.test(image.data))
307
+ throw new Error(`Image ${index + 1}: data must be non-empty base64`);
308
+ return { mediaType: image.mediaType, data: image.data };
309
+ });
273
310
  }
274
311
 
275
312
  // ---------------------------------------------------------------------------
package/src/index.ts CHANGED
@@ -13,9 +13,19 @@ export {
13
13
  // Claude Code adapter (persistent stream-json session, subscription auth).
14
14
  export {
15
15
  CLAUDE_TEXT_ONLY_ARGS,
16
+ type ClaudeAgentDefinition,
16
17
  ClaudeCodeSession,
17
18
  type ClaudeCodeSessionConfig,
19
+ type ClaudeSessionExtension,
20
+ type ClaudeSessionHost,
18
21
  } from './claude-code-session';
22
+ export {
23
+ type ClaudeInSessionConfig,
24
+ ClaudeInSessionDecisions,
25
+ IN_SESSION_AGENTS,
26
+ IN_SESSION_SERVER,
27
+ type InSessionSetup,
28
+ } from './claude-in-session';
19
29
  export { type ClaudePrimedConfig, ClaudePrimedSessions } from './claude-primed';
20
30
  export { type ClaudeSubagentConfig, ClaudeSubagentSessions } from './claude-subagents';
21
31
  export { parseClaudeUsage } from './claude-usage';
package/src/transport.ts CHANGED
@@ -36,6 +36,8 @@ export interface TransportCapabilities {
36
36
  readonly limits: { readonly stream: boolean; readonly poll: boolean };
37
37
  /** A tool-free, text-only session can be enforced (decisions). */
38
38
  readonly textOnly: boolean;
39
+ /** send() takes images beside the text (`SessionSendOptions.images`): png, jpeg, gif, webp. */
40
+ readonly images: boolean;
39
41
  /** Primed decision host on this transport and how it returns to its primed state each cycle (see primed.ts). */
40
42
  readonly primed: 'fork' | 'none';
41
43
  readonly billing: 'subscription' | 'api';
@@ -70,6 +72,7 @@ export const TRANSPORTS: Readonly<Record<TransportKind, TransportDescriptor>> =
70
72
  usage: true,
71
73
  limits: { stream: true, poll: true },
72
74
  textOnly: true,
75
+ images: true,
73
76
  primed: 'fork',
74
77
  billing: 'subscription',
75
78
  },
@@ -80,6 +83,7 @@ export const TRANSPORTS: Readonly<Record<TransportKind, TransportDescriptor>> =
80
83
  "interruptNative() and readLimits() use the CLI's stdio control protocol (`interrupt`, `get_usage`).",
81
84
  'toolGate: true launches with `--permission-prompt-tool stdio` and registers a PreToolUse callback over the control protocol: onToolRequest() gates see every call (`pre-use`, any mode, bypass included; only a deny counts) and the asks the permission mode raises (`permission`, final).',
82
85
  'Rollback (`--resume-session-at`) exists natively but is not wired here.',
86
+ 'Images ride in the same stream-json user message as base64 `image` content blocks, before the text.',
83
87
  ],
84
88
  },
85
89
  'codex-app-server': {
@@ -97,6 +101,7 @@ export const TRANSPORTS: Readonly<Record<TransportKind, TransportDescriptor>> =
97
101
  usage: true,
98
102
  limits: { stream: true, poll: true },
99
103
  textOnly: false,
104
+ images: true,
100
105
  primed: 'fork',
101
106
  billing: 'subscription',
102
107
  },
@@ -107,6 +112,7 @@ export const TRANSPORTS: Readonly<Record<TransportKind, TransportDescriptor>> =
107
112
  'appServer.approvalPolicy/sandbox (default never / danger-full-access; `codexPermissions(mode)`) set the thread policy; command and file-change approvals for the owned turn go to onToolRequest() gates (`permission`) and are answered accept/decline. Other server requests are refused (-32601).',
108
113
  'Primed decisions (CodexPrimedSessions) host every key as a thread of one process per account and fork the primed thread per cycle.',
109
114
  'thread/rollback is refused by codex-cli 0.155 for both ephemeral and paginated threads; session fork() is not wired.',
115
+ 'Images go to turn/start as `image` inputs with a `data:` URL, after the text.',
110
116
  ],
111
117
  },
112
118
  'claude-agent-sdk': {
@@ -124,6 +130,7 @@ export const TRANSPORTS: Readonly<Record<TransportKind, TransportDescriptor>> =
124
130
  usage: true,
125
131
  limits: { stream: true, poll: false },
126
132
  textOnly: true,
133
+ images: true,
127
134
  primed: 'none',
128
135
  billing: 'subscription',
129
136
  },
@@ -147,6 +154,7 @@ export const TRANSPORTS: Readonly<Record<TransportKind, TransportDescriptor>> =
147
154
  usage: false,
148
155
  limits: none,
149
156
  textOnly: false,
157
+ images: false,
150
158
  primed: 'none',
151
159
  billing: 'subscription',
152
160
  },
@@ -169,6 +177,7 @@ export const TRANSPORTS: Readonly<Record<TransportKind, TransportDescriptor>> =
169
177
  usage: false,
170
178
  limits: none,
171
179
  textOnly: false,
180
+ images: false,
172
181
  primed: 'none',
173
182
  billing: 'api',
174
183
  },