shortcutxl 0.3.69 → 0.3.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/BINARY-INVENTORY.json +821 -813
  2. package/CHANGELOG.md +5 -0
  3. package/agent-docs/docs/providers.md +1 -1
  4. package/agent-docs/docs/rpc.md +1 -1
  5. package/agent-docs/docs/session.md +1 -1
  6. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/index.ts +6 -5
  7. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/test.ts +1 -1
  8. package/agent-docs/examples/extensions/summarize.ts +6 -3
  9. package/dist/ai/models.d.ts +1 -1
  10. package/dist/ai/models.js +4 -2
  11. package/dist/ai/providers/anthropic.js +34 -2
  12. package/dist/ai/providers/openai-responses-shared.d.ts +1 -1
  13. package/dist/ai/providers/openai-responses-shared.js +68 -5
  14. package/dist/ai/providers/openai-responses.d.ts +2 -2
  15. package/dist/ai/providers/openai-responses.js +6 -7
  16. package/dist/ai/providers/provider-error-normalization.js +14 -4
  17. package/dist/app/agent-session.d.ts +4 -1
  18. package/dist/app/agent-session.js +15 -8
  19. package/dist/app/providers/model-resolver.js +11 -7
  20. package/dist/app/providers/register-shortcut-provider.js +22 -6
  21. package/dist/app/providers/shortcut-llm-proxy-client.js +2 -3
  22. package/dist/app/session/compaction-observability.d.ts +17 -0
  23. package/dist/app/session/compaction-observability.js +83 -0
  24. package/dist/app/session/session-models.js +4 -5
  25. package/dist/app/subagents/defaults.d.ts +2 -2
  26. package/dist/app/subagents/defaults.js +6 -3
  27. package/dist/app/tools/read.js +14 -0
  28. package/dist/app/tools/task/task.js +2 -2
  29. package/dist/cli/args.js +3 -2
  30. package/dist/cli/file-processor.d.ts +2 -2
  31. package/dist/cli/file-processor.js +8 -27
  32. package/dist/cli.js +643 -193
  33. package/dist/contracts/agent-failure.d.ts +1 -0
  34. package/dist/contracts/agent-failure.js +1 -0
  35. package/dist/contracts/agent-submit.d.ts +2 -2
  36. package/dist/contracts/model-call.d.ts +1 -0
  37. package/dist/contracts/model-stream.d.ts +7 -5
  38. package/dist/contracts/model-stream.js +52 -0
  39. package/dist/contracts/wire.d.ts +3 -0
  40. package/dist/core/prompts/agent-guidelines.d.ts +1 -1
  41. package/dist/core/prompts/agent-guidelines.js +1 -1
  42. package/dist/core/run-failure.js +14 -0
  43. package/dist/core/session/session-compaction.js +1 -1
  44. package/dist/embedded-agent/ai-invoke.d.ts +1 -0
  45. package/dist/embedded-agent/ai-invoke.js +2 -0
  46. package/dist/embedded-agent/anthropic-messages-transport.js +121 -23
  47. package/dist/embedded-agent/compose.js +2 -1
  48. package/dist/embedded-agent/google-vertex-transport.js +21 -9
  49. package/dist/embedded-agent/host-tools/invoke-llm/contract.d.ts +1 -0
  50. package/dist/embedded-agent/host-tools/task/agent-definition.d.ts +2 -1
  51. package/dist/embedded-agent/host-tools/task/agent-definition.js +2 -0
  52. package/dist/embedded-agent/host-tools/task/agents/clone.js +2 -3
  53. package/dist/embedded-agent/host-tools/task/agents/document-reader.js +2 -3
  54. package/dist/embedded-agent/host-tools/task/agents/simulation.js +2 -3
  55. package/dist/embedded-agent/host-tools/task/agents/verification.js +2 -4
  56. package/dist/embedded-agent/host-tools/task/agents/workbook-reader.js +2 -3
  57. package/dist/embedded-agent/host-tools/task/contract.d.ts +1 -1
  58. package/dist/embedded-agent/host-tools/task/contract.js +5 -4
  59. package/dist/embedded-agent/host-tools/task/index.d.ts +1 -1
  60. package/dist/embedded-agent/host-tools/task/index.js +1 -0
  61. package/dist/embedded-agent/model-registry.d.ts +1 -2
  62. package/dist/embedded-agent/model-registry.js +3 -6
  63. package/dist/embedded-agent/openai-completions-transport.js +24 -12
  64. package/dist/embedded-agent/openai-responses-transport.js +25 -12
  65. package/dist/embedded-agent/provider-fast-mode.d.ts +30 -0
  66. package/dist/embedded-agent/provider-fast-mode.js +120 -0
  67. package/dist/embedded-agent/stream-errors.js +4 -3
  68. package/dist/embedded-agent/suggestions.d.ts +1 -0
  69. package/dist/embedded-agent/suggestions.js +1 -0
  70. package/dist/fast-mode.d.ts +1 -1
  71. package/dist/fast-mode.js +8 -7
  72. package/dist/main.js +5 -0
  73. package/dist/model-display.js +2 -1
  74. package/dist/model-ids.d.ts +4 -4
  75. package/dist/model-ids.js +5 -5
  76. package/dist/shared/files/file-content.d.ts +7 -0
  77. package/dist/shared/files/file-content.js +60 -0
  78. package/dist/shell/theme/theme-schema.json +1 -1
  79. package/dist/shortcut-model-catalog.d.ts +1 -1
  80. package/dist/shortcut-model-catalog.js +5 -15
  81. package/package.json +52 -53
  82. package/user-docs/dist/shortcutxl-docs.pdf +0 -0
  83. package/xll/ShortcutXL.xll +0 -0
  84. package/xll/python/Lib/site-packages/httpx-0.28.1.dist-info/RECORD +1 -1
  85. package/xll/python/Lib/site-packages/idna-3.18.dist-info/RECORD +1 -1
  86. package/xll/python/Lib/site-packages/pip-26.1.2.dist-info/RECORD +3 -3
  87. package/xll/python/Lib/site-packages/pywin32-311.dist-info/RECORD +2 -2
  88. package/xll/python/Scripts/httpx.exe +0 -0
  89. package/xll/python/Scripts/idna.exe +0 -0
  90. package/xll/python/Scripts/pip.exe +0 -0
  91. package/xll/python/Scripts/pip3.13.exe +0 -0
  92. package/xll/python/Scripts/pip3.exe +0 -0
  93. package/xll/python/Scripts/pywin32_postinstall.exe +0 -0
  94. package/xll/python/Scripts/pywin32_testall.exe +0 -0
  95. package/xll/python/vcruntime140.dll +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.70]
