@@ -48,15 +48,35 @@ export type ResolvedAuth =
4848// ---------------------------------------------------------------------------
4949
5050const DEFAULT_TIMEOUT_MS = 15_000 ;
51+ const MAX_ERROR_MESSAGE_LENGTH = 300 ;
52+ const SERVICE_ACCOUNT_TOKEN_RE = / o p s _ [ A - Z a - z 0 - 9 _ - ] + / g;
5153type OnePasswordSdkModule = typeof import ( "@1password/sdk" ) ;
5254
55+ const formatCause = ( cause : unknown ) : string => {
56+ const maybeMessage = ( cause as { readonly message ?: unknown } | null | undefined ) ?. message ;
57+ const raw =
58+ typeof maybeMessage === "string" && maybeMessage . length > 0 ? maybeMessage : String ( cause ) ;
59+ return raw
60+ . replace ( SERVICE_ACCOUNT_TOKEN_RE , "[redacted 1Password token]" )
61+ . replace ( / \s + / g, " " )
62+ . trim ( ) ;
63+ } ;
64+
65+ const messageWithCause = ( prefix : string , cause : unknown ) : string => {
66+ const causeMessage = formatCause ( cause ) ;
67+ const message = causeMessage ? `${ prefix } : ${ causeMessage } ` : prefix ;
68+ return message . length > MAX_ERROR_MESSAGE_LENGTH
69+ ? `${ message . slice ( 0 , MAX_ERROR_MESSAGE_LENGTH - 3 ) } ...`
70+ : message ;
71+ } ;
72+
5373const loadOnePasswordSdk = ( ) : Effect . Effect < OnePasswordSdkModule , OnePasswordError > =>
5474 Effect . tryPromise ( {
5575 try : ( ) => import ( "@1password/sdk" ) ,
56- catch : ( ) =>
76+ catch : ( cause ) =>
5777 new OnePasswordError ( {
5878 operation : "sdk module load" ,
59- message : "Failed to load 1Password SDK" ,
79+ message : messageWithCause ( "Failed to load 1Password SDK" , cause ) ,
6080 } ) ,
6181 } ) ;
6282
@@ -99,20 +119,20 @@ export const makeNativeSdkService = (
99119 integrationName : "Executor" ,
100120 integrationVersion : "0.0.0" ,
101121 } ) ,
102- catch : ( ) =>
122+ catch : ( cause ) =>
103123 new OnePasswordError ( {
104124 operation : "client setup" ,
105- message : "Failed to set up 1Password client" ,
125+ message : messageWithCause ( "Failed to set up 1Password client" , cause ) ,
106126 } ) ,
107127 } ) . pipe ( timeoutWithOnePasswordError ( "client setup" , timeoutMs ) ) ;
108128
109129 const wrap = < A > ( fn : ( ) => Promise < A > , operation : string ) : Effect . Effect < A , OnePasswordError > =>
110130 Effect . tryPromise ( {
111131 try : fn ,
112- catch : ( ) =>
132+ catch : ( cause ) =>
113133 new OnePasswordError ( {
114134 operation,
115- message : `1Password SDK ${ operation } failed` ,
135+ message : messageWithCause ( `1Password SDK ${ operation } failed` , cause ) ,
116136 } ) ,
117137 } ) . pipe (
118138 timeoutWithOnePasswordError ( operation , timeoutMs ) ,
@@ -158,10 +178,10 @@ export const makeCliService = (
158178 }
159179 return fn ( ) ;
160180 } ,
161- catch : ( ) =>
181+ catch : ( cause ) =>
162182 new OnePasswordError ( {
163183 operation,
164- message : `1Password CLI ${ operation } failed` ,
184+ message : messageWithCause ( `1Password CLI ${ operation } failed` , cause ) ,
165185 } ) ,
166186 } ) ,
167187 )
@@ -186,6 +206,23 @@ export const makeCliService = (
186206// Smart factory — tries CLI first (avoids IPC hang), falls back to SDK
187207// ---------------------------------------------------------------------------
188208
209+ const isCliUnavailable = ( error : OnePasswordError ) : boolean => {
210+ const message = error . message . toLowerCase ( ) ;
211+ return (
212+ message . includes ( "enoent" ) ||
213+ message . includes ( "not found" ) ||
214+ message . includes ( "command not found" ) ||
215+ message . includes ( "not installed" ) ||
216+ message . includes ( "no such file" ) ||
217+ message . includes ( "spawn op" )
218+ ) ;
219+ } ;
220+
221+ const chooseFallbackError = (
222+ cliError : OnePasswordError ,
223+ sdkError : OnePasswordError ,
224+ ) : OnePasswordError => ( isCliUnavailable ( cliError ) ? sdkError : cliError ) ;
225+
189226export const makeOnePasswordService = (
190227 auth : ResolvedAuth ,
191228 options ?: { readonly preferSdk ?: boolean ; readonly timeoutMs ?: number } ,
@@ -196,11 +233,33 @@ export const makeOnePasswordService = (
196233 return makeNativeSdkService ( auth , timeoutMs ) ;
197234 }
198235
199- // Default: prefer CLI to avoid the IPC hang bug
200- return makeCliService ( auth ) . pipe (
201- Effect . catch ( ( cliError : OnePasswordError ) =>
202- // CLI unavailable (e.g. `op` not installed) — fall back to SDK
203- makeNativeSdkService ( auth , timeoutMs ) . pipe ( Effect . mapError ( ( ) => cliError ) ) ,
204- ) ,
205- ) ;
236+ return Effect . gen ( function * ( ) {
237+ const cliService = yield * makeCliService ( auth ) ;
238+ const sdkService = yield * Effect . cached ( makeNativeSdkService ( auth , timeoutMs ) ) ;
239+
240+ const withSdkFallback = < A > (
241+ cliEffect : Effect . Effect < A , OnePasswordError > ,
242+ sdkEffect : ( service : OnePasswordService ) => Effect . Effect < A , OnePasswordError > ,
243+ ) : Effect . Effect < A , OnePasswordError > =>
244+ cliEffect . pipe (
245+ Effect . catch ( ( cliError : OnePasswordError ) =>
246+ sdkService . pipe (
247+ Effect . flatMap ( sdkEffect ) ,
248+ Effect . mapError ( ( sdkError : OnePasswordError ) =>
249+ chooseFallbackError ( cliError , sdkError ) ,
250+ ) ,
251+ ) ,
252+ ) ,
253+ ) ;
254+
255+ return OnePasswordServiceTag . of ( {
256+ resolveSecret : ( uri ) =>
257+ withSdkFallback ( cliService . resolveSecret ( uri ) , ( service ) => service . resolveSecret ( uri ) ) ,
258+
259+ listVaults : ( ) => withSdkFallback ( cliService . listVaults ( ) , ( service ) => service . listVaults ( ) ) ,
260+
261+ listItems : ( vaultId ) =>
262+ withSdkFallback ( cliService . listItems ( vaultId ) , ( service ) => service . listItems ( vaultId ) ) ,
263+ } ) ;
264+ } ) . pipe ( Effect . withSpan ( "onepassword.make_service" ) ) ;
206265} ;
0 commit comments