4
+
5
+ - **GPT-5.6 Sol default** - ShortcutXL now defaults CLI and embedded sessions to GPT-5.6 Sol and shows clearer compact model labels.
6
+ - **Path-based attachments** - Fixed CLI file attachments that reference files by path.
7
+
3
8
  ## [0.3.69]
4
9
 
5
10
  - **CLI screenshot capture** - ShortcutXL can now capture screenshots from CLI sessions, giving agents visual context when working with Excel or the desktop.
@@ -101,7 +101,7 @@ export AZURE_OPENAI_RESOURCE_NAME=your-resource
101
101
 
102
102
  # Optional
103
103
  export AZURE_OPENAI_API_VERSION=2024-02-01
104
- export AZURE_OPENAI_DEPLOYMENT_NAME_MAP=gpt-5.5-2026-04-23=my-gpt55
104
+ export AZURE_OPENAI_DEPLOYMENT_NAME_MAP=<openai-model-id>=my-openai-deployment
105
105
  ```
106
106
 
107
107
  ### Amazon Bedrock
@@ -302,7 +302,7 @@ Set the reasoning/thinking level for models that support it.
302
302
 
303
303
  Levels: `"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`
304
304
 
305
- Note: `"xhigh"` is only supported by the GPT 5.5 and Claude Opus 4.8 model families.
305
+ Note: `"xhigh"` is only supported by the OpenAI reasoning and Claude Opus 4.8 model families.
306
306
 
307
307
  Response:
308
308
 
@@ -212,7 +212,7 @@ A message in the conversation. The `message` field contains an `AgentMessage`.
212
212
  Emitted when the user switches models mid-session.
213
213
 
214
214
  ```json
215
- {"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"gpt-5.5-2026-04-23"}
215
+ {"type":"model_change","id":"d4e5f6g7","parentId":"c3d4e5f6","timestamp":"2024-12-03T14:05:00.000Z","provider":"openai","modelId":"openai-reasoning-model"}
216
216
  ```
217
217
 
218
218
  ### ThinkingLevelChangeEntry
@@ -30,6 +30,7 @@ const GITLAB_COM_URL = 'https://gitlab.com';
30
30
  const AI_GATEWAY_URL = 'https://cloud.gitlab.com';
31
31
  const ANTHROPIC_PROXY_URL = `${AI_GATEWAY_URL}/ai/v1/proxy/anthropic/`;
32
32
  const OPENAI_PROXY_URL = `${AI_GATEWAY_URL}/ai/v1/proxy/openai/v1`;
33
+ const OPENAI_REASONING_MODEL_ID = 'gpt-5.6-sol';
33
34
 
34
35
  const BUNDLED_CLIENT_ID = 'da4edff2e6ebd2bc3208611e2768bc1c1dd7be791dc5ff26ca34ca9ee44f7d4b';
35
36
  const OAUTH_SCOPES = ['.shortcut'];
@@ -91,15 +92,15 @@ export const MODELS: GitLabModel[] = [
91
92
  },
92
93
  // OpenAI (all use Responses API)
93
94
  {
94
- id: 'gpt-5.5-2026-04-23',
95
- name: 'GPT 5.5',
95
+ id: OPENAI_REASONING_MODEL_ID,
96
+ name: 'GPT 5.6 Sol',
96
97
  backend: 'openai',
97
98
  baseUrl: OPENAI_PROXY_URL,
98
99
  reasoning: true,
99
100
  input: ['text', 'image'],
100
- cost: { input: 2.5, output: 10, cacheRead: 0, cacheWrite: 0 },
101
- contextWindow: 128000,
102
- maxTokens: 16384
101
+ cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 },
102
+ contextWindow: 1000000,
103
+ maxTokens: 128000
103
104
  }
104
105
  ];
105
106
 
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Examples:
6
6
  * pnpm exec tsx test.ts # Test default (claude-sonnet-5)
7
- * pnpm exec tsx test.ts gpt-5.5-2026-04-23 # Test GPT 5.5
7
+ * pnpm exec tsx test.ts <openai-model-id> # Test an OpenAI model
8
8
  * pnpm exec tsx test.ts claude-sonnet-5 --thinking
9
9
  */
10
10
 
@@ -3,6 +3,9 @@ import type { ExtensionAPI, ExtensionCommandContext } from 'shortcutxl/app';
3
3
  import { DynamicBorder, getMarkdownTheme } from 'shortcutxl/shell';
4
4
  import { Container, Markdown, matchesKey, Text } from 'shortcutxl/tui';
5
5
 
6
+ const OPENAI_SUMMARY_MODEL_ID = 'gpt-5.6-sol';
7
+ const OPENAI_SUMMARY_MODEL_REF = `openai/${OPENAI_SUMMARY_MODEL_ID}`;
8
+
6
9
  type ContentBlock = {
7
10
  type?: string;
8
11
  text?: string;
@@ -160,14 +163,14 @@ export default function (shortcut: ExtensionAPI) {
160
163
  ctx.ui.notify('Preparing summary...', 'info');
161
164
  }
162
165
 
163
- const model = getModel('openai', 'gpt-5.5-2026-04-23');
166
+ const model = getModel('openai', OPENAI_SUMMARY_MODEL_ID);
164
167
  if (!model && ctx.hasUI) {
165
- ctx.ui.notify('Model openai/gpt-5.5-2026-04-23 not found', 'warning');
168
+ ctx.ui.notify(`Model ${OPENAI_SUMMARY_MODEL_REF} not found`, 'warning');
166
169
  }
167
170
 
168
171
  const apiKey = model ? await ctx.modelRegistry.getApiKey(model) : undefined;
169
172
  if (!apiKey && ctx.hasUI) {
170
- ctx.ui.notify('No API key for openai/gpt-5.5-2026-04-23', 'warning');
173
+ ctx.ui.notify(`No API key for ${OPENAI_SUMMARY_MODEL_REF}`, 'warning');
171
174
  }
172
175
 
173
176
  if (!model || !apiKey) {
@@ -11,7 +11,7 @@ export declare function calculateCost<TApi extends Api>(model: Model<TApi>, usag
11
11
  * Check if a model supports xhigh thinking level.
12
12
  *
13
13
  * Supported today:
14
- * - GPT-5.5 model family
14
+ * - Shortcut's current and legacy OpenAI reasoning models
15
15
  * - Anthropic Messages API Opus 4.8 models (xhigh maps to adaptive effort "max")
16
16
  */
17
17
  export declare function supportsXhigh<TApi extends Api>(model: Model<TApi>): boolean;
package/dist/ai/models.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { SHORTCUT_OPENAI_MODEL_ID } from '../model-ids.js';
1
2
  import { MODELS } from './models.generated.js';
2
3
  const modelRegistry = new Map();
3
4
  // Initialize registry from MODELS on module load
@@ -35,11 +36,12 @@ export function calculateCost(model, usage) {
35
36
  * Check if a model supports xhigh thinking level.
36
37
  *
37
38
  * Supported today:
38
- * - GPT-5.5 model family
39
+ * - Shortcut's current and legacy OpenAI reasoning models
39
40
  * - Anthropic Messages API Opus 4.8 models (xhigh maps to adaptive effort "max")
40
41
  */
41
42
  export function supportsXhigh(model) {
42
- if (model.id.includes('gpt-5.5')) {
43
+ const modelId = model.id.startsWith('openai/') ? model.id.slice('openai/'.length) : model.id;
44
+ if (modelId.includes(SHORTCUT_OPENAI_MODEL_ID) || modelId.includes('gpt-5.5')) {
43
45
  return true;
44
46
  }
45
47
  if (model.api === 'anthropic-messages') {
@@ -85,11 +85,43 @@ function convertContentBlocks(content) {
85
85
  }
86
86
  function mergeHeaders(...headerSources) {
87
87
  const merged = {};
88
+ const betaHeaderName = 'anthropic-beta';
89
+ const betaTokens = [];
90
+ const seenBetaTokens = new Set();
91
+ const addBetaTokens = (value) => {
92
+ for (const token of value.split(',')) {
93
+ const trimmed = token.trim();
94
+ if (!trimmed)
95
+ continue;
96
+ const key = trimmed.toLowerCase();
97
+ if (seenBetaTokens.has(key))
98
+ continue;
99
+ seenBetaTokens.add(key);
100
+ betaTokens.push(trimmed);
101
+ }
102
+ };
88
103
  for (const headers of headerSources) {
89
104
  if (headers) {
90
- Object.assign(merged, headers);
105
+ for (const [key, value] of Object.entries(headers)) {
106
+ if (key.toLowerCase() === betaHeaderName) {
107
+ delete merged[key];
108
+ if (typeof value === 'string') {
109
+ addBetaTokens(value);
110
+ }
111
+ else {
112
+ betaTokens.length = 0;
113
+ seenBetaTokens.clear();
114
+ merged[betaHeaderName] = value;
115
+ }
116
+ continue;
117
+ }
118
+ merged[key] = value;
119
+ }
91
120
  }
92
121
  }
122
+ if (betaTokens.length > 0) {
123
+ merged[betaHeaderName] = betaTokens.join(',');
124
+ }
93
125
  return merged;
94
126
  }
95
127
  export const streamAnthropic = (model, context, options) => {
@@ -444,7 +476,7 @@ function buildParams(model, context, options) {
444
476
  if (options?.thinkingEnabled && model.reasoning) {
445
477
  if (supportsAdaptiveThinking(model.id)) {
446
478
  // Adaptive thinking: Claude decides when and how much to think
447
- params.thinking = { type: 'adaptive' };
479
+ params.thinking = { type: 'adaptive', display: 'summarized' };
448
480
  if (options.effort) {
449
481
  params.output_config = { effort: options.effort };
450
482
  }
@@ -3,7 +3,7 @@ import type { Api, AssistantMessage, Context, Model, Tool, Usage } from '../type
3
3
  import type { AssistantMessageEventStream } from '../utils/event-stream.js';
4
4
  export interface OpenAIResponsesStreamOptions {
5
5
  serviceTier?: ResponseCreateParamsStreaming['service_tier'];
6
- applyServiceTierPricing?: (usage: Usage, serviceTier: ResponseCreateParamsStreaming['service_tier'] | undefined, model: Pick<Model<Api>, 'id'>) => void;
6
+ applyServiceTierPricing?: (usage: Usage, serviceTier: ResponseCreateParamsStreaming['service_tier'] | undefined) => void;
7
7
  }
8
8
  export interface ConvertResponsesMessagesOptions {
9
9
  includeSystemPrompt?: boolean;
@@ -1,3 +1,4 @@
1
+ import { safeModelStreamErrorMessage } from '../../contracts/model-stream.js';
1
2
  import { calculateCost } from '../models.js';
2
3
  import { parseStreamingJson } from '../utils/json-parse.js';
3
4
  import { sanitizeSurrogates } from '../utils/sanitize-unicode.js';
@@ -5,6 +6,46 @@ import { transformMessages } from './transform-messages.js';
5
6
  // =============================================================================
6
7
  // Utilities
7
8
  // =============================================================================
9
+ class OpenAIResponsesStreamError extends Error {
10
+ details;
11
+ constructor(details) {
12
+ super(safeModelStreamErrorMessage(details));
13
+ this.name = 'OpenAIResponsesStreamError';
14
+ this.details = details;
15
+ }
16
+ }
17
+ function isOpenAIResponsesInvalidRequestCode(code) {
18
+ return (code?.startsWith('invalid_') === true ||
19
+ code === 'empty_image_file' ||
20
+ code === 'failed_to_download_image' ||
21
+ code === 'image_content_policy_violation' ||
22
+ code === 'image_file_not_found' ||
23
+ code === 'image_parse_error' ||
24
+ code === 'image_too_large' ||
25
+ code === 'image_too_small' ||
26
+ code === 'unsupported_image_media_type');
27
+ }
28
+ function openAIResponsesStreamErrorDetails(error) {
29
+ const code = error?.code?.trim() || undefined;
30
+ if (code === 'rate_limit_exceeded') {
31
+ return { code, providerErrorType: 'rate_limit', retryable: true, status: 429 };
32
+ }
33
+ if (code === 'server_error' || code === 'vector_store_timeout') {
34
+ return {
35
+ ...(code ? { code } : {}),
36
+ providerErrorType: 'internal',
37
+ retryable: true,
38
+ status: 503
39
+ };
40
+ }
41
+ if (isOpenAIResponsesInvalidRequestCode(code)) {
42
+ return { code, providerErrorType: 'invalid_request', retryable: false, status: 400 };
43
+ }
44
+ return { ...(code ? { code } : {}), providerErrorType: 'internal', retryable: true, status: 503 };
45
+ }
46
+ function throwOpenAIResponsesStreamError(error) {
47
+ throw new OpenAIResponsesStreamError(openAIResponsesStreamErrorDetails(error));
48
+ }
8
49
  /** Fast deterministic hash to shorten long strings */
9
50
  function shortHash(str) {
10
51
  let h1 = 0xdeadbeef;
@@ -18,6 +59,18 @@ function shortHash(str) {
18
59
  h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
19
60
  return (h2 >>> 0).toString(36) + (h1 >>> 0).toString(36);
20
61
  }
62
+ function readNumericTokenDetail(details, keys) {
63
+ if (!details || typeof details !== 'object')
64
+ return 0;
65
+ const record = details;
66
+ for (const key of keys) {
67
+ const value = record[key];
68
+ if (typeof value === 'number' && Number.isFinite(value)) {
69
+ return value;
70
+ }
71
+ }
72
+ return 0;
73
+ }
21
74
  // =============================================================================
22
75
  // Message conversion
23
76
  // =============================================================================
@@ -383,14 +436,21 @@ export async function processResponsesStream(openaiStream, output, stream, model
383
436
  else if (event.type === 'response.completed') {
384
437
  const response = event.response;
385
438
  if (response?.usage) {
386
- const cachedTokens = response.usage.input_tokens_details?.cached_tokens || 0;
439
+ const inputDetails = response.usage.input_tokens_details;
440
+ const cachedTokens = readNumericTokenDetail(inputDetails, ['cached_tokens']);
441
+ const cacheWriteTokens = readNumericTokenDetail(inputDetails, [
442
+ 'cache_write_tokens',
443
+ 'cache_creation_tokens',
444
+ 'cache_write_input_tokens',
445
+ 'cache_creation_input_tokens'
446
+ ]);
387
447
  output.usage = {
388
448
  // OpenAI includes cached tokens in input_tokens; cost calculation
389
449
  // derives uncached input from input - cacheRead - cacheWrite.
390
450
  input: response.usage.input_tokens || 0,
391
451
  output: response.usage.output_tokens || 0,
392
452
  cacheRead: cachedTokens,
393
- cacheWrite: 0,
453
+ cacheWrite: cacheWriteTokens,
394
454
  totalTokens: response.usage.total_tokens || 0,
395
455
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
396
456
  };
@@ -398,7 +458,7 @@ export async function processResponsesStream(openaiStream, output, stream, model
398
458
  calculateCost(model, output.usage);
399
459
  if (options?.applyServiceTierPricing) {
400
460
  const serviceTier = response?.service_tier ?? options.serviceTier;
401
- options.applyServiceTierPricing(output.usage, serviceTier, model);
461
+ options.applyServiceTierPricing(output.usage, serviceTier);
402
462
  }
403
463
  // Map status to stop reason
404
464
  output.stopReason = mapStopReason(response?.status);
@@ -407,10 +467,13 @@ export async function processResponsesStream(openaiStream, output, stream, model
407
467
  }
408
468
  }
409
469
  else if (event.type === 'error') {
410
- throw new Error(`Error Code ${event.code}: ${event.message}` || 'Unknown error');
470
+ throwOpenAIResponsesStreamError({
471
+ code: event.code,
472
+ message: event.message
473
+ });
411
474
  }
412
475
  else if (event.type === 'response.failed') {
413
- throw new Error('Unknown error');
476
+ throwOpenAIResponsesStreamError(event.response.error);
414
477
  }
415
478
  }
416
479
  }
@@ -1,5 +1,5 @@
1
1
  import type { ResponseCreateParamsStreaming } from 'openai/resources/responses/responses.js';
2
- import type { Api, Model, SimpleStreamOptions, StreamFunction, StreamOptions } from '../types.js';
2
+ import type { SimpleStreamOptions, StreamFunction, StreamOptions } from '../types.js';
3
3
  export interface OpenAIResponsesOptions extends StreamOptions {
4
4
  reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
5
5
  reasoningSummary?: 'auto' | 'detailed' | 'concise' | null;
@@ -10,5 +10,5 @@ export interface OpenAIResponsesOptions extends StreamOptions {
10
10
  */
11
11
  export declare const streamOpenAIResponses: StreamFunction<'openai-responses', OpenAIResponsesOptions>;
12
12
  export declare const streamSimpleOpenAIResponses: StreamFunction<'openai-responses', SimpleStreamOptions>;
13
- export declare function getServiceTierCostMultiplier(serviceTier: ResponseCreateParamsStreaming['service_tier'] | undefined, model: Pick<Model<Api>, 'id'>): number;
13
+ export declare function getServiceTierCostMultiplier(serviceTier: ResponseCreateParamsStreaming['service_tier'] | undefined): number;
14
14
  //# sourceMappingURL=openai-responses.d.ts.map
@@ -1,5 +1,4 @@
1
1
  import OpenAI from 'openai';
2
- import { SHORTCUT_MODEL_ID } from '../../model-ids.js';
3
2
  import { getEnvApiKey } from '../env-api-keys.js';
4
3
  import { supportsXhigh } from '../models.js';
5
4
  import { AssistantMessageEventStream } from '../utils/event-stream.js';
@@ -98,9 +97,11 @@ export const streamSimpleOpenAIResponses = (model, context, options) => {
98
97
  const reasoningEffort = supportsXhigh(model)
99
98
  ? options?.reasoning
100
99
  : clampReasoning(options?.reasoning);
100
+ const serviceTier = options?.serviceTier;
101
101
  return streamOpenAIResponses(model, context, {
102
102
  ...base,
103
- reasoningEffort
103
+ reasoningEffort,
104
+ serviceTier
104
105
  });
105
106
  };
106
107
  function createClient(model, context, apiKey, optionsHeaders, fetchOptions) {
@@ -170,20 +171,18 @@ function buildParams(model, context, options) {
170
171
  }
171
172
  return params;
172
173
  }
173
- export function getServiceTierCostMultiplier(serviceTier, model) {
174
+ export function getServiceTierCostMultiplier(serviceTier) {
174
175
  switch (serviceTier) {
175
176
  case 'flex':
176
177
  return 0.5;
177
178
  case 'priority':
178
- if (model.id === SHORTCUT_MODEL_ID.Gpt55)
179
- return 2.5;
180
179
  return 2;
181
180
  default:
182
181
  return 1;
183
182
  }
184
183
  }
185
- function applyServiceTierPricing(usage, serviceTier, model) {
186
- const multiplier = getServiceTierCostMultiplier(serviceTier, model);
184
+ function applyServiceTierPricing(usage, serviceTier) {
185
+ const multiplier = getServiceTierCostMultiplier(serviceTier);
187
186
  if (multiplier === 1)
188
187
  return;
189
188
  usage.cost.input *= multiplier;
@@ -1,4 +1,4 @@
1
- import { LLMErrorMetadataHeaders, parseStreamErrorPayload, safeModelStreamErrorMessage } from '../../contracts/model-stream.js';
1
+ import { applyModelAccessDeniedErrorDetails, isShortcutXLModelStreamErrorDetails, LLMErrorMetadataHeaders, parseStreamErrorPayload, safeModelStreamErrorMessage } from '../../contracts/model-stream.js';
2
2
  import { isContextOverflowErrorMessage } from '../utils/overflow.js';
3
3
  function getErrorMessage(error) {
4
4
  return error instanceof Error ? error.message : (JSON.stringify(error) ?? String(error));
@@ -30,14 +30,24 @@ function normalizeApiV2ErrorDetails(error, rawMessage) {
30
30
  const details = parseStreamErrorPayload(payload, status) ?? (status ? { status } : undefined);
31
31
  if (!details)
32
32
  return undefined;
33
+ const accessAwareDetails = applyModelAccessDeniedErrorDetails(details, rawMessage);
33
34
  if (isContextOverflowErrorMessage(rawMessage)) {
34
- return { ...details, code: 'context_overflow' };
35
+ return { ...accessAwareDetails, code: 'context_overflow' };
35
36
  }
36
- return details;
37
+ return accessAwareDetails;
38
+ }
39
+ function streamDetailsFromError(error) {
40
+ const detailsError = error;
41
+ if (isShortcutXLModelStreamErrorDetails(detailsError.details))
42
+ return detailsError.details;
43
+ if (isShortcutXLModelStreamErrorDetails(detailsError.errorDetails))
44
+ return detailsError.errorDetails;
45
+ return undefined;
37
46
  }
38
47
  export function applyNormalizedProviderError(output, error) {
39
48
  const rawMessage = getErrorMessage(error);
40
- const details = normalizeApiV2ErrorDetails(error, rawMessage);
49
+ const details = streamDetailsFromError(error) ??
50
+ normalizeApiV2ErrorDetails(error, rawMessage);
41
51
  if (!details) {
42
52
  output.errorMessage = rawMessage;
43
53
  return { usedStructuredDetails: false };
@@ -129,7 +129,9 @@ export interface AgentSessionConfig {
129
129
  info?: (input: {
130
130
  module: string;
131
131
  event: string;
132
+ analyticsEvent?: boolean;
132
133
  data?: Record<string, unknown>;
134
+ duration?: number;
133
135
  }) => void;
134
136
  };
135
137
  }
@@ -187,6 +189,7 @@ export declare class AgentSession {
187
189
  private _summaryEmitter;
188
190
  private _toolSummaryDiagnostics;
189
191
  private _observability?;
192
+ private _nextCompactionOpId;
190
193
  private _tools;
191
194
  private _models;
192
195
  private _fastModeAllowed;
@@ -481,8 +484,8 @@ export declare class AgentSession {
481
484
  supportsXhighThinking(): boolean;
482
485
  /** True if the active model exposes any thinking/reasoning configuration. */
483
486
  supportsThinking(): boolean;
484
- /** Check compaction decision and trigger if needed. */
485
487
  private _triggerCompactionIfNeeded;
488
+ private _createCompactionObservabilityWrap;
486
489
  /** Run a manual compaction pass over the conversation, optionally with custom instructions. */
487
490
  compact(customInstructions?: string): Promise<CompactionResult>;
488
491
  /** Cancel any in-flight compaction run. */
@@ -27,6 +27,7 @@ import { expandPromptTemplate } from './resources/prompt-template-expansion.js';
27
27
  import { AUTONOMOUS_STATE_CUSTOM_TYPE, buildAutonomousRefreshPrompt, extractProgressFolder, inferAutonomousStateFromEntries } from './session/autonomous-workflow.js';
28
28
  import { BranchManager } from './session/branch-manager.js';
29
29
  import { parseBudgetCommand } from './session/budget-command.js';
30
+ import { createCompactionObservabilityWrap } from './session/compaction-observability.js';
30
31
  import { emitExtensionEvent } from './session/extension-emitter.js';
31
32
  import { ExtensionLifecycle } from './session/extension-lifecycle.js';
32
33
  import { ModelManager } from './session/model-manager.js';
@@ -114,6 +115,7 @@ export class AgentSession {
114
115
  _summaryEmitter;
115
116
  _toolSummaryDiagnostics = [];
116
117
  _observability;
118
+ _nextCompactionOpId = 0;
117
119
  // Tool registry — owns tool maps, prompt snippets, system prompt rebuild
118
120
  _tools;
119
121
  // Model manager — model switching, cycling, thinking level
@@ -1378,15 +1380,18 @@ export class AgentSession {
1378
1380
  supportsThinking() {
1379
1381
  return supportsThinking(this.model);
1380
1382
  }
1381
- // =========================================================================
1382
- // Compaction (delegated to SessionCompaction)
1383
- // =========================================================================
1384
- // =========================================================================
1385
1383
  // Compaction (delegated to SessionCompaction + bridge factories)
1386
- // =========================================================================
1387
- /** Check compaction decision and trigger if needed. */
1388
1384
  async _triggerCompactionIfNeeded(msg, skipAbortedCheck, opts) {
1389
- return triggerCompactionIfNeeded(this._compactionTriggerDeps, msg, skipAbortedCheck, opts);
1385
+ return triggerCompactionIfNeeded(this._compactionTriggerDeps, msg, skipAbortedCheck, {
1386
+ ...opts,
1387
+ heartbeatWrap: this._createCompactionObservabilityWrap()
1388
+ });
1389
+ }
1390
+ _createCompactionObservabilityWrap() {
1391
+ return createCompactionObservabilityWrap({
1392
+ observability: this._observability,
1393
+ nextOpId: () => `compaction_${++this._nextCompactionOpId}`
1394
+ });
1390
1395
  }
1391
1396
  /** Run a manual compaction pass over the conversation, optionally with custom instructions. */
1392
1397
  async compact(customInstructions) {
@@ -1398,7 +1403,9 @@ export class AgentSession {
1398
1403
  const apiKey = await this._modelRegistry.getApiKey(this.model);
1399
1404
  if (!apiKey)
1400
1405
  throw new Error(`No API key for ${this.model.provider}`);
1401
- return await this._compaction.runManual(this.model, apiKey, this.sessionManager.getBranch(), customInstructions, this._compactionActions, { userMessageId: getActiveUserMessageId(this.messages) });
1406
+ const compact = this._compaction.runManual(this.model, apiKey, this.sessionManager.getBranch(), customInstructions, this._compactionActions, { userMessageId: getActiveUserMessageId(this.messages) });
1407
+ const wrap = this._createCompactionObservabilityWrap();
1408
+ return await (wrap ? wrap(compact, 'compaction', { label: 'Compacting context…' }) : compact);
1402
1409
  }
1403
1410
  finally {
1404
1411
  this._reconnectToAgent();
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import chalk from 'chalk';
5
5
  import { DEFAULT_THINKING_LEVEL, isValidThinkingLevel } from '../../core/defaults.js';
6
- import { SHORTCUT_MODEL_ID, SHORTCUT_PROVIDER_ID } from '../../model-ids.js';
6
+ import { SHORTCUT_MODEL_ID, SHORTCUT_OPENAI_MODEL_ID, SHORTCUT_PROVIDER_ID } from '../../model-ids.js';
7
7
  export function formatModelRef(model) {
8
8
  return `${model.provider}/${model.id}`;
9
9
  }
@@ -36,13 +36,13 @@ export function resolveExactModelRef(availableModels, requested) {
36
36
  }
37
37
  /** Default model IDs for each provider with a preferred initial model. */
38
38
  export const defaultModelPerProvider = {
39
- [SHORTCUT_PROVIDER_ID]: SHORTCUT_MODEL_ID.ClaudeFable5,
39
+ [SHORTCUT_PROVIDER_ID]: SHORTCUT_OPENAI_MODEL_ID,
40
40
  anthropic: SHORTCUT_MODEL_ID.ClaudeOpus48,
41
- openai: 'gpt-5.5-2026-04-23',
42
- 'azure-openai-responses': 'gpt-5.5-2026-04-23',
41
+ openai: SHORTCUT_OPENAI_MODEL_ID,
42
+ 'azure-openai-responses': SHORTCUT_OPENAI_MODEL_ID,
43
43
  google: 'gemini-2.5-pro',
44
44
  'google-vertex': 'gemini-3-pro-preview',
45
- openrouter: 'openai/gpt-5.5-2026-04-23',
45
+ openrouter: `openai/${SHORTCUT_OPENAI_MODEL_ID}`,
46
46
  'vercel-ai-gateway': 'anthropic/claude-opus-4-8',
47
47
  xai: 'grok-4-fast-non-reasoning',
48
48
  groq: 'openai/gpt-oss-120b',
@@ -56,7 +56,11 @@ export const defaultModelPerProvider = {
56
56
  };
57
57
  function getDefaultModelCandidatesForProvider(provider) {
58
58
  if (provider === SHORTCUT_PROVIDER_ID) {
59
- return [SHORTCUT_MODEL_ID.ClaudeFable5, SHORTCUT_MODEL_ID.ClaudeOpus48];
59
+ return [
60
+ SHORTCUT_OPENAI_MODEL_ID,
61
+ SHORTCUT_MODEL_ID.ClaudeFable5,
62
+ SHORTCUT_MODEL_ID.ClaudeOpus48
63
+ ];
60
64
  }
61
65
  return [defaultModelPerProvider[provider]].filter((modelId) => !!modelId);
62
66
  }
@@ -271,7 +275,7 @@ export function resolveCliModel(options) {
271
275
  }
272
276
  // If we inferred a provider from the slash but found no match within that provider,
273
277
  // fall back to matching the full input as a raw model id across all models.
274
- // This handles OpenRouter-style IDs like "openai/gpt-5.5-2026-04-23:extended" where "openai"
278
+ // This handles OpenRouter-style IDs like "openai/<model>:extended" where "openai"
275
279
  // looks like a provider but the full string is actually a model id on openrouter.
276
280
  if (inferredProvider) {
277
281
  const lower = cliModel.toLowerCase();
@@ -14,6 +14,7 @@ import { streamSimpleOpenAICompletions } from '../../ai/providers/openai-complet
14
14
  import { streamSimpleOpenAIResponses } from '../../ai/providers/openai-responses.js';
15
15
  import { NPM_PACKAGE_NAME, VERSION } from '../../config.js';
16
16
  import { gatewayBaseUrlForTransport } from '../../embedded-agent/gateway-base-url.js';
17
+ import { OPENAI_PRIORITY_SERVICE_TIER, shortcutProviderFastModeForContext, shortcutSpeedFromOptions, withProviderFastModeHeaders, withProviderFastModePayloadOverride } from '../../embedded-agent/provider-fast-mode.js';
17
18
  import { SHORTCUT_PROVIDER_ID } from '../../model-ids.js';
18
19
  import { createShortcutModelDefinitions } from '../../shortcut-model-catalog.js';
19
20
  import { shortcutOAuth } from '../auth/shortcut-oauth.js';
@@ -72,11 +73,15 @@ function createShortcutApiV2AttributionHeaders(options) {
72
73
  if (attribution?.fileAttachmentIds?.length) {
73
74
  headers['X-File-Attachment-Ids'] = attribution.fileAttachmentIds.join(',');
74
75
  }
75
- if (attribution?.speed === 'fast' || attribution?.speed === 'standard') {
76
- headers['X-Shortcut-Speed'] = attribution.speed;
77
- }
78
76
  return headers;
79
77
  }
78
+ function providerFastModeKindForTransport(transport) {
79
+ if (transport === 'anthropic-messages')
80
+ return 'anthropic';
81
+ if (transport === 'openai-completions' || transport === 'openai-responses')
82
+ return 'openai';
83
+ return undefined;
84
+ }
80
85
  /**
81
86
  * Dispatch a Shortcut model to its provider-native stream function, routed at
82
87
  * the api-v2 gateway with Shortcut auth. `options.apiKey` is the Shortcut
@@ -110,10 +115,21 @@ export function createShortcutApiV2Dispatcher(getExtraHeaders) {
110
115
  api: transport,
111
116
  baseUrl: gatewayBaseUrlForTransport(model.baseUrl, transport)
112
117
  };
113
- return streamFn(nativeModel, context, {
118
+ const providerFastModeKind = providerFastModeKindForTransport(transport);
119
+ const fastMode = providerFastModeKind
120
+ ? shortcutProviderFastModeForContext({
121
+ context,
122
+ modelId: model.id,
123
+ provider: providerFastModeKind,
124
+ speed: shortcutSpeedFromOptions(options)
125
+ })
126
+ : undefined;
127
+ const streamOptions = withProviderFastModePayloadOverride({
114
128
  ...options,
115
- headers: { ...authHeaders, ...options?.headers }
116
- });
129
+ headers: withProviderFastModeHeaders({ ...authHeaders, ...options?.headers }, fastMode),
130
+ ...(fastMode === 'openai' ? { serviceTier: OPENAI_PRIORITY_SERVICE_TIER } : {})
131
+ }, fastMode);
132
+ return streamFn(nativeModel, context, streamOptions);
117
133
  };
118
134
  }
119
135
  /** Register the Shortcut provider, routing all models through the api-v2 gateway. */
@@ -118,8 +118,6 @@ function createShortcutLlmProxyHeaders(request, attribution) {
118
118
  headers['X-Turn-Id'] = attribution.userMessageId;
119
119
  if (request.llmOperation)
120
120
  headers['X-LLM-Operation'] = request.llmOperation;
121
- if (request.speed)
122
- headers['X-Shortcut-Speed'] = request.speed;
123
121
  return Object.keys(headers).length > 0 ? headers : undefined;
124
122
  }
125
123
  /**
@@ -148,7 +146,8 @@ export function createShortcutLlmProxyClient(options) {
148
146
  apiKey,
149
147
  maxTokens: request.max_tokens ?? request.max_output_tokens,
150
148
  signal: callOptions?.signal,
151
- headers
149
+ headers,
150
+ ...(request.speed !== undefined ? { speed: request.speed } : {})
152
151
  });
153
152
  const text = await collectAssistantText(stream);
154
153
  if (!text.trim()) {
@@ -0,0 +1,17 @@
1
+ import type { HeartbeatWrapFn } from '../../core/agent-engine.js';
2
+ export interface CompactionObservability {
3
+ info?: (input: {
4
+ module: string;
5
+ event: string;
6
+ analyticsEvent?: boolean;
7
+ data?: Record<string, unknown>;
8
+ duration?: number;
9
+ }) => void;
10
+ }
11
+ export interface CreateCompactionObservabilityWrapOptions {
12
+ nextOpId: () => string;
13
+ now?: () => number;
14
+ observability?: CompactionObservability;
15
+ }
16
+ export declare function createCompactionObservabilityWrap(options: CreateCompactionObservabilityWrapOptions): HeartbeatWrapFn | undefined;
17
+ //# sourceMappingURL=compaction-observability.d.ts.